From 9dc1bd53d32a8e8f05471c8ef6bee51807bbf78f Mon Sep 17 00:00:00 2001 From: Rajib Quayum Date: Fri, 27 Feb 2026 12:32:54 -0500 Subject: [PATCH 001/191] fix: move MUI 5 class name setup to own file with new entry point in package, update various index files to import class name setup as the first thing Signed-off-by: Rajib Quayum --- packages/app-legacy/src/index.tsx | 1 + packages/app/src/index.tsx | 1 + .../default-app/packages/app/src/index.tsx | 1 + .../next-app/packages/app/src/index.tsx | 1 + packages/theme/package.json | 9 ++++- .../theme/src/unified/MuiClassNameSetup.ts | 40 +++++++++++++++++++ .../src/unified/UnifiedThemeProvider.tsx | 11 +---- packages/theme/src/unified/index.ts | 1 + 8 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 packages/theme/src/unified/MuiClassNameSetup.ts diff --git a/packages/app-legacy/src/index.tsx b/packages/app-legacy/src/index.tsx index 05dcc024bf..d7699e3d9d 100644 --- a/packages/app-legacy/src/index.tsx +++ b/packages/app-legacy/src/index.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import '@backstage/theme/MuiClassNameSetup'; import '@backstage/cli/asset-types'; import ReactDOM from 'react-dom/client'; import App from './App'; diff --git a/packages/app/src/index.tsx b/packages/app/src/index.tsx index fd86261385..873670de0f 100644 --- a/packages/app/src/index.tsx +++ b/packages/app/src/index.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import '@backstage/theme/MuiClassNameSetup'; import '@backstage/cli/asset-types'; import ReactDOM from 'react-dom/client'; import app from './App'; diff --git a/packages/create-app/templates/default-app/packages/app/src/index.tsx b/packages/create-app/templates/default-app/packages/app/src/index.tsx index 46f31902f4..3cc56c886c 100644 --- a/packages/create-app/templates/default-app/packages/app/src/index.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/index.tsx @@ -1,3 +1,4 @@ +import '@backstage/theme/MuiClassNameSetup'; import '@backstage/cli/asset-types'; import ReactDOM from 'react-dom/client'; import App from './App'; diff --git a/packages/create-app/templates/next-app/packages/app/src/index.tsx b/packages/create-app/templates/next-app/packages/app/src/index.tsx index ac9e52bdc1..6936ff50bc 100644 --- a/packages/create-app/templates/next-app/packages/app/src/index.tsx +++ b/packages/create-app/templates/next-app/packages/app/src/index.tsx @@ -1,3 +1,4 @@ +import '@backstage/theme/MuiClassNameSetup'; import '@backstage/cli/asset-types'; import ReactDOM from 'react-dom/client'; import App from './App'; diff --git a/packages/theme/package.json b/packages/theme/package.json index e95ae20b36..f27fc9e385 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -20,7 +20,14 @@ "directory": "packages/theme" }, "license": "Apache-2.0", - "sideEffects": false, + "sideEffects": [ + "./src/unified/MuiClassNameSetup.ts" + ], + "exports": { + ".": "./src/index.ts", + "./MuiClassNameSetup": "./src/unified/MuiClassNameSetup.ts", + "./package.json": "./package.json" + }, "main": "src/index.ts", "types": "src/index.ts", "files": [ diff --git a/packages/theme/src/unified/MuiClassNameSetup.ts b/packages/theme/src/unified/MuiClassNameSetup.ts new file mode 100644 index 0000000000..1c1b854211 --- /dev/null +++ b/packages/theme/src/unified/MuiClassNameSetup.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { 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/*`. + * + * This needs to be configured before any MUI 5 component can possibly be imported. See: https://v5.mui.com/material-ui/experimental-api/classname-generator/#caveat + * + * ```packages/app/index.ts + * + * import '@backstage/theme/MuiClassNameSetup'; // must be the very first import! + * import '@backstage/cli/asset-types'; + * import ReactDOM from 'react-dom/client'; + * import app from './App'; + * import '@backstage/ui/css/styles.css'; + * + * ReactDOM.createRoot(document.getElementById('root')!).render(app); + * ``` + */ +ClassNameGenerator.configure(componentName => { + return componentName.startsWith('v5-') + ? componentName + : `v5-${componentName}`; +}); + +export {}; diff --git a/packages/theme/src/unified/UnifiedThemeProvider.tsx b/packages/theme/src/unified/UnifiedThemeProvider.tsx index 69d843ed3e..b93746dad4 100644 --- a/packages/theme/src/unified/UnifiedThemeProvider.tsx +++ b/packages/theme/src/unified/UnifiedThemeProvider.tsx @@ -27,7 +27,7 @@ import { Theme as Mui5Theme, } from '@mui/material/styles'; import { UnifiedTheme } from './types'; -import { unstable_ClassNameGenerator as ClassNameGenerator } from '@mui/material/className'; +import './MuiClassNameSetup'; /** * Props for {@link UnifiedThemeProvider}. @@ -41,15 +41,6 @@ export interface UnifiedThemeProviderProps { themeName?: string; } -/** - * 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 the separate class name generator diff --git a/packages/theme/src/unified/index.ts b/packages/theme/src/unified/index.ts index 747662e195..2428902981 100644 --- a/packages/theme/src/unified/index.ts +++ b/packages/theme/src/unified/index.ts @@ -21,3 +21,4 @@ export { themes } from './themes'; export { UnifiedThemeProvider } from './UnifiedThemeProvider'; export type { UnifiedThemeProviderProps } from './UnifiedThemeProvider'; export type { UnifiedTheme, SupportedThemes, SupportedVersions } from './types'; +export * from './MuiClassNameSetup'; From 83856ffe55a2b936e286ed11f3042e44f288ee64 Mon Sep 17 00:00:00 2001 From: Rajib Quayum Date: Fri, 27 Feb 2026 12:45:57 -0500 Subject: [PATCH 002/191] chore: add changeset Signed-off-by: Rajib Quayum --- .changeset/vast-jeans-boil.md | 24 +++++++++++++++++++ .../theme/src/unified/MuiClassNameSetup.ts | 5 ++-- 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 .changeset/vast-jeans-boil.md diff --git a/.changeset/vast-jeans-boil.md b/.changeset/vast-jeans-boil.md new file mode 100644 index 0000000000..86f63b2a6d --- /dev/null +++ b/.changeset/vast-jeans-boil.md @@ -0,0 +1,24 @@ +--- +'example-app-legacy': minor +'@backstage/create-app': minor +'@backstage/theme': minor +'example-app': minor +--- + +Separates MUI 5 class name generator code into separate file and entry point that can be imported before any MUI 5 component loads. + +This addresses the problem where the elements contain the `v5-` prefix for MUI class names, but the static class names from the library do not. + +Import should be made as follows to ensure the prefix problem is addressed correctly: + +```diff +// packages/app/index.ts + ++ import '@backstage/theme/MuiClassNameSetup'; // must be the very first import! +import '@backstage/cli/asset-types'; +import ReactDOM from 'react-dom/client'; +import app from './App'; +import '@backstage/ui/css/styles.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render(app); +``` diff --git a/packages/theme/src/unified/MuiClassNameSetup.ts b/packages/theme/src/unified/MuiClassNameSetup.ts index 1c1b854211..5d9fcbd93d 100644 --- a/packages/theme/src/unified/MuiClassNameSetup.ts +++ b/packages/theme/src/unified/MuiClassNameSetup.ts @@ -20,9 +20,10 @@ import { unstable_ClassNameGenerator as ClassNameGenerator } from '@mui/material * * This needs to be configured before any MUI 5 component can possibly be imported. See: https://v5.mui.com/material-ui/experimental-api/classname-generator/#caveat * - * ```packages/app/index.ts + * ```diff + * // packages/app/index.ts * - * import '@backstage/theme/MuiClassNameSetup'; // must be the very first import! + * + import '@backstage/theme/MuiClassNameSetup'; // must be the very first import! * import '@backstage/cli/asset-types'; * import ReactDOM from 'react-dom/client'; * import app from './App'; From f738d47c357f7df52d8688254e5498f549004d46 Mon Sep 17 00:00:00 2001 From: Rajib Quayum Date: Fri, 27 Feb 2026 12:50:04 -0500 Subject: [PATCH 003/191] chore: add api report file for MuiClassNameSetup Signed-off-by: Rajib Quayum --- packages/theme/report-MuiClassNameSetup.api.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 packages/theme/report-MuiClassNameSetup.api.md diff --git a/packages/theme/report-MuiClassNameSetup.api.md b/packages/theme/report-MuiClassNameSetup.api.md new file mode 100644 index 0000000000..c05c3c72a8 --- /dev/null +++ b/packages/theme/report-MuiClassNameSetup.api.md @@ -0,0 +1,7 @@ +## API Report File for "@backstage/theme" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +// (No @packageDocumentation comment for this package) +``` From 8ac301afc1b0855ece294b6631d3a166b3d46513 Mon Sep 17 00:00:00 2001 From: Rajib Quayum Date: Fri, 27 Feb 2026 13:01:58 -0500 Subject: [PATCH 004/191] chore: fix index.ts path Signed-off-by: Rajib Quayum --- .changeset/vast-jeans-boil.md | 2 +- packages/theme/src/unified/MuiClassNameSetup.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/vast-jeans-boil.md b/.changeset/vast-jeans-boil.md index 86f63b2a6d..9eb9ed235b 100644 --- a/.changeset/vast-jeans-boil.md +++ b/.changeset/vast-jeans-boil.md @@ -12,7 +12,7 @@ This addresses the problem where the elements contain the `v5-` prefix for MUI c Import should be made as follows to ensure the prefix problem is addressed correctly: ```diff -// packages/app/index.ts +// packages/app/src/index.ts + import '@backstage/theme/MuiClassNameSetup'; // must be the very first import! import '@backstage/cli/asset-types'; diff --git a/packages/theme/src/unified/MuiClassNameSetup.ts b/packages/theme/src/unified/MuiClassNameSetup.ts index 5d9fcbd93d..9511376f5a 100644 --- a/packages/theme/src/unified/MuiClassNameSetup.ts +++ b/packages/theme/src/unified/MuiClassNameSetup.ts @@ -21,7 +21,7 @@ import { unstable_ClassNameGenerator as ClassNameGenerator } from '@mui/material * This needs to be configured before any MUI 5 component can possibly be imported. See: https://v5.mui.com/material-ui/experimental-api/classname-generator/#caveat * * ```diff - * // packages/app/index.ts + * // packages/app/src/index.ts * * + import '@backstage/theme/MuiClassNameSetup'; // must be the very first import! * import '@backstage/cli/asset-types'; From bac85bb5a8da5e4351982f2978a12ae20db02260 Mon Sep 17 00:00:00 2001 From: Rajib Quayum Date: Fri, 27 Feb 2026 13:10:23 -0500 Subject: [PATCH 005/191] chore: update changeset to remove unnecessary ones Signed-off-by: Rajib Quayum --- .changeset/vast-jeans-boil.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/.changeset/vast-jeans-boil.md b/.changeset/vast-jeans-boil.md index 9eb9ed235b..74b2c481b4 100644 --- a/.changeset/vast-jeans-boil.md +++ b/.changeset/vast-jeans-boil.md @@ -1,8 +1,6 @@ --- -'example-app-legacy': minor '@backstage/create-app': minor '@backstage/theme': minor -'example-app': minor --- Separates MUI 5 class name generator code into separate file and entry point that can be imported before any MUI 5 component loads. From 91f86f80b62905a3166ee238565cc4d1cdd1e9bb Mon Sep 17 00:00:00 2001 From: Rajib Quayum Date: Fri, 27 Feb 2026 13:33:08 -0500 Subject: [PATCH 006/191] fix: repo fix Signed-off-by: Rajib Quayum --- packages/theme/package.json | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/theme/package.json b/packages/theme/package.json index f27fc9e385..a23c87c13d 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -6,9 +6,7 @@ "role": "web-library" }, "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" + "access": "public" }, "keywords": [ "backstage" @@ -28,6 +26,16 @@ "./MuiClassNameSetup": "./src/unified/MuiClassNameSetup.ts", "./package.json": "./package.json" }, + "typesVersions": { + "*": { + "MuiClassNameSetup": [ + "src/unified/MuiClassNameSetup.ts" + ], + "package.json": [ + "package.json" + ] + } + }, "main": "src/index.ts", "types": "src/index.ts", "files": [ From 4e762e6893a661e94256e4efc44777e2c623011a Mon Sep 17 00:00:00 2001 From: Vivek Hipparkar Date: Sun, 21 Dec 2025 00:41:44 +0530 Subject: [PATCH 007/191] fix(scaffolder): open markdown links in new tab for template outputs Signed-off-by: Vivek Hipparkar --- .../components/TemplateOutputs/DefaultTemplateOutputs.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx index cfe3d57e74..6e2c6171e5 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx @@ -96,7 +96,10 @@ export const DefaultTemplateOutputs = (props: { titleTypographyProps={{ component: 'h2' }} > - + From de1d2829434d5f2f806affc1aa19d417efca9d4e Mon Sep 17 00:00:00 2001 From: Vivek Hipparkar Date: Sat, 21 Feb 2026 11:44:00 +0530 Subject: [PATCH 008/191] chore: add changeset for scaffolder markdown list behavior fix Signed-off-by: Vivek Hipparkar --- .changeset/common-shrimps-wink.md | 5 +++++ .changeset/solid-pianos-act.md | 5 +++++ .../src/components/MarkdownContent/MarkdownContent.tsx | 10 ++++++++++ .../TemplateOutputs/DefaultTemplateOutputs.tsx | 5 +---- 4 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 .changeset/common-shrimps-wink.md create mode 100644 .changeset/solid-pianos-act.md diff --git a/.changeset/common-shrimps-wink.md b/.changeset/common-shrimps-wink.md new file mode 100644 index 0000000000..796840a32c --- /dev/null +++ b/.changeset/common-shrimps-wink.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-react': major +--- + +Fixed template output markdown links to open in a new tab, preventing users from losing context when navigating to external resources. diff --git a/.changeset/solid-pianos-act.md b/.changeset/solid-pianos-act.md new file mode 100644 index 0000000000..701e7ed755 --- /dev/null +++ b/.changeset/solid-pianos-act.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Use Backstage Link component for markdown anchor rendering to ensure consistent internal and external link behavior. diff --git a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx index 6d978ebb6f..50bcf3956e 100644 --- a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx +++ b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx @@ -23,6 +23,7 @@ import { HeadingProps } from 'react-markdown/lib/ast-to-react'; import rehypeRaw from 'rehype-raw'; import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'; import type { PluggableList } from 'react-markdown/lib/react-markdown'; +import { Link } from '../Link'; export type MarkdownContentClassKey = 'markdown'; @@ -109,6 +110,15 @@ const components: Options['components'] = { h4: headingRenderer, h5: headingRenderer, h6: headingRenderer, + + a: ({ href, children, ...props }) => + href ? ( + + {children} + + ) : ( + <>{children} + ), }; const gfmRehypePlugins: PluggableList = [ diff --git a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx index 6e2c6171e5..cfe3d57e74 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx @@ -96,10 +96,7 @@ export const DefaultTemplateOutputs = (props: { titleTypographyProps={{ component: 'h2' }} > - + From 15279864d68dbd272cdc56d7f371c59056db5194 Mon Sep 17 00:00:00 2001 From: Rajib Quayum Date: Mon, 2 Mar 2026 09:57:19 -0500 Subject: [PATCH 009/191] chore: remove unnecessary export, reorder import in UnififedThemeProvider Signed-off-by: Rajib Quayum --- packages/theme/src/unified/UnifiedThemeProvider.tsx | 2 +- packages/theme/src/unified/index.ts | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/theme/src/unified/UnifiedThemeProvider.tsx b/packages/theme/src/unified/UnifiedThemeProvider.tsx index b93746dad4..678be82209 100644 --- a/packages/theme/src/unified/UnifiedThemeProvider.tsx +++ b/packages/theme/src/unified/UnifiedThemeProvider.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import './MuiClassNameSetup'; import { ReactNode } from 'react'; import { ThemeProvider, @@ -27,7 +28,6 @@ import { Theme as Mui5Theme, } from '@mui/material/styles'; import { UnifiedTheme } from './types'; -import './MuiClassNameSetup'; /** * Props for {@link UnifiedThemeProvider}. diff --git a/packages/theme/src/unified/index.ts b/packages/theme/src/unified/index.ts index 2428902981..747662e195 100644 --- a/packages/theme/src/unified/index.ts +++ b/packages/theme/src/unified/index.ts @@ -21,4 +21,3 @@ export { themes } from './themes'; export { UnifiedThemeProvider } from './UnifiedThemeProvider'; export type { UnifiedThemeProviderProps } from './UnifiedThemeProvider'; export type { UnifiedTheme, SupportedThemes, SupportedVersions } from './types'; -export * from './MuiClassNameSetup'; From 39557edd70b1667a3fcd825789ae9169a5af9d06 Mon Sep 17 00:00:00 2001 From: Rajib Quayum Date: Mon, 2 Mar 2026 10:01:02 -0500 Subject: [PATCH 010/191] chore: update sideEffects Signed-off-by: Rajib Quayum --- packages/theme/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/theme/package.json b/packages/theme/package.json index a23c87c13d..fe272bcf1c 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -19,7 +19,8 @@ }, "license": "Apache-2.0", "sideEffects": [ - "./src/unified/MuiClassNameSetup.ts" + "./src/unified/MuiClassNameSetup.ts", + "./dist/MuiClassNameSetup.*" ], "exports": { ".": "./src/index.ts", From beb8eda6a3eb39107a4b0e63ce5057ab73ef54ae Mon Sep 17 00:00:00 2001 From: Rajib Quayum Date: Mon, 2 Mar 2026 10:01:28 -0500 Subject: [PATCH 011/191] chore: fix references to index.tsx in docs Signed-off-by: Rajib Quayum --- .changeset/vast-jeans-boil.md | 2 +- packages/theme/src/unified/MuiClassNameSetup.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/vast-jeans-boil.md b/.changeset/vast-jeans-boil.md index 74b2c481b4..7fbc49cdfa 100644 --- a/.changeset/vast-jeans-boil.md +++ b/.changeset/vast-jeans-boil.md @@ -10,7 +10,7 @@ This addresses the problem where the elements contain the `v5-` prefix for MUI c Import should be made as follows to ensure the prefix problem is addressed correctly: ```diff -// packages/app/src/index.ts +// packages/app/src/index.tsx + import '@backstage/theme/MuiClassNameSetup'; // must be the very first import! import '@backstage/cli/asset-types'; diff --git a/packages/theme/src/unified/MuiClassNameSetup.ts b/packages/theme/src/unified/MuiClassNameSetup.ts index 9511376f5a..b069394838 100644 --- a/packages/theme/src/unified/MuiClassNameSetup.ts +++ b/packages/theme/src/unified/MuiClassNameSetup.ts @@ -21,7 +21,7 @@ import { unstable_ClassNameGenerator as ClassNameGenerator } from '@mui/material * This needs to be configured before any MUI 5 component can possibly be imported. See: https://v5.mui.com/material-ui/experimental-api/classname-generator/#caveat * * ```diff - * // packages/app/src/index.ts + * // packages/app/src/index.tsx * * + import '@backstage/theme/MuiClassNameSetup'; // must be the very first import! * import '@backstage/cli/asset-types'; From 58b9f3f88c815342059d87f240e5f693cd2fb107 Mon Sep 17 00:00:00 2001 From: Vivek Hipparkar Date: Sat, 21 Feb 2026 11:44:00 +0530 Subject: [PATCH 012/191] chore: add changeset for scaffolder markdown list behavior fix Signed-off-by: Vivek Hipparkar --- .changeset/solid-pianos-act.md | 5 +++++ .../src/components/MarkdownContent/MarkdownContent.tsx | 10 ++++++++++ .../TemplateOutputs/DefaultTemplateOutputs.tsx | 5 +---- 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 .changeset/solid-pianos-act.md diff --git a/.changeset/solid-pianos-act.md b/.changeset/solid-pianos-act.md new file mode 100644 index 0000000000..701e7ed755 --- /dev/null +++ b/.changeset/solid-pianos-act.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Use Backstage Link component for markdown anchor rendering to ensure consistent internal and external link behavior. diff --git a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx index 6d978ebb6f..50bcf3956e 100644 --- a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx +++ b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx @@ -23,6 +23,7 @@ import { HeadingProps } from 'react-markdown/lib/ast-to-react'; import rehypeRaw from 'rehype-raw'; import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'; import type { PluggableList } from 'react-markdown/lib/react-markdown'; +import { Link } from '../Link'; export type MarkdownContentClassKey = 'markdown'; @@ -109,6 +110,15 @@ const components: Options['components'] = { h4: headingRenderer, h5: headingRenderer, h6: headingRenderer, + + a: ({ href, children, ...props }) => + href ? ( + + {children} + + ) : ( + <>{children} + ), }; const gfmRehypePlugins: PluggableList = [ diff --git a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx index 6e2c6171e5..cfe3d57e74 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateOutputs/DefaultTemplateOutputs.tsx @@ -96,10 +96,7 @@ export const DefaultTemplateOutputs = (props: { titleTypographyProps={{ component: 'h2' }} > - + From da9779c0a6a22a8979d21bfccd20f22a19e18ebb Mon Sep 17 00:00:00 2001 From: Matthias Lindinger Date: Fri, 6 Mar 2026 08:06:14 +0100 Subject: [PATCH 013/191] Fix display of description Signed-off-by: Matthias Lindinger --- .../components/fields/RepoUrlPicker/GitlabRepoPicker.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx index 0e869b44bf..2a428727e3 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx @@ -139,9 +139,6 @@ export const GitlabRepoPicker = ( selected={owner} items={ownerItems} /> - - {t('fields.gitlabRepoPicker.owner.description')} - ) : ( )} + + {t('fields.gitlabRepoPicker.owner.description')} + ); From 3129718fb91e2c6cad6c46c56a7d18bfd17616d5 Mon Sep 17 00:00:00 2001 From: Matthias Lindinger Date: Fri, 6 Mar 2026 08:06:20 +0100 Subject: [PATCH 014/191] Fix title Signed-off-by: Matthias Lindinger --- .../src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx index 2a428727e3..0df4914983 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.tsx @@ -150,7 +150,7 @@ export const GitlabRepoPicker = ( renderInput={params => ( From 864a7993b219764e22d48077315cc4ff82994e69 Mon Sep 17 00:00:00 2001 From: Matthias Lindinger Date: Fri, 6 Mar 2026 08:18:54 +0100 Subject: [PATCH 015/191] Add changeset Signed-off-by: Matthias Lindinger --- .changeset/olive-peaches-fly.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/olive-peaches-fly.md diff --git a/.changeset/olive-peaches-fly.md b/.changeset/olive-peaches-fly.md new file mode 100644 index 0000000000..58dd3250df --- /dev/null +++ b/.changeset/olive-peaches-fly.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Fix the display of the description in `GitlabRepoPicker`: + +- Move `owner.description` helper text outside the `allowedOwners` conditional so it renders for both `Select` and `Autocomplete` modes. +- Update the `Autocomplete` label to use `fields.gitlabRepoPicker.owner.inputTitle` instead of `fields.gitlabRepoPicker.owner.title`. From a6b90538ef7ba0c25b571fcf12e137afbb1331f9 Mon Sep 17 00:00:00 2001 From: Matthias Lindinger Date: Tue, 10 Mar 2026 08:18:30 +0100 Subject: [PATCH 016/191] Add test case Signed-off-by: Matthias Lindinger --- .../RepoUrlPicker/GitlabRepoPicker.test.tsx | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx index 0e461d745a..6cf1825460 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GitlabRepoPicker.test.tsx @@ -176,5 +176,42 @@ describe('GitlabRepoPicker', () => { expect(onChange).toHaveBeenCalledWith({ owner: 'my-mock-owner' }); }); + + it('should render description if allowed owners are passed', async () => { + const { findByText } = await renderInTestApp( + + + , + ); + + expect( + await findByText( + /GitLab namespace where this repository will belong to./, + ), + ).toBeInTheDocument(); + }); + + it('should render description if no allowed owners are passed', async () => { + const { findByText } = await renderInTestApp( + + + , + ); + + expect( + await findByText( + /GitLab namespace where this repository will belong to./, + ), + ).toBeInTheDocument(); + }); }); }); From baa269f33f89cbff129e54bc0fa4c61f30de5974 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Sun, 15 Mar 2026 19:08:28 -0500 Subject: [PATCH 017/191] feat(catalog-backend-module-azure): add Azure DevOps webhook SCM event analyzer Signed-off-by: Lokesh Kaki --- .../analyzeAzureDevOpsWebhookEvent.test.ts | 256 +++++++++ .../events/analyzeAzureDevOpsWebhookEvent.ts | 517 ++++++++++++++++++ 2 files changed, 773 insertions(+) create mode 100644 plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.test.ts create mode 100644 plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts diff --git a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.test.ts b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.test.ts new file mode 100644 index 0000000000..9edeb1602d --- /dev/null +++ b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.test.ts @@ -0,0 +1,256 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { analyzeAzureDevOpsWebhookEvent } from './analyzeAzureDevOpsWebhookEvent'; + +const isRelevantPath = (path: string): boolean => path.endsWith('.yaml'); + +const baseRepository = { + id: 'repo-id', + name: 'example-repo', + defaultBranch: 'refs/heads/main', + remoteUrl: 'https://dev.azure.com/example-org/example-project/_git/example-repo', +}; + +const withPushEvent = (resource: Record) => ({ + eventType: 'git.push', + resource, +}); + +describe('analyzeAzureDevOpsWebhookEvent', () => { + describe('git.push', () => { + it('translates file add, edit, delete, and rename operations to catalog scm events', async () => { + await expect( + analyzeAzureDevOpsWebhookEvent( + 'git.push', + withPushEvent({ + repository: baseRepository, + refUpdates: [{ name: 'refs/heads/main' }], + commits: [ + { + commitId: '1111111111111111111111111111111111111111', + url: `${baseRepository.remoteUrl}/commit/1111111111111111111111111111111111111111`, + changes: [ + { + changeType: 'add', + item: { path: '/catalog-info.yaml' }, + }, + { + changeType: 'edit', + item: { path: '/service.yaml' }, + }, + { + changeType: 'delete', + item: { path: '/obsolete.yaml' }, + }, + { + changeType: 'rename', + originalPath: '/old-name.yaml', + item: { path: '/new-name.yaml' }, + }, + { + changeType: 'rename', + originalPath: '/catalog-out.yaml', + item: { path: '/docs/readme.md' }, + }, + { + changeType: 'rename', + originalPath: '/docs/intro.md', + item: { path: '/catalog-in.yaml' }, + }, + ], + }, + ], + }), + { isRelevantPath }, + ), + ).resolves.toEqual({ + result: 'ok', + events: [ + { + type: 'location.created', + url: `${baseRepository.remoteUrl}?path=/catalog-info.yaml&version=GBmain`, + context: { + commitUrl: `${baseRepository.remoteUrl}/commit/1111111111111111111111111111111111111111`, + }, + }, + { + type: 'location.updated', + url: `${baseRepository.remoteUrl}?path=/service.yaml&version=GBmain`, + context: { + commitUrl: `${baseRepository.remoteUrl}/commit/1111111111111111111111111111111111111111`, + }, + }, + { + type: 'location.deleted', + url: `${baseRepository.remoteUrl}?path=/obsolete.yaml&version=GBmain`, + context: { + commitUrl: `${baseRepository.remoteUrl}/commit/1111111111111111111111111111111111111111`, + }, + }, + { + type: 'location.moved', + fromUrl: `${baseRepository.remoteUrl}?path=/old-name.yaml&version=GBmain`, + toUrl: `${baseRepository.remoteUrl}?path=/new-name.yaml&version=GBmain`, + context: { + commitUrl: `${baseRepository.remoteUrl}/commit/1111111111111111111111111111111111111111`, + }, + }, + { + type: 'location.deleted', + url: `${baseRepository.remoteUrl}?path=/catalog-out.yaml&version=GBmain`, + context: { + commitUrl: `${baseRepository.remoteUrl}/commit/1111111111111111111111111111111111111111`, + }, + }, + { + type: 'location.created', + url: `${baseRepository.remoteUrl}?path=/catalog-in.yaml&version=GBmain`, + context: { + commitUrl: `${baseRepository.remoteUrl}/commit/1111111111111111111111111111111111111111`, + }, + }, + ], + }); + }); + + it('ignores non-default-branch pushes', async () => { + await expect( + analyzeAzureDevOpsWebhookEvent( + 'git.push', + withPushEvent({ + repository: baseRepository, + refUpdates: [{ name: 'refs/heads/feature-branch' }], + commits: [{ commitId: 'a', changes: [] }], + }), + { isRelevantPath }, + ), + ).resolves.toEqual({ + result: 'ignored', + reason: + 'Azure DevOps push event did not target the default branch, expected "refs/heads/main": https://dev.azure.com/example-org/example-project/_git/example-repo', + }); + }); + + it('ignores pushes without file-level change data', async () => { + await expect( + analyzeAzureDevOpsWebhookEvent( + 'git.push', + withPushEvent({ + repository: baseRepository, + refUpdates: [{ name: 'refs/heads/main' }], + commits: [{ commitId: 'a' }], + url: 'https://dev.azure.com/example-org/example-project/_apis/repos/git/repositories/repo-id/pushes/10', + }), + { isRelevantPath }, + ), + ).resolves.toEqual({ + result: 'ignored', + reason: + 'Azure DevOps push event did not affect any relevant paths: https://dev.azure.com/example-org/example-project/_apis/repos/git/repositories/repo-id/pushes/10', + }); + }); + }); + + describe('git.repo.*', () => { + it('translates repository created events', async () => { + await expect( + analyzeAzureDevOpsWebhookEvent( + 'git.repo.created', + { resource: { repository: baseRepository } }, + { isRelevantPath }, + ), + ).resolves.toEqual({ + result: 'ok', + events: [{ type: 'repository.created', url: baseRepository.remoteUrl }], + }); + }); + + it('translates repository deleted events', async () => { + await expect( + analyzeAzureDevOpsWebhookEvent( + 'git.repo.deleted', + { resource: { repository: baseRepository } }, + { isRelevantPath }, + ), + ).resolves.toEqual({ + result: 'ok', + events: [{ type: 'repository.deleted', url: baseRepository.remoteUrl }], + }); + }); + + it('translates repository status changed events', async () => { + await expect( + analyzeAzureDevOpsWebhookEvent( + 'git.repo.statuschanged', + { resource: { repository: baseRepository } }, + { isRelevantPath }, + ), + ).resolves.toEqual({ + result: 'ok', + events: [{ type: 'repository.updated', url: baseRepository.remoteUrl }], + }); + }); + + it('translates repository renamed events', async () => { + await expect( + analyzeAzureDevOpsWebhookEvent( + 'git.repo.renamed', + { + resource: { + oldName: 'legacy-repo', + repository: baseRepository, + }, + }, + { isRelevantPath }, + ), + ).resolves.toEqual({ + result: 'ok', + events: [ + { + type: 'repository.moved', + fromUrl: + 'https://dev.azure.com/example-org/example-project/_git/legacy-repo', + toUrl: baseRepository.remoteUrl, + }, + ], + }); + }); + }); + + describe('general behavior', () => { + it('throws on non-object payloads', async () => { + await expect( + analyzeAzureDevOpsWebhookEvent('git.push', undefined, { + isRelevantPath, + }), + ).rejects.toThrow('Azure DevOps webhook event payload is not an object'); + }); + + it('returns unsupported events', async () => { + await expect( + analyzeAzureDevOpsWebhookEvent( + 'git.pullrequest.created', + { resource: {} }, + { isRelevantPath }, + ), + ).resolves.toEqual({ + result: 'unsupported-event', + event: 'git.pullrequest.created', + }); + }); + }); +}); \ No newline at end of file diff --git a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts new file mode 100644 index 0000000000..054eb310f7 --- /dev/null +++ b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts @@ -0,0 +1,517 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InputError } from '@backstage/errors'; +import { CatalogScmEvent } from '@backstage/plugin-catalog-node/alpha'; + +export interface AnalyzeAzureDevOpsWebhookEventOptions { + isRelevantPath: (path: string) => boolean; +} + +export type AnalyzeAzureDevOpsWebhookEventResult = + | { + result: 'unsupported-event'; + event: string; + } + | { + result: 'ignored'; + reason: string; + } + | { + result: 'aborted'; + reason: string; + } + | { + result: 'ok'; + events: CatalogScmEvent[]; + }; + +type JsonObject = Record; + +type AzureRepository = { + name?: string; + defaultBranch?: string; + remoteUrl?: string; +}; + +type AzurePushRefUpdate = { + name?: string; +}; + +type AzurePushCommit = { + commitId?: string; + url?: string; + changes?: AzurePushCommitChange[]; + added?: string[]; + removed?: string[]; + modified?: string[]; +}; + +type AzurePushCommitChange = { + changeType?: string; + item?: { + path?: string; + originalPath?: string; + }; + path?: string; + newPath?: string; + oldPath?: string; + originalPath?: string; + sourceServerItem?: string; +}; + +type PushPathState = + | { + type: 'added'; + commit: AzurePushCommit; + } + | { + type: 'removed'; + commit: AzurePushCommit; + } + | { + type: 'changed'; + commit: AzurePushCommit; + } + | { + type: 'renamed'; + fromPath: string; + commit: AzurePushCommit; + }; + +type NormalizedPushChange = + | { + type: 'added'; + path: string; + } + | { + type: 'removed'; + path: string; + } + | { + type: 'changed'; + path: string; + } + | { + type: 'renamed'; + fromPath: string; + toPath: string; + }; + +function asObject(value: unknown): JsonObject | undefined { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return undefined; + } + return value as JsonObject; +} + +function asString(value: unknown): string | undefined { + return typeof value === 'string' ? value : undefined; +} + +function normalizePath(path: string | undefined): string | undefined { + if (!path) { + return undefined; + } + return path.startsWith('/') ? path : `/${path}`; +} + +function branchNameFromRef(ref: string | undefined): string | undefined { + if (!ref) { + return undefined; + } + return ref.replace(/^refs\/heads\//, ''); +} + +function getRepository(resource: JsonObject | undefined): AzureRepository { + const repository = asObject(resource?.repository); + return { + name: asString(repository?.name), + defaultBranch: asString(repository?.defaultBranch), + remoteUrl: asString(repository?.remoteUrl), + }; +} + +function toLocationUrl(options: { + remoteUrl: string | undefined; + path: string; + branchRef: string | undefined; +}): string | undefined { + if (!options.remoteUrl) { + return undefined; + } + + const branch = branchNameFromRef(options.branchRef); + const branchSuffix = branch ? `&version=GB${branch}` : ''; + return encodeURI(`${options.remoteUrl}?path=${options.path}${branchSuffix}`); +} + +function toCommitUrl( + repository: AzureRepository, + commit: AzurePushCommit, +): string | undefined { + if (commit.url) { + return commit.url; + } + if (repository.remoteUrl && commit.commitId) { + return `${repository.remoteUrl}/commit/${commit.commitId}`; + } + return undefined; +} + +function toCatalogScmEventForPathState(options: { + repository: AzureRepository; + branchRef: string | undefined; + path: string; + pathState: PushPathState; + isRelevantPath: (path: string) => boolean; +}): CatalogScmEvent[] { + const { repository, branchRef, path, pathState, isRelevantPath } = options; + const commitUrl = toCommitUrl(repository, pathState.commit); + const context = commitUrl ? { commitUrl } : undefined; + + if (pathState.type === 'renamed') { + const fromRelevant = isRelevantPath(pathState.fromPath); + const toRelevant = isRelevantPath(path); + const fromUrl = toLocationUrl({ + remoteUrl: repository.remoteUrl, + path: pathState.fromPath, + branchRef, + }); + const toUrl = toLocationUrl({ + remoteUrl: repository.remoteUrl, + path, + branchRef, + }); + + if (fromRelevant && toRelevant && fromUrl && toUrl) { + return [{ type: 'location.moved', fromUrl, toUrl, context }]; + } + if (fromRelevant && !toRelevant && fromUrl) { + return [{ type: 'location.deleted', url: fromUrl, context }]; + } + if (!fromRelevant && toRelevant && toUrl) { + return [{ type: 'location.created', url: toUrl, context }]; + } + return []; + } + + if (!isRelevantPath(path)) { + return []; + } + + const url = toLocationUrl({ + remoteUrl: repository.remoteUrl, + path, + branchRef, + }); + if (!url) { + return []; + } + + if (pathState.type === 'added') { + return [{ type: 'location.created', url, context }]; + } + if (pathState.type === 'removed') { + return [{ type: 'location.deleted', url, context }]; + } + + return [{ type: 'location.updated', url, context }]; +} + +function normalizePushCommitChanges( + commit: AzurePushCommit, +): NormalizedPushChange[] { + const normalized: NormalizedPushChange[] = []; + + for (const path of commit.added ?? []) { + const normalizedPath = normalizePath(path); + if (normalizedPath) { + normalized.push({ type: 'added', path: normalizedPath }); + } + } + + for (const path of commit.removed ?? []) { + const normalizedPath = normalizePath(path); + if (normalizedPath) { + normalized.push({ type: 'removed', path: normalizedPath }); + } + } + + for (const path of commit.modified ?? []) { + const normalizedPath = normalizePath(path); + if (normalizedPath) { + normalized.push({ type: 'changed', path: normalizedPath }); + } + } + + for (const change of commit.changes ?? []) { + const changeType = change.changeType?.toLowerCase() ?? ''; + const toPath = normalizePath(change.item?.path ?? change.path ?? change.newPath); + const fromPath = normalizePath( + change.originalPath ?? + change.item?.originalPath ?? + change.oldPath ?? + change.sourceServerItem, + ); + + if (changeType.includes('rename') && fromPath && toPath) { + normalized.push({ type: 'renamed', fromPath, toPath }); + continue; + } + + if (changeType.includes('add') && toPath) { + normalized.push({ type: 'added', path: toPath }); + continue; + } + + if (changeType.includes('delete') && (toPath ?? fromPath)) { + normalized.push({ type: 'removed', path: toPath ?? fromPath! }); + continue; + } + + if ( + (changeType.includes('edit') || + changeType.includes('modify') || + changeType.includes('update')) && + (toPath ?? fromPath) + ) { + normalized.push({ type: 'changed', path: toPath ?? fromPath! }); + } + } + + return normalized; +} + +function applyPushChange( + state: Map, + change: NormalizedPushChange, + commit: AzurePushCommit, +) { + if (change.type === 'renamed') { + const previous = state.get(change.fromPath); + state.delete(change.fromPath); + + let next: PushPathState | undefined; + if (!previous) { + next = { type: 'renamed', fromPath: change.fromPath, commit }; + } else if (previous.type === 'added') { + next = { type: 'added', commit }; + } else if (previous.type === 'changed') { + next = { type: 'renamed', fromPath: change.fromPath, commit }; + } else if (previous.type === 'renamed') { + next = { type: 'renamed', fromPath: previous.fromPath, commit }; + } + + if (next) { + state.set(change.toPath, next); + } + return; + } + + const previous = state.get(change.path); + + if (change.type === 'added') { + if (!previous) { + state.set(change.path, { type: 'added', commit }); + } else if (previous.type === 'removed') { + state.set(change.path, { type: 'changed', commit }); + } + return; + } + + if (change.type === 'removed') { + if (!previous) { + state.set(change.path, { type: 'removed', commit }); + } else if (previous.type === 'added') { + state.delete(change.path); + } else if (previous.type === 'changed') { + state.set(change.path, { type: 'removed', commit }); + } else if (previous.type === 'renamed') { + state.delete(change.path); + state.set(previous.fromPath, { type: 'removed', commit }); + } + return; + } + + if (!previous) { + state.set(change.path, { type: 'changed', commit }); + } +} + +function replaceRepoNameInRemoteUrl( + remoteUrl: string | undefined, + repoName: string | undefined, +): string | undefined { + if (!remoteUrl || !repoName) { + return undefined; + } + const match = remoteUrl.match(/^(.*\/_git\/)([^/?#]+)(.*)$/); + if (!match) { + return undefined; + } + return `${match[1]}${repoName}${match[3]}`; +} + +async function onPushEvent( + eventPayload: JsonObject, + options: AnalyzeAzureDevOpsWebhookEventOptions, +): Promise { + const resource = asObject(eventPayload.resource); + const repository = getRepository(resource); + const refUpdates = (resource?.refUpdates as AzurePushRefUpdate[] | undefined) ?? []; + const commits = (resource?.commits as AzurePushCommit[] | undefined) ?? []; + const contextUrl = asString(resource?.url) ?? repository.remoteUrl ?? ''; + + if (commits.length === 0) { + return { + result: 'ignored', + reason: `Azure DevOps push event does not contain commits: ${contextUrl}`, + }; + } + + if (repository.defaultBranch) { + const updatesToDefaultBranch = refUpdates.filter( + update => update.name === repository.defaultBranch, + ); + if (updatesToDefaultBranch.length === 0) { + return { + result: 'ignored', + reason: `Azure DevOps push event did not target the default branch, expected "${repository.defaultBranch}": ${contextUrl}`, + }; + } + } + + const state = new Map(); + + for (const commit of commits) { + const changes = normalizePushCommitChanges(commit); + for (const change of changes) { + applyPushChange(state, change, commit); + } + } + + if (state.size === 0) { + return { + result: 'ignored', + reason: `Azure DevOps push event did not affect any relevant paths: ${contextUrl}`, + }; + } + + const branchRef = + repository.defaultBranch ?? asString(refUpdates[0]?.name) ?? undefined; + + const events = Array.from(state.entries()).flatMap(([path, pathState]) => + toCatalogScmEventForPathState({ + repository, + branchRef, + path, + pathState, + isRelevantPath: options.isRelevantPath, + }), + ); + + if (events.length === 0) { + return { + result: 'ignored', + reason: `Azure DevOps push event did not affect any relevant paths: ${contextUrl}`, + }; + } + + return { result: 'ok', events }; +} + +async function onRepositoryEvent( + eventType: string, + eventPayload: JsonObject, +): Promise { + const resource = asObject(eventPayload.resource); + const repository = getRepository(resource); + const toUrl = repository.remoteUrl; + + if (eventType === 'git.repo.created' && toUrl) { + return { + result: 'ok', + events: [{ type: 'repository.created', url: toUrl }], + }; + } + + if (eventType === 'git.repo.deleted' && toUrl) { + return { + result: 'ok', + events: [{ type: 'repository.deleted', url: toUrl }], + }; + } + + if (eventType === 'git.repo.statuschanged' && toUrl) { + return { + result: 'ok', + events: [{ type: 'repository.updated', url: toUrl }], + }; + } + + if (eventType === 'git.repo.renamed' && toUrl) { + const oldName = asString(resource?.oldName); + const fromUrl = replaceRepoNameInRemoteUrl(toUrl, oldName); + if (!fromUrl) { + return { + result: 'ignored', + reason: 'Azure DevOps repository renamed event is missing oldName', + }; + } + + return { + result: 'ok', + events: [{ type: 'repository.moved', fromUrl, toUrl }], + }; + } + + if (eventType.startsWith('git.repo.')) { + return { + result: 'unsupported-event', + event: eventType, + }; + } + + return { + result: 'unsupported-event', + event: eventType, + }; +} + +export async function analyzeAzureDevOpsWebhookEvent( + eventType: string, + eventPayload: unknown, + options: AnalyzeAzureDevOpsWebhookEventOptions, +): Promise { + const payload = asObject(eventPayload); + if (!payload) { + throw new InputError('Azure DevOps webhook event payload is not an object'); + } + + if (eventType === 'git.push') { + return await onPushEvent(payload, options); + } + + if (eventType.startsWith('git.repo.')) { + return await onRepositoryEvent(eventType, payload); + } + + return { + result: 'unsupported-event', + event: eventType, + }; +} From 0cd53934b031bcf582ac34589ef6dbbcf9f92093 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Sun, 15 Mar 2026 19:08:33 -0500 Subject: [PATCH 018/191] feat(catalog-backend-module-azure): add Azure DevOps SCM events bridge wiring Signed-off-by: Lokesh Kaki --- .../src/events/AzureDevOpsScmEventsBridge.ts | 115 ++++++++++++++++++ .../catalogModuleAzureDevOpsEntityProvider.ts | 28 ++++- 2 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 plugins/catalog-backend-module-azure/src/events/AzureDevOpsScmEventsBridge.ts diff --git a/plugins/catalog-backend-module-azure/src/events/AzureDevOpsScmEventsBridge.ts b/plugins/catalog-backend-module-azure/src/events/AzureDevOpsScmEventsBridge.ts new file mode 100644 index 0000000000..2f03a1b080 --- /dev/null +++ b/plugins/catalog-backend-module-azure/src/events/AzureDevOpsScmEventsBridge.ts @@ -0,0 +1,115 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LoggerService } from '@backstage/backend-plugin-api'; +import { CatalogScmEventsService } from '@backstage/plugin-catalog-node/alpha'; +import { EventParams, EventsService } from '@backstage/plugin-events-node'; +import { analyzeAzureDevOpsWebhookEvent } from './analyzeAzureDevOpsWebhookEvent'; + +/** + * Takes Azure DevOps webhook events, analyzes them, and publishes them as + * catalog SCM events that entity providers and others can subscribe to. + */ +export class AzureDevOpsScmEventsBridge { + readonly #logger: LoggerService; + readonly #events: EventsService; + readonly #catalogScmEvents: CatalogScmEventsService; + #shuttingDown: boolean; + #pendingPublish: Promise | undefined; + + constructor(options: { + logger: LoggerService; + events: EventsService; + catalogScmEvents: CatalogScmEventsService; + }) { + this.#logger = options.logger; + this.#events = options.events; + this.#catalogScmEvents = options.catalogScmEvents; + this.#shuttingDown = false; + } + + async start() { + await this.#events.subscribe({ + id: 'catalog-azure-devops-scm-events-bridge', + topics: ['azureDevOps'], + onEvent: this.#onEvent.bind(this), + }); + } + + async stop() { + this.#shuttingDown = true; + await this.#pendingPublish; + } + + async #onEvent(params: EventParams): Promise { + const eventPayload = params.eventPayload as + | { eventType?: string } + | undefined; + const eventType = eventPayload?.eventType; + if (!eventType || !eventPayload) { + return; + } + + while (this.#pendingPublish) { + await this.#pendingPublish; + } + + if (this.#shuttingDown) { + this.#logger.warn( + `Skipping Azure DevOps webhook event of type "${eventType}" on topic "${params.topic}" because the bridge is shutting down`, + ); + return; + } + + this.#pendingPublish = Promise.resolve().then(async () => { + try { + const output = await analyzeAzureDevOpsWebhookEvent( + eventType, + eventPayload, + { + isRelevantPath: path => + path.endsWith('.yaml') || path.endsWith('.yml'), + }, + ); + + if (output.result === 'ok') { + await this.#catalogScmEvents.publish(output.events); + } else if (output.result === 'ignored') { + this.#logger.debug( + `Skipping Azure DevOps webhook event of type "${eventType}" on topic "${params.topic}" because it is ignored: ${output.reason}`, + ); + } else if (output.result === 'aborted') { + this.#logger.warn( + `Skipping Azure DevOps webhook event of type "${eventType}" on topic "${params.topic}" because it is aborted: ${output.reason}`, + ); + } else if (output.result === 'unsupported-event') { + this.#logger.debug( + `Skipping Azure DevOps webhook event of type "${eventType}" on topic "${params.topic}" because it is unsupported: ${output.event}`, + ); + } + } catch (error) { + this.#logger.warn( + `Failed to handle Azure DevOps webhook event of type "${eventType}"`, + error, + ); + } finally { + this.#pendingPublish = undefined; + } + }); + + await this.#pendingPublish; + } +} diff --git a/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.ts b/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.ts index fff597c165..b7ad3cf6e6 100644 --- a/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.ts +++ b/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.ts @@ -19,10 +19,13 @@ import { createBackendModule, } from '@backstage/backend-plugin-api'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { catalogScmEventsServiceRef } from '@backstage/plugin-catalog-node/alpha'; +import { eventsServiceRef } from '@backstage/plugin-events-node'; import { AzureBlobStorageEntityProvider, AzureDevOpsEntityProvider, } from '../providers'; +import { AzureDevOpsScmEventsBridge } from '../events/AzureDevOpsScmEventsBridge'; /** * Registers the AzureDevOpsEntityProvider with the catalog processing extension point. @@ -39,8 +42,19 @@ export const catalogModuleAzureEntityProvider = createBackendModule({ catalog: catalogProcessingExtensionPoint, logger: coreServices.logger, scheduler: coreServices.scheduler, + events: eventsServiceRef, + catalogScmEvents: catalogScmEventsServiceRef, + lifecycle: coreServices.lifecycle, }, - async init({ config, catalog, logger, scheduler }) { + async init({ + config, + catalog, + logger, + scheduler, + events, + catalogScmEvents, + lifecycle, + }) { // Check for Azure Blob Storage provider configuration and register it if (config.has('catalog.providers.azureBlob')) { catalog.addEntityProvider( @@ -60,6 +74,18 @@ export const catalogModuleAzureEntityProvider = createBackendModule({ }), ); } + + const bridge = new AzureDevOpsScmEventsBridge({ + logger, + events, + catalogScmEvents, + }); + lifecycle.addStartupHook(async () => { + await bridge.start(); + }); + lifecycle.addShutdownHook(async () => { + await bridge.stop(); + }); }, }); }, From 31ce5da2eaec18b10e03d20d2edaeb121b5b9741 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Sun, 15 Mar 2026 19:08:36 -0500 Subject: [PATCH 019/191] feat(catalog-backend-module-azure): export Azure webhook analyzer and add changeset Signed-off-by: Lokesh Kaki --- .changeset/thin-lies-deliver.md | 5 +++++ plugins/catalog-backend-module-azure/src/index.ts | 1 + 2 files changed, 6 insertions(+) create mode 100644 .changeset/thin-lies-deliver.md diff --git a/.changeset/thin-lies-deliver.md b/.changeset/thin-lies-deliver.md new file mode 100644 index 0000000000..22ca304f96 --- /dev/null +++ b/.changeset/thin-lies-deliver.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-azure': patch +--- + +Add Azure DevOps SCM event translation layer for instant catalog reprocessing. diff --git a/plugins/catalog-backend-module-azure/src/index.ts b/plugins/catalog-backend-module-azure/src/index.ts index dbbdff67ce..f1c5c66c48 100644 --- a/plugins/catalog-backend-module-azure/src/index.ts +++ b/plugins/catalog-backend-module-azure/src/index.ts @@ -23,3 +23,4 @@ export { default } from './module'; export { AzureDevOpsDiscoveryProcessor } from './processors'; export { AzureDevOpsEntityProvider } from './providers'; +export { analyzeAzureDevOpsWebhookEvent } from './events/analyzeAzureDevOpsWebhookEvent'; From 5ee1f50a526684fdb76141ece55d745db70a60a4 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Sun, 15 Mar 2026 19:50:24 -0500 Subject: [PATCH 020/191] feat(catalog-backend-module-gitlab): add GitLab SCM event translation and bridge wiring Signed-off-by: Lokesh Kaki --- .../package.json | 1 + .../src/events/GitLabScmEventsBridge.ts | 152 +++++ .../src/events/analyzeGitLabWebhookEvent.ts | 535 ++++++++++++++++++ .../src/index.ts | 1 + ...alogModuleGitlabDiscoveryEntityProvider.ts | 27 +- 5 files changed, 715 insertions(+), 1 deletion(-) create mode 100644 plugins/catalog-backend-module-gitlab/src/events/GitLabScmEventsBridge.ts create mode 100644 plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index e7b562c4e5..099ffd8832 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -55,6 +55,7 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", diff --git a/plugins/catalog-backend-module-gitlab/src/events/GitLabScmEventsBridge.ts b/plugins/catalog-backend-module-gitlab/src/events/GitLabScmEventsBridge.ts new file mode 100644 index 0000000000..34b9b5c9c2 --- /dev/null +++ b/plugins/catalog-backend-module-gitlab/src/events/GitLabScmEventsBridge.ts @@ -0,0 +1,152 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LoggerService } from '@backstage/backend-plugin-api'; +import { CatalogScmEventsService } from '@backstage/plugin-catalog-node/alpha'; +import { EventParams, EventsService } from '@backstage/plugin-events-node'; +import { analyzeGitLabWebhookEvent } from './analyzeGitLabWebhookEvent'; + +function determineEventType(params: EventParams): string | undefined { + const payload = params.eventPayload; + + if ( + payload && + typeof payload === 'object' && + !Array.isArray(payload) && + typeof (payload as { object_kind?: unknown }).object_kind === 'string' + ) { + return (payload as { object_kind: string }).object_kind; + } + + const eventName = + payload && + typeof payload === 'object' && + !Array.isArray(payload) && + typeof (payload as { event_name?: unknown }).event_name === 'string' + ? (payload as { event_name: string }).event_name + : undefined; + if (eventName) { + return eventName; + } + + const metadataType = params.metadata?.['x-gitlab-event']; + if (typeof metadataType === 'string' && metadataType.trim()) { + return metadataType + .trim() + .toLowerCase() + .replace(/\s+hook$/, '') + .replace(/\s+/g, '_'); + } + + if (params.topic.startsWith('gitlab.')) { + return params.topic.slice('gitlab.'.length); + } + + return undefined; +} + +/** + * Takes GitLab webhook events, analyzes them, and publishes them as catalog + * SCM events that entity providers and others can subscribe to. + */ +export class GitLabScmEventsBridge { + readonly #logger: LoggerService; + readonly #events: EventsService; + readonly #catalogScmEvents: CatalogScmEventsService; + #shuttingDown: boolean; + #pendingPublish: Promise | undefined; + + constructor(options: { + logger: LoggerService; + events: EventsService; + catalogScmEvents: CatalogScmEventsService; + }) { + this.#logger = options.logger; + this.#events = options.events; + this.#catalogScmEvents = options.catalogScmEvents; + this.#shuttingDown = false; + } + + async start() { + await this.#events.subscribe({ + id: 'catalog-gitlab-scm-events-bridge', + topics: ['gitlab'], + onEvent: this.#onEvent.bind(this), + }); + } + + async stop() { + this.#shuttingDown = true; + await this.#pendingPublish; + } + + async #onEvent(params: EventParams): Promise { + const eventType = determineEventType(params); + if (!eventType || !params.eventPayload) { + return; + } + + while (this.#pendingPublish) { + await this.#pendingPublish; + } + + if (this.#shuttingDown) { + this.#logger.warn( + `Skipping GitLab webhook event of type "${eventType}" on topic "${params.topic}" because the bridge is shutting down`, + ); + return; + } + + this.#pendingPublish = Promise.resolve().then(async () => { + try { + const output = await analyzeGitLabWebhookEvent( + eventType, + params.eventPayload, + { + logger: this.#logger, + isRelevantPath: path => + path.endsWith('.yaml') || path.endsWith('.yml'), + }, + ); + + if (output.result === 'ok') { + await this.#catalogScmEvents.publish(output.events); + } else if (output.result === 'ignored') { + this.#logger.debug( + `Skipping GitLab webhook event of type "${eventType}" on topic "${params.topic}" because it is ignored: ${output.reason}`, + ); + } else if (output.result === 'aborted') { + this.#logger.warn( + `Skipping GitLab webhook event of type "${eventType}" on topic "${params.topic}" because it is aborted: ${output.reason}`, + ); + } else if (output.result === 'unsupported-event') { + this.#logger.debug( + `Skipping GitLab webhook event of type "${eventType}" on topic "${params.topic}" because it is unsupported: ${output.event}`, + ); + } + } catch (error) { + this.#logger.warn( + `Failed to handle GitLab webhook event of type "${eventType}"`, + error, + ); + } finally { + this.#pendingPublish = undefined; + } + }); + + await this.#pendingPublish; + } +} \ No newline at end of file diff --git a/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts b/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts new file mode 100644 index 0000000000..4bea0230f0 --- /dev/null +++ b/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts @@ -0,0 +1,535 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LoggerService } from '@backstage/backend-plugin-api'; +import { InputError } from '@backstage/errors'; +import { CatalogScmEvent } from '@backstage/plugin-catalog-node/alpha'; +import { WebhookPushEventSchema } from '@gitbeaker/rest'; + +type StringRecord = Record; + +export interface AnalyzeWebhookEventOptions { + logger: LoggerService; + isRelevantPath: (path: string) => boolean; +} + +export type AnalyzeWebhookEventResult = + | { + result: 'unsupported-event'; + event: string; + } + | { + result: 'ignored'; + reason: string; + } + | { + result: 'aborted'; + reason: string; + } + | { + result: 'ok'; + events: CatalogScmEvent[]; + }; + +type PathState = + | { + type: 'added'; + commitUrl?: string; + } + | { + type: 'removed'; + commitUrl?: string; + } + | { + type: 'modified'; + commitUrl?: string; + } + | { + type: 'renamed'; + fromPath: string; + commitUrl?: string; + } + | { + type: 'changed'; + commitUrl?: string; + }; + +type GitLabPushCommit = { + id?: string; + url?: string; + added?: string[]; + removed?: string[]; + modified?: string[]; +}; + +type ChangeDescriptor = { + from?: unknown; + to?: unknown; + old?: unknown; + new?: unknown; + previous?: unknown; + current?: unknown; + before?: unknown; + after?: unknown; +}; + +type GitLabRepositoryUpdateEvent = { + object_kind?: string; + event_name?: string; + action?: string; + deleted_at?: string | null; + path_with_namespace?: string; + old_path_with_namespace?: string; + project?: { + web_url?: string; + path_with_namespace?: string; + deleted_at?: string | null; + }; + changes?: { + web_url?: ChangeDescriptor; + path_with_namespace?: ChangeDescriptor; + old_path_with_namespace?: ChangeDescriptor; + deleted_at?: ChangeDescriptor; + }; +}; + +function isObject(value: unknown): value is StringRecord { + return Boolean(value) && typeof value === 'object' && !Array.isArray(value); +} + +function asString(value: unknown): string | undefined { + return typeof value === 'string' ? value : undefined; +} + +function getFromChange(change?: ChangeDescriptor): string | undefined { + return ( + asString(change?.from) ?? + asString(change?.old) ?? + asString(change?.previous) ?? + asString(change?.before) + ); +} + +function getToChange(change?: ChangeDescriptor): string | undefined { + return ( + asString(change?.to) ?? + asString(change?.new) ?? + asString(change?.current) ?? + asString(change?.after) + ); +} + +function extractBranchName(ref?: string): string | undefined { + if (!ref || !ref.startsWith('refs/heads/')) { + return undefined; + } + return ref.slice('refs/heads/'.length); +} + +function getCommitUrl(commit: GitLabPushCommit, repositoryUrl?: string): + | string + | undefined { + if (commit.url) { + return commit.url; + } + if (commit.id && repositoryUrl) { + return `${repositoryUrl}/-/commit/${commit.id}`; + } + return undefined; +} + +function pathStateToCatalogScmEvent( + path: string, + event: PathState, + repositoryUrl: string, + branch: string, +): CatalogScmEvent { + const toBlobUrl = (p: string) => `${repositoryUrl}/-/blob/${branch}/${p}`; + const context = event.commitUrl ? { commitUrl: event.commitUrl } : undefined; + + switch (event.type) { + case 'added': + return { + type: 'location.created', + url: toBlobUrl(path), + context, + }; + case 'removed': + return { + type: 'location.deleted', + url: toBlobUrl(path), + context, + }; + case 'modified': + return { + type: 'location.updated', + url: toBlobUrl(path), + context, + }; + case 'renamed': + return { + type: 'location.moved', + fromUrl: toBlobUrl(event.fromPath), + toUrl: toBlobUrl(path), + context, + }; + case 'changed': + return { + type: 'location.updated', + url: toBlobUrl(path), + context, + }; + default: + // @ts-expect-error Intentionally expected, to check for exhaustive checking of the types + throw new Error(`Unknown file event type: ${event.type}`); + } +} + +function applyAddedPath( + pathState: Map, + path: string, + commitUrl: string | undefined, +) { + const previous = pathState.get(path); + if (!previous) { + pathState.set(path, { type: 'added', commitUrl }); + return; + } + if (previous.type === 'removed') { + pathState.set(path, { type: 'changed', commitUrl }); + return; + } + pathState.set(path, previous); +} + +function applyRemovedPath( + pathState: Map, + path: string, + commitUrl: string | undefined, +) { + const previous = pathState.get(path); + if (!previous) { + pathState.set(path, { type: 'removed', commitUrl }); + return; + } + if (previous.type === 'added') { + pathState.delete(path); + return; + } + if (previous.type === 'changed') { + pathState.set(path, { type: 'removed', commitUrl }); + return; + } + if (previous.type === 'renamed') { + if (!pathState.has(previous.fromPath)) { + pathState.set(previous.fromPath, { type: 'removed', commitUrl }); + } + pathState.delete(path); + return; + } + pathState.set(path, previous); +} + +function applyModifiedPath( + pathState: Map, + path: string, + commitUrl: string | undefined, +) { + const previous = pathState.get(path); + if (!previous) { + pathState.set(path, { type: 'changed', commitUrl }); + return; + } + if (previous.type === 'removed') { + pathState.set(path, previous); + return; + } + pathState.set(path, previous); +} + +function applyRenamedPath( + pathState: Map, + fromPath: string, + toPath: string, + commitUrl: string | undefined, +) { + const previous = pathState.get(fromPath); + pathState.delete(fromPath); + + if (!previous) { + pathState.set(toPath, { type: 'renamed', fromPath, commitUrl }); + return; + } + if (previous.type === 'added') { + pathState.set(toPath, { type: 'added', commitUrl }); + return; + } + if (previous.type === 'renamed') { + pathState.set(toPath, { + type: 'renamed', + fromPath: previous.fromPath, + commitUrl, + }); + return; + } + pathState.set(toPath, { type: 'renamed', fromPath, commitUrl }); +} + +async function onPushEvent( + event: WebhookPushEventSchema, + options: AnalyzeWebhookEventOptions, +): Promise { + const project = isObject(event.project) ? event.project : undefined; + const repositoryUrl = asString(project?.web_url); + const contextUrl = repositoryUrl ?? ''; + const defaultBranch = asString(project?.default_branch); + + if (defaultBranch) { + const expectedRef = `refs/heads/${defaultBranch}`; + if (event.ref !== expectedRef) { + return { + result: 'ignored', + reason: `GitLab push event did not target the default branch, found "${event.ref}" but expected "${expectedRef}": ${contextUrl}`, + }; + } + } + + const commits = (Array.isArray(event.commits) + ? event.commits + : []) as GitLabPushCommit[]; + + if (!commits.length) { + return { + result: 'ignored', + reason: `GitLab push event did not contain any commits: ${contextUrl}`, + }; + } + + const pathState = new Map(); + let hasRelevantPaths = false; + + for (const commit of commits) { + const commitUrl = getCommitUrl(commit, repositoryUrl); + const added = (commit.added ?? []).filter(options.isRelevantPath); + const modified = (commit.modified ?? []).filter(options.isRelevantPath); + const removed = (commit.removed ?? []).filter(options.isRelevantPath); + + if (added.length || modified.length || removed.length) { + hasRelevantPaths = true; + } + + for (const path of modified) { + applyModifiedPath(pathState, path, commitUrl); + } + + const renamePairs = Math.min(added.length, removed.length); + for (let i = 0; i < renamePairs; i++) { + applyRenamedPath(pathState, removed[i], added[i], commitUrl); + } + + for (const path of added.slice(renamePairs)) { + applyAddedPath(pathState, path, commitUrl); + } + + for (const path of removed.slice(renamePairs)) { + applyRemovedPath(pathState, path, commitUrl); + } + } + + if (!hasRelevantPaths) { + return { + result: 'ignored', + reason: `GitLab push event did not affect any relevant paths: ${contextUrl}`, + }; + } + + if (!repositoryUrl) { + return { + result: 'aborted', + reason: 'GitLab push event did not include project.web_url', + }; + } + + const branch = defaultBranch ?? extractBranchName(event.ref) ?? 'main'; + return { + result: 'ok', + events: Array.from(pathState.entries()).map(([path, e]) => + pathStateToCatalogScmEvent(path, e, repositoryUrl, branch), + ), + }; +} + +function getOrigin(url: string): string | undefined { + try { + return new URL(url).origin; + } catch { + return undefined; + } +} + +function toRepositoryUrl(baseUrl: string, pathWithNamespace: string): string { + return `${baseUrl}/${pathWithNamespace}`; +} + +function getCurrentRepositoryUrl( + event: GitLabRepositoryUpdateEvent, +): string | undefined { + const projectUrl = asString(event.project?.web_url); + if (projectUrl) { + return projectUrl; + } + + return getToChange(event.changes?.web_url); +} + +function getPreviousRepositoryUrl( + event: GitLabRepositoryUpdateEvent, + currentRepositoryUrl?: string, +): string | undefined { + const changedUrl = getFromChange(event.changes?.web_url); + if (changedUrl) { + return changedUrl; + } + + const oldPathWithNamespace = + asString(event.old_path_with_namespace) ?? + getFromChange(event.changes?.path_with_namespace) ?? + getFromChange(event.changes?.old_path_with_namespace); + if (!oldPathWithNamespace) { + return undefined; + } + + const projectPathWithNamespace = asString(event.project?.path_with_namespace); + const projectUrl = asString(event.project?.web_url); + + if ( + currentRepositoryUrl && + projectPathWithNamespace && + currentRepositoryUrl.endsWith(`/${projectPathWithNamespace}`) + ) { + const prefix = currentRepositoryUrl.slice( + 0, + -projectPathWithNamespace.length - 1, + ); + return toRepositoryUrl(prefix, oldPathWithNamespace); + } + + const baseUrl = + (projectUrl && getOrigin(projectUrl)) || + (currentRepositoryUrl && getOrigin(currentRepositoryUrl)); + if (!baseUrl) { + return undefined; + } + + return toRepositoryUrl(baseUrl, oldPathWithNamespace); +} + +function isRepositoryDeletionEvent(event: GitLabRepositoryUpdateEvent): boolean { + const eventName = asString(event.event_name)?.toLowerCase() ?? ''; + const action = asString(event.action)?.toLowerCase() ?? ''; + + if ( + eventName.includes('destroy') || + eventName.includes('delete') || + action.includes('destroy') || + action.includes('delete') || + action.includes('remove') + ) { + return true; + } + + if (event.deleted_at || event.project?.deleted_at) { + return true; + } + + return Boolean(getToChange(event.changes?.deleted_at)); +} + +async function onRepositoryUpdateEvent( + event: GitLabRepositoryUpdateEvent, +): Promise { + const currentRepositoryUrl = getCurrentRepositoryUrl(event); + const previousRepositoryUrl = getPreviousRepositoryUrl( + event, + currentRepositoryUrl, + ); + + if (isRepositoryDeletionEvent(event)) { + const repositoryUrl = currentRepositoryUrl ?? previousRepositoryUrl; + if (!repositoryUrl) { + return { + result: 'ignored', + reason: + 'GitLab repository_update event did not include sufficient data for repository deletion handling', + }; + } + + return { + result: 'ok', + events: [ + { + type: 'repository.deleted', + url: repositoryUrl, + }, + ], + }; + } + + if ( + previousRepositoryUrl && + currentRepositoryUrl && + previousRepositoryUrl !== currentRepositoryUrl + ) { + return { + result: 'ok', + events: [ + { + type: 'repository.moved', + fromUrl: previousRepositoryUrl, + toUrl: currentRepositoryUrl, + }, + ], + }; + } + + return { + result: 'ignored', + reason: 'GitLab repository_update event did not contain supported changes', + }; +} + +export async function analyzeGitLabWebhookEvent( + eventType: string, + eventPayload: unknown, + options: AnalyzeWebhookEventOptions, +): Promise { + if (!isObject(eventPayload)) { + throw new InputError('GitLab webhook event payload is not an object'); + } + + if (eventType === 'push') { + return onPushEvent(eventPayload as WebhookPushEventSchema, options); + } + + if (eventType === 'repository_update') { + return onRepositoryUpdateEvent(eventPayload as GitLabRepositoryUpdateEvent); + } + + return { + result: 'unsupported-event', + event: eventType, + }; +} \ No newline at end of file diff --git a/plugins/catalog-backend-module-gitlab/src/index.ts b/plugins/catalog-backend-module-gitlab/src/index.ts index 4f64e76395..0ad28b8a4b 100644 --- a/plugins/catalog-backend-module-gitlab/src/index.ts +++ b/plugins/catalog-backend-module-gitlab/src/index.ts @@ -26,6 +26,7 @@ export { GitlabDiscoveryEntityProvider, GitlabOrgDiscoveryEntityProvider, } from './providers'; +export { analyzeGitLabWebhookEvent } from './events/analyzeGitLabWebhookEvent'; export type { GitLabUser, GitLabGroup, diff --git a/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.ts index 4f9626a130..d776e6ecd6 100644 --- a/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.ts @@ -19,7 +19,9 @@ import { createBackendModule, } from '@backstage/backend-plugin-api'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { catalogScmEventsServiceRef } from '@backstage/plugin-catalog-node/alpha'; import { eventsServiceRef } from '@backstage/plugin-events-node'; +import { GitLabScmEventsBridge } from '../events/GitLabScmEventsBridge'; import { GitlabDiscoveryEntityProvider } from '../providers'; /** @@ -36,11 +38,21 @@ export const catalogModuleGitlabDiscoveryEntityProvider = createBackendModule({ deps: { config: coreServices.rootConfig, catalog: catalogProcessingExtensionPoint, + catalogScmEvents: catalogScmEventsServiceRef, logger: coreServices.logger, scheduler: coreServices.scheduler, events: eventsServiceRef, + lifecycle: coreServices.lifecycle, }, - async init({ config, catalog, logger, scheduler, events }) { + async init({ + config, + catalog, + catalogScmEvents, + logger, + scheduler, + events, + lifecycle, + }) { const gitlabDiscoveryEntityProvider = GitlabDiscoveryEntityProvider.fromConfig(config, { logger, @@ -48,6 +60,19 @@ export const catalogModuleGitlabDiscoveryEntityProvider = createBackendModule({ scheduler, }); catalog.addEntityProvider(gitlabDiscoveryEntityProvider); + + const bridge = new GitLabScmEventsBridge({ + logger, + events, + catalogScmEvents, + }); + + lifecycle.addStartupHook(async () => { + await bridge.start(); + }); + lifecycle.addShutdownHook(async () => { + await bridge.stop(); + }); }, }); }, From 093b09d5ec8ff799ac47b859ba8b7bef2f572429 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Sun, 15 Mar 2026 19:50:29 -0500 Subject: [PATCH 021/191] test(catalog-backend-module-gitlab): add analyzer coverage and update module wiring assertions Signed-off-by: Lokesh Kaki --- .../events/analyzeGitLabWebhookEvent.test.ts | 258 ++++++++++++++++++ ...oduleGitlabDiscoveryEntityProvider.test.ts | 13 +- 2 files changed, 268 insertions(+), 3 deletions(-) create mode 100644 plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.test.ts diff --git a/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.test.ts b/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.test.ts new file mode 100644 index 0000000000..bda802d67a --- /dev/null +++ b/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.test.ts @@ -0,0 +1,258 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { mockServices } from '@backstage/backend-test-utils'; +import { InputError } from '@backstage/errors'; +import { analyzeGitLabWebhookEvent } from './analyzeGitLabWebhookEvent'; + +const isRelevantPath = (path: string): boolean => + path.endsWith('.yaml') || path.endsWith('.yml'); + +describe('analyzeGitLabWebhookEvent', () => { + const logger = mockServices.logger.mock(); + + describe('push', () => { + it('handles file add, modify, and delete', async () => { + const payload = { + object_kind: 'push', + ref: 'refs/heads/main', + project: { + web_url: 'https://gitlab.example.com/group-a/repo-a', + path_with_namespace: 'group-a/repo-a', + default_branch: 'main', + }, + commits: [ + { + id: 'c1', + added: ['catalog-info.yaml'], + modified: ['docs/catalog-info.yml'], + removed: [], + }, + { + id: 'c2', + added: [], + modified: [], + removed: ['old/catalog-info.yaml'], + }, + ], + }; + + await expect( + analyzeGitLabWebhookEvent('push', payload, { + logger, + isRelevantPath, + }), + ).resolves.toMatchInlineSnapshot(` + { + "events": [ + { + "context": { + "commitUrl": "https://gitlab.example.com/group-a/repo-a/-/commit/c1", + }, + "type": "location.updated", + "url": "https://gitlab.example.com/group-a/repo-a/-/blob/main/docs/catalog-info.yml", + }, + { + "context": { + "commitUrl": "https://gitlab.example.com/group-a/repo-a/-/commit/c1", + }, + "type": "location.created", + "url": "https://gitlab.example.com/group-a/repo-a/-/blob/main/catalog-info.yaml", + }, + { + "context": { + "commitUrl": "https://gitlab.example.com/group-a/repo-a/-/commit/c2", + }, + "type": "location.deleted", + "url": "https://gitlab.example.com/group-a/repo-a/-/blob/main/old/catalog-info.yaml", + }, + ], + "result": "ok", + } + `); + }); + + it('handles file rename as location move', async () => { + const payload = { + object_kind: 'push', + ref: 'refs/heads/main', + project: { + web_url: 'https://gitlab.example.com/group-a/repo-a', + path_with_namespace: 'group-a/repo-a', + default_branch: 'main', + }, + commits: [ + { + id: 'c3', + added: ['new/catalog-info.yaml'], + modified: [], + removed: ['old/catalog-info.yaml'], + }, + ], + }; + + await expect( + analyzeGitLabWebhookEvent('push', payload, { + logger, + isRelevantPath, + }), + ).resolves.toMatchInlineSnapshot(` + { + "events": [ + { + "context": { + "commitUrl": "https://gitlab.example.com/group-a/repo-a/-/commit/c3", + }, + "fromUrl": "https://gitlab.example.com/group-a/repo-a/-/blob/main/old/catalog-info.yaml", + "toUrl": "https://gitlab.example.com/group-a/repo-a/-/blob/main/new/catalog-info.yaml", + "type": "location.moved", + }, + ], + "result": "ok", + } + `); + }); + }); + + describe('repository_update', () => { + it('handles repository rename as repository move', async () => { + const payload = { + object_kind: 'repository_update', + event_name: 'project_rename', + old_path_with_namespace: 'group-a/repo-a-old', + project: { + web_url: 'https://gitlab.example.com/group-a/repo-a', + path_with_namespace: 'group-a/repo-a', + }, + }; + + await expect( + analyzeGitLabWebhookEvent('repository_update', payload, { + logger, + isRelevantPath, + }), + ).resolves.toMatchInlineSnapshot(` + { + "events": [ + { + "fromUrl": "https://gitlab.example.com/group-a/repo-a-old", + "toUrl": "https://gitlab.example.com/group-a/repo-a", + "type": "repository.moved", + }, + ], + "result": "ok", + } + `); + }); + + it('handles repository transfer as repository move', async () => { + const payload = { + object_kind: 'repository_update', + event_name: 'project_transfer', + project: { + web_url: 'https://gitlab.example.com/group-b/repo-a', + path_with_namespace: 'group-b/repo-a', + }, + changes: { + path_with_namespace: { + from: 'group-a/repo-a', + to: 'group-b/repo-a', + }, + }, + }; + + await expect( + analyzeGitLabWebhookEvent('repository_update', payload, { + logger, + isRelevantPath, + }), + ).resolves.toMatchInlineSnapshot(` + { + "events": [ + { + "fromUrl": "https://gitlab.example.com/group-a/repo-a", + "toUrl": "https://gitlab.example.com/group-b/repo-a", + "type": "repository.moved", + }, + ], + "result": "ok", + } + `); + }); + + it('handles repository delete', async () => { + const payload = { + object_kind: 'repository_update', + event_name: 'project_destroy', + project: { + web_url: 'https://gitlab.example.com/group-a/repo-a', + path_with_namespace: 'group-a/repo-a', + }, + }; + + await expect( + analyzeGitLabWebhookEvent('repository_update', payload, { + logger, + isRelevantPath, + }), + ).resolves.toMatchInlineSnapshot(` + { + "events": [ + { + "type": "repository.deleted", + "url": "https://gitlab.example.com/group-a/repo-a", + }, + ], + "result": "ok", + } + `); + }); + }); + + it('returns unsupported-event for unsupported event types', async () => { + await expect( + analyzeGitLabWebhookEvent( + 'merge_request', + { + object_kind: 'merge_request', + }, + { + logger, + isRelevantPath, + }, + ), + ).resolves.toEqual({ + result: 'unsupported-event', + event: 'merge_request', + }); + }); + + it('throws on malformed payloads', async () => { + await expect( + analyzeGitLabWebhookEvent('push', undefined, { + logger, + isRelevantPath, + }), + ).rejects.toBeInstanceOf(InputError); + + await expect( + analyzeGitLabWebhookEvent('push', [], { + logger, + isRelevantPath, + }), + ).rejects.toBeInstanceOf(InputError); + }); +}); \ No newline at end of file diff --git a/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.test.ts index b99fc6e2da..e664ecdada 100644 --- a/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/module/catalogModuleGitlabDiscoveryEntityProvider.test.ts @@ -100,9 +100,16 @@ describe('catalogModuleGitlabDiscoveryEntityProvider', () => { 'GitlabDiscoveryEntityProvider:test-id', ); await provider.connect(connection); - expect(events.subscribed).toHaveLength(1); - expect(events.subscribed[0].id).toEqual( - 'GitlabDiscoveryEntityProvider:test-id', + expect(events.subscribed).toHaveLength(2); + expect(events.subscribed).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: 'catalog-gitlab-scm-events-bridge', + }), + expect.objectContaining({ + id: 'GitlabDiscoveryEntityProvider:test-id', + }), + ]), ); expect(runner).toHaveBeenCalledTimes(1); }); From 54a830018115ff7345b882f33bb56ff376957d04 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Sun, 15 Mar 2026 19:50:33 -0500 Subject: [PATCH 022/191] chore(changeset): add patch changeset for GitLab SCM event translation layer Signed-off-by: Lokesh Kaki --- .changeset/gitlab-scm-events-layer.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/gitlab-scm-events-layer.md diff --git a/.changeset/gitlab-scm-events-layer.md b/.changeset/gitlab-scm-events-layer.md new file mode 100644 index 0000000000..24349d01b1 --- /dev/null +++ b/.changeset/gitlab-scm-events-layer.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +Add GitLab SCM event translation layer for instant catalog reprocessing. From bf7aeb5e31584bb700df921ca056cfc97f615473 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Sun, 15 Mar 2026 20:12:12 -0500 Subject: [PATCH 023/191] fix(catalog-backend-module-azure): fix ReDoS regex, URL encoding, error message, and missing deps Signed-off-by: Lokesh Kaki --- .../catalog-backend-module-azure/package.json | 2 ++ .../events/analyzeAzureDevOpsWebhookEvent.ts | 33 +++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 9c8de87ffb..51e87bdd06 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -55,9 +55,11 @@ "@azure/storage-blob": "^12.5.0", "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-node": "workspace:^", + "@backstage/plugin-events-node": "workspace:^", "uuid": "^11.0.0" }, "devDependencies": { diff --git a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts index 054eb310f7..10bf5e75d7 100644 --- a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts +++ b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts @@ -154,9 +154,18 @@ function toLocationUrl(options: { return undefined; } + const url = new URL(options.remoteUrl); const branch = branchNameFromRef(options.branchRef); - const branchSuffix = branch ? `&version=GB${branch}` : ''; - return encodeURI(`${options.remoteUrl}?path=${options.path}${branchSuffix}`); + // Encode each path segment individually to protect against special chars while + // preserving '/' separators, which is what Azure DevOps expects in the path param. + const encodedPath = options.path + .split('/') + .map(encodeURIComponent) + .join('/'); + url.search = branch + ? `path=${encodedPath}&version=GB${encodeURIComponent(branch)}` + : `path=${encodedPath}`; + return url.toString(); } function toCommitUrl( @@ -359,11 +368,16 @@ function replaceRepoNameInRemoteUrl( if (!remoteUrl || !repoName) { return undefined; } - const match = remoteUrl.match(/^(.*\/_git\/)([^/?#]+)(.*)$/); - if (!match) { + const gitMarker = '/_git/'; + const gitIdx = remoteUrl.indexOf(gitMarker); + if (gitIdx === -1) { return undefined; } - return `${match[1]}${repoName}${match[3]}`; + const prefix = remoteUrl.slice(0, gitIdx + gitMarker.length); + const rest = remoteUrl.slice(gitIdx + gitMarker.length); + const endIdx = rest.search(/[/?#]/); + const suffix = endIdx === -1 ? '' : rest.slice(endIdx); + return `${prefix}${repoName}${suffix}`; } async function onPushEvent( @@ -465,11 +479,18 @@ async function onRepositoryEvent( if (eventType === 'git.repo.renamed' && toUrl) { const oldName = asString(resource?.oldName); + if (!oldName) { + return { + result: 'ignored', + reason: 'Azure DevOps repository renamed event is missing oldName', + }; + } const fromUrl = replaceRepoNameInRemoteUrl(toUrl, oldName); if (!fromUrl) { return { result: 'ignored', - reason: 'Azure DevOps repository renamed event is missing oldName', + reason: + 'Azure DevOps repository renamed event has an unexpected repository.remoteUrl format', }; } From 17b97abca63a42de901f8716eb96d5bc51303ba5 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Sun, 15 Mar 2026 20:28:07 -0500 Subject: [PATCH 024/191] fix(catalog-backend-module-gitlab): move analyzer to alpha export, make logger optional, fix type cast Signed-off-by: Lokesh Kaki --- plugins/catalog-backend-module-gitlab/src/alpha.ts | 2 ++ .../src/events/analyzeGitLabWebhookEvent.ts | 4 ++-- plugins/catalog-backend-module-gitlab/src/index.ts | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-backend-module-gitlab/src/alpha.ts b/plugins/catalog-backend-module-gitlab/src/alpha.ts index 4c49bb9de9..0b29a09ba9 100644 --- a/plugins/catalog-backend-module-gitlab/src/alpha.ts +++ b/plugins/catalog-backend-module-gitlab/src/alpha.ts @@ -19,3 +19,5 @@ import { catalogModuleGitlabDiscoveryEntityProvider } from './module/catalogModu /** @alpha */ const _feature = catalogModuleGitlabDiscoveryEntityProvider; export default _feature; + +export { analyzeGitLabWebhookEvent } from './events/analyzeGitLabWebhookEvent'; diff --git a/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts b/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts index 4bea0230f0..6b2d44ddd6 100644 --- a/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts +++ b/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts @@ -22,7 +22,7 @@ import { WebhookPushEventSchema } from '@gitbeaker/rest'; type StringRecord = Record; export interface AnalyzeWebhookEventOptions { - logger: LoggerService; + logger?: LoggerService; isRelevantPath: (path: string) => boolean; } @@ -521,7 +521,7 @@ export async function analyzeGitLabWebhookEvent( } if (eventType === 'push') { - return onPushEvent(eventPayload as WebhookPushEventSchema, options); + return onPushEvent(eventPayload as unknown as WebhookPushEventSchema, options); } if (eventType === 'repository_update') { diff --git a/plugins/catalog-backend-module-gitlab/src/index.ts b/plugins/catalog-backend-module-gitlab/src/index.ts index 0ad28b8a4b..4f64e76395 100644 --- a/plugins/catalog-backend-module-gitlab/src/index.ts +++ b/plugins/catalog-backend-module-gitlab/src/index.ts @@ -26,7 +26,6 @@ export { GitlabDiscoveryEntityProvider, GitlabOrgDiscoveryEntityProvider, } from './providers'; -export { analyzeGitLabWebhookEvent } from './events/analyzeGitLabWebhookEvent'; export type { GitLabUser, GitLabGroup, From 121690f95cea00c6533f2615e821111c4d289237 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Sun, 15 Mar 2026 20:34:18 -0500 Subject: [PATCH 025/191] chore: update yarn.lock for azure scm events deps Signed-off-by: Lokesh Kaki --- yarn.lock | 2 ++ 1 file changed, 2 insertions(+) diff --git a/yarn.lock b/yarn.lock index 32b09cc5e3..db4e5b1e50 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4474,9 +4474,11 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" + "@backstage/plugin-events-node": "workspace:^" msw: "npm:^1.0.0" uuid: "npm:^11.0.0" languageName: unknown From 0b0d8fa7f164fea369c63f208711e33a884c8bc6 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Sun, 15 Mar 2026 20:34:36 -0500 Subject: [PATCH 026/191] chore: update yarn.lock for gitlab scm events deps Signed-off-by: Lokesh Kaki --- yarn.lock | 1 + 1 file changed, 1 insertion(+) diff --git a/yarn.lock b/yarn.lock index 32b09cc5e3..1adcb16cb6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4669,6 +4669,7 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-node": "workspace:^" From 308b36ffad8a7b920c1653df8bf8c9cf76a5229a Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Sun, 15 Mar 2026 21:15:13 -0500 Subject: [PATCH 027/191] fix(catalog-backend-module-azure): fix URL encoding alignment and move analyzer to alpha export Signed-off-by: Lokesh Kaki --- .../catalog-backend-module-azure/src/alpha.ts | 2 ++ .../analyzeAzureDevOpsWebhookEvent.test.ts | 34 +++++++++++++++++++ .../events/analyzeAzureDevOpsWebhookEvent.ts | 12 ++----- .../catalog-backend-module-azure/src/index.ts | 1 - 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/plugins/catalog-backend-module-azure/src/alpha.ts b/plugins/catalog-backend-module-azure/src/alpha.ts index cba672ce49..3e2354114f 100644 --- a/plugins/catalog-backend-module-azure/src/alpha.ts +++ b/plugins/catalog-backend-module-azure/src/alpha.ts @@ -19,3 +19,5 @@ import { default as feature } from './module'; /** @alpha */ const _feature = feature; export default _feature; + +export { analyzeAzureDevOpsWebhookEvent } from './events/analyzeAzureDevOpsWebhookEvent'; diff --git a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.test.ts b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.test.ts index 9edeb1602d..77e30946cc 100644 --- a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.test.ts +++ b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.test.ts @@ -127,6 +127,40 @@ describe('analyzeAzureDevOpsWebhookEvent', () => { }); }); + it('does not double-encode branch names containing slashes', async () => { + const repoWithSlashBranch = { + ...baseRepository, + defaultBranch: 'refs/heads/feature/my-branch', + }; + await expect( + analyzeAzureDevOpsWebhookEvent( + 'git.push', + withPushEvent({ + repository: repoWithSlashBranch, + refUpdates: [{ name: 'refs/heads/feature/my-branch' }], + commits: [ + { + commitId: 'abc', + changes: [ + { changeType: 'add', item: { path: '/catalog-info.yaml' } }, + ], + }, + ], + }), + { isRelevantPath }, + ), + ).resolves.toEqual({ + result: 'ok', + events: [ + { + type: 'location.created', + url: `${baseRepository.remoteUrl}?path=/catalog-info.yaml&version=GBfeature/my-branch`, + context: { commitUrl: `${baseRepository.remoteUrl}/commit/abc` }, + }, + ], + }); + }); + it('ignores non-default-branch pushes', async () => { await expect( analyzeAzureDevOpsWebhookEvent( diff --git a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts index 10bf5e75d7..0840c8b1d3 100644 --- a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts +++ b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts @@ -156,16 +156,10 @@ function toLocationUrl(options: { const url = new URL(options.remoteUrl); const branch = branchNameFromRef(options.branchRef); - // Encode each path segment individually to protect against special chars while - // preserving '/' separators, which is what Azure DevOps expects in the path param. - const encodedPath = options.path - .split('/') - .map(encodeURIComponent) - .join('/'); url.search = branch - ? `path=${encodedPath}&version=GB${encodeURIComponent(branch)}` - : `path=${encodedPath}`; - return url.toString(); + ? `path=${options.path}&version=GB${branch}` + : `path=${options.path}`; + return encodeURI(url.toString()); } function toCommitUrl( diff --git a/plugins/catalog-backend-module-azure/src/index.ts b/plugins/catalog-backend-module-azure/src/index.ts index f1c5c66c48..dbbdff67ce 100644 --- a/plugins/catalog-backend-module-azure/src/index.ts +++ b/plugins/catalog-backend-module-azure/src/index.ts @@ -23,4 +23,3 @@ export { default } from './module'; export { AzureDevOpsDiscoveryProcessor } from './processors'; export { AzureDevOpsEntityProvider } from './providers'; -export { analyzeAzureDevOpsWebhookEvent } from './events/analyzeAzureDevOpsWebhookEvent'; From 98316359294e02d7258ef8f454733813ee0a500c Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Sun, 15 Mar 2026 21:19:59 -0500 Subject: [PATCH 028/191] fix(catalog-backend-module-gitlab): fix bridge race condition, remove unused logger and dead PathState variant Signed-off-by: Lokesh Kaki --- .../src/events/GitLabScmEventsBridge.ts | 13 +++----- .../src/events/analyzeGitLabWebhookEvent.ts | 31 +++++++++---------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/plugins/catalog-backend-module-gitlab/src/events/GitLabScmEventsBridge.ts b/plugins/catalog-backend-module-gitlab/src/events/GitLabScmEventsBridge.ts index 34b9b5c9c2..1809b31156 100644 --- a/plugins/catalog-backend-module-gitlab/src/events/GitLabScmEventsBridge.ts +++ b/plugins/catalog-backend-module-gitlab/src/events/GitLabScmEventsBridge.ts @@ -99,10 +99,6 @@ export class GitLabScmEventsBridge { return; } - while (this.#pendingPublish) { - await this.#pendingPublish; - } - if (this.#shuttingDown) { this.#logger.warn( `Skipping GitLab webhook event of type "${eventType}" on topic "${params.topic}" because the bridge is shutting down`, @@ -110,7 +106,8 @@ export class GitLabScmEventsBridge { return; } - this.#pendingPublish = Promise.resolve().then(async () => { + const previous = this.#pendingPublish ?? Promise.resolve(); + const current = previous.then(async () => { try { const output = await analyzeGitLabWebhookEvent( eventType, @@ -143,10 +140,10 @@ export class GitLabScmEventsBridge { error, ); } finally { - this.#pendingPublish = undefined; + // no-op; chain handles ordering } }); - - await this.#pendingPublish; + this.#pendingPublish = current; + await current; } } \ No newline at end of file diff --git a/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts b/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts index 6b2d44ddd6..77f11f6432 100644 --- a/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts +++ b/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts @@ -53,10 +53,6 @@ type PathState = type: 'removed'; commitUrl?: string; } - | { - type: 'modified'; - commitUrl?: string; - } | { type: 'renamed'; fromPath: string; @@ -173,12 +169,6 @@ function pathStateToCatalogScmEvent( url: toBlobUrl(path), context, }; - case 'modified': - return { - type: 'location.updated', - url: toBlobUrl(path), - context, - }; case 'renamed': return { type: 'location.moved', @@ -520,16 +510,23 @@ export async function analyzeGitLabWebhookEvent( throw new InputError('GitLab webhook event payload is not an object'); } + let result: AnalyzeWebhookEventResult; + if (eventType === 'push') { - return onPushEvent(eventPayload as unknown as WebhookPushEventSchema, options); + result = await onPushEvent(eventPayload as unknown as WebhookPushEventSchema, options); + } else if (eventType === 'repository_update') { + result = await onRepositoryUpdateEvent(eventPayload as GitLabRepositoryUpdateEvent); + } else { + result = { result: 'unsupported-event', event: eventType }; } - if (eventType === 'repository_update') { - return onRepositoryUpdateEvent(eventPayload as GitLabRepositoryUpdateEvent); + if (result.result === 'ignored') { + options.logger?.debug(`GitLab webhook event ignored: ${result.reason}`); + } else if (result.result === 'aborted') { + options.logger?.debug(`GitLab webhook event aborted: ${result.reason}`); + } else if (result.result === 'unsupported-event') { + options.logger?.debug(`GitLab webhook event unsupported: ${result.event}`); } - return { - result: 'unsupported-event', - event: eventType, - }; + return result; } \ No newline at end of file From 1759bfefc817bd7ac0e95a7dcf535b774d63b03a Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Tue, 17 Mar 2026 19:35:43 -0500 Subject: [PATCH 029/191] fix(catalog-backend-module-gitlab): fix prettier formatting, export alpha types, and add API reports - Run prettier on changed files (GitLabScmEventsBridge, analyzeGitLabWebhookEvent, test files) - Export AnalyzeWebhookEventOptions and AnalyzeWebhookEventResult from alpha entry point - Add @alpha JSDoc tags to exported types and function - Regenerate report-alpha.api.md with updated API surface Signed-off-by: Lokesh Kaki --- .../report-alpha.api.md | 36 +++++++++++++++++++ .../src/alpha.ts | 6 +++- .../src/events/GitLabScmEventsBridge.ts | 2 +- .../events/analyzeGitLabWebhookEvent.test.ts | 2 +- .../src/events/analyzeGitLabWebhookEvent.ts | 31 ++++++++++------ 5 files changed, 64 insertions(+), 13 deletions(-) diff --git a/plugins/catalog-backend-module-gitlab/report-alpha.api.md b/plugins/catalog-backend-module-gitlab/report-alpha.api.md index e8ef479d82..00e452642e 100644 --- a/plugins/catalog-backend-module-gitlab/report-alpha.api.md +++ b/plugins/catalog-backend-module-gitlab/report-alpha.api.md @@ -4,6 +4,42 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; +import { CatalogScmEvent } from '@backstage/plugin-catalog-node/alpha'; +import { LoggerService } from '@backstage/backend-plugin-api'; + +// @alpha (undocumented) +export function analyzeGitLabWebhookEvent( + eventType: string, + eventPayload: unknown, + options: AnalyzeWebhookEventOptions, +): Promise; + +// @alpha (undocumented) +export interface AnalyzeWebhookEventOptions { + // (undocumented) + isRelevantPath: (path: string) => boolean; + // (undocumented) + logger?: LoggerService; +} + +// @alpha (undocumented) +export type AnalyzeWebhookEventResult = + | { + result: 'unsupported-event'; + event: string; + } + | { + result: 'ignored'; + reason: string; + } + | { + result: 'aborted'; + reason: string; + } + | { + result: 'ok'; + events: CatalogScmEvent[]; + }; // @alpha (undocumented) const _feature: BackendFeature; diff --git a/plugins/catalog-backend-module-gitlab/src/alpha.ts b/plugins/catalog-backend-module-gitlab/src/alpha.ts index 0b29a09ba9..aaf5096742 100644 --- a/plugins/catalog-backend-module-gitlab/src/alpha.ts +++ b/plugins/catalog-backend-module-gitlab/src/alpha.ts @@ -20,4 +20,8 @@ import { catalogModuleGitlabDiscoveryEntityProvider } from './module/catalogModu const _feature = catalogModuleGitlabDiscoveryEntityProvider; export default _feature; -export { analyzeGitLabWebhookEvent } from './events/analyzeGitLabWebhookEvent'; +export { + analyzeGitLabWebhookEvent, + type AnalyzeWebhookEventOptions, + type AnalyzeWebhookEventResult, +} from './events/analyzeGitLabWebhookEvent'; diff --git a/plugins/catalog-backend-module-gitlab/src/events/GitLabScmEventsBridge.ts b/plugins/catalog-backend-module-gitlab/src/events/GitLabScmEventsBridge.ts index 1809b31156..1e44ffcf72 100644 --- a/plugins/catalog-backend-module-gitlab/src/events/GitLabScmEventsBridge.ts +++ b/plugins/catalog-backend-module-gitlab/src/events/GitLabScmEventsBridge.ts @@ -146,4 +146,4 @@ export class GitLabScmEventsBridge { this.#pendingPublish = current; await current; } -} \ No newline at end of file +} diff --git a/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.test.ts b/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.test.ts index bda802d67a..8e792f1822 100644 --- a/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.test.ts @@ -255,4 +255,4 @@ describe('analyzeGitLabWebhookEvent', () => { }), ).rejects.toBeInstanceOf(InputError); }); -}); \ No newline at end of file +}); diff --git a/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts b/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts index 77f11f6432..2ca7a14a6a 100644 --- a/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts +++ b/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts @@ -21,11 +21,13 @@ import { WebhookPushEventSchema } from '@gitbeaker/rest'; type StringRecord = Record; +/** @alpha */ export interface AnalyzeWebhookEventOptions { logger?: LoggerService; isRelevantPath: (path: string) => boolean; } +/** @alpha */ export type AnalyzeWebhookEventResult = | { result: 'unsupported-event'; @@ -135,9 +137,10 @@ function extractBranchName(ref?: string): string | undefined { return ref.slice('refs/heads/'.length); } -function getCommitUrl(commit: GitLabPushCommit, repositoryUrl?: string): - | string - | undefined { +function getCommitUrl( + commit: GitLabPushCommit, + repositoryUrl?: string, +): string | undefined { if (commit.url) { return commit.url; } @@ -297,9 +300,9 @@ async function onPushEvent( } } - const commits = (Array.isArray(event.commits) - ? event.commits - : []) as GitLabPushCommit[]; + const commits = ( + Array.isArray(event.commits) ? event.commits : [] + ) as GitLabPushCommit[]; if (!commits.length) { return { @@ -427,7 +430,9 @@ function getPreviousRepositoryUrl( return toRepositoryUrl(baseUrl, oldPathWithNamespace); } -function isRepositoryDeletionEvent(event: GitLabRepositoryUpdateEvent): boolean { +function isRepositoryDeletionEvent( + event: GitLabRepositoryUpdateEvent, +): boolean { const eventName = asString(event.event_name)?.toLowerCase() ?? ''; const action = asString(event.action)?.toLowerCase() ?? ''; @@ -501,6 +506,7 @@ async function onRepositoryUpdateEvent( }; } +/** @alpha */ export async function analyzeGitLabWebhookEvent( eventType: string, eventPayload: unknown, @@ -513,9 +519,14 @@ export async function analyzeGitLabWebhookEvent( let result: AnalyzeWebhookEventResult; if (eventType === 'push') { - result = await onPushEvent(eventPayload as unknown as WebhookPushEventSchema, options); + result = await onPushEvent( + eventPayload as unknown as WebhookPushEventSchema, + options, + ); } else if (eventType === 'repository_update') { - result = await onRepositoryUpdateEvent(eventPayload as GitLabRepositoryUpdateEvent); + result = await onRepositoryUpdateEvent( + eventPayload as GitLabRepositoryUpdateEvent, + ); } else { result = { result: 'unsupported-event', event: eventType }; } @@ -529,4 +540,4 @@ export async function analyzeGitLabWebhookEvent( } return result; -} \ No newline at end of file +} From 9af9fc1a85340bed776459f2988d8de1005c2b61 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Tue, 17 Mar 2026 20:00:48 -0500 Subject: [PATCH 030/191] fix(catalog-backend-module-gitlab): fix push event rename heuristic and add API docs Signed-off-by: Lokesh Kaki --- .../events/analyzeGitLabWebhookEvent.test.ts | 14 ++- .../src/events/analyzeGitLabWebhookEvent.ts | 92 +++++++------------ 2 files changed, 45 insertions(+), 61 deletions(-) diff --git a/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.test.ts b/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.test.ts index 8e792f1822..73c702c790 100644 --- a/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.test.ts @@ -85,7 +85,7 @@ describe('analyzeGitLabWebhookEvent', () => { `); }); - it('handles file rename as location move', async () => { + it('handles file add and delete in the same commit as separate events', async () => { const payload = { object_kind: 'push', ref: 'refs/heads/main', @@ -116,9 +116,15 @@ describe('analyzeGitLabWebhookEvent', () => { "context": { "commitUrl": "https://gitlab.example.com/group-a/repo-a/-/commit/c3", }, - "fromUrl": "https://gitlab.example.com/group-a/repo-a/-/blob/main/old/catalog-info.yaml", - "toUrl": "https://gitlab.example.com/group-a/repo-a/-/blob/main/new/catalog-info.yaml", - "type": "location.moved", + "type": "location.created", + "url": "https://gitlab.example.com/group-a/repo-a/-/blob/main/new/catalog-info.yaml", + }, + { + "context": { + "commitUrl": "https://gitlab.example.com/group-a/repo-a/-/commit/c3", + }, + "type": "location.deleted", + "url": "https://gitlab.example.com/group-a/repo-a/-/blob/main/old/catalog-info.yaml", }, ], "result": "ok", diff --git a/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts b/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts index 2ca7a14a6a..f733d3b07d 100644 --- a/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts +++ b/plugins/catalog-backend-module-gitlab/src/events/analyzeGitLabWebhookEvent.ts @@ -21,13 +21,31 @@ import { WebhookPushEventSchema } from '@gitbeaker/rest'; type StringRecord = Record; -/** @alpha */ +/** + * Options for {@link analyzeGitLabWebhookEvent}. + * @alpha + */ export interface AnalyzeWebhookEventOptions { + /** Optional logger for debug output when events are ignored or unsupported. */ logger?: LoggerService; + /** + * Predicate that returns true for file paths that are relevant to the + * catalog (e.g. paths ending in `.yaml` or `.yml`). + */ isRelevantPath: (path: string) => boolean; } -/** @alpha */ +/** + * The result of analyzing a GitLab webhook event. + * + * - `ok` — one or more catalog SCM events were produced. + * - `ignored` — the event was valid but not relevant (e.g. push to a + * non-default branch, or no catalog files affected). + * - `aborted` — the event could not be fully processed due to missing data. + * - `unsupported-event` — the event type is not handled by this analyzer. + * + * @alpha + */ export type AnalyzeWebhookEventResult = | { result: 'unsupported-event'; @@ -55,11 +73,6 @@ type PathState = type: 'removed'; commitUrl?: string; } - | { - type: 'renamed'; - fromPath: string; - commitUrl?: string; - } | { type: 'changed'; commitUrl?: string; @@ -172,13 +185,6 @@ function pathStateToCatalogScmEvent( url: toBlobUrl(path), context, }; - case 'renamed': - return { - type: 'location.moved', - fromUrl: toBlobUrl(event.fromPath), - toUrl: toBlobUrl(path), - context, - }; case 'changed': return { type: 'location.updated', @@ -226,13 +232,6 @@ function applyRemovedPath( pathState.set(path, { type: 'removed', commitUrl }); return; } - if (previous.type === 'renamed') { - if (!pathState.has(previous.fromPath)) { - pathState.set(previous.fromPath, { type: 'removed', commitUrl }); - } - pathState.delete(path); - return; - } pathState.set(path, previous); } @@ -253,34 +252,6 @@ function applyModifiedPath( pathState.set(path, previous); } -function applyRenamedPath( - pathState: Map, - fromPath: string, - toPath: string, - commitUrl: string | undefined, -) { - const previous = pathState.get(fromPath); - pathState.delete(fromPath); - - if (!previous) { - pathState.set(toPath, { type: 'renamed', fromPath, commitUrl }); - return; - } - if (previous.type === 'added') { - pathState.set(toPath, { type: 'added', commitUrl }); - return; - } - if (previous.type === 'renamed') { - pathState.set(toPath, { - type: 'renamed', - fromPath: previous.fromPath, - commitUrl, - }); - return; - } - pathState.set(toPath, { type: 'renamed', fromPath, commitUrl }); -} - async function onPushEvent( event: WebhookPushEventSchema, options: AnalyzeWebhookEventOptions, @@ -328,16 +299,11 @@ async function onPushEvent( applyModifiedPath(pathState, path, commitUrl); } - const renamePairs = Math.min(added.length, removed.length); - for (let i = 0; i < renamePairs; i++) { - applyRenamedPath(pathState, removed[i], added[i], commitUrl); - } - - for (const path of added.slice(renamePairs)) { + for (const path of added) { applyAddedPath(pathState, path, commitUrl); } - for (const path of removed.slice(renamePairs)) { + for (const path of removed) { applyRemovedPath(pathState, path, commitUrl); } } @@ -506,7 +472,19 @@ async function onRepositoryUpdateEvent( }; } -/** @alpha */ +/** + * Analyzes a GitLab webhook event and translates it into zero or more catalog + * SCM events that entity providers can act on. + * + * Supported event types: + * - `push` — translates file-level adds, modifications, and deletions on the + * default branch into `location.created`, `location.updated`, and + * `location.deleted` events for paths matching `isRelevantPath`. + * - `repository_update` — translates repository renames, transfers, and + * deletions into `repository.moved` and `repository.deleted` events. + * + * @alpha + */ export async function analyzeGitLabWebhookEvent( eventType: string, eventPayload: unknown, From 47d475113cff9b899b0c0ca507414df18bd1183b Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Tue, 17 Mar 2026 20:27:18 -0500 Subject: [PATCH 031/191] fix(catalog-backend-module-gitlab): regenerate API report after JSDoc additions Signed-off-by: Lokesh Kaki --- plugins/catalog-backend-module-gitlab/report-alpha.api.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/plugins/catalog-backend-module-gitlab/report-alpha.api.md b/plugins/catalog-backend-module-gitlab/report-alpha.api.md index 00e452642e..b02e99363d 100644 --- a/plugins/catalog-backend-module-gitlab/report-alpha.api.md +++ b/plugins/catalog-backend-module-gitlab/report-alpha.api.md @@ -7,22 +7,20 @@ import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogScmEvent } from '@backstage/plugin-catalog-node/alpha'; import { LoggerService } from '@backstage/backend-plugin-api'; -// @alpha (undocumented) +// @alpha export function analyzeGitLabWebhookEvent( eventType: string, eventPayload: unknown, options: AnalyzeWebhookEventOptions, ): Promise; -// @alpha (undocumented) +// @alpha export interface AnalyzeWebhookEventOptions { - // (undocumented) isRelevantPath: (path: string) => boolean; - // (undocumented) logger?: LoggerService; } -// @alpha (undocumented) +// @alpha export type AnalyzeWebhookEventResult = | { result: 'unsupported-event'; From 5bc3a400d5e6f257806dff617a8a77664257e3b4 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Tue, 17 Mar 2026 21:13:59 -0500 Subject: [PATCH 032/191] fix(catalog-backend-module-azure): fix prettier formatting, export alpha types, and add API reports Signed-off-by: Lokesh Kaki --- .../report-alpha.api.md | 32 +++++++++++++++ .../catalog-backend-module-azure/src/alpha.ts | 6 ++- .../analyzeAzureDevOpsWebhookEvent.test.ts | 5 ++- .../events/analyzeAzureDevOpsWebhookEvent.ts | 39 +++++++++++++++++-- 4 files changed, 76 insertions(+), 6 deletions(-) diff --git a/plugins/catalog-backend-module-azure/report-alpha.api.md b/plugins/catalog-backend-module-azure/report-alpha.api.md index 3336b07e09..35853db61c 100644 --- a/plugins/catalog-backend-module-azure/report-alpha.api.md +++ b/plugins/catalog-backend-module-azure/report-alpha.api.md @@ -4,6 +4,38 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; +import { CatalogScmEvent } from '@backstage/plugin-catalog-node/alpha'; + +// @alpha +export function analyzeAzureDevOpsWebhookEvent( + eventType: string, + eventPayload: unknown, + options: AnalyzeAzureDevOpsWebhookEventOptions, +): Promise; + +// @alpha +export interface AnalyzeAzureDevOpsWebhookEventOptions { + isRelevantPath: (path: string) => boolean; +} + +// @alpha +export type AnalyzeAzureDevOpsWebhookEventResult = + | { + result: 'unsupported-event'; + event: string; + } + | { + result: 'ignored'; + reason: string; + } + | { + result: 'aborted'; + reason: string; + } + | { + result: 'ok'; + events: CatalogScmEvent[]; + }; // @alpha (undocumented) const _feature: BackendFeature; diff --git a/plugins/catalog-backend-module-azure/src/alpha.ts b/plugins/catalog-backend-module-azure/src/alpha.ts index 3e2354114f..14a7fe1379 100644 --- a/plugins/catalog-backend-module-azure/src/alpha.ts +++ b/plugins/catalog-backend-module-azure/src/alpha.ts @@ -20,4 +20,8 @@ import { default as feature } from './module'; const _feature = feature; export default _feature; -export { analyzeAzureDevOpsWebhookEvent } from './events/analyzeAzureDevOpsWebhookEvent'; +export { + analyzeAzureDevOpsWebhookEvent, + type AnalyzeAzureDevOpsWebhookEventOptions, + type AnalyzeAzureDevOpsWebhookEventResult, +} from './events/analyzeAzureDevOpsWebhookEvent'; diff --git a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.test.ts b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.test.ts index 77e30946cc..4a520aa5fb 100644 --- a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.test.ts +++ b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.test.ts @@ -22,7 +22,8 @@ const baseRepository = { id: 'repo-id', name: 'example-repo', defaultBranch: 'refs/heads/main', - remoteUrl: 'https://dev.azure.com/example-org/example-project/_git/example-repo', + remoteUrl: + 'https://dev.azure.com/example-org/example-project/_git/example-repo', }; const withPushEvent = (resource: Record) => ({ @@ -287,4 +288,4 @@ describe('analyzeAzureDevOpsWebhookEvent', () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts index 0840c8b1d3..0dfa1c0f99 100644 --- a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts +++ b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts @@ -17,10 +17,28 @@ import { InputError } from '@backstage/errors'; import { CatalogScmEvent } from '@backstage/plugin-catalog-node/alpha'; +/** + * Options for {@link analyzeAzureDevOpsWebhookEvent}. + * @alpha + */ export interface AnalyzeAzureDevOpsWebhookEventOptions { + /** + * Predicate that returns true for file paths that are relevant to the + * catalog (e.g. paths ending in `.yaml` or `.yml`). + */ isRelevantPath: (path: string) => boolean; } +/** + * The result of analyzing an Azure DevOps webhook event. + * + * - `ok` — one or more catalog SCM events were produced. + * - `ignored` — the event was valid but not relevant. + * - `aborted` — the event could not be fully processed due to missing data. + * - `unsupported-event` — the event type is not handled by this analyzer. + * + * @alpha + */ export type AnalyzeAzureDevOpsWebhookEventResult = | { result: 'unsupported-event'; @@ -263,7 +281,9 @@ function normalizePushCommitChanges( for (const change of commit.changes ?? []) { const changeType = change.changeType?.toLowerCase() ?? ''; - const toPath = normalizePath(change.item?.path ?? change.path ?? change.newPath); + const toPath = normalizePath( + change.item?.path ?? change.path ?? change.newPath, + ); const fromPath = normalizePath( change.originalPath ?? change.item?.originalPath ?? @@ -380,9 +400,11 @@ async function onPushEvent( ): Promise { const resource = asObject(eventPayload.resource); const repository = getRepository(resource); - const refUpdates = (resource?.refUpdates as AzurePushRefUpdate[] | undefined) ?? []; + const refUpdates = + (resource?.refUpdates as AzurePushRefUpdate[] | undefined) ?? []; const commits = (resource?.commits as AzurePushCommit[] | undefined) ?? []; - const contextUrl = asString(resource?.url) ?? repository.remoteUrl ?? ''; + const contextUrl = + asString(resource?.url) ?? repository.remoteUrl ?? ''; if (commits.length === 0) { return { @@ -507,6 +529,17 @@ async function onRepositoryEvent( }; } +/** + * Analyzes an Azure DevOps webhook event and translates it into zero or more + * catalog SCM events that entity providers can act on. + * + * Supported event types: + * - `git.push` — translates file-level adds, modifications, and deletions on + * the default branch into catalog SCM events for paths matching + * `isRelevantPath`. + * + * @alpha + */ export async function analyzeAzureDevOpsWebhookEvent( eventType: string, eventPayload: unknown, From f2158637435a3d99add283be9708b96f506a54d1 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Tue, 17 Mar 2026 21:42:22 -0500 Subject: [PATCH 033/191] feat(catalog-backend-module-bitbucket-cloud): add Bitbucket Cloud SCM event translation and bridge wiring Signed-off-by: Lokesh Kaki --- .../bitbucket-cloud-scm-events-layer.md | 5 + .../package.json | 1 + .../report-alpha.api.md | 32 ++ .../src/alpha.ts | 6 + .../events/BitbucketCloudScmEventsBridge.ts | 121 +++++++ .../analyzeBitbucketCloudWebhookEvent.test.ts | 299 ++++++++++++++++++ .../analyzeBitbucketCloudWebhookEvent.ts | 252 +++++++++++++++ ...ModuleBitbucketCloudEntityProvider.test.ts | 9 +- ...talogModuleBitbucketCloudEntityProvider.ts | 18 ++ yarn.lock | 1 + 10 files changed, 742 insertions(+), 2 deletions(-) create mode 100644 .changeset/bitbucket-cloud-scm-events-layer.md create mode 100644 plugins/catalog-backend-module-bitbucket-cloud/src/events/BitbucketCloudScmEventsBridge.ts create mode 100644 plugins/catalog-backend-module-bitbucket-cloud/src/events/analyzeBitbucketCloudWebhookEvent.test.ts create mode 100644 plugins/catalog-backend-module-bitbucket-cloud/src/events/analyzeBitbucketCloudWebhookEvent.ts diff --git a/.changeset/bitbucket-cloud-scm-events-layer.md b/.changeset/bitbucket-cloud-scm-events-layer.md new file mode 100644 index 0000000000..711a744dc9 --- /dev/null +++ b/.changeset/bitbucket-cloud-scm-events-layer.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch +--- + +Added Bitbucket Cloud SCM event translation layer for the catalog backend module. The module now subscribes to Bitbucket Cloud webhook events and translates them into generic catalog SCM events, enabling instant catalog reprocessing when repositories are pushed to, renamed, transferred, or deleted. The `analyzeBitbucketCloudWebhookEvent` function is exported from the alpha entry point for custom integrations. diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index 62b4543fff..c7e2fada14 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -54,6 +54,7 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-bitbucket-cloud-common": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", diff --git a/plugins/catalog-backend-module-bitbucket-cloud/report-alpha.api.md b/plugins/catalog-backend-module-bitbucket-cloud/report-alpha.api.md index ae6ba51eff..67c166912d 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/report-alpha.api.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/report-alpha.api.md @@ -4,6 +4,38 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; +import { CatalogScmEvent } from '@backstage/plugin-catalog-node/alpha'; + +// @alpha +export function analyzeBitbucketCloudWebhookEvent( + eventType: string, + eventPayload: unknown, + _options: AnalyzeBitbucketCloudWebhookEventOptions, +): Promise; + +// @alpha +export interface AnalyzeBitbucketCloudWebhookEventOptions { + isRelevantPath: (path: string) => boolean; +} + +// @alpha +export type AnalyzeBitbucketCloudWebhookEventResult = + | { + result: 'unsupported-event'; + event: string; + } + | { + result: 'ignored'; + reason: string; + } + | { + result: 'aborted'; + reason: string; + } + | { + result: 'ok'; + events: CatalogScmEvent[]; + }; // @alpha (undocumented) const _feature: BackendFeature; diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/alpha.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/alpha.ts index cba672ce49..19212727a4 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/alpha.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/alpha.ts @@ -19,3 +19,9 @@ import { default as feature } from './module'; /** @alpha */ const _feature = feature; export default _feature; + +export { + analyzeBitbucketCloudWebhookEvent, + type AnalyzeBitbucketCloudWebhookEventOptions, + type AnalyzeBitbucketCloudWebhookEventResult, +} from './events/analyzeBitbucketCloudWebhookEvent'; diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/events/BitbucketCloudScmEventsBridge.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/events/BitbucketCloudScmEventsBridge.ts new file mode 100644 index 0000000000..4ae59f7ed7 --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/events/BitbucketCloudScmEventsBridge.ts @@ -0,0 +1,121 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LoggerService } from '@backstage/backend-plugin-api'; +import { CatalogScmEventsService } from '@backstage/plugin-catalog-node/alpha'; +import { EventParams, EventsService } from '@backstage/plugin-events-node'; +import { analyzeBitbucketCloudWebhookEvent } from './analyzeBitbucketCloudWebhookEvent'; + +/** + * Takes Bitbucket Cloud webhook events, analyzes them, and publishes them as + * catalog SCM events that entity providers and others can subscribe to. + */ +export class BitbucketCloudScmEventsBridge { + readonly #logger: LoggerService; + readonly #events: EventsService; + readonly #catalogScmEvents: CatalogScmEventsService; + #shuttingDown: boolean; + #pendingPublish: Promise | undefined; + + constructor(options: { + logger: LoggerService; + events: EventsService; + catalogScmEvents: CatalogScmEventsService; + }) { + this.#logger = options.logger; + this.#events = options.events; + this.#catalogScmEvents = options.catalogScmEvents; + this.#shuttingDown = false; + } + + async start() { + await this.#events.subscribe({ + id: 'catalog-bitbucket-cloud-scm-events-bridge', + topics: ['bitbucketCloud'], + onEvent: this.#onEvent.bind(this), + }); + } + + async stop() { + this.#shuttingDown = true; + await this.#pendingPublish; + } + + async #onEvent(params: EventParams): Promise { + const eventType = + (params.metadata?.['x-event-key'] as string | undefined) ?? + this.#extractEventTypeFromTopic(params.topic); + if (!eventType || !params.eventPayload) { + return; + } + + while (this.#pendingPublish) { + await this.#pendingPublish; + } + + if (this.#shuttingDown) { + this.#logger.warn( + `Skipping Bitbucket Cloud webhook event of type "${eventType}" on topic "${params.topic}" because the bridge is shutting down`, + ); + return; + } + + this.#pendingPublish = Promise.resolve().then(async () => { + try { + const output = await analyzeBitbucketCloudWebhookEvent( + eventType, + params.eventPayload, + { + isRelevantPath: path => + path.endsWith('.yaml') || path.endsWith('.yml'), + }, + ); + + if (output.result === 'ok') { + await this.#catalogScmEvents.publish(output.events); + } else if (output.result === 'ignored') { + this.#logger.debug( + `Skipping Bitbucket Cloud webhook event of type "${eventType}" on topic "${params.topic}" because it is ignored: ${output.reason}`, + ); + } else if (output.result === 'aborted') { + this.#logger.warn( + `Skipping Bitbucket Cloud webhook event of type "${eventType}" on topic "${params.topic}" because it is aborted: ${output.reason}`, + ); + } else if (output.result === 'unsupported-event') { + this.#logger.debug( + `Skipping Bitbucket Cloud webhook event of type "${eventType}" on topic "${params.topic}" because it is unsupported: ${output.event}`, + ); + } + } catch (error) { + this.#logger.warn( + `Failed to handle Bitbucket Cloud webhook event of type "${eventType}"`, + error, + ); + } finally { + this.#pendingPublish = undefined; + } + }); + + await this.#pendingPublish; + } + + #extractEventTypeFromTopic(topic: string): string | undefined { + if (topic.startsWith('bitbucketCloud.')) { + return topic.slice('bitbucketCloud.'.length); + } + return undefined; + } +} diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/events/analyzeBitbucketCloudWebhookEvent.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/events/analyzeBitbucketCloudWebhookEvent.test.ts new file mode 100644 index 0000000000..fbbd7c9a4f --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/events/analyzeBitbucketCloudWebhookEvent.test.ts @@ -0,0 +1,299 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { analyzeBitbucketCloudWebhookEvent } from './analyzeBitbucketCloudWebhookEvent'; + +const isRelevantPath = (path: string): boolean => + path.endsWith('.yaml') || path.endsWith('.yml'); + +const baseRepository = { + type: 'repository', + full_name: 'test-ws/test-repo', + links: { + html: { + href: 'https://bitbucket.org/test-ws/test-repo', + }, + }, + workspace: { + type: 'workspace', + slug: 'test-ws', + }, +}; + +describe('analyzeBitbucketCloudWebhookEvent', () => { + describe('repo:push', () => { + it('emits repository.updated for a push event', async () => { + await expect( + analyzeBitbucketCloudWebhookEvent( + 'repo:push', + { + actor: { type: 'user' }, + repository: baseRepository, + push: { changes: [] }, + }, + { isRelevantPath }, + ), + ).resolves.toEqual({ + result: 'ok', + events: [ + { + type: 'repository.updated', + url: 'https://bitbucket.org/test-ws/test-repo', + }, + ], + }); + }); + + it('aborts when repository URL is missing', async () => { + await expect( + analyzeBitbucketCloudWebhookEvent( + 'repo:push', + { + actor: { type: 'user' }, + repository: { type: 'repository' }, + push: { changes: [] }, + }, + { isRelevantPath }, + ), + ).resolves.toEqual({ + result: 'aborted', + reason: + 'Bitbucket Cloud repo:push event did not include repository.links.html.href', + }); + }); + }); + + describe('repo:updated', () => { + it('emits repository.moved when the URL changes', async () => { + await expect( + analyzeBitbucketCloudWebhookEvent( + 'repo:updated', + { + actor: { type: 'user' }, + repository: { + ...baseRepository, + full_name: 'test-ws/test-repo-new', + links: { + html: { + href: 'https://bitbucket.org/test-ws/test-repo-new', + }, + }, + }, + changes: { + name: { new: 'test-repo-new', old: 'test-repo-old' }, + full_name: { + new: 'test-ws/test-repo-new', + old: 'test-ws/test-repo-old', + }, + links: { + new: { + html: { + href: 'https://bitbucket.org/test-ws/test-repo-new', + }, + }, + old: { + html: { + href: 'https://bitbucket.org/test-ws/test-repo-old', + }, + }, + }, + }, + }, + { isRelevantPath }, + ), + ).resolves.toEqual({ + result: 'ok', + events: [ + { + type: 'repository.moved', + fromUrl: 'https://bitbucket.org/test-ws/test-repo-old', + toUrl: 'https://bitbucket.org/test-ws/test-repo-new', + }, + ], + }); + }); + + it('falls back to full_name for old URL when links.old is missing', async () => { + await expect( + analyzeBitbucketCloudWebhookEvent( + 'repo:updated', + { + actor: { type: 'user' }, + repository: { + ...baseRepository, + full_name: 'test-ws/test-repo-new', + links: { + html: { + href: 'https://bitbucket.org/test-ws/test-repo-new', + }, + }, + }, + changes: { + full_name: { + new: 'test-ws/test-repo-new', + old: 'test-ws/test-repo-old', + }, + }, + }, + { isRelevantPath }, + ), + ).resolves.toEqual({ + result: 'ok', + events: [ + { + type: 'repository.moved', + fromUrl: 'https://bitbucket.org/test-ws/test-repo-old', + toUrl: 'https://bitbucket.org/test-ws/test-repo-new', + }, + ], + }); + }); + + it('emits repository.updated when no URL change is detected', async () => { + await expect( + analyzeBitbucketCloudWebhookEvent( + 'repo:updated', + { + actor: { type: 'user' }, + repository: baseRepository, + changes: { + description: { new: 'new desc', old: 'old desc' }, + }, + }, + { isRelevantPath }, + ), + ).resolves.toEqual({ + result: 'ok', + events: [ + { + type: 'repository.updated', + url: 'https://bitbucket.org/test-ws/test-repo', + }, + ], + }); + }); + }); + + describe('repo:transfer', () => { + it('emits repository.moved when transferred to a new workspace', async () => { + await expect( + analyzeBitbucketCloudWebhookEvent( + 'repo:transfer', + { + actor: { type: 'user' }, + repository: { + ...baseRepository, + full_name: 'new-ws/test-repo', + links: { + html: { + href: 'https://bitbucket.org/new-ws/test-repo', + }, + }, + workspace: { + type: 'workspace', + slug: 'new-ws', + }, + }, + changes: { + full_name: { + new: 'new-ws/test-repo', + old: 'test-ws/test-repo', + }, + links: { + new: { + html: { + href: 'https://bitbucket.org/new-ws/test-repo', + }, + }, + old: { + html: { + href: 'https://bitbucket.org/test-ws/test-repo', + }, + }, + }, + }, + }, + { isRelevantPath }, + ), + ).resolves.toEqual({ + result: 'ok', + events: [ + { + type: 'repository.moved', + fromUrl: 'https://bitbucket.org/test-ws/test-repo', + toUrl: 'https://bitbucket.org/new-ws/test-repo', + }, + ], + }); + }); + }); + + describe('repo:deleted', () => { + it('emits repository.deleted', async () => { + await expect( + analyzeBitbucketCloudWebhookEvent( + 'repo:deleted', + { + actor: { type: 'user' }, + repository: baseRepository, + }, + { isRelevantPath }, + ), + ).resolves.toEqual({ + result: 'ok', + events: [ + { + type: 'repository.deleted', + url: 'https://bitbucket.org/test-ws/test-repo', + }, + ], + }); + }); + }); + + describe('general behavior', () => { + it('throws on non-object payloads', async () => { + await expect( + analyzeBitbucketCloudWebhookEvent('repo:push', undefined, { + isRelevantPath, + }), + ).rejects.toThrow( + 'Bitbucket Cloud webhook event payload is not an object', + ); + + await expect( + analyzeBitbucketCloudWebhookEvent('repo:push', [], { + isRelevantPath, + }), + ).rejects.toThrow( + 'Bitbucket Cloud webhook event payload is not an object', + ); + }); + + it('returns unsupported-event for unknown event types', async () => { + await expect( + analyzeBitbucketCloudWebhookEvent( + 'pullrequest:created', + { actor: { type: 'user' } }, + { isRelevantPath }, + ), + ).resolves.toEqual({ + result: 'unsupported-event', + event: 'pullrequest:created', + }); + }); + }); +}); diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/events/analyzeBitbucketCloudWebhookEvent.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/events/analyzeBitbucketCloudWebhookEvent.ts new file mode 100644 index 0000000000..93b1b41934 --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/events/analyzeBitbucketCloudWebhookEvent.ts @@ -0,0 +1,252 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InputError } from '@backstage/errors'; +import { CatalogScmEvent } from '@backstage/plugin-catalog-node/alpha'; + +/** + * Options for {@link analyzeBitbucketCloudWebhookEvent}. + * @alpha + */ +export interface AnalyzeBitbucketCloudWebhookEventOptions { + /** + * Predicate that returns true for file paths that are relevant to the + * catalog (e.g. paths ending in `.yaml` or `.yml`). + */ + isRelevantPath: (path: string) => boolean; +} + +/** + * The result of analyzing a Bitbucket Cloud webhook event. + * + * - `ok` — one or more catalog SCM events were produced. + * - `ignored` — the event was valid but not relevant. + * - `aborted` — the event could not be fully processed due to missing data. + * - `unsupported-event` — the event type is not handled by this analyzer. + * + * @alpha + */ +export type AnalyzeBitbucketCloudWebhookEventResult = + | { + result: 'unsupported-event'; + event: string; + } + | { + result: 'ignored'; + reason: string; + } + | { + result: 'aborted'; + reason: string; + } + | { + result: 'ok'; + events: CatalogScmEvent[]; + }; + +type JsonObject = Record; + +function asObject(value: unknown): JsonObject | undefined { + if (!value || typeof value !== 'object' || Array.isArray(value)) { + return undefined; + } + return value as JsonObject; +} + +function asString(value: unknown): string | undefined { + return typeof value === 'string' ? value : undefined; +} + +function getRepositoryUrl(payload: JsonObject): string | undefined { + const repository = asObject(payload.repository); + if (!repository) { + return undefined; + } + const links = asObject(repository.links); + const html = asObject(links?.html); + return asString(html?.href); +} + +function getOldRepositoryUrl(payload: JsonObject): string | undefined { + const changes = asObject(payload.changes); + if (!changes) { + return undefined; + } + + const linksChange = asObject(changes.links); + if (linksChange) { + const oldLinks = asObject(linksChange.old); + const html = asObject(oldLinks?.html); + const href = asString(html?.href); + if (href) { + return href; + } + } + + const fullNameChange = asObject(changes.full_name); + const oldFullName = asString(fullNameChange?.old); + if (oldFullName) { + return `https://bitbucket.org/${oldFullName}`; + } + + return undefined; +} + +async function onPushEvent( + payload: JsonObject, +): Promise { + const repositoryUrl = getRepositoryUrl(payload); + + if (!repositoryUrl) { + return { + result: 'aborted', + reason: + 'Bitbucket Cloud repo:push event did not include repository.links.html.href', + }; + } + + return { + result: 'ok', + events: [{ type: 'repository.updated', url: repositoryUrl }], + }; +} + +async function onRepoUpdatedEvent( + payload: JsonObject, +): Promise { + const repositoryUrl = getRepositoryUrl(payload); + const oldRepositoryUrl = getOldRepositoryUrl(payload); + + if (!repositoryUrl) { + return { + result: 'aborted', + reason: + 'Bitbucket Cloud repo:updated event did not include repository.links.html.href', + }; + } + + if (oldRepositoryUrl && oldRepositoryUrl !== repositoryUrl) { + return { + result: 'ok', + events: [ + { + type: 'repository.moved', + fromUrl: oldRepositoryUrl, + toUrl: repositoryUrl, + }, + ], + }; + } + + return { + result: 'ok', + events: [{ type: 'repository.updated', url: repositoryUrl }], + }; +} + +async function onRepoTransferEvent( + payload: JsonObject, +): Promise { + const repositoryUrl = getRepositoryUrl(payload); + const oldRepositoryUrl = getOldRepositoryUrl(payload); + + if (!repositoryUrl) { + return { + result: 'aborted', + reason: + 'Bitbucket Cloud repo:transfer event did not include repository.links.html.href', + }; + } + + if (oldRepositoryUrl && oldRepositoryUrl !== repositoryUrl) { + return { + result: 'ok', + events: [ + { + type: 'repository.moved', + fromUrl: oldRepositoryUrl, + toUrl: repositoryUrl, + }, + ], + }; + } + + return { + result: 'ok', + events: [{ type: 'repository.updated', url: repositoryUrl }], + }; +} + +async function onRepoDeletedEvent( + payload: JsonObject, +): Promise { + const repositoryUrl = getRepositoryUrl(payload); + + if (!repositoryUrl) { + return { + result: 'aborted', + reason: + 'Bitbucket Cloud repo:deleted event did not include repository.links.html.href', + }; + } + + return { + result: 'ok', + events: [{ type: 'repository.deleted', url: repositoryUrl }], + }; +} + +/** + * Analyzes a Bitbucket Cloud webhook event and translates it into zero or more + * catalog SCM events that entity providers can act on. + * + * Supported event types: + * - `repo:push` — emits a `repository.updated` event to trigger catalog + * refresh for the repository. Bitbucket Cloud push payloads do not include + * file-level change data, so only repository-level events are produced. + * - `repo:updated` — translates repository renames into `repository.moved` + * events, or emits `repository.updated` for other metadata changes. + * - `repo:transfer` — translates repository transfers into `repository.moved` + * events. + * - `repo:deleted` — emits a `repository.deleted` event. + * + * @alpha + */ +export async function analyzeBitbucketCloudWebhookEvent( + eventType: string, + eventPayload: unknown, + _options: AnalyzeBitbucketCloudWebhookEventOptions, +): Promise { + const payload = asObject(eventPayload); + if (!payload) { + throw new InputError( + 'Bitbucket Cloud webhook event payload is not an object', + ); + } + + switch (eventType) { + case 'repo:push': + return onPushEvent(payload); + case 'repo:updated': + return onRepoUpdatedEvent(payload); + case 'repo:transfer': + return onRepoTransferEvent(payload); + case 'repo:deleted': + return onRepoDeletedEvent(payload); + default: + return { result: 'unsupported-event', event: eventType }; + } +} diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.test.ts index 50fa5796ac..888db3bd9d 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.test.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.test.ts @@ -88,8 +88,13 @@ describe('catalogModuleBitbucketCloudEntityProvider', () => { 'bitbucketCloud-provider:default', ); await provider.connect(connection); - expect(events.subscribed).toHaveLength(1); - expect(events.subscribed[0].id).toEqual('bitbucketCloud-provider:default'); + expect(events.subscribed).toHaveLength(2); + expect(events.subscribed.map(s => s.id)).toContain( + 'bitbucketCloud-provider:default', + ); + expect(events.subscribed.map(s => s.id)).toContain( + 'catalog-bitbucket-cloud-scm-events-bridge', + ); expect(runner).toHaveBeenCalledTimes(1); }); }); diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts index 7cd839bb63..222453fb32 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/module/catalogModuleBitbucketCloudEntityProvider.ts @@ -20,8 +20,10 @@ import { } from '@backstage/backend-plugin-api'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; import { catalogServiceRef } from '@backstage/plugin-catalog-node'; +import { catalogScmEventsServiceRef } from '@backstage/plugin-catalog-node/alpha'; import { eventsServiceRef } from '@backstage/plugin-events-node'; import { BitbucketCloudEntityProvider } from '../providers/BitbucketCloudEntityProvider'; +import { BitbucketCloudScmEventsBridge } from '../events/BitbucketCloudScmEventsBridge'; /** * @public @@ -39,6 +41,8 @@ export const catalogModuleBitbucketCloudEntityProvider = createBackendModule({ events: eventsServiceRef, logger: coreServices.logger, scheduler: coreServices.scheduler, + catalogScmEvents: catalogScmEventsServiceRef, + lifecycle: coreServices.lifecycle, }, async init({ auth, @@ -48,6 +52,8 @@ export const catalogModuleBitbucketCloudEntityProvider = createBackendModule({ events, logger, scheduler, + catalogScmEvents, + lifecycle, }) { const providers = BitbucketCloudEntityProvider.fromConfig(config, { auth, @@ -58,6 +64,18 @@ export const catalogModuleBitbucketCloudEntityProvider = createBackendModule({ }); catalogProcessing.addEntityProvider(providers); + + const bridge = new BitbucketCloudScmEventsBridge({ + logger, + events, + catalogScmEvents, + }); + lifecycle.addStartupHook(async () => { + await bridge.start(); + }); + lifecycle.addShutdownHook(async () => { + await bridge.stop(); + }); }, }); }, diff --git a/yarn.lock b/yarn.lock index fd201d7eea..6c3e8bf24f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4792,6 +4792,7 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-bitbucket-cloud-common": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" From ffb5ff47e20dd6e7fdd3c6a8381597383625e380 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Tue, 17 Mar 2026 22:16:07 -0500 Subject: [PATCH 034/191] fix(catalog-backend-module-azure): conditionally wire SCM events bridge, fix URL encoding and error messages Signed-off-by: Lokesh Kaki --- .../events/analyzeAzureDevOpsWebhookEvent.ts | 30 ++++++++++--------- .../catalogModuleAzureDevOpsEntityProvider.ts | 27 ++++++++++------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts index 0dfa1c0f99..9b7f246e12 100644 --- a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts +++ b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts @@ -172,12 +172,17 @@ function toLocationUrl(options: { return undefined; } - const url = new URL(options.remoteUrl); + // Match the URL format produced by AzureDevOpsEntityProvider.createObjectUrl + // which uses encodeURI on the full URL string with path and version as raw + // query parameter values. Using URL/searchParams would produce different + // encoding (e.g. %2F for slashes in branch names) and fail to match existing + // catalog location targets. const branch = branchNameFromRef(options.branchRef); - url.search = branch - ? `path=${options.path}&version=GB${branch}` - : `path=${options.path}`; - return encodeURI(url.toString()); + let fullUrl = `${options.remoteUrl}?path=${options.path}`; + if (branch) { + fullUrl += `&version=GB${branch}`; + } + return encodeURI(fullUrl); } function toCommitUrl( @@ -495,18 +500,15 @@ async function onRepositoryEvent( if (eventType === 'git.repo.renamed' && toUrl) { const oldName = asString(resource?.oldName); - if (!oldName) { - return { - result: 'ignored', - reason: 'Azure DevOps repository renamed event is missing oldName', - }; - } - const fromUrl = replaceRepoNameInRemoteUrl(toUrl, oldName); + const fromUrl = oldName + ? replaceRepoNameInRemoteUrl(toUrl, oldName) + : undefined; if (!fromUrl) { return { result: 'ignored', - reason: - 'Azure DevOps repository renamed event has an unexpected repository.remoteUrl format', + reason: oldName + ? 'Azure DevOps repository renamed event has an unexpected repository.remoteUrl format' + : 'Azure DevOps repository renamed event is missing oldName', }; } diff --git a/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.ts b/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.ts index b7ad3cf6e6..c3475de239 100644 --- a/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.ts +++ b/plugins/catalog-backend-module-azure/src/module/catalogModuleAzureDevOpsEntityProvider.ts @@ -75,17 +75,22 @@ export const catalogModuleAzureEntityProvider = createBackendModule({ ); } - const bridge = new AzureDevOpsScmEventsBridge({ - logger, - events, - catalogScmEvents, - }); - lifecycle.addStartupHook(async () => { - await bridge.start(); - }); - lifecycle.addShutdownHook(async () => { - await bridge.stop(); - }); + // Only wire up the SCM events bridge when Azure DevOps provider + // configuration is present — Azure Blob Storage users should not be + // required to handle Azure DevOps webhook events. + if (config.has('catalog.providers.azureDevOps')) { + const bridge = new AzureDevOpsScmEventsBridge({ + logger, + events, + catalogScmEvents, + }); + lifecycle.addStartupHook(async () => { + await bridge.start(); + }); + lifecycle.addShutdownHook(async () => { + await bridge.stop(); + }); + } }, }); }, From deb9a8374687c6d27ebb66ed411c295b79b342f4 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Tue, 17 Mar 2026 22:31:07 -0500 Subject: [PATCH 035/191] fix(catalog-backend-module-bitbucket-cloud): remove unused isRelevantPath options parameter from analyzer Signed-off-by: Lokesh Kaki --- .../report-alpha.api.md | 6 - .../src/alpha.ts | 1 - .../events/BitbucketCloudScmEventsBridge.ts | 4 - .../analyzeBitbucketCloudWebhookEvent.test.ts | 215 ++++++++---------- .../analyzeBitbucketCloudWebhookEvent.ts | 20 +- 5 files changed, 94 insertions(+), 152 deletions(-) diff --git a/plugins/catalog-backend-module-bitbucket-cloud/report-alpha.api.md b/plugins/catalog-backend-module-bitbucket-cloud/report-alpha.api.md index 67c166912d..31762b83a2 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/report-alpha.api.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/report-alpha.api.md @@ -10,14 +10,8 @@ import { CatalogScmEvent } from '@backstage/plugin-catalog-node/alpha'; export function analyzeBitbucketCloudWebhookEvent( eventType: string, eventPayload: unknown, - _options: AnalyzeBitbucketCloudWebhookEventOptions, ): Promise; -// @alpha -export interface AnalyzeBitbucketCloudWebhookEventOptions { - isRelevantPath: (path: string) => boolean; -} - // @alpha export type AnalyzeBitbucketCloudWebhookEventResult = | { diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/alpha.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/alpha.ts index 19212727a4..a4945aff93 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/alpha.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/alpha.ts @@ -22,6 +22,5 @@ export default _feature; export { analyzeBitbucketCloudWebhookEvent, - type AnalyzeBitbucketCloudWebhookEventOptions, type AnalyzeBitbucketCloudWebhookEventResult, } from './events/analyzeBitbucketCloudWebhookEvent'; diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/events/BitbucketCloudScmEventsBridge.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/events/BitbucketCloudScmEventsBridge.ts index 4ae59f7ed7..16d7c2fc5c 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/events/BitbucketCloudScmEventsBridge.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/events/BitbucketCloudScmEventsBridge.ts @@ -78,10 +78,6 @@ export class BitbucketCloudScmEventsBridge { const output = await analyzeBitbucketCloudWebhookEvent( eventType, params.eventPayload, - { - isRelevantPath: path => - path.endsWith('.yaml') || path.endsWith('.yml'), - }, ); if (output.result === 'ok') { diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/events/analyzeBitbucketCloudWebhookEvent.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/events/analyzeBitbucketCloudWebhookEvent.test.ts index fbbd7c9a4f..6ddb8f15a9 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/events/analyzeBitbucketCloudWebhookEvent.test.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/events/analyzeBitbucketCloudWebhookEvent.test.ts @@ -16,9 +16,6 @@ import { analyzeBitbucketCloudWebhookEvent } from './analyzeBitbucketCloudWebhookEvent'; -const isRelevantPath = (path: string): boolean => - path.endsWith('.yaml') || path.endsWith('.yml'); - const baseRepository = { type: 'repository', full_name: 'test-ws/test-repo', @@ -37,15 +34,11 @@ describe('analyzeBitbucketCloudWebhookEvent', () => { describe('repo:push', () => { it('emits repository.updated for a push event', async () => { await expect( - analyzeBitbucketCloudWebhookEvent( - 'repo:push', - { - actor: { type: 'user' }, - repository: baseRepository, - push: { changes: [] }, - }, - { isRelevantPath }, - ), + analyzeBitbucketCloudWebhookEvent('repo:push', { + actor: { type: 'user' }, + repository: baseRepository, + push: { changes: [] }, + }), ).resolves.toEqual({ result: 'ok', events: [ @@ -59,15 +52,11 @@ describe('analyzeBitbucketCloudWebhookEvent', () => { it('aborts when repository URL is missing', async () => { await expect( - analyzeBitbucketCloudWebhookEvent( - 'repo:push', - { - actor: { type: 'user' }, - repository: { type: 'repository' }, - push: { changes: [] }, - }, - { isRelevantPath }, - ), + analyzeBitbucketCloudWebhookEvent('repo:push', { + actor: { type: 'user' }, + repository: { type: 'repository' }, + push: { changes: [] }, + }), ).resolves.toEqual({ result: 'aborted', reason: @@ -79,41 +68,37 @@ describe('analyzeBitbucketCloudWebhookEvent', () => { describe('repo:updated', () => { it('emits repository.moved when the URL changes', async () => { await expect( - analyzeBitbucketCloudWebhookEvent( - 'repo:updated', - { - actor: { type: 'user' }, - repository: { - ...baseRepository, - full_name: 'test-ws/test-repo-new', - links: { + analyzeBitbucketCloudWebhookEvent('repo:updated', { + actor: { type: 'user' }, + repository: { + ...baseRepository, + full_name: 'test-ws/test-repo-new', + links: { + html: { + href: 'https://bitbucket.org/test-ws/test-repo-new', + }, + }, + }, + changes: { + name: { new: 'test-repo-new', old: 'test-repo-old' }, + full_name: { + new: 'test-ws/test-repo-new', + old: 'test-ws/test-repo-old', + }, + links: { + new: { html: { href: 'https://bitbucket.org/test-ws/test-repo-new', }, }, - }, - changes: { - name: { new: 'test-repo-new', old: 'test-repo-old' }, - full_name: { - new: 'test-ws/test-repo-new', - old: 'test-ws/test-repo-old', - }, - links: { - new: { - html: { - href: 'https://bitbucket.org/test-ws/test-repo-new', - }, - }, - old: { - html: { - href: 'https://bitbucket.org/test-ws/test-repo-old', - }, + old: { + html: { + href: 'https://bitbucket.org/test-ws/test-repo-old', }, }, }, }, - { isRelevantPath }, - ), + }), ).resolves.toEqual({ result: 'ok', events: [ @@ -128,28 +113,24 @@ describe('analyzeBitbucketCloudWebhookEvent', () => { it('falls back to full_name for old URL when links.old is missing', async () => { await expect( - analyzeBitbucketCloudWebhookEvent( - 'repo:updated', - { - actor: { type: 'user' }, - repository: { - ...baseRepository, - full_name: 'test-ws/test-repo-new', - links: { - html: { - href: 'https://bitbucket.org/test-ws/test-repo-new', - }, - }, - }, - changes: { - full_name: { - new: 'test-ws/test-repo-new', - old: 'test-ws/test-repo-old', + analyzeBitbucketCloudWebhookEvent('repo:updated', { + actor: { type: 'user' }, + repository: { + ...baseRepository, + full_name: 'test-ws/test-repo-new', + links: { + html: { + href: 'https://bitbucket.org/test-ws/test-repo-new', }, }, }, - { isRelevantPath }, - ), + changes: { + full_name: { + new: 'test-ws/test-repo-new', + old: 'test-ws/test-repo-old', + }, + }, + }), ).resolves.toEqual({ result: 'ok', events: [ @@ -164,17 +145,13 @@ describe('analyzeBitbucketCloudWebhookEvent', () => { it('emits repository.updated when no URL change is detected', async () => { await expect( - analyzeBitbucketCloudWebhookEvent( - 'repo:updated', - { - actor: { type: 'user' }, - repository: baseRepository, - changes: { - description: { new: 'new desc', old: 'old desc' }, - }, + analyzeBitbucketCloudWebhookEvent('repo:updated', { + actor: { type: 'user' }, + repository: baseRepository, + changes: { + description: { new: 'new desc', old: 'old desc' }, }, - { isRelevantPath }, - ), + }), ).resolves.toEqual({ result: 'ok', events: [ @@ -190,44 +167,40 @@ describe('analyzeBitbucketCloudWebhookEvent', () => { describe('repo:transfer', () => { it('emits repository.moved when transferred to a new workspace', async () => { await expect( - analyzeBitbucketCloudWebhookEvent( - 'repo:transfer', - { - actor: { type: 'user' }, - repository: { - ...baseRepository, - full_name: 'new-ws/test-repo', - links: { + analyzeBitbucketCloudWebhookEvent('repo:transfer', { + actor: { type: 'user' }, + repository: { + ...baseRepository, + full_name: 'new-ws/test-repo', + links: { + html: { + href: 'https://bitbucket.org/new-ws/test-repo', + }, + }, + workspace: { + type: 'workspace', + slug: 'new-ws', + }, + }, + changes: { + full_name: { + new: 'new-ws/test-repo', + old: 'test-ws/test-repo', + }, + links: { + new: { html: { href: 'https://bitbucket.org/new-ws/test-repo', }, }, - workspace: { - type: 'workspace', - slug: 'new-ws', - }, - }, - changes: { - full_name: { - new: 'new-ws/test-repo', - old: 'test-ws/test-repo', - }, - links: { - new: { - html: { - href: 'https://bitbucket.org/new-ws/test-repo', - }, - }, - old: { - html: { - href: 'https://bitbucket.org/test-ws/test-repo', - }, + old: { + html: { + href: 'https://bitbucket.org/test-ws/test-repo', }, }, }, }, - { isRelevantPath }, - ), + }), ).resolves.toEqual({ result: 'ok', events: [ @@ -244,14 +217,10 @@ describe('analyzeBitbucketCloudWebhookEvent', () => { describe('repo:deleted', () => { it('emits repository.deleted', async () => { await expect( - analyzeBitbucketCloudWebhookEvent( - 'repo:deleted', - { - actor: { type: 'user' }, - repository: baseRepository, - }, - { isRelevantPath }, - ), + analyzeBitbucketCloudWebhookEvent('repo:deleted', { + actor: { type: 'user' }, + repository: baseRepository, + }), ).resolves.toEqual({ result: 'ok', events: [ @@ -267,17 +236,13 @@ describe('analyzeBitbucketCloudWebhookEvent', () => { describe('general behavior', () => { it('throws on non-object payloads', async () => { await expect( - analyzeBitbucketCloudWebhookEvent('repo:push', undefined, { - isRelevantPath, - }), + analyzeBitbucketCloudWebhookEvent('repo:push', undefined), ).rejects.toThrow( 'Bitbucket Cloud webhook event payload is not an object', ); await expect( - analyzeBitbucketCloudWebhookEvent('repo:push', [], { - isRelevantPath, - }), + analyzeBitbucketCloudWebhookEvent('repo:push', []), ).rejects.toThrow( 'Bitbucket Cloud webhook event payload is not an object', ); @@ -285,11 +250,9 @@ describe('analyzeBitbucketCloudWebhookEvent', () => { it('returns unsupported-event for unknown event types', async () => { await expect( - analyzeBitbucketCloudWebhookEvent( - 'pullrequest:created', - { actor: { type: 'user' } }, - { isRelevantPath }, - ), + analyzeBitbucketCloudWebhookEvent('pullrequest:created', { + actor: { type: 'user' }, + }), ).resolves.toEqual({ result: 'unsupported-event', event: 'pullrequest:created', diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/events/analyzeBitbucketCloudWebhookEvent.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/events/analyzeBitbucketCloudWebhookEvent.ts index 93b1b41934..68ce66d03d 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/events/analyzeBitbucketCloudWebhookEvent.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/events/analyzeBitbucketCloudWebhookEvent.ts @@ -17,18 +17,6 @@ import { InputError } from '@backstage/errors'; import { CatalogScmEvent } from '@backstage/plugin-catalog-node/alpha'; -/** - * Options for {@link analyzeBitbucketCloudWebhookEvent}. - * @alpha - */ -export interface AnalyzeBitbucketCloudWebhookEventOptions { - /** - * Predicate that returns true for file paths that are relevant to the - * catalog (e.g. paths ending in `.yaml` or `.yml`). - */ - isRelevantPath: (path: string) => boolean; -} - /** * The result of analyzing a Bitbucket Cloud webhook event. * @@ -213,10 +201,13 @@ async function onRepoDeletedEvent( * Analyzes a Bitbucket Cloud webhook event and translates it into zero or more * catalog SCM events that entity providers can act on. * + * Bitbucket Cloud push payloads do not include file-level change data, so only + * repository-level events are produced (unlike GitLab and Azure DevOps + * analyzers which can emit fine-grained `location.*` events). + * * Supported event types: * - `repo:push` — emits a `repository.updated` event to trigger catalog - * refresh for the repository. Bitbucket Cloud push payloads do not include - * file-level change data, so only repository-level events are produced. + * refresh for the repository. * - `repo:updated` — translates repository renames into `repository.moved` * events, or emits `repository.updated` for other metadata changes. * - `repo:transfer` — translates repository transfers into `repository.moved` @@ -228,7 +219,6 @@ async function onRepoDeletedEvent( export async function analyzeBitbucketCloudWebhookEvent( eventType: string, eventPayload: unknown, - _options: AnalyzeBitbucketCloudWebhookEventOptions, ): Promise { const payload = asObject(eventPayload); if (!payload) { From 39d27eea8775eac03f62c4964866c77a9782d3e1 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Tue, 17 Mar 2026 22:33:37 -0500 Subject: [PATCH 036/191] chore(changeset): rename Azure SCM events changeset to match naming convention Signed-off-by: Lokesh Kaki --- .changeset/{thin-lies-deliver.md => azure-scm-events-layer.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .changeset/{thin-lies-deliver.md => azure-scm-events-layer.md} (100%) diff --git a/.changeset/thin-lies-deliver.md b/.changeset/azure-scm-events-layer.md similarity index 100% rename from .changeset/thin-lies-deliver.md rename to .changeset/azure-scm-events-layer.md From 86466d29557558d4379a1f29ace702b6e6488443 Mon Sep 17 00:00:00 2001 From: Lokesh Kaki Date: Tue, 17 Mar 2026 22:50:07 -0500 Subject: [PATCH 037/191] fix(catalog-backend-module-azure): omit version param from location URLs to match provider format, complete JSDoc Signed-off-by: Lokesh Kaki --- .../analyzeAzureDevOpsWebhookEvent.test.ts | 26 +++++------- .../events/analyzeAzureDevOpsWebhookEvent.ts | 40 +++++++------------ 2 files changed, 26 insertions(+), 40 deletions(-) diff --git a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.test.ts b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.test.ts index 4a520aa5fb..943e7ac9f1 100644 --- a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.test.ts +++ b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.test.ts @@ -83,43 +83,43 @@ describe('analyzeAzureDevOpsWebhookEvent', () => { events: [ { type: 'location.created', - url: `${baseRepository.remoteUrl}?path=/catalog-info.yaml&version=GBmain`, + url: `${baseRepository.remoteUrl}?path=/catalog-info.yaml`, context: { commitUrl: `${baseRepository.remoteUrl}/commit/1111111111111111111111111111111111111111`, }, }, { type: 'location.updated', - url: `${baseRepository.remoteUrl}?path=/service.yaml&version=GBmain`, + url: `${baseRepository.remoteUrl}?path=/service.yaml`, context: { commitUrl: `${baseRepository.remoteUrl}/commit/1111111111111111111111111111111111111111`, }, }, { type: 'location.deleted', - url: `${baseRepository.remoteUrl}?path=/obsolete.yaml&version=GBmain`, + url: `${baseRepository.remoteUrl}?path=/obsolete.yaml`, context: { commitUrl: `${baseRepository.remoteUrl}/commit/1111111111111111111111111111111111111111`, }, }, { type: 'location.moved', - fromUrl: `${baseRepository.remoteUrl}?path=/old-name.yaml&version=GBmain`, - toUrl: `${baseRepository.remoteUrl}?path=/new-name.yaml&version=GBmain`, + fromUrl: `${baseRepository.remoteUrl}?path=/old-name.yaml`, + toUrl: `${baseRepository.remoteUrl}?path=/new-name.yaml`, context: { commitUrl: `${baseRepository.remoteUrl}/commit/1111111111111111111111111111111111111111`, }, }, { type: 'location.deleted', - url: `${baseRepository.remoteUrl}?path=/catalog-out.yaml&version=GBmain`, + url: `${baseRepository.remoteUrl}?path=/catalog-out.yaml`, context: { commitUrl: `${baseRepository.remoteUrl}/commit/1111111111111111111111111111111111111111`, }, }, { type: 'location.created', - url: `${baseRepository.remoteUrl}?path=/catalog-in.yaml&version=GBmain`, + url: `${baseRepository.remoteUrl}?path=/catalog-in.yaml`, context: { commitUrl: `${baseRepository.remoteUrl}/commit/1111111111111111111111111111111111111111`, }, @@ -128,17 +128,13 @@ describe('analyzeAzureDevOpsWebhookEvent', () => { }); }); - it('does not double-encode branch names containing slashes', async () => { - const repoWithSlashBranch = { - ...baseRepository, - defaultBranch: 'refs/heads/feature/my-branch', - }; + it('omits version parameter to match default provider URL format', async () => { await expect( analyzeAzureDevOpsWebhookEvent( 'git.push', withPushEvent({ - repository: repoWithSlashBranch, - refUpdates: [{ name: 'refs/heads/feature/my-branch' }], + repository: baseRepository, + refUpdates: [{ name: 'refs/heads/main' }], commits: [ { commitId: 'abc', @@ -155,7 +151,7 @@ describe('analyzeAzureDevOpsWebhookEvent', () => { events: [ { type: 'location.created', - url: `${baseRepository.remoteUrl}?path=/catalog-info.yaml&version=GBfeature/my-branch`, + url: `${baseRepository.remoteUrl}?path=/catalog-info.yaml`, context: { commitUrl: `${baseRepository.remoteUrl}/commit/abc` }, }, ], diff --git a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts index 9b7f246e12..284c9837a8 100644 --- a/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts +++ b/plugins/catalog-backend-module-azure/src/events/analyzeAzureDevOpsWebhookEvent.ts @@ -147,13 +147,6 @@ function normalizePath(path: string | undefined): string | undefined { return path.startsWith('/') ? path : `/${path}`; } -function branchNameFromRef(ref: string | undefined): string | undefined { - if (!ref) { - return undefined; - } - return ref.replace(/^refs\/heads\//, ''); -} - function getRepository(resource: JsonObject | undefined): AzureRepository { const repository = asObject(resource?.repository); return { @@ -166,7 +159,6 @@ function getRepository(resource: JsonObject | undefined): AzureRepository { function toLocationUrl(options: { remoteUrl: string | undefined; path: string; - branchRef: string | undefined; }): string | undefined { if (!options.remoteUrl) { return undefined; @@ -174,14 +166,15 @@ function toLocationUrl(options: { // Match the URL format produced by AzureDevOpsEntityProvider.createObjectUrl // which uses encodeURI on the full URL string with path and version as raw - // query parameter values. Using URL/searchParams would produce different - // encoding (e.g. %2F for slashes in branch names) and fail to match existing - // catalog location targets. - const branch = branchNameFromRef(options.branchRef); - let fullUrl = `${options.remoteUrl}?path=${options.path}`; - if (branch) { - fullUrl += `&version=GB${branch}`; - } + // query parameter values. + // + // The version parameter is intentionally omitted here because the entity + // provider only includes it when the user explicitly configures a `branch` + // in the provider config. Since we cannot know at analysis time whether a + // branch was configured, omitting version matches the default provider + // behavior and avoids URL mismatches that would prevent SCM events from + // triggering catalog refreshes. + const fullUrl = `${options.remoteUrl}?path=${options.path}`; return encodeURI(fullUrl); } @@ -200,12 +193,11 @@ function toCommitUrl( function toCatalogScmEventForPathState(options: { repository: AzureRepository; - branchRef: string | undefined; path: string; pathState: PushPathState; isRelevantPath: (path: string) => boolean; }): CatalogScmEvent[] { - const { repository, branchRef, path, pathState, isRelevantPath } = options; + const { repository, path, pathState, isRelevantPath } = options; const commitUrl = toCommitUrl(repository, pathState.commit); const context = commitUrl ? { commitUrl } : undefined; @@ -215,12 +207,10 @@ function toCatalogScmEventForPathState(options: { const fromUrl = toLocationUrl({ remoteUrl: repository.remoteUrl, path: pathState.fromPath, - branchRef, }); const toUrl = toLocationUrl({ remoteUrl: repository.remoteUrl, path, - branchRef, }); if (fromRelevant && toRelevant && fromUrl && toUrl) { @@ -242,7 +232,6 @@ function toCatalogScmEventForPathState(options: { const url = toLocationUrl({ remoteUrl: repository.remoteUrl, path, - branchRef, }); if (!url) { return []; @@ -446,13 +435,9 @@ async function onPushEvent( }; } - const branchRef = - repository.defaultBranch ?? asString(refUpdates[0]?.name) ?? undefined; - const events = Array.from(state.entries()).flatMap(([path, pathState]) => toCatalogScmEventForPathState({ repository, - branchRef, path, pathState, isRelevantPath: options.isRelevantPath, @@ -539,6 +524,11 @@ async function onRepositoryEvent( * - `git.push` — translates file-level adds, modifications, and deletions on * the default branch into catalog SCM events for paths matching * `isRelevantPath`. + * - `git.repo.created` — emits a `repository.created` event. + * - `git.repo.deleted` — emits a `repository.deleted` event. + * - `git.repo.statuschanged` — emits a `repository.updated` event. + * - `git.repo.renamed` — emits a `repository.moved` event with the old and + * new repository URLs. * * @alpha */ From 980b7f56323664936e4afee8c6fc5f6d2d86f76f Mon Sep 17 00:00:00 2001 From: Jon Koops Date: Thu, 19 Mar 2026 11:35:17 +0100 Subject: [PATCH 038/191] docs: remove legacy corporate proxy documentation All Node.js versions in Backstage's support matrix (v22 and v24 LTS) include built-in proxy support via NODE_USE_ENV_PROXY, making the legacy global-agent/undici workarounds unnecessary. Remove the legacy proxy guide from contrib/ and all references to the legacy approach across the docs. Signed-off-by: Jon Koops --- .../remove-legacy-proxy-techdocs-cli.md | 5 + .../help-im-behind-a-corporate-proxy.md | 104 ------------------ docs/features/techdocs/cli.md | 10 +- .../keeping-backstage-updated.md | 11 +- .../create-app/keeping-backstage-updated.md | 11 +- docs/tutorials/corporate-proxy.md | 4 - packages/techdocs-cli/README.md | 7 +- 7 files changed, 16 insertions(+), 136 deletions(-) create mode 100644 .changeset/remove-legacy-proxy-techdocs-cli.md delete mode 100644 contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md diff --git a/.changeset/remove-legacy-proxy-techdocs-cli.md b/.changeset/remove-legacy-proxy-techdocs-cli.md new file mode 100644 index 0000000000..f99fe0f429 --- /dev/null +++ b/.changeset/remove-legacy-proxy-techdocs-cli.md @@ -0,0 +1,5 @@ +--- +'@techdocs/cli': patch +--- + +Updated proxy documentation to recommend Node.js built-in proxy support via `NODE_USE_ENV_PROXY` instead of `global-agent`. diff --git a/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md b/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md deleted file mode 100644 index 4a7e0ad233..0000000000 --- a/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md +++ /dev/null @@ -1,104 +0,0 @@ -# Legacy: Running the backend behind a Corporate Proxy - -> [!NOTE] -> On Node.js 22.21.0 or later, you can use Node.js's built-in proxy support instead of the workarounds described here. See the [recommended proxy setup guide](../../../docs/tutorials/corporate-proxy.md) for details. - -This article helps you get your backend installation up and running making calls through corporate proxies. - -## Background - -Let's admit it, we've all been there. Sometimes you have to run stuff with no way out to the public internet, except via the smallest of corporate proxy tunnels. It's most likely that you're going to run into these issues from the backend part of Backstage as that's the part that isn't helped by your browser or OS settings for the corporate proxy. - -Unfortunately, neither the Node.js native `fetch` nor the other frequently used library `node-fetch` (see [ADR013](https://backstage.io/docs/architecture-decisions/adrs-adr013)) respect `HTTP(S)_PROXY` environment variables by default. As an additional complication, there is no single solution for configuring both native `fetch` and `node-fetch` at once, uniformly. - -There are however some ways to get this to work without too much effort. - -## Installation - -**Note:** You're going to want to be in your backend working directory for these solutions as that's where the requests come from that don't go through this proxy. - -1. Install the required packages in your backend, by running the following command inside your backend directory (typically `packages/backend` under your repository root). - - ```bash - yarn add undici global-agent - ``` - - `undici` exposes the settings for native `fetch`, and `global-agent` can set things up for `node-fetch`. - -1. Go to the entry file for the backend (typically `packages/backend/src/index.ts`), and add the following at the VERY top, before all other imports etc: - - ```ts - import 'global-agent/bootstrap'; - import { setGlobalDispatcher, EnvHttpProxyAgent } from 'undici'; - - setGlobalDispatcher(new EnvHttpProxyAgent()); - ``` - - The first import automatically bootstraps `global-agent`, which addresses `node-fetch` proxying. The lines below that set up the `undici` package which affects native `fetch`. - -1. Start the backend with the correct environment variables set. For example: - - ```sh - export HTTP_PROXY=http://username:password@proxy.example.net:8888 - export GLOBAL_AGENT_HTTP_PROXY=${HTTP_PROXY} - yarn start - ``` - - The default for `global-agent` is to have a prefix on the variable names, hence the need for specifying it twice. For further information about `HTTP(S)_PROXY` and `NO_PROXY` excludes, see [the global-agent documentation](https://github.com/gajus/global-agent) and [undici documentation](https://github.com/nodejs/undici). - -## Configuration - -If your development environment is in the cloud (like with [AWS Cloud9](https://aws.amazon.com/cloud9/) or an instance of [Theia](https://theia-ide.org/)), you will need to update your configuration. - -You will probably need to make some changes in `app-config.yaml` (or another config file like `app-config.local.yaml` if you've created it, see the [configuration doc](https://backstage.io/docs/conf/#supplying-configuration)). -The exact values will depend on your setup but for instance, if your public URL is `https://your-public-url.com` and the port `3000` and `8080` are open: - -```yaml -app: - baseUrl: https://your-public-url.com:3000 - listen: - host: 0.0.0.0 # This makes the dev server bind to all IPv4 interfaces and not just the baseUrl hostname - -backend: - baseUrl: https://your-public-url.com:8080 - listen: - port: 8080 - cors: - origin: https://your-public-url.com:3000 -``` - -The app port must proxy web socket connections in order to make hot reloading work. - -## Alternatives to `global-agent` - -The `proxy-agent` package can be used as an alternative to `global-agent` (do not install both!), and also ensures that the `node-fetch` library correctly respects proxy settings, but [does NOT work](https://github.com/TooTallNate/proxy-agents/issues/239) for modern `undici` based native Node.js `fetch`, so you'll still have to also do the `undici` steps in the section above in addition to this. - -`proxy-agent` is a library that you can use to override the `globalAgents` of `node` land with a tunnel to use for each request. - -1. Install `proxy-agent` using `yarn add proxy-agent` -2. Go to the entry file for the backend (`src/index.ts`) -3. At the top of the file paste the following: - - ```ts - import ProxyAgent from 'proxy-agent'; - import http from 'http'; - import https from 'https'; - - /* - Something to note here, this might need different configuration depending on your own setup. - If you only have an http_proxy then you'll need to set that as both the http and https globalAgent instead. - */ - if (process.env.HTTP_PROXY) { - http.globalAgent = new ProxyAgent(process.env.HTTP_PROXY); - } - - if (process.env.HTTPS_PROXY) { - https.globalAgent = new ProxyAgent(process.env.HTTPS_PROXY); - } - ``` - -4. Start the backend with `yarn start` - -## Backstage CLI - -The Backstage CLI [versions:bump](https://backstage.io/docs/tooling/cli/commands#versionsbump) command also supports proxies via `global-agent` environment variable configuration. See the [keeping Backstage updated](https://backstage.io/docs/getting-started/keeping-backstage-updated/#proxy) docs for more information. diff --git a/docs/features/techdocs/cli.md b/docs/features/techdocs/cli.md index fa3d9148e2..7d3e0c209c 100644 --- a/docs/features/techdocs/cli.md +++ b/docs/features/techdocs/cli.md @@ -208,15 +208,7 @@ Options: #### Publishing from behind a proxy -On Node.js 22.21.0+, set `NODE_USE_ENV_PROXY=1` along with `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY` to route TechDocs publishing through a proxy. See the [corporate proxy guide](../../tutorials/corporate-proxy.md) for details. - -On older Node.js versions, the TechDocs CLI leverages `global-agent` to navigate the proxy. To enable `global-agent`, the following variables need to be set prior to running the techdocs-cli command: - -```bash -export GLOBAL_AGENT_HTTP_PROXY=${HTTP_PROXY} -export GLOBAL_AGENT_HTTPS_PROXY=${HTTPS_PROXY} -export GLOBAL_AGENT_NO_PROXY=${NO_PROXY} -``` +Set `NODE_USE_ENV_PROXY=1` along with `HTTP_PROXY`/`HTTPS_PROXY`/`NO_PROXY` to route TechDocs publishing through a proxy. See the [corporate proxy guide](../../tutorials/corporate-proxy.md) for details. ### Migrate content for case-insensitive access diff --git a/docs/getting-started/keeping-backstage-updated.md b/docs/getting-started/keeping-backstage-updated.md index a55f872f53..ce2f4a477c 100644 --- a/docs/getting-started/keeping-backstage-updated.md +++ b/docs/getting-started/keeping-backstage-updated.md @@ -151,9 +151,7 @@ down the number of duplicate packages. ## Proxy -On Node.js 22.21.0+, the Backstage CLI respects the standard `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables when `NODE_USE_ENV_PROXY=1` is set. See the [corporate proxy guide](../tutorials/corporate-proxy.md) for full details. - -On older Node.js versions, the CLI falls back to [global-agent](https://www.npmjs.com/package/global-agent) and `undici` for proxy support, which require their own environment variables (prefixed with `GLOBAL_AGENT_`). This allows you to route the CLI’s network traffic through a proxy server, which can be useful in environments with restricted internet access. +The Backstage CLI respects the standard `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables when `NODE_USE_ENV_PROXY=1` is set. See the [corporate proxy guide](../tutorials/corporate-proxy.md) for full details. Additionally, yarn needs a proxy too (sometimes), when in environments with restricted internet access. It uses different settings than the other modules. If you decide to use the backstage yarn plugin [mentioned above](#plugin), you will need to set additional proxy values. If you will always need proxy settings in all environments and situations, you can add `httpProxy` and `httpsProxy` values to [the yarnrc.yml file](https://yarnpkg.com/configuration/yarnrc). If some environments need it (say a developer workstation) but other environments do not (perhaps a CI build server running on AWS), then you may not want to update the yarnrc.yml file but just set environment variables `YARN_HTTP_PROXY` and `YARN_HTTPS_PROXY` in the environments/situations where you need to proxy. @@ -164,12 +162,9 @@ If you will always need proxy settings in all environments and situations, you c ```bash export HTTP_PROXY=http://proxy.company.com:8080 -export HTTPS_PROXY=https://secure-proxy.company.com:8080 +export HTTPS_PROXY=http://proxy.company.com:8080 export NO_PROXY=localhost,internal.company.com -export NODE_USE_ENV_PROXY=1 # Node.js 22.21.0+ -export GLOBAL_AGENT_HTTP_PROXY=${HTTP_PROXY} # Node.js < 22.21.0 -export GLOBAL_AGENT_HTTPS_PROXY=${HTTPS_PROXY} # Node.js < 22.21.0 -export GLOBAL_AGENT_NO_PROXY=${NO_PROXY} # Node.js < 22.21.0 +export NODE_USE_ENV_PROXY=1 export YARN_HTTP_PROXY=${HTTP_PROXY} # optional export YARN_HTTPS_PROXY=${HTTPS_PROXY} # optional ``` diff --git a/docs/golden-path/create-app/keeping-backstage-updated.md b/docs/golden-path/create-app/keeping-backstage-updated.md index b0561e5304..9663ed6690 100644 --- a/docs/golden-path/create-app/keeping-backstage-updated.md +++ b/docs/golden-path/create-app/keeping-backstage-updated.md @@ -142,9 +142,7 @@ down the number of duplicate packages. ## Proxy -On Node.js 22.21.0+, the Backstage CLI respects the standard `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables when `NODE_USE_ENV_PROXY=1` is set. See the [corporate proxy guide](../../tutorials/corporate-proxy.md) for full details. - -On older Node.js versions, the CLI falls back to [global-agent](https://www.npmjs.com/package/global-agent) and `undici` for proxy support, which require their own environment variables (prefixed with `GLOBAL_AGENT_`). This allows you to route the CLI’s network traffic through a proxy server, which can be useful in environments with restricted internet access. +The Backstage CLI respects the standard `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables when `NODE_USE_ENV_PROXY=1` is set. See the [corporate proxy guide](../../tutorials/corporate-proxy.md) for full details. Additionally, `yarn` needs a proxy too (sometimes), when in environments with restricted internet access. It uses different settings than the other modules. If you decide to use the backstage yarn plugin [mentioned above](#plugin), you will need to set additional proxy values. If you will always need proxy settings in all environments and situations, you can add `httpProxy` and `httpsProxy` values to [the yarnrc.yml file](https://yarnpkg.com/configuration/yarnrc). If some environments need it (say a developer workstation) but other environments do not (perhaps a CI build server running on AWS), then you may not want to update the yarnrc.yml file but just set environment variables `YARN_HTTP_PROXY` and `YARN_HTTPS_PROXY` in the environments/situations where you need to proxy. @@ -155,12 +153,9 @@ If you will always need proxy settings in all environments and situations, you c ```bash export HTTP_PROXY=http://proxy.company.com:8080 -export HTTPS_PROXY=https://secure-proxy.company.com:8080 +export HTTPS_PROXY=http://proxy.company.com:8080 export NO_PROXY=localhost,internal.company.com -export NODE_USE_ENV_PROXY=1 # Node.js 22.21.0+ -export GLOBAL_AGENT_HTTP_PROXY=${HTTP_PROXY} # Node.js < 22.21.0 -export GLOBAL_AGENT_HTTPS_PROXY=${HTTPS_PROXY} # Node.js < 22.21.0 -export GLOBAL_AGENT_NO_PROXY=${NO_PROXY} # Node.js < 22.21.0 +export NODE_USE_ENV_PROXY=1 export YARN_HTTP_PROXY=${HTTP_PROXY} # optional export YARN_HTTPS_PROXY=${HTTPS_PROXY} # optional ``` diff --git a/docs/tutorials/corporate-proxy.md b/docs/tutorials/corporate-proxy.md index e4346e4f94..ad03a436cf 100644 --- a/docs/tutorials/corporate-proxy.md +++ b/docs/tutorials/corporate-proxy.md @@ -27,7 +27,3 @@ yarn start Per [ADR014](../architecture-decisions/adr014-use-fetch.md), Backstage backend code should use native `fetch()`, which works with Node.js's proxy out of the box. Some core packages and many [community plugins](https://github.com/backstage/community-plugins/) still use `node-fetch` (see [ADR013](../architecture-decisions/adr013-use-node-fetch.md)) or `cross-fetch` (for isomorphic packages). Both libraries delegate to `node:http`/`node:https` internally and do **not** set a custom HTTP agent by default, which means Node.js's proxy works for them as well. The exception is code that explicitly passes a custom `agent` to its fetch calls (e.g. the Kubernetes plugins, which use `new https.Agent(...)` for TLS client certificates). In those cases, the custom agent takes precedence and the built-in proxy is bypassed. This is generally the desired behavior, since those agents are configured for direct connections to specific endpoints like cluster APIs. - -## Legacy approach - -If you are on a Node.js version older than 22.21.0, you can use third-party packages to add proxy support. See the [legacy proxy setup guide](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/help-im-behind-a-corporate-proxy.md) for instructions using `undici`, `global-agent`, and `proxy-agent`. diff --git a/packages/techdocs-cli/README.md b/packages/techdocs-cli/README.md index 22f0093067..82dd0911cf 100644 --- a/packages/techdocs-cli/README.md +++ b/packages/techdocs-cli/README.md @@ -44,9 +44,10 @@ yarn techdocs-cli:dev [...options] ```sh # Prior to executing the techdocs-cli command -export GLOBAL_AGENT_HTTP_PROXY=${HTTP_PROXY} -export GLOBAL_AGENT_HTTPS_PROXY=${HTTPS_PROXY} -export GLOBAL_AGENT_NO_PROXY=${NO_PROXY} +export HTTP_PROXY=http://proxy.company.com:8080 +export HTTPS_PROXY=http://proxy.company.com:8080 +export NO_PROXY=localhost,internal.company.com +export NODE_USE_ENV_PROXY=1 ``` ### Using an example docs project From 64a91d0005d9436ae723669d60b3cdb59490ae71 Mon Sep 17 00:00:00 2001 From: Juan Pablo Garcia Ripa Date: Thu, 19 Mar 2026 21:57:48 +0100 Subject: [PATCH 039/191] Rename frontend-plugin to legacy-frontend-plugin rename the template to not get naming conficts with the nfs template Signed-off-by: Juan Pablo Garcia Ripa --- .changeset/angry-clouds-tell.md | 5 +++++ .../templates/legacy-frontend-plugin/portable-template.yaml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/angry-clouds-tell.md diff --git a/.changeset/angry-clouds-tell.md b/.changeset/angry-clouds-tell.md new file mode 100644 index 0000000000..326def62f0 --- /dev/null +++ b/.changeset/angry-clouds-tell.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli-module-new': minor +--- + +Rename the legacy `frontend-plugin` to `legacy-frontend-plugin` diff --git a/packages/cli-module-new/templates/legacy-frontend-plugin/portable-template.yaml b/packages/cli-module-new/templates/legacy-frontend-plugin/portable-template.yaml index d69a1f35df..ec4bd338c6 100644 --- a/packages/cli-module-new/templates/legacy-frontend-plugin/portable-template.yaml +++ b/packages/cli-module-new/templates/legacy-frontend-plugin/portable-template.yaml @@ -1,4 +1,4 @@ -name: frontend-plugin +name: legacy-frontend-plugin role: frontend-plugin description: A new frontend plugin (legacy system) values: From b468b7f105ff196158d1025fd90b6221db877a9d Mon Sep 17 00:00:00 2001 From: Juan Pablo Garcia Ripa Date: Fri, 20 Mar 2026 07:39:23 +0100 Subject: [PATCH 040/191] add patches file Signed-off-by: Juan Pablo Garcia Ripa --- .patches/pr-33446.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 .patches/pr-33446.txt diff --git a/.patches/pr-33446.txt b/.patches/pr-33446.txt new file mode 100644 index 0000000000..b040b7b898 --- /dev/null +++ b/.patches/pr-33446.txt @@ -0,0 +1 @@ +Rename the legacy to to avoid template name collision when running From 3d27f2312b3e6e55cc34c7fa141ca91438b064b6 Mon Sep 17 00:00:00 2001 From: Juan Pablo Garcia Ripa Date: Fri, 20 Mar 2026 08:41:14 +0100 Subject: [PATCH 041/191] Update .changeset/angry-clouds-tell.md Co-authored-by: Patrik Oldsberg Signed-off-by: Juan Pablo Garcia Ripa --- .changeset/angry-clouds-tell.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/angry-clouds-tell.md b/.changeset/angry-clouds-tell.md index 326def62f0..c2da96699f 100644 --- a/.changeset/angry-clouds-tell.md +++ b/.changeset/angry-clouds-tell.md @@ -1,5 +1,5 @@ --- -'@backstage/cli-module-new': minor +'@backstage/cli-module-new': patch --- Rename the legacy `frontend-plugin` to `legacy-frontend-plugin` From 4db987920c4e3313f73c2559689280e912dcfeab Mon Sep 17 00:00:00 2001 From: Juan Pablo Garcia Ripa Date: Fri, 20 Mar 2026 08:43:08 +0100 Subject: [PATCH 042/191] Update .patches/pr-33446.txt Co-authored-by: Patrik Oldsberg Signed-off-by: Juan Pablo Garcia Ripa --- .patches/pr-33446.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.patches/pr-33446.txt b/.patches/pr-33446.txt index b040b7b898..ebef1bc0c2 100644 --- a/.patches/pr-33446.txt +++ b/.patches/pr-33446.txt @@ -1 +1 @@ -Rename the legacy to to avoid template name collision when running +Fixed incorrect name of the `legacy-frontend-plugin` template From 961e2745487f247e7340230901e39af4845e4f17 Mon Sep 17 00:00:00 2001 From: Kurt King Date: Mon, 23 Mar 2026 23:37:58 -0600 Subject: [PATCH 043/191] refactor: use MetricsService Signed-off-by: Kurt King --- .changeset/evil-seals-smell.md | 5 ++++ plugins/scaffolder-backend/package.json | 1 - .../src/ScaffolderPlugin.ts | 4 ++++ .../src/scaffolder/dryrun/createDryRunner.ts | 2 ++ .../tasks/NunjucksWorkflowRunner.test.ts | 6 ++++- .../tasks/NunjucksWorkflowRunner.ts | 23 ++++++++++--------- .../src/scaffolder/tasks/TaskWorker.test.ts | 6 +++++ .../src/scaffolder/tasks/TaskWorker.ts | 4 ++++ .../src/service/router.test.ts | 6 ++++- .../scaffolder-backend/src/service/router.ts | 9 +++++++- yarn.lock | 1 - 11 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 .changeset/evil-seals-smell.md diff --git a/.changeset/evil-seals-smell.md b/.changeset/evil-seals-smell.md new file mode 100644 index 0000000000..a533720434 --- /dev/null +++ b/.changeset/evil-seals-smell.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Migrated OpenTelemetry metrics to use the `MetricsService` from `@backstage/backend-plugin-api/alpha` instead of the raw `@opentelemetry/api` meter. diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index df6fcb9c89..57730ec700 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -78,7 +78,6 @@ "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", "@backstage/types": "workspace:^", - "@opentelemetry/api": "^1.9.0", "@types/luxon": "^3.0.0", "express": "^4.22.0", "fs-extra": "^11.2.0", diff --git a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts index 17997da485..6cef0d3cde 100644 --- a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts +++ b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts @@ -63,6 +63,7 @@ import { import { actionsServiceRef, actionsRegistryServiceRef, + metricsServiceRef, } from '@backstage/backend-plugin-api/alpha'; import { createScaffolderActions } from './actions'; @@ -151,6 +152,7 @@ export const scaffolderPlugin = createBackendPlugin({ actionsRegistry: actionsServiceRef, actionsRegistryService: actionsRegistryServiceRef, scaffolderService: scaffolderServiceRef, + metrics: metricsServiceRef, }, async init({ logger, @@ -168,6 +170,7 @@ export const scaffolderPlugin = createBackendPlugin({ actionsRegistry, actionsRegistryService, scaffolderService, + metrics, }) { const log = loggerToWinstonLogger(logger); const integrations = ScmIntegrations.fromConfig(config); @@ -244,6 +247,7 @@ export const scaffolderPlugin = createBackendPlugin({ events, auditor, actionsRegistry, + metrics, }); httpRouter.use(router); }, diff --git a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts index e01e76064e..1c56248081 100644 --- a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts @@ -19,6 +19,7 @@ import { BackstageCredentials, LoggerService, } from '@backstage/backend-plugin-api'; +import type { MetricsService } from '@backstage/backend-plugin-api/alpha'; import type { UserEntity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; @@ -81,6 +82,7 @@ export type TemplateTesterCreateOptions = { additionalTemplateGlobals?: Record; permissions?: PermissionEvaluator; config?: Config; + metrics: MetricsService; }; /** diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index c063e38cb2..6b07936e57 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts @@ -39,7 +39,10 @@ import { mockCredentials, mockServices, } from '@backstage/backend-test-utils'; -import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; +import { + actionsRegistryServiceMock, + metricsServiceMock, +} from '@backstage/backend-test-utils/alpha'; describe('NunjucksWorkflowRunner', () => { let actionRegistry: TemplateActionRegistry; @@ -249,6 +252,7 @@ describe('NunjucksWorkflowRunner', () => { logger, permissions: mockedPermissionApi, config, + metrics: metricsServiceMock.mock(), }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 589ad2e204..5e291973a8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -23,7 +23,6 @@ import { TaskStep, } from '@backstage/plugin-scaffolder-common'; import { JsonArray, JsonObject, JsonValue } from '@backstage/types'; -import { metrics } from '@opentelemetry/api'; import fs from 'fs-extra'; import { validate as validateJsonSchema } from 'jsonschema'; import nunjucks from 'nunjucks'; @@ -42,6 +41,7 @@ import type { LoggerService, PermissionsService, } from '@backstage/backend-plugin-api'; +import type { MetricsService } from '@backstage/backend-plugin-api/alpha'; import { UserEntity } from '@backstage/catalog-model'; import { AuthorizeResult, @@ -78,6 +78,7 @@ type NunjucksWorkflowRunnerOptions = { additionalTemplateGlobals?: Record; permissions?: PermissionsService; config?: Config; + metrics: MetricsService; }; type TemplateContext = { @@ -188,6 +189,8 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { secrets?: Record; } = { parameters: {}, secrets: {} }; + private readonly tracker; + constructor(options: NunjucksWorkflowRunnerOptions) { this.options = options; this.defaultTemplateFilters = convertFiltersToRecord( @@ -195,10 +198,9 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { integrations: this.options.integrations, }), ); + this.tracker = scaffoldingTracker(options.metrics); } - private readonly tracker = scaffoldingTracker(); - async getEnvironmentConfig(): Promise<{ parameters: JsonObject; secrets?: TaskSecrets; @@ -700,7 +702,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { } } -function scaffoldingTracker() { +function scaffoldingTracker(metrics: MetricsService) { // prom-client metrics are deprecated in favour of OpenTelemetry metrics. const promTaskCount = createCounterMetric({ name: 'scaffolder_task_count', @@ -723,23 +725,22 @@ function scaffoldingTracker() { labelNames: ['template', 'step', 'result'], }); - const meter = metrics.getMeter('default'); - const taskCount = meter.createCounter('scaffolder.task.count', { + const taskCount = metrics.createCounter('scaffolder.task.count', { description: 'Count of task runs', }); - const taskDuration = meter.createHistogram('scaffolder.task.duration', { + const taskDuration = metrics.createHistogram('scaffolder.task.duration', { description: 'Duration of a task run', - unit: 'seconds', + unit: 's', }); - const stepCount = meter.createCounter('scaffolder.step.count', { + const stepCount = metrics.createCounter('scaffolder.step.count', { description: 'Count of step runs', }); - const stepDuration = meter.createHistogram('scaffolder.step.duration', { + const stepDuration = metrics.createHistogram('scaffolder.step.duration', { description: 'Duration of a step runs', - unit: 'seconds', + unit: 's', }); async function taskStart(task: TaskContext) { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index 578786cc65..71ed6b9eb4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -36,6 +36,7 @@ import { WorkflowRunner } from './types'; import ObservableImpl from 'zen-observable'; import waitForExpect from 'wait-for-expect'; import { mockServices } from '@backstage/backend-test-utils'; +import { metricsServiceMock } from '@backstage/backend-test-utils/alpha'; import { loggerToWinstonLogger } from '../../util/loggerToWinstonLogger'; jest.mock('./NunjucksWorkflowRunner'); @@ -93,6 +94,7 @@ describe('TaskWorker', () => { integrations, taskBroker: broker, actionRegistry, + metrics: metricsServiceMock.mock(), }); await broker.dispatch({ @@ -124,6 +126,7 @@ describe('TaskWorker', () => { integrations, taskBroker: broker, actionRegistry, + metrics: metricsServiceMock.mock(), }); const { taskId } = await broker.dispatch({ @@ -174,6 +177,7 @@ describe('TaskWorker', () => { }, }, }), + metrics: metricsServiceMock.mock(), }); await taskWorker.runOneTask({ @@ -261,6 +265,7 @@ describe('Concurrent TaskWorker', () => { taskBroker: broker, actionRegistry, concurrentTasksLimit: expectedConcurrentTasks, + metrics: metricsServiceMock.mock(), }); taskWorker.start(); @@ -307,6 +312,7 @@ describe('Cancellable TaskWorker', () => { integrations, taskBroker, actionRegistry, + metrics: metricsServiceMock.mock(), }); const steps = [...Array(10)].map(n => ({ diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index b98badd9f6..fdae61a84b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -15,6 +15,7 @@ */ import { AuditorService, LoggerService } from '@backstage/backend-plugin-api'; +import type { MetricsService } from '@backstage/backend-plugin-api/alpha'; import { assertError, InputError, stringifyError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; @@ -78,6 +79,7 @@ export type CreateWorkerOptions = { additionalTemplateGlobals?: Record; permissions?: PermissionEvaluator; gracefulShutdown?: boolean; + metrics: MetricsService; }; /** @@ -123,6 +125,7 @@ export class TaskWorker { additionalTemplateGlobals, permissions, gracefulShutdown, + metrics, } = options; const workflowRunner = new NunjucksWorkflowRunner({ @@ -135,6 +138,7 @@ export class TaskWorker { additionalTemplateGlobals, permissions, config, + metrics, }); return new TaskWorker({ diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 9da276c67b..464614fbea 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -58,7 +58,10 @@ import { import { createDefaultFilters } from '../lib/templating/filters/createDefaultFilters'; import { createRouter } from './router'; import { DatabaseTaskStore } from '../scaffolder/tasks/DatabaseTaskStore'; -import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; +import { + actionsRegistryServiceMock, + metricsServiceMock, +} from '@backstage/backend-test-utils/alpha'; import { ActionsService } from '@backstage/backend-plugin-api/alpha'; function createDatabase(): DatabaseService { @@ -229,6 +232,7 @@ const createTestRouter = async ( createDebugLogAction(), ], actionsRegistry: overrides.actionsRegistry ?? actionsRegistryServiceMock(), + metrics: metricsServiceMock.mock(), }); router.use(mockErrorHandler()); diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 9e84b38e39..a393df253e 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -131,7 +131,10 @@ import { scaffolderTaskRules, scaffolderTemplateRules, } from './rules'; -import { ActionsService } from '@backstage/backend-plugin-api/alpha'; +import { + ActionsService, + MetricsService, +} from '@backstage/backend-plugin-api/alpha'; /** * RouterOptions @@ -165,6 +168,7 @@ export interface RouterOptions { auditor?: AuditorService; autocompleteHandlers?: Record; actionsRegistry: ActionsService; + metrics: MetricsService; } function isSupportedTemplate(entity: TemplateEntityV1beta3) { @@ -256,6 +260,7 @@ export async function createRouter( httpAuth, auditor, actionsRegistry, + metrics, } = options; const concurrentTasksLimit = @@ -344,6 +349,7 @@ export async function createRouter( concurrentTasksLimit, permissions, gracefulShutdown, + metrics, ...templateExtensions, }); @@ -375,6 +381,7 @@ export async function createRouter( workingDirectory, permissions, config, + metrics, ...templateExtensions, }); diff --git a/yarn.lock b/yarn.lock index 6e0cb55ac9..48fb115c86 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6813,7 +6813,6 @@ __metadata: "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" "@backstage/repo-tools": "workspace:^" "@backstage/types": "workspace:^" - "@opentelemetry/api": "npm:^1.9.0" "@types/express": "npm:^4.17.6" "@types/fs-extra": "npm:^11.0.0" "@types/luxon": "npm:^3.0.0" From 61f754504b2f1acb16d737c571bf67c5ba2d5088 Mon Sep 17 00:00:00 2001 From: Kurt King Date: Tue, 24 Mar 2026 17:28:00 -0600 Subject: [PATCH 044/191] Add type to tracker Signed-off-by: Kurt King --- .../src/scaffolder/tasks/NunjucksWorkflowRunner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 5e291973a8..505dac00f2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -189,7 +189,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { secrets?: Record; } = { parameters: {}, secrets: {} }; - private readonly tracker; + private readonly tracker: ReturnType; constructor(options: NunjucksWorkflowRunnerOptions) { this.options = options; From 088454063c743c8d9ecaf020c15080ef251da94d Mon Sep 17 00:00:00 2001 From: Kurt King Date: Tue, 24 Mar 2026 17:37:27 -0600 Subject: [PATCH 045/191] unpuralize run Signed-off-by: Kurt King --- .../src/scaffolder/tasks/NunjucksWorkflowRunner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 505dac00f2..6f6f0a6d93 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -739,7 +739,7 @@ function scaffoldingTracker(metrics: MetricsService) { }); const stepDuration = metrics.createHistogram('scaffolder.step.duration', { - description: 'Duration of a step runs', + description: 'Duration of a step run', unit: 's', }); From 90aadc64992cb23c350c473ec0a2657a8a85eed7 Mon Sep 17 00:00:00 2001 From: Kurt King Date: Tue, 24 Mar 2026 17:44:02 -0600 Subject: [PATCH 046/191] Be more specific in metric descriptions Signed-off-by: Kurt King --- .../src/scaffolder/tasks/NunjucksWorkflowRunner.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 6f6f0a6d93..a6a2eb2483 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -726,20 +726,20 @@ function scaffoldingTracker(metrics: MetricsService) { }); const taskCount = metrics.createCounter('scaffolder.task.count', { - description: 'Count of task runs', + description: 'Total number of scaffolder tasks executed', }); const taskDuration = metrics.createHistogram('scaffolder.task.duration', { - description: 'Duration of a task run', + description: 'Time taken to complete a scaffolder task end-to-end', unit: 's', }); const stepCount = metrics.createCounter('scaffolder.step.count', { - description: 'Count of step runs', + description: 'Total number of individual scaffolder action steps executed', }); const stepDuration = metrics.createHistogram('scaffolder.step.duration', { - description: 'Duration of a step run', + description: 'Time taken to complete a single scaffolder action step', unit: 's', }); From 5f9a531412b3370b7322069283275305b4b4b3cd Mon Sep 17 00:00:00 2001 From: Marat Dyatko Date: Wed, 25 Mar 2026 16:15:27 +0100 Subject: [PATCH 047/191] Replace deprecated humanizeEntityRef with Catalog Presentation API Migrate all humanizeEntityRef and humanizeEntity usages to the Catalog Presentation API across catalog, catalog-react, org-react, catalog-import, scaffolder, and techdocs plugins. - Use useEntityPresentation hook in React component contexts (AncestryPage) - Use defaultEntityPresentation for non-React contexts like sort comparators, filter functions, and data mappers - Add @deprecated tags to humanizeEntityRef and humanizeEntity - Improve TSDoc on entityPresentationApiRef, EntityPresentationApi, useEntityPresentation, EntityDisplayName, and defaultEntityPresentation with guidance on which to use when - Add Entity Presentation docs page with usage examples and migration guide Made-with: Cursor Signed-off-by: Marat Dyatko --- ...lace-humanize-entity-ref-catalog-import.md | 5 + ...place-humanize-entity-ref-catalog-react.md | 5 + .../replace-humanize-entity-ref-catalog.md | 5 + .../replace-humanize-entity-ref-org-react.md | 5 + .../replace-humanize-entity-ref-scaffolder.md | 5 + .../replace-humanize-entity-ref-techdocs.md | 5 + .../software-catalog/entity-presentation.md | 117 ++++++++++++++++++ mkdocs.yml | 1 + .../StepPrepareCreatePullRequest.tsx | 7 +- plugins/catalog-react/report.api.md | 2 +- .../EntityPresentationApi.ts | 30 ++++- .../defaultEntityPresentation.ts | 15 ++- .../useEntityPresentation.ts | 13 ++ .../EntityDataTable/columnFactories.tsx | 18 +-- .../EntityDisplayName/EntityDisplayName.tsx | 10 ++ .../EntityOwnerPicker/EntityOwnerPicker.tsx | 4 +- .../src/components/EntityRefLink/humanize.ts | 8 ++ .../src/components/EntityTable/columns.tsx | 16 +-- .../components/AncestryPage.tsx | 12 +- .../components/CatalogTable/CatalogTable.tsx | 26 ++-- .../src/components/CatalogTable/columns.tsx | 11 +- .../GroupListPicker/GroupListPicker.tsx | 8 +- .../TemplateFormPreviewer.tsx | 8 +- .../src/home/components/Tables/helpers.ts | 8 +- 24 files changed, 275 insertions(+), 69 deletions(-) create mode 100644 .changeset/replace-humanize-entity-ref-catalog-import.md create mode 100644 .changeset/replace-humanize-entity-ref-catalog-react.md create mode 100644 .changeset/replace-humanize-entity-ref-catalog.md create mode 100644 .changeset/replace-humanize-entity-ref-org-react.md create mode 100644 .changeset/replace-humanize-entity-ref-scaffolder.md create mode 100644 .changeset/replace-humanize-entity-ref-techdocs.md create mode 100644 docs/features/software-catalog/entity-presentation.md diff --git a/.changeset/replace-humanize-entity-ref-catalog-import.md b/.changeset/replace-humanize-entity-ref-catalog-import.md new file mode 100644 index 0000000000..544e91bac3 --- /dev/null +++ b/.changeset/replace-humanize-entity-ref-catalog-import.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': patch +--- + +Replaced `humanizeEntityRef` with `defaultEntityPresentation` from the Catalog Presentation API in `StepPrepareCreatePullRequest`. diff --git a/.changeset/replace-humanize-entity-ref-catalog-react.md b/.changeset/replace-humanize-entity-ref-catalog-react.md new file mode 100644 index 0000000000..b44e796624 --- /dev/null +++ b/.changeset/replace-humanize-entity-ref-catalog-react.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Replaced `humanizeEntityRef` with `defaultEntityPresentation` and `useEntityPresentation` from the Catalog Presentation API in `EntityOwnerPicker`, `EntityTable`, `EntityDataTable`, and `AncestryPage` components. diff --git a/.changeset/replace-humanize-entity-ref-catalog.md b/.changeset/replace-humanize-entity-ref-catalog.md new file mode 100644 index 0000000000..2a27c87f47 --- /dev/null +++ b/.changeset/replace-humanize-entity-ref-catalog.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Replaced `humanizeEntityRef` with `defaultEntityPresentation` from the Catalog Presentation API in `CatalogTable` and its column factories. diff --git a/.changeset/replace-humanize-entity-ref-org-react.md b/.changeset/replace-humanize-entity-ref-org-react.md new file mode 100644 index 0000000000..afb04d3348 --- /dev/null +++ b/.changeset/replace-humanize-entity-ref-org-react.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org-react': patch +--- + +Replaced `humanizeEntityRef` with `defaultEntityPresentation` from the Catalog Presentation API in `GroupListPicker`. diff --git a/.changeset/replace-humanize-entity-ref-scaffolder.md b/.changeset/replace-humanize-entity-ref-scaffolder.md new file mode 100644 index 0000000000..5064875451 --- /dev/null +++ b/.changeset/replace-humanize-entity-ref-scaffolder.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Replaced `humanizeEntityRef` with `defaultEntityPresentation` from the Catalog Presentation API in `TemplateFormPreviewer`. diff --git a/.changeset/replace-humanize-entity-ref-techdocs.md b/.changeset/replace-humanize-entity-ref-techdocs.md new file mode 100644 index 0000000000..374a0c2e8e --- /dev/null +++ b/.changeset/replace-humanize-entity-ref-techdocs.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Replaced `humanizeEntityRef` with `defaultEntityPresentation` from the Catalog Presentation API in TechDocs table helpers. diff --git a/docs/features/software-catalog/entity-presentation.md b/docs/features/software-catalog/entity-presentation.md new file mode 100644 index 0000000000..5d65baac07 --- /dev/null +++ b/docs/features/software-catalog/entity-presentation.md @@ -0,0 +1,117 @@ +--- +id: entity-presentation +title: Entity Presentation +description: How to display entity names and control how entities are represented in the Backstage interface +--- + +The _Entity Presentation API_ controls how catalog entities are displayed +throughout the Backstage interface. Instead of rendering raw entity refs like +`component:default/my-service`, the API resolves a human-friendly display +name from fields such as `metadata.title` and `spec.profile.displayName`. + +## Displaying entity names + +There are three ways to display entity names, depending on context: + +### `EntityDisplayName` component + +The simplest option for React components. Renders a styled entity name with +an optional icon and tooltip: + +```tsx +import { EntityDisplayName } from '@backstage/plugin-catalog-react'; + +; +``` + +You can pass an entity ref string, an `Entity` object, or a +`CompoundEntityRef`. The component supports optional `hideIcon` and +`disableTooltip` props. + +### `useEntityPresentation` hook + +Use this hook when you need access to the raw presentation data in a React +component, for example to render the title in a custom layout: + +```tsx +import { useEntityPresentation } from '@backstage/plugin-catalog-react'; + +function MyComponent({ entityRef }: { entityRef: string }) { + const { primaryTitle, secondaryTitle, Icon } = + useEntityPresentation(entityRef); + + return ( + + {Icon && } + {primaryTitle} + + ); +} +``` + +The hook subscribes to the `EntityPresentationApi` and returns a snapshot +that may update over time as additional data is fetched in the background. +If no presentation API is registered, it falls back to +`defaultEntityPresentation`. + +### `defaultEntityPresentation` function + +A synchronous helper for non-React contexts where hooks are not available. +Use it in sort comparators, filter functions, table column factories, and +data mappers: + +```ts +import { defaultEntityPresentation } from '@backstage/plugin-catalog-react'; + +const title = defaultEntityPresentation(entity, { + defaultKind: 'Component', +}).primaryTitle; +``` + +This resolves `primaryTitle` as the first available value among +`spec.profile.displayName`, `metadata.title`, and a shortened entity ref. + +## Customizing entity presentation + +To customize how entities are rendered, provide your own implementation of +the `EntityPresentationApi` interface and register it with the app's API +factory: + +```ts +import { + entityPresentationApiRef, + type EntityPresentationApi, +} from '@backstage/plugin-catalog-react'; +import { createApiFactory } from '@backstage/core-plugin-api'; + +const myPresentationApi: EntityPresentationApi = { + forEntity(entityOrRef, context) { + // Return an EntityRefPresentation with snapshot, update$, and promise + }, +}; + +createApiFactory({ + api: entityPresentationApiRef, + deps: {}, + factory: () => myPresentationApi, +}); +``` + +The presentation snapshot includes `primaryTitle`, an optional +`secondaryTitle` for tooltips, and an optional `Icon` component. You can +also emit updated snapshots over time via the `update$` observable. + +## Migrating from `humanizeEntityRef` + +The `humanizeEntityRef` and `humanizeEntity` functions are deprecated. They +only produce a shortened entity ref string and do not resolve display names +from `metadata.title` or `spec.profile.displayName`. + +Replace them as follows: + +| Old code | Replacement | +| :------------------------------------------------------------------ | :---------------------------------------------------------------- | +| `humanizeEntityRef(entity)` in JSX | `` | +| `humanizeEntityRef(entity)` in a hook-accessible context | `useEntityPresentation(entity).primaryTitle` | +| `humanizeEntityRef(entity, { defaultKind })` in a sort/filter/label | `defaultEntityPresentation(entity, { defaultKind }).primaryTitle` | +| `humanizeEntity(entity, fallback)` | `defaultEntityPresentation(entity).primaryTitle` | diff --git a/mkdocs.yml b/mkdocs.yml index e01ab2a431..ef0b2bae91 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -62,6 +62,7 @@ nav: - Extending the model: 'features/software-catalog/extending-the-model.md' - External integrations: 'features/software-catalog/external-integrations.md' - Catalog Customization: 'features/software-catalog/catalog-customization.md' + - Entity Presentation: 'features/software-catalog/entity-presentation.md' - API: 'features/software-catalog/api.md' - FAQ: 'features/software-catalog/faq.md' - Kubernetes: diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx index e42de5257e..3cb9fbbd39 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx @@ -20,7 +20,7 @@ import { assertError } from '@backstage/errors'; import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { catalogApiRef, - humanizeEntityRef, + defaultEntityPresentation, } from '@backstage/plugin-catalog-react'; import Box from '@material-ui/core/Box'; import FormHelperText from '@material-ui/core/FormHelperText'; @@ -162,7 +162,10 @@ export const StepPrepareCreatePullRequest = ( }); return groupEntities.items - .map(e => humanizeEntityRef(e, { defaultKind: 'group' })) + .map( + e => + defaultEntityPresentation(e, { defaultKind: 'group' }).primaryTitle, + ) .sort(); }); diff --git a/plugins/catalog-react/report.api.md b/plugins/catalog-react/report.api.md index f7f195c226..bb9d43edb0 100644 --- a/plugins/catalog-react/report.api.md +++ b/plugins/catalog-react/report.api.md @@ -834,7 +834,7 @@ export function getEntitySourceLocation( scmIntegrationsApi: typeof scmIntegrationsApiRef.T, ): EntitySourceLocation | undefined; -// @public (undocumented) +// @public @deprecated (undocumented) export function humanizeEntityRef( entityRef: Entity | CompoundEntityRef, opts?: { diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts index 642dd6dbac..e293d1e0ab 100644 --- a/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts +++ b/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts @@ -25,6 +25,21 @@ import { Observable } from '@backstage/types'; /** * An API that handles how to represent entities in the interface. * + * @remarks + * + * There are several ways to consume this API depending on context: + * + * - In React components, use the {@link useEntityPresentation} hook to get a + * reactive presentation snapshot that updates over time. + * + * - For simple inline rendering, use the {@link EntityDisplayName} component + * which wraps the hook and renders a styled entity name with optional icon + * and tooltip. + * + * - In non-React contexts such as sort comparators, filter functions, or data + * mappers, use the {@link defaultEntityPresentation} function which + * synchronously extracts a display name from an already-loaded entity. + * * @public */ export const entityPresentationApiRef: ApiRef = @@ -120,8 +135,19 @@ export interface EntityRefPresentation { * * @remarks * - * Most consumers will want to use the {@link useEntityPresentation} hook - * instead of this interface directly. + * Most consumers will not need to interact with this interface directly. + * Instead, use one of the following: + * + * - {@link useEntityPresentation} — React hook for reactive presentation data. + * + * - {@link EntityDisplayName} — React component that renders an entity name + * with optional icon and tooltip. + * + * - {@link defaultEntityPresentation} — synchronous helper for non-React + * contexts where you already have the entity object. + * + * Implement this interface to customize how entities are displayed throughout + * the Backstage interface. * * @public */ diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts index b546f9aa93..9c1cbd5325 100644 --- a/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts +++ b/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts @@ -24,7 +24,20 @@ import get from 'lodash/get'; import { EntityRefPresentationSnapshot } from './EntityPresentationApi'; /** - * This returns the default representation of an entity. + * Returns the default representation of an entity. + * + * @remarks + * + * This is a synchronous helper that extracts a display name from an + * already-loaded entity or entity ref. It resolves `primaryTitle` as the + * first available value among `spec.profile.displayName`, `metadata.title`, + * and a shortened entity ref string. + * + * Use this in non-React contexts where hooks are not available, such as sort + * comparators, filter functions, table column factories, and data mappers. + * In React components, prefer the {@link useEntityPresentation} hook or the + * {@link EntityDisplayName} component, which support async enrichment via + * the {@link EntityPresentationApi}. * * @public * @param entityOrRef - Either an entity, or a ref to it. diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/useEntityPresentation.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/useEntityPresentation.ts index e397558ed0..2e01485dcc 100644 --- a/plugins/catalog-react/src/apis/EntityPresentationApi/useEntityPresentation.ts +++ b/plugins/catalog-react/src/apis/EntityPresentationApi/useEntityPresentation.ts @@ -32,6 +32,19 @@ import { useUpdatingObservable } from './useUpdatingObservable'; /** * Returns information about how to represent an entity in the interface. * + * @remarks + * + * This hook subscribes to the {@link EntityPresentationApi} and returns a + * snapshot that may update over time as richer data is fetched (for example, + * resolving `metadata.title` from a string entity ref). If no presentation + * API is registered, it falls back to {@link defaultEntityPresentation}. + * + * For simple inline rendering, consider using the {@link EntityDisplayName} + * component instead, which wraps this hook with icon and tooltip support. + * + * For non-React contexts such as sort comparators or data mappers, use + * {@link defaultEntityPresentation} directly. + * * @public * @param entityOrRef - The entity to represent, or an entity ref to it. If you * pass in an entity, it is assumed that it is NOT a partial one - i.e. only diff --git a/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx b/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx index 49fb64caa8..647e7c7f3b 100644 --- a/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx +++ b/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx @@ -20,11 +20,8 @@ import { RELATION_PART_OF, } from '@backstage/catalog-model'; import { Cell, CellText, Column, ColumnConfig, TableItem } from '@backstage/ui'; -import { - EntityRefLink, - EntityRefLinks, - humanizeEntityRef, -} from '../EntityRefLink'; +import { EntityRefLink, EntityRefLinks } from '../EntityRefLink'; +import { defaultEntityPresentation } from '../../apis'; import { EntityTableColumnTitle } from '../EntityTable/TitleColumn'; import { getEntityRelations } from '../../utils'; @@ -63,8 +60,8 @@ export const columnFactories = Object.freeze({ ), sortValue: entity => - entity.metadata?.title || - humanizeEntityRef(entity, { defaultKind: options.defaultKind }), + defaultEntityPresentation(entity, { defaultKind: options.defaultKind }) + .primaryTitle, }; }, @@ -98,7 +95,12 @@ export const columnFactories = Object.freeze({ ), sortValue: entity => getEntityRelations(entity, options.relation, options.filter) - .map(r => humanizeEntityRef(r, { defaultKind: options.defaultKind })) + .map( + r => + defaultEntityPresentation(r, { + defaultKind: options.defaultKind, + }).primaryTitle, + ) .join(', '), }; }, diff --git a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx index a4134df465..6ed863f180 100644 --- a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx +++ b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx @@ -62,6 +62,16 @@ export type EntityDisplayNameProps = { /** * Shows a nice representation of a reference to an entity. * + * @remarks + * + * This component uses the {@link useEntityPresentation} hook internally and + * renders the entity's primary title with optional icon and tooltip. It is + * the simplest way to display an entity name in JSX. + * + * For more control over the presentation data, use the + * {@link useEntityPresentation} hook directly. For non-React contexts, use + * {@link defaultEntityPresentation}. + * * @public */ export const EntityDisplayName = ( diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx index ed200c77c4..16e00e5ccd 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx @@ -33,7 +33,7 @@ import { EntityOwnerFilter } from '../../filters'; import { useDebouncedEffect } from '@react-hookz/web'; import PersonIcon from '@material-ui/icons/Person'; import GroupIcon from '@material-ui/icons/Group'; -import { humanizeEntity, humanizeEntityRef } from '../EntityRefLink/humanize'; +import { defaultEntityPresentation } from '../../apis'; import { useFetchEntities } from './useFetchEntities'; import { withStyles } from '@material-ui/core/styles'; import { useEntityPresentation } from '../../apis'; @@ -203,7 +203,7 @@ export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => { defaultNamespace: 'default', }) : o; - return humanizeEntity(entity, humanizeEntityRef(entity)); + return defaultEntityPresentation(entity).primaryTitle; }} onChange={(_: object, owners) => { setText(''); diff --git a/plugins/catalog-react/src/components/EntityRefLink/humanize.ts b/plugins/catalog-react/src/components/EntityRefLink/humanize.ts index 478f0dce9d..ae1fb5e685 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/humanize.ts +++ b/plugins/catalog-react/src/components/EntityRefLink/humanize.ts @@ -25,6 +25,11 @@ import get from 'lodash/get'; * @param defaultNamespace - if set to false then namespace is never omitted, * if set to string which matches namespace of entity then omitted * + * @deprecated Use {@link defaultEntityPresentation} for non-React contexts, + * or {@link useEntityPresentation} / {@link EntityDisplayName} in React + * components. These provide richer display names using `metadata.title` and + * `spec.profile.displayName` in addition to the entity ref. + * * @public **/ export function humanizeEntityRef( @@ -76,6 +81,9 @@ export function humanizeEntityRef( * * If neither of those are found or populated, fallback to `defaultName`. * + * @deprecated Use {@link defaultEntityPresentation} instead, which provides + * the same resolution logic via `primaryTitle`. + * * @param entity - Entity to convert. * @param defaultName - If entity readable name is not available, `defaultName` will be returned. * @returns Readable name, defaults to `defaultName`. diff --git a/plugins/catalog-react/src/components/EntityTable/columns.tsx b/plugins/catalog-react/src/components/EntityTable/columns.tsx index 1bea56dc27..6b47fa02ff 100644 --- a/plugins/catalog-react/src/components/EntityTable/columns.tsx +++ b/plugins/catalog-react/src/components/EntityTable/columns.tsx @@ -22,11 +22,8 @@ import { } from '@backstage/catalog-model'; import { OverflowTooltip, TableColumn } from '@backstage/core-components'; import { getEntityRelations } from '../../utils'; -import { - EntityRefLink, - EntityRefLinks, - humanizeEntityRef, -} from '../EntityRefLink'; +import { EntityRefLink, EntityRefLinks } from '../EntityRefLink'; +import { defaultEntityPresentation } from '../../apis'; import { EntityTableColumnTitle } from './TitleColumn'; /** @public */ @@ -36,12 +33,7 @@ export const columnFactories = Object.freeze({ }): TableColumn { const { defaultKind } = options; function formatContent(entity: T): string { - return ( - entity.metadata?.title || - humanizeEntityRef(entity, { - defaultKind, - }) - ); + return defaultEntityPresentation(entity, { defaultKind }).primaryTitle; } return { @@ -84,7 +76,7 @@ export const columnFactories = Object.freeze({ function formatContent(entity: T): string { return getRelations(entity) - .map(r => humanizeEntityRef(r, { defaultKind })) + .map(r => defaultEntityPresentation(r, { defaultKind }).primaryTitle) .join(', '); } diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/AncestryPage.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/AncestryPage.tsx index 771eb7b279..ec7c0dd54f 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/AncestryPage.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/AncestryPage.tsx @@ -35,8 +35,8 @@ import { useLayoutEffect, useRef, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import useAsync from 'react-use/esm/useAsync'; import { catalogApiRef } from '../../../api'; -import { humanizeEntityRef } from '../../EntityRefLink'; import { entityRouteRef } from '../../../routes'; +import { useEntityPresentation } from '../../../apis'; import { EntityKindIcon } from './EntityKindIcon'; import { catalogReactTranslationRef } from '../../../translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; @@ -137,15 +137,7 @@ function CustomNode({ node }: DependencyGraphTypes.RenderNodeProps) { const paddedWidth = paddedIconWidth + width + padding * 2; const paddedHeight = height + padding * 2; - const displayTitle = - node.metadata.title || - (node.kind && node.metadata.name && node.metadata.namespace - ? humanizeEntityRef({ - kind: node.kind, - name: node.metadata.name, - namespace: node.metadata.namespace || '', - }) - : node.id); + const { primaryTitle: displayTitle } = useEntityPresentation(node); const onClick = () => { navigate( diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 80b37002c5..200505f5be 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -29,8 +29,8 @@ import { WarningPanel, } from '@backstage/core-components'; import { + defaultEntityPresentation, getEntityRelations, - humanizeEntityRef, useEntityList, useStarredEntities, } from '@backstage/plugin-catalog-react'; @@ -71,10 +71,8 @@ export interface CatalogTableProps { const refCompare = (a: Entity, b: Entity) => { const toRef = (entity: Entity) => - entity.metadata.title || - humanizeEntityRef(entity, { - defaultKind: 'Component', - }); + defaultEntityPresentation(entity, { defaultKind: 'Component' }) + .primaryTitle; return toRef(a).localeCompare(toRef(b)); }; @@ -292,19 +290,21 @@ function toEntityRow(entity: Entity) { // This name is here for backwards compatibility mostly; the // presentation of refs in the table should in general be handled with // EntityRefLink / EntityName components - name: humanizeEntityRef(entity, { - defaultKind: 'Component', - }), + name: defaultEntityPresentation(entity, { defaultKind: 'Component' }) + .primaryTitle, entityRef: stringifyEntityRef(entity), ownedByRelationsTitle: ownedByRelations - .map(r => humanizeEntityRef(r, { defaultKind: 'group' })) + .map( + r => + defaultEntityPresentation(r, { defaultKind: 'group' }).primaryTitle, + ) .join(', '), ownedByRelations, partOfSystemRelationTitle: partOfSystemRelations - .map(r => - humanizeEntityRef(r, { - defaultKind: 'system', - }), + .map( + r => + defaultEntityPresentation(r, { defaultKind: 'system' }) + .primaryTitle, ) .join(', '), partOfSystemRelations, diff --git a/plugins/catalog/src/components/CatalogTable/columns.tsx b/plugins/catalog/src/components/CatalogTable/columns.tsx index d2216149cd..8a18b301e6 100644 --- a/plugins/catalog/src/components/CatalogTable/columns.tsx +++ b/plugins/catalog/src/components/CatalogTable/columns.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import { - humanizeEntityRef, + defaultEntityPresentation, EntityRefLink, EntityRefLinks, } from '@backstage/plugin-catalog-react'; @@ -33,12 +33,9 @@ export const columnFactories = Object.freeze({ defaultKind?: string; }): TableColumn { function formatContent(entity: Entity): string { - return ( - entity.metadata?.title || - humanizeEntityRef(entity, { - defaultKind: options?.defaultKind, - }) - ); + return defaultEntityPresentation(entity, { + defaultKind: options?.defaultKind, + }).primaryTitle; } return { diff --git a/plugins/org-react/src/components/GroupListPicker/GroupListPicker.tsx b/plugins/org-react/src/components/GroupListPicker/GroupListPicker.tsx index 12dc69073b..b836d30aff 100644 --- a/plugins/org-react/src/components/GroupListPicker/GroupListPicker.tsx +++ b/plugins/org-react/src/components/GroupListPicker/GroupListPicker.tsx @@ -17,7 +17,7 @@ import { MouseEvent, useState, useCallback } from 'react'; import { catalogApiRef, - humanizeEntityRef, + defaultEntityPresentation, } from '@backstage/plugin-catalog-react'; import TextField from '@material-ui/core/TextField'; import Autocomplete from '@material-ui/lab/Autocomplete'; @@ -25,7 +25,7 @@ import useAsync from 'react-use/esm/useAsync'; import Popover from '@material-ui/core/Popover'; import { useApi } from '@backstage/core-plugin-api'; import { ResponseErrorPanel } from '@backstage/core-components'; -import { Entity, GroupEntity } from '@backstage/catalog-model'; +import { GroupEntity } from '@backstage/catalog-model'; import { GroupListPickerButton } from './GroupListPickerButton'; /** @@ -85,8 +85,6 @@ export const GroupListPicker = (props: GroupListPickerProps) => { return ; } - const getHumanEntityRef = (entity: Entity) => humanizeEntityRef(entity); - return ( <> { options={groups ?? []} groupBy={option => option.spec.type} getOptionLabel={option => - option.spec.profile?.displayName ?? getHumanEntityRef(option) + defaultEntityPresentation(option).primaryTitle } inputValue={inputValue} onInputChange={(_, value) => setInputValue(value)} diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx index cb40ff2766..d2db8c4a62 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx @@ -24,7 +24,7 @@ import { makeStyles } from '@material-ui/core/styles'; import { alertApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; import { catalogApiRef, - humanizeEntityRef, + defaultEntityPresentation, } from '@backstage/plugin-catalog-react'; import { LayoutOptions, @@ -169,9 +169,9 @@ export const TemplateFormPreviewer = ({ .then(({ items }) => setTemplateOptions( items.map(template => ({ - label: - template.metadata.title ?? - humanizeEntityRef(template, { defaultKind: 'template' }), + label: defaultEntityPresentation(template, { + defaultKind: 'template', + }).primaryTitle, value: template, })), ), diff --git a/plugins/techdocs/src/home/components/Tables/helpers.ts b/plugins/techdocs/src/home/components/Tables/helpers.ts index 4c6383c35e..5c972b0233 100644 --- a/plugins/techdocs/src/home/components/Tables/helpers.ts +++ b/plugins/techdocs/src/home/components/Tables/helpers.ts @@ -16,8 +16,8 @@ import { RELATION_OWNED_BY, Entity } from '@backstage/catalog-model'; import { + defaultEntityPresentation, getEntityRelations, - humanizeEntityRef, } from '@backstage/plugin-catalog-react'; import { toLowerMaybe } from '../../../helpers'; import { ConfigApi, RouteFunc } from '@backstage/core-plugin-api'; @@ -48,7 +48,11 @@ export function entitiesToDocsMapper( }), ownedByRelations, ownedByRelationsTitle: ownedByRelations - .map(r => humanizeEntityRef(r, { defaultKind: 'group' })) + .map( + r => + defaultEntityPresentation(r, { defaultKind: 'group' }) + .primaryTitle, + ) .join(', '), }, }; From e4f3588f4053866d8cf13460a523edd3c17b08c2 Mon Sep 17 00:00:00 2001 From: Nikita Shalnov Date: Thu, 26 Mar 2026 11:00:05 +0100 Subject: [PATCH 048/191] Add AWS plugins to plugin directory (ECR, Config, GenAI, Security Hub) Signed-off-by: Nikita Shalnov --- microsite/data/plugins/aws-amazon-ecr.yaml | 11 +++++++++++ microsite/data/plugins/aws-config.yaml | 11 +++++++++++ microsite/data/plugins/aws-genai.yaml | 11 +++++++++++ microsite/data/plugins/aws-securityhub.yaml | 11 +++++++++++ 4 files changed, 44 insertions(+) create mode 100644 microsite/data/plugins/aws-amazon-ecr.yaml create mode 100644 microsite/data/plugins/aws-config.yaml create mode 100644 microsite/data/plugins/aws-genai.yaml create mode 100644 microsite/data/plugins/aws-securityhub.yaml diff --git a/microsite/data/plugins/aws-amazon-ecr.yaml b/microsite/data/plugins/aws-amazon-ecr.yaml new file mode 100644 index 0000000000..1749b84f04 --- /dev/null +++ b/microsite/data/plugins/aws-amazon-ecr.yaml @@ -0,0 +1,11 @@ +--- +title: Amazon Elastic Container Registry +author: Amazon Web Services +authorUrl: https://aws.amazon.com/ +category: Infrastructure +description: View Amazon ECR repositories and container image scan findings for your components in Backstage. +documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/ecr#readme +iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/ecr.png +npmPackageName: '@aws/amazon-ecr-plugin-for-backstage' +addedDate: '2026-03-26' +status: active diff --git a/microsite/data/plugins/aws-config.yaml b/microsite/data/plugins/aws-config.yaml new file mode 100644 index 0000000000..a57a601c4b --- /dev/null +++ b/microsite/data/plugins/aws-config.yaml @@ -0,0 +1,11 @@ +--- +title: AWS Config +author: Amazon Web Services +authorUrl: https://aws.amazon.com/ +category: Infrastructure +description: Ingest AWS resources from AWS Config into the Backstage catalog using incremental ingestion. +documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/core/catalog-config#readme +iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/config.png +npmPackageName: '@aws/aws-config-catalog-module-for-backstage' +addedDate: '2026-03-26' +status: active diff --git a/microsite/data/plugins/aws-genai.yaml b/microsite/data/plugins/aws-genai.yaml new file mode 100644 index 0000000000..f881ccc769 --- /dev/null +++ b/microsite/data/plugins/aws-genai.yaml @@ -0,0 +1,11 @@ +--- +title: Generative AI +author: Amazon Web Services +authorUrl: https://aws.amazon.com/ +category: Machine Learning +description: Build generative AI assistants in Backstage that leverage the plugin ecosystem with tool use and LLM integration. +documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/genai#readme +iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/bedrock.png +npmPackageName: '@aws/genai-plugin-for-backstage' +addedDate: '2026-03-26' +status: active diff --git a/microsite/data/plugins/aws-securityhub.yaml b/microsite/data/plugins/aws-securityhub.yaml new file mode 100644 index 0000000000..b2cec6e6ba --- /dev/null +++ b/microsite/data/plugins/aws-securityhub.yaml @@ -0,0 +1,11 @@ +--- +title: AWS Security Hub +author: Amazon Web Services +authorUrl: https://aws.amazon.com/ +category: Security +description: View and manage AWS Security Hub findings for your resources with severity filtering and AI remediation. +documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/securityhub#readme +iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/securityhub.png +npmPackageName: '@aws/aws-securityhub-plugin-for-backstage' +addedDate: '2026-03-26' +status: active From e5af44c846b4c5082739f4b266ea6f58350b75b6 Mon Sep 17 00:00:00 2001 From: Marat Dyatko Date: Fri, 27 Mar 2026 13:57:16 +0100 Subject: [PATCH 049/191] Address PR review feedback from freben - Consolidate changesets: one for catalog-react (deprecation), one combined for the remaining 5 plugins - Add entity-presentation to microsite/sidebars.ts - Update @deprecated tags to point to useEntityPresentation / entityPresentationApiRef only, not defaultEntityPresentation - Use entityPresentationApiRef with .promise in async loaders (StepPrepareCreatePullRequest, TemplateFormPreviewer) - Add optional entityPresentation?: EntityPresentationApi param to sync column factories and use .snapshot when available (columnFactories, EntityTable/columns, EntityOwnerPicker, CatalogTable) - Rewrite entity-presentation.md to recommend .snapshot/.promise instead of defaultEntityPresentation - Update TSDoc on entityPresentationApiRef, EntityPresentationApi, defaultEntityPresentation, useEntityPresentation, EntityDisplayName - Re-export presentation API types from alpha entry point - Update API reports - Fix test mocks for entityPresentationApiRef Made-with: Cursor Signed-off-by: Marat Dyatko Made-with: Cursor --- ...lace-humanize-entity-ref-catalog-import.md | 5 -- ...place-humanize-entity-ref-catalog-react.md | 2 +- .../replace-humanize-entity-ref-catalog.md | 5 -- .../replace-humanize-entity-ref-org-react.md | 5 -- .../replace-humanize-entity-ref-plugins.md | 9 +++ .../replace-humanize-entity-ref-scaffolder.md | 5 -- .../replace-humanize-entity-ref-techdocs.md | 5 -- .../software-catalog/entity-presentation.md | 48 +++++++++------ microsite/sidebars.ts | 1 + .../StepPrepareCreatePullRequest.test.tsx | 18 +++++- .../StepPrepareCreatePullRequest.tsx | 14 +++-- plugins/catalog-react/report-alpha.api.md | 61 +++++++++++++++++++ plugins/catalog-react/report.api.md | 4 ++ plugins/catalog-react/src/alpha/index.ts | 8 +++ .../EntityPresentationApi.ts | 11 ++-- .../defaultEntityPresentation.ts | 11 ++-- .../useEntityPresentation.ts | 2 +- .../EntityDataTable/columnFactories.tsx | 34 ++++++++--- .../EntityDisplayName/EntityDisplayName.tsx | 2 +- .../EntityOwnerPicker/EntityOwnerPicker.tsx | 12 +++- .../src/components/EntityRefLink/humanize.ts | 10 +-- .../src/components/EntityTable/columns.tsx | 30 +++++++-- plugins/catalog/report.api.md | 1 + .../components/CatalogTable/CatalogTable.tsx | 51 ++++++++++------ .../src/components/CatalogTable/columns.tsx | 7 +++ .../GroupListPicker/GroupListPicker.tsx | 9 ++- .../TemplateFormPage.test.tsx | 31 +++++++++- .../TemplateFormPreviewer.tsx | 22 ++++--- .../src/home/components/Tables/helpers.ts | 22 +++++-- 29 files changed, 323 insertions(+), 122 deletions(-) delete mode 100644 .changeset/replace-humanize-entity-ref-catalog-import.md delete mode 100644 .changeset/replace-humanize-entity-ref-catalog.md delete mode 100644 .changeset/replace-humanize-entity-ref-org-react.md create mode 100644 .changeset/replace-humanize-entity-ref-plugins.md delete mode 100644 .changeset/replace-humanize-entity-ref-scaffolder.md delete mode 100644 .changeset/replace-humanize-entity-ref-techdocs.md diff --git a/.changeset/replace-humanize-entity-ref-catalog-import.md b/.changeset/replace-humanize-entity-ref-catalog-import.md deleted file mode 100644 index 544e91bac3..0000000000 --- a/.changeset/replace-humanize-entity-ref-catalog-import.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-import': patch ---- - -Replaced `humanizeEntityRef` with `defaultEntityPresentation` from the Catalog Presentation API in `StepPrepareCreatePullRequest`. diff --git a/.changeset/replace-humanize-entity-ref-catalog-react.md b/.changeset/replace-humanize-entity-ref-catalog-react.md index b44e796624..7f44b101c4 100644 --- a/.changeset/replace-humanize-entity-ref-catalog-react.md +++ b/.changeset/replace-humanize-entity-ref-catalog-react.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-react': patch --- -Replaced `humanizeEntityRef` with `defaultEntityPresentation` and `useEntityPresentation` from the Catalog Presentation API in `EntityOwnerPicker`, `EntityTable`, `EntityDataTable`, and `AncestryPage` components. +Deprecated `humanizeEntityRef` and `humanizeEntity` in favor of the Catalog Presentation API. Use `useEntityPresentation`, `EntityDisplayName`, or `entityPresentationApiRef` instead. diff --git a/.changeset/replace-humanize-entity-ref-catalog.md b/.changeset/replace-humanize-entity-ref-catalog.md deleted file mode 100644 index 2a27c87f47..0000000000 --- a/.changeset/replace-humanize-entity-ref-catalog.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Replaced `humanizeEntityRef` with `defaultEntityPresentation` from the Catalog Presentation API in `CatalogTable` and its column factories. diff --git a/.changeset/replace-humanize-entity-ref-org-react.md b/.changeset/replace-humanize-entity-ref-org-react.md deleted file mode 100644 index afb04d3348..0000000000 --- a/.changeset/replace-humanize-entity-ref-org-react.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-org-react': patch ---- - -Replaced `humanizeEntityRef` with `defaultEntityPresentation` from the Catalog Presentation API in `GroupListPicker`. diff --git a/.changeset/replace-humanize-entity-ref-plugins.md b/.changeset/replace-humanize-entity-ref-plugins.md new file mode 100644 index 0000000000..5f523e7b88 --- /dev/null +++ b/.changeset/replace-humanize-entity-ref-plugins.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-catalog': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-org-react': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-techdocs': patch +--- + +Replaced deprecated `humanizeEntityRef` usage with the Catalog Presentation API. diff --git a/.changeset/replace-humanize-entity-ref-scaffolder.md b/.changeset/replace-humanize-entity-ref-scaffolder.md deleted file mode 100644 index 5064875451..0000000000 --- a/.changeset/replace-humanize-entity-ref-scaffolder.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Replaced `humanizeEntityRef` with `defaultEntityPresentation` from the Catalog Presentation API in `TemplateFormPreviewer`. diff --git a/.changeset/replace-humanize-entity-ref-techdocs.md b/.changeset/replace-humanize-entity-ref-techdocs.md deleted file mode 100644 index 374a0c2e8e..0000000000 --- a/.changeset/replace-humanize-entity-ref-techdocs.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Replaced `humanizeEntityRef` with `defaultEntityPresentation` from the Catalog Presentation API in TechDocs table helpers. diff --git a/docs/features/software-catalog/entity-presentation.md b/docs/features/software-catalog/entity-presentation.md index 5d65baac07..c5ca8edada 100644 --- a/docs/features/software-catalog/entity-presentation.md +++ b/docs/features/software-catalog/entity-presentation.md @@ -11,7 +11,7 @@ name from fields such as `metadata.title` and `spec.profile.displayName`. ## Displaying entity names -There are three ways to display entity names, depending on context: +There are several ways to display entity names, depending on context: ### `EntityDisplayName` component @@ -51,25 +51,36 @@ function MyComponent({ entityRef }: { entityRef: string }) { The hook subscribes to the `EntityPresentationApi` and returns a snapshot that may update over time as additional data is fetched in the background. -If no presentation API is registered, it falls back to -`defaultEntityPresentation`. -### `defaultEntityPresentation` function +### Using the API directly -A synchronous helper for non-React contexts where hooks are not available. -Use it in sort comparators, filter functions, table column factories, and -data mappers: +In contexts where hooks are not available, you can use the +`entityPresentationApiRef` API directly. The API provides two access +patterns: + +- **`.snapshot`** for synchronous access (for example in sort comparators or + filter callbacks): ```ts -import { defaultEntityPresentation } from '@backstage/plugin-catalog-react'; +import { entityPresentationApiRef } from '@backstage/plugin-catalog-react'; -const title = defaultEntityPresentation(entity, { +const title = entityPresentationApi.forEntity(entity, { defaultKind: 'Component', -}).primaryTitle; +}).snapshot.primaryTitle; ``` -This resolves `primaryTitle` as the first available value among -`spec.profile.displayName`, `metadata.title`, and a shortened entity ref. +- **`.promise`** for async contexts (for example inside data loaders): + +```ts +const presentation = await entityPresentationApi.forEntity(entity, { + defaultKind: 'group', +}).promise; +const title = presentation.primaryTitle; +``` + +The `.snapshot` path uses cached data when available, so it performs well +even in tight loops like sorting. The `.promise` path resolves to a richer +presentation that may include data fetched from the catalog. ## Customizing entity presentation @@ -109,9 +120,10 @@ from `metadata.title` or `spec.profile.displayName`. Replace them as follows: -| Old code | Replacement | -| :------------------------------------------------------------------ | :---------------------------------------------------------------- | -| `humanizeEntityRef(entity)` in JSX | `` | -| `humanizeEntityRef(entity)` in a hook-accessible context | `useEntityPresentation(entity).primaryTitle` | -| `humanizeEntityRef(entity, { defaultKind })` in a sort/filter/label | `defaultEntityPresentation(entity, { defaultKind }).primaryTitle` | -| `humanizeEntity(entity, fallback)` | `defaultEntityPresentation(entity).primaryTitle` | +| Old code | Replacement | +| :---------------------------------------------------- | :--------------------------------------------------------------------- | +| `humanizeEntityRef(entity)` in JSX | `` | +| `humanizeEntityRef(entity)` in a React component | `useEntityPresentation(entity).primaryTitle` | +| `humanizeEntityRef(entity)` in a sort/filter callback | `entityPresentationApi.forEntity(entity).snapshot.primaryTitle` | +| `humanizeEntityRef(entity)` in an async loader | `(await entityPresentationApi.forEntity(entity).promise).primaryTitle` | +| `humanizeEntity(entity, fallback)` | `useEntityPresentation(entity).primaryTitle` | diff --git a/microsite/sidebars.ts b/microsite/sidebars.ts index 871fbe09e9..eb002736b2 100644 --- a/microsite/sidebars.ts +++ b/microsite/sidebars.ts @@ -263,6 +263,7 @@ export default { 'features/software-catalog/extending-the-model', 'features/software-catalog/external-integrations', 'features/software-catalog/catalog-customization', + 'features/software-catalog/entity-presentation', 'features/software-catalog/audit-events', { type: 'category', diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx index 6f69b739bd..8c21966ed5 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx @@ -15,7 +15,11 @@ */ import { configApiRef, errorApiRef } from '@backstage/core-plugin-api'; -import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import { + catalogApiRef, + defaultEntityPresentation, + entityPresentationApiRef, +} from '@backstage/plugin-catalog-react'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; import { mockApis, @@ -42,6 +46,17 @@ describe('', () => { const catalogApi = catalogApiMock.mock(); + const entityPresentationApi: typeof entityPresentationApiRef.T = { + forEntity(entityOrRef, context) { + const presentation = defaultEntityPresentation(entityOrRef, context); + return { + snapshot: presentation, + update$: { subscribe: () => ({ unsubscribe: () => {} }) } as any, + promise: Promise.resolve(presentation), + }; + }, + }; + const errorApi: jest.Mocked = { error$: jest.fn(), post: jest.fn(), @@ -54,6 +69,7 @@ describe('', () => { apis={[ [catalogImportApiRef, catalogImportApi], [catalogApiRef, catalogApi], + [entityPresentationApiRef, entityPresentationApi], [errorApiRef, errorApi], [configApiRef, configApi], ]} diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx index 3cb9fbbd39..758ade4ee9 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx @@ -20,7 +20,7 @@ import { assertError } from '@backstage/errors'; import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { catalogApiRef, - defaultEntityPresentation, + entityPresentationApiRef, } from '@backstage/plugin-catalog-react'; import Box from '@material-ui/core/Box'; import FormHelperText from '@material-ui/core/FormHelperText'; @@ -133,6 +133,7 @@ export const StepPrepareCreatePullRequest = ( const { t } = useTranslationRef(catalogImportTranslationRef); const classes = useStyles(); const catalogApi = useApi(catalogApiRef); + const entityPresentationApi = useApi(entityPresentationApiRef); const catalogImportApi = useApi(catalogImportApiRef); const errorApi = useApi(errorApiRef); @@ -161,12 +162,13 @@ export const StepPrepareCreatePullRequest = ( filter: { kind: 'group' }, }); - return groupEntities.items - .map( + const presentations = await Promise.all( + groupEntities.items.map( e => - defaultEntityPresentation(e, { defaultKind: 'group' }).primaryTitle, - ) - .sort(); + entityPresentationApi.forEntity(e, { defaultKind: 'group' }).promise, + ), + ); + return presentations.map(p => p.primaryTitle).sort(); }); const handleResult = useCallback( diff --git a/plugins/catalog-react/report-alpha.api.md b/plugins/catalog-react/report-alpha.api.md index 0135fe7d64..17bd8de8af 100644 --- a/plugins/catalog-react/report-alpha.api.md +++ b/plugins/catalog-react/report-alpha.api.md @@ -6,16 +6,19 @@ import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ColumnConfig } from '@backstage/ui'; import { ComponentType } from 'react'; +import { CompoundEntityRef } from '@backstage/catalog-model'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { FilterPredicate } from '@backstage/filter-predicates'; +import { IconComponent } from '@backstage/core-plugin-api'; import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; import { JSX as JSX_3 } from 'react/jsx-runtime'; import { JSXElementConstructor } from 'react'; +import { Observable } from '@backstage/types'; import { ReactElement } from 'react'; import { ReactNode } from 'react'; import { ResourcePermission } from '@backstage/plugin-permission-common'; @@ -183,6 +186,15 @@ export const defaultEntityContentGroups: Record< string >; +// @public +export function defaultEntityPresentation( + entityOrRef: Entity | CompoundEntityRef | string, + context?: { + defaultKind?: string; + defaultNamespace?: string; + }, +): EntityRefPresentationSnapshot; + // @alpha export const EntityCardBlueprint: ExtensionBlueprint<{ kind: 'entity-card'; @@ -488,6 +500,7 @@ export const entityDataTableColumns: Readonly<{ createEntityRefColumn(options: { defaultKind?: string; isRowHeader?: boolean; + entityPresentation?: EntityPresentationApi; }): EntityColumnConfig; createEntityRelationColumn(options: { id: string; @@ -497,6 +510,7 @@ export const entityDataTableColumns: Readonly<{ filter?: { kind: string; }; + entityPresentation?: EntityPresentationApi; }): EntityColumnConfig; createOwnerColumn(): EntityColumnConfig; createSystemColumn(): EntityColumnConfig; @@ -520,6 +534,18 @@ export interface EntityDataTableProps { loading?: boolean; } +// @public +export const EntityDisplayName: (props: EntityDisplayNameProps) => JSX.Element; + +// @public +export type EntityDisplayNameProps = { + entityRef: Entity | CompoundEntityRef | string; + hideIcon?: boolean; + disableTooltip?: boolean; + defaultKind?: string; + defaultNamespace?: string; +}; + // @alpha (undocumented) export const EntityHeaderBlueprint: ExtensionBlueprint<{ kind: 'entity-header'; @@ -627,6 +653,32 @@ export const EntityIconLinkBlueprint: ExtensionBlueprint<{ }; }>; +// @public +export interface EntityPresentationApi { + forEntity( + entityOrRef: Entity | string, + context?: { + defaultKind?: string; + defaultNamespace?: string; + }, + ): EntityRefPresentation; +} + +// @public +export interface EntityRefPresentation { + promise: Promise; + snapshot: EntityRefPresentationSnapshot; + update$?: Observable; +} + +// @public +export interface EntityRefPresentationSnapshot { + entityRef: string; + Icon?: IconComponent | undefined | false; + primaryTitle: string; + secondaryTitle?: string; +} + // @public (undocumented) export function EntityRelationCard( props: EntityRelationCardProps, @@ -700,6 +752,15 @@ export function useEntityPermission( error?: Error; }; +// @public +export function useEntityPresentation( + entityOrRef: Entity | CompoundEntityRef | string, + context?: { + defaultKind?: string; + defaultNamespace?: string; + }, +): EntityRefPresentationSnapshot; + // @alpha (undocumented) export type UseProps = () => | { diff --git a/plugins/catalog-react/report.api.md b/plugins/catalog-react/report.api.md index bb9d43edb0..806d329fe0 100644 --- a/plugins/catalog-react/report.api.md +++ b/plugins/catalog-react/report.api.md @@ -266,6 +266,7 @@ export type CatalogReactUserListPickerClassKey = export const columnFactories: Readonly<{ createEntityRefColumn(options: { defaultKind?: string; + entityPresentation?: EntityPresentationApi; }): TableColumn; createEntityRelationColumn(options: { title: string | JSX.Element; @@ -274,6 +275,7 @@ export const columnFactories: Readonly<{ filter?: { kind: string; }; + entityPresentation?: EntityPresentationApi; }): TableColumn; createOwnerColumn(): TableColumn; createDomainColumn(): TableColumn; @@ -687,6 +689,7 @@ export const EntityTable: { columns: Readonly<{ createEntityRefColumn(options: { defaultKind?: string; + entityPresentation?: EntityPresentationApi; }): TableColumn; createEntityRelationColumn(options: { title: string | JSX.Element; @@ -695,6 +698,7 @@ export const EntityTable: { filter?: { kind: string; }; + entityPresentation?: EntityPresentationApi; }): TableColumn; createOwnerColumn(): TableColumn; createDomainColumn(): TableColumn; diff --git a/plugins/catalog-react/src/alpha/index.ts b/plugins/catalog-react/src/alpha/index.ts index d473b0923f..47418eaa2a 100644 --- a/plugins/catalog-react/src/alpha/index.ts +++ b/plugins/catalog-react/src/alpha/index.ts @@ -26,5 +26,13 @@ export const catalogReactTranslationRef = _catalogReactTranslationRef; export { isOwnerOf } from '../utils/isOwnerOf'; export { useEntityPermission } from '../hooks/useEntityPermission'; export * from '../components/EntityTable/TitleColumn'; +export type { + EntityPresentationApi, + EntityRefPresentation, + EntityRefPresentationSnapshot, +} from '../apis'; +export { useEntityPresentation, defaultEntityPresentation } from '../apis'; +export { EntityDisplayName } from '../components/EntityDisplayName'; +export type { EntityDisplayNameProps } from '../components/EntityDisplayName'; export * from '../components/EntityDataTable'; export * from '../components/EntityRelationCard'; diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts index e293d1e0ab..316bc9aff5 100644 --- a/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts +++ b/plugins/catalog-react/src/apis/EntityPresentationApi/EntityPresentationApi.ts @@ -36,9 +36,9 @@ import { Observable } from '@backstage/types'; * which wraps the hook and renders a styled entity name with optional icon * and tooltip. * - * - In non-React contexts such as sort comparators, filter functions, or data - * mappers, use the {@link defaultEntityPresentation} function which - * synchronously extracts a display name from an already-loaded entity. + * - In non-React contexts such as sort comparators or data mappers, use the + * API directly via `forEntity().snapshot` for synchronous access, or + * `forEntity().promise` in async loaders. * * @public */ @@ -143,8 +143,9 @@ export interface EntityRefPresentation { * - {@link EntityDisplayName} — React component that renders an entity name * with optional icon and tooltip. * - * - {@link defaultEntityPresentation} — synchronous helper for non-React - * contexts where you already have the entity object. + * For non-React contexts, you can use the API directly via + * `forEntity().snapshot` for synchronous access, or `forEntity().promise` + * for async contexts. * * Implement this interface to customize how entities are displayed throughout * the Backstage interface. diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts index 9c1cbd5325..1984593cfa 100644 --- a/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts +++ b/plugins/catalog-react/src/apis/EntityPresentationApi/defaultEntityPresentation.ts @@ -33,11 +33,12 @@ import { EntityRefPresentationSnapshot } from './EntityPresentationApi'; * first available value among `spec.profile.displayName`, `metadata.title`, * and a shortened entity ref string. * - * Use this in non-React contexts where hooks are not available, such as sort - * comparators, filter functions, table column factories, and data mappers. - * In React components, prefer the {@link useEntityPresentation} hook or the - * {@link EntityDisplayName} component, which support async enrichment via - * the {@link EntityPresentationApi}. + * This function is primarily used as the internal fallback within the + * {@link EntityPresentationApi} when no custom implementation is registered. + * Prefer using the API directly via `forEntity().snapshot` or + * `forEntity().promise`, which respects custom presentation overrides. + * In React components, use the {@link useEntityPresentation} hook or the + * {@link EntityDisplayName} component. * * @public * @param entityOrRef - Either an entity, or a ref to it. diff --git a/plugins/catalog-react/src/apis/EntityPresentationApi/useEntityPresentation.ts b/plugins/catalog-react/src/apis/EntityPresentationApi/useEntityPresentation.ts index 2e01485dcc..125557ceb7 100644 --- a/plugins/catalog-react/src/apis/EntityPresentationApi/useEntityPresentation.ts +++ b/plugins/catalog-react/src/apis/EntityPresentationApi/useEntityPresentation.ts @@ -43,7 +43,7 @@ import { useUpdatingObservable } from './useUpdatingObservable'; * component instead, which wraps this hook with icon and tooltip support. * * For non-React contexts such as sort comparators or data mappers, use - * {@link defaultEntityPresentation} directly. + * the {@link EntityPresentationApi} directly via `forEntity().snapshot`. * * @public * @param entityOrRef - The entity to represent, or an entity ref to it. If you diff --git a/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx b/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx index 647e7c7f3b..aed29dbd56 100644 --- a/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx +++ b/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx @@ -21,7 +21,7 @@ import { } from '@backstage/catalog-model'; import { Cell, CellText, Column, ColumnConfig, TableItem } from '@backstage/ui'; import { EntityRefLink, EntityRefLinks } from '../EntityRefLink'; -import { defaultEntityPresentation } from '../../apis'; +import { defaultEntityPresentation, EntityPresentationApi } from '../../apis'; import { EntityTableColumnTitle } from '../EntityTable/TitleColumn'; import { getEntityRelations } from '../../utils'; @@ -33,11 +33,24 @@ export interface EntityColumnConfig extends ColumnConfig { sortValue?: (entity: EntityRow) => string; } +function getEntityTitle( + entityOrRef: Entity | { kind: string; namespace?: string; name: string }, + context: { defaultKind?: string }, + entityPresentation?: EntityPresentationApi, +): string { + if (entityPresentation) { + return entityPresentation.forEntity(entityOrRef as Entity, context).snapshot + .primaryTitle; + } + return defaultEntityPresentation(entityOrRef as Entity, context).primaryTitle; +} + /** @public */ export const columnFactories = Object.freeze({ createEntityRefColumn(options: { defaultKind?: string; isRowHeader?: boolean; + entityPresentation?: EntityPresentationApi; }): EntityColumnConfig { const isRowHeader = options.isRowHeader ?? true; return { @@ -60,8 +73,11 @@ export const columnFactories = Object.freeze({ ), sortValue: entity => - defaultEntityPresentation(entity, { defaultKind: options.defaultKind }) - .primaryTitle, + getEntityTitle( + entity, + { defaultKind: options.defaultKind }, + options.entityPresentation, + ), }; }, @@ -71,6 +87,7 @@ export const columnFactories = Object.freeze({ relation: string; defaultKind?: string; filter?: { kind: string }; + entityPresentation?: EntityPresentationApi; }): EntityColumnConfig { return { id: options.id, @@ -95,11 +112,12 @@ export const columnFactories = Object.freeze({ ), sortValue: entity => getEntityRelations(entity, options.relation, options.filter) - .map( - r => - defaultEntityPresentation(r, { - defaultKind: options.defaultKind, - }).primaryTitle, + .map(r => + getEntityTitle( + r, + { defaultKind: options.defaultKind }, + options.entityPresentation, + ), ) .join(', '), }; diff --git a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx index 6ed863f180..a6b4ed94fe 100644 --- a/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx +++ b/plugins/catalog-react/src/components/EntityDisplayName/EntityDisplayName.tsx @@ -70,7 +70,7 @@ export type EntityDisplayNameProps = { * * For more control over the presentation data, use the * {@link useEntityPresentation} hook directly. For non-React contexts, use - * {@link defaultEntityPresentation}. + * the {@link EntityPresentationApi} directly via `forEntity().snapshot`. * * @public */ diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx index 16e00e5ccd..1375140ebc 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx @@ -33,11 +33,15 @@ import { EntityOwnerFilter } from '../../filters'; import { useDebouncedEffect } from '@react-hookz/web'; import PersonIcon from '@material-ui/icons/Person'; import GroupIcon from '@material-ui/icons/Group'; -import { defaultEntityPresentation } from '../../apis'; +import { + defaultEntityPresentation, + entityPresentationApiRef, +} from '../../apis'; import { useFetchEntities } from './useFetchEntities'; import { withStyles } from '@material-ui/core/styles'; import { useEntityPresentation } from '../../apis'; import { catalogReactTranslationRef } from '../../translation'; +import { useApiHolder } from '@backstage/core-plugin-api'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { CatalogAutocomplete } from '../CatalogAutocomplete'; @@ -124,6 +128,8 @@ function RenderOptionLabel(props: { entity: Entity; isSelected: boolean }) { export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => { const classes = useStyles(); const { mode = 'owners-only' } = props || {}; + const apis = useApiHolder(); + const entityPresentationApi = apis.get(entityPresentationApiRef); const { updateFilters, filters, @@ -203,6 +209,10 @@ export const EntityOwnerPicker = (props?: EntityOwnerPickerProps) => { defaultNamespace: 'default', }) : o; + if (entityPresentationApi) { + return entityPresentationApi.forEntity(entity as Entity).snapshot + .primaryTitle; + } return defaultEntityPresentation(entity).primaryTitle; }} onChange={(_: object, owners) => { diff --git a/plugins/catalog-react/src/components/EntityRefLink/humanize.ts b/plugins/catalog-react/src/components/EntityRefLink/humanize.ts index ae1fb5e685..80bd290806 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/humanize.ts +++ b/plugins/catalog-react/src/components/EntityRefLink/humanize.ts @@ -25,9 +25,9 @@ import get from 'lodash/get'; * @param defaultNamespace - if set to false then namespace is never omitted, * if set to string which matches namespace of entity then omitted * - * @deprecated Use {@link defaultEntityPresentation} for non-React contexts, - * or {@link useEntityPresentation} / {@link EntityDisplayName} in React - * components. These provide richer display names using `metadata.title` and + * @deprecated Use {@link useEntityPresentation} or {@link EntityDisplayName} + * in React components, or access the {@link entityPresentationApiRef} directly. + * These provide richer display names using `metadata.title` and * `spec.profile.displayName` in addition to the entity ref. * * @public @@ -81,8 +81,8 @@ export function humanizeEntityRef( * * If neither of those are found or populated, fallback to `defaultName`. * - * @deprecated Use {@link defaultEntityPresentation} instead, which provides - * the same resolution logic via `primaryTitle`. + * @deprecated Use {@link useEntityPresentation} or {@link EntityDisplayName} + * in React components, or access the {@link entityPresentationApiRef} directly. * * @param entity - Entity to convert. * @param defaultName - If entity readable name is not available, `defaultName` will be returned. diff --git a/plugins/catalog-react/src/components/EntityTable/columns.tsx b/plugins/catalog-react/src/components/EntityTable/columns.tsx index 6b47fa02ff..3768cf595e 100644 --- a/plugins/catalog-react/src/components/EntityTable/columns.tsx +++ b/plugins/catalog-react/src/components/EntityTable/columns.tsx @@ -23,17 +23,30 @@ import { import { OverflowTooltip, TableColumn } from '@backstage/core-components'; import { getEntityRelations } from '../../utils'; import { EntityRefLink, EntityRefLinks } from '../EntityRefLink'; -import { defaultEntityPresentation } from '../../apis'; +import { defaultEntityPresentation, EntityPresentationApi } from '../../apis'; import { EntityTableColumnTitle } from './TitleColumn'; +function getEntityTitle( + entityOrRef: Entity | CompoundEntityRef, + context: { defaultKind?: string }, + entityPresentation?: EntityPresentationApi, +): string { + if (entityPresentation) { + return entityPresentation.forEntity(entityOrRef as Entity, context).snapshot + .primaryTitle; + } + return defaultEntityPresentation(entityOrRef, context).primaryTitle; +} + /** @public */ export const columnFactories = Object.freeze({ createEntityRefColumn(options: { defaultKind?: string; + entityPresentation?: EntityPresentationApi; }): TableColumn { - const { defaultKind } = options; + const { defaultKind, entityPresentation } = options; function formatContent(entity: T): string { - return defaultEntityPresentation(entity, { defaultKind }).primaryTitle; + return getEntityTitle(entity, { defaultKind }, entityPresentation); } return { @@ -67,8 +80,15 @@ export const columnFactories = Object.freeze({ relation: string; defaultKind?: string; filter?: { kind: string }; + entityPresentation?: EntityPresentationApi; }): TableColumn { - const { title, relation, defaultKind, filter: entityFilter } = options; + const { + title, + relation, + defaultKind, + filter: entityFilter, + entityPresentation, + } = options; function getRelations(entity: T): CompoundEntityRef[] { return getEntityRelations(entity, relation, entityFilter); @@ -76,7 +96,7 @@ export const columnFactories = Object.freeze({ function formatContent(entity: T): string { return getRelations(entity) - .map(r => defaultEntityPresentation(r, { defaultKind }).primaryTitle) + .map(r => getEntityTitle(r, { defaultKind }, entityPresentation)) .join(', '); } diff --git a/plugins/catalog/report.api.md b/plugins/catalog/report.api.md index 84d9a8bd91..816d469d13 100644 --- a/plugins/catalog/report.api.md +++ b/plugins/catalog/report.api.md @@ -153,6 +153,7 @@ export const CatalogTable: { columns: Readonly<{ createNameColumn(options?: { defaultKind?: string; + entityPresentation?: EntityPresentationApi; }): TableColumn; createSystemColumn(): TableColumn; createOwnerColumn(): TableColumn; diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 200505f5be..a57a5f3d31 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -16,6 +16,7 @@ import { ANNOTATION_EDIT_URL, ANNOTATION_VIEW_URL, + CompoundEntityRef, Entity, RELATION_OWNED_BY, RELATION_PART_OF, @@ -30,9 +31,11 @@ import { } from '@backstage/core-components'; import { defaultEntityPresentation, + entityPresentationApiRef, getEntityRelations, useEntityList, useStarredEntities, + type EntityPresentationApi, } from '@backstage/plugin-catalog-react'; import CircularProgress from '@material-ui/core/CircularProgress'; import Typography from '@material-ui/core/Typography'; @@ -47,6 +50,7 @@ import { CatalogTableColumnsFunc, CatalogTableRow } from './types'; import { OffsetPaginatedCatalogTable } from './OffsetPaginatedCatalogTable'; import { CursorPaginatedCatalogTable } from './CursorPaginatedCatalogTable'; import { defaultCatalogTableColumnsFunc } from './defaultCatalogTableColumnsFunc'; +import { useApiHolder } from '@backstage/core-plugin-api'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { catalogTranslationRef } from '../../alpha'; import { FavoriteToggleIcon } from '@backstage/core-components'; @@ -69,12 +73,23 @@ export interface CatalogTableProps { subtitle?: string; } -const refCompare = (a: Entity, b: Entity) => { - const toRef = (entity: Entity) => - defaultEntityPresentation(entity, { defaultKind: 'Component' }) - .primaryTitle; +function getTitle( + entityOrRef: Entity | CompoundEntityRef, + context: { defaultKind?: string }, + api?: EntityPresentationApi, +): string { + if (api) { + const ref = + 'metadata' in entityOrRef ? entityOrRef : stringifyEntityRef(entityOrRef); + return api.forEntity(ref, context).snapshot.primaryTitle; + } + return defaultEntityPresentation(entityOrRef, context).primaryTitle; +} - return toRef(a).localeCompare(toRef(b)); +const refCompare = (a: Entity, b: Entity, api?: EntityPresentationApi) => { + return getTitle(a, { defaultKind: 'Component' }, api).localeCompare( + getTitle(b, { defaultKind: 'Component' }, api), + ); }; /** @@ -95,6 +110,8 @@ export const CatalogTable = (props: CatalogTableProps) => { emptyContent, } = props; const { isStarredEntity, toggleStarredEntity } = useStarredEntities(); + const apis = useApiHolder(); + const entityPresentationApi = apis.get(entityPresentationApiRef); const entityListContext = useEntityList(); const { @@ -232,7 +249,7 @@ export const CatalogTable = (props: CatalogTableProps) => { actions={actions} subtitle={subtitle} options={options} - data={entities.map(toEntityRow)} + data={entities.map(e => toEntityRow(e, entityPresentationApi))} next={pageInfo?.next} prev={pageInfo?.prev} /> @@ -247,12 +264,14 @@ export const CatalogTable = (props: CatalogTableProps) => { actions={actions} subtitle={subtitle} options={options} - data={entities.map(toEntityRow)} + data={entities.map(e => toEntityRow(e, entityPresentationApi))} /> ); } - const rows = entities.sort(refCompare).map(toEntityRow); + const rows = entities + .sort((a, b) => refCompare(a, b, entityPresentationApi)) + .map(e => toEntityRow(e, entityPresentationApi)); const pageSize = 20; const showPagination = rows.length > pageSize; @@ -278,7 +297,7 @@ export const CatalogTable = (props: CatalogTableProps) => { CatalogTable.columns = columnFactories; CatalogTable.defaultColumnsFunc = defaultCatalogTableColumnsFunc; -function toEntityRow(entity: Entity) { +function toEntityRow(entity: Entity, api?: EntityPresentationApi) { const partOfSystemRelations = getEntityRelations(entity, RELATION_PART_OF, { kind: 'system', }); @@ -290,22 +309,14 @@ function toEntityRow(entity: Entity) { // This name is here for backwards compatibility mostly; the // presentation of refs in the table should in general be handled with // EntityRefLink / EntityName components - name: defaultEntityPresentation(entity, { defaultKind: 'Component' }) - .primaryTitle, + name: getTitle(entity, { defaultKind: 'Component' }, api), entityRef: stringifyEntityRef(entity), ownedByRelationsTitle: ownedByRelations - .map( - r => - defaultEntityPresentation(r, { defaultKind: 'group' }).primaryTitle, - ) + .map(r => getTitle(r, { defaultKind: 'group' }, api)) .join(', '), ownedByRelations, partOfSystemRelationTitle: partOfSystemRelations - .map( - r => - defaultEntityPresentation(r, { defaultKind: 'system' }) - .primaryTitle, - ) + .map(r => getTitle(r, { defaultKind: 'system' }, api)) .join(', '), partOfSystemRelations, }, diff --git a/plugins/catalog/src/components/CatalogTable/columns.tsx b/plugins/catalog/src/components/CatalogTable/columns.tsx index 8a18b301e6..05a8b1979b 100644 --- a/plugins/catalog/src/components/CatalogTable/columns.tsx +++ b/plugins/catalog/src/components/CatalogTable/columns.tsx @@ -17,6 +17,7 @@ import { defaultEntityPresentation, EntityRefLink, EntityRefLinks, + type EntityPresentationApi, } from '@backstage/plugin-catalog-react'; import Chip from '@material-ui/core/Chip'; import { CatalogTableRow } from './types'; @@ -31,8 +32,14 @@ import { EntityTableColumnTitle } from '@backstage/plugin-catalog-react/alpha'; export const columnFactories = Object.freeze({ createNameColumn(options?: { defaultKind?: string; + entityPresentation?: EntityPresentationApi; }): TableColumn { function formatContent(entity: Entity): string { + if (options?.entityPresentation) { + return options.entityPresentation.forEntity(entity, { + defaultKind: options?.defaultKind, + }).snapshot.primaryTitle; + } return defaultEntityPresentation(entity, { defaultKind: options?.defaultKind, }).primaryTitle; diff --git a/plugins/org-react/src/components/GroupListPicker/GroupListPicker.tsx b/plugins/org-react/src/components/GroupListPicker/GroupListPicker.tsx index b836d30aff..b193d4e092 100644 --- a/plugins/org-react/src/components/GroupListPicker/GroupListPicker.tsx +++ b/plugins/org-react/src/components/GroupListPicker/GroupListPicker.tsx @@ -18,12 +18,13 @@ import { MouseEvent, useState, useCallback } from 'react'; import { catalogApiRef, defaultEntityPresentation, + entityPresentationApiRef, } from '@backstage/plugin-catalog-react'; import TextField from '@material-ui/core/TextField'; import Autocomplete from '@material-ui/lab/Autocomplete'; import useAsync from 'react-use/esm/useAsync'; import Popover from '@material-ui/core/Popover'; -import { useApi } from '@backstage/core-plugin-api'; +import { useApi, useApiHolder } from '@backstage/core-plugin-api'; import { ResponseErrorPanel } from '@backstage/core-components'; import { GroupEntity } from '@backstage/catalog-model'; import { GroupListPickerButton } from './GroupListPickerButton'; @@ -43,6 +44,8 @@ export type GroupListPickerProps = { /** @public */ export const GroupListPicker = (props: GroupListPickerProps) => { const catalogApi = useApi(catalogApiRef); + const apis = useApiHolder(); + const entityPresentationApi = apis.get(entityPresentationApiRef); const { onChange, groupTypes, placeholder = '', defaultValue = '' } = props; const [anchorEl, setAnchorEl] = useState(null); @@ -99,7 +102,9 @@ export const GroupListPicker = (props: GroupListPickerProps) => { options={groups ?? []} groupBy={option => option.spec.type} getOptionLabel={option => - defaultEntityPresentation(option).primaryTitle + entityPresentationApi + ? entityPresentationApi.forEntity(option).snapshot.primaryTitle + : defaultEntityPresentation(option).primaryTitle } inputValue={inputValue} onInputChange={(_, value) => setInputValue(value)} diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPage.test.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPage.test.tsx index 1174d84b11..1fd7544ea4 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPage.test.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPage.test.tsx @@ -18,14 +18,34 @@ import { screen } from '@testing-library/react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { TemplateFormPage } from './TemplateFormPage'; import { rootRouteRef } from '../../../routes'; -import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import { + catalogApiRef, + defaultEntityPresentation, + entityPresentationApiRef, +} from '@backstage/plugin-catalog-react'; describe('TemplateFormPage', () => { const catalogApiMock = { getEntities: jest.fn().mockResolvedValue([]) }; + const entityPresentationApi: typeof entityPresentationApiRef.T = { + forEntity(entityOrRef, context) { + const presentation = defaultEntityPresentation(entityOrRef, context); + return { + snapshot: presentation, + update$: { subscribe: () => ({ unsubscribe: () => {} }) } as any, + promise: Promise.resolve(presentation), + }; + }, + }; + it('Should render without exploding', async () => { await renderInTestApp( - + , { @@ -41,7 +61,12 @@ describe('TemplateFormPage', () => { it('Should have an link back to the edit page', async () => { await renderInTestApp( - + , { diff --git a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx index d2db8c4a62..b436f56427 100644 --- a/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx +++ b/plugins/scaffolder/src/alpha/components/TemplateEditorPage/TemplateFormPreviewer.tsx @@ -24,7 +24,7 @@ import { makeStyles } from '@material-ui/core/styles'; import { alertApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; import { catalogApiRef, - defaultEntityPresentation, + entityPresentationApiRef, } from '@backstage/plugin-catalog-react'; import { LayoutOptions, @@ -139,6 +139,7 @@ export const TemplateFormPreviewer = ({ const classes = useStyles(); const alertApi = useApi(alertApiRef); const catalogApi = useApi(catalogApiRef); + const entityPresentationApi = useApi(entityPresentationApiRef); const navigate = useNavigate(); const editLink = useRouteRef(editRouteRef); @@ -166,16 +167,19 @@ export const TemplateFormPreviewer = ({ 'spec.output', ], }) - .then(({ items }) => - setTemplateOptions( - items.map(template => ({ - label: defaultEntityPresentation(template, { - defaultKind: 'template', - }).primaryTitle, + .then(async ({ items }) => { + const options = await Promise.all( + items.map(async template => ({ + label: ( + await entityPresentationApi.forEntity(template, { + defaultKind: 'template', + }).promise + ).primaryTitle, value: template, })), - ), - ) + ); + setTemplateOptions(options); + }) .catch(e => alertApi.post({ message: `Error loading existing templates: ${e.message}`, diff --git a/plugins/techdocs/src/home/components/Tables/helpers.ts b/plugins/techdocs/src/home/components/Tables/helpers.ts index 5c972b0233..4ec3123fe8 100644 --- a/plugins/techdocs/src/home/components/Tables/helpers.ts +++ b/plugins/techdocs/src/home/components/Tables/helpers.ts @@ -14,10 +14,15 @@ * limitations under the License. */ -import { RELATION_OWNED_BY, Entity } from '@backstage/catalog-model'; +import { + RELATION_OWNED_BY, + Entity, + stringifyEntityRef, +} from '@backstage/catalog-model'; import { defaultEntityPresentation, getEntityRelations, + type EntityPresentationApi, } from '@backstage/plugin-catalog-react'; import { toLowerMaybe } from '../../../helpers'; import { ConfigApi, RouteFunc } from '@backstage/core-plugin-api'; @@ -32,6 +37,7 @@ export function entitiesToDocsMapper( entities: Entity[], getRouteToReaderPageFor: getRouteFunc, config: ConfigApi, + entityPresentation?: EntityPresentationApi, ) { return entities.map(entity => { const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); @@ -48,11 +54,15 @@ export function entitiesToDocsMapper( }), ownedByRelations, ownedByRelationsTitle: ownedByRelations - .map( - r => - defaultEntityPresentation(r, { defaultKind: 'group' }) - .primaryTitle, - ) + .map(r => { + if (entityPresentation) { + return entityPresentation.forEntity(stringifyEntityRef(r), { + defaultKind: 'group', + }).snapshot.primaryTitle; + } + return defaultEntityPresentation(r, { defaultKind: 'group' }) + .primaryTitle; + }) .join(', '), }, }; From 5cd814f54148955ca8c83adfc0f78a1127e8a42d Mon Sep 17 00:00:00 2001 From: Jon Koops Date: Fri, 27 Mar 2026 14:47:48 +0100 Subject: [PATCH 050/191] refactor(backend-defaults): migrate internal Zod usage from v3 to v4 The auditor's severity log level mappings previously used a `zod/v3` `z.record()` schema with manual fallbacks for defaults and relied on casting into Zod error internals (`.received`, `.options`) that changed between v3 and v4. This replaces it with a `z.object()` schema using `.default()` so that Zod owns the default values and type inference, and derives the valid values and received input without reaching into undocumented error properties. This does not migrate all `zod/v3` imports in the package, as the remaining usages are tied to public API types (e.g. `AnyZodObject` from `@backstage/backend-plugin-api`). Signed-off-by: Jon Koops --- .changeset/auditor-zod-v4-refactor.md | 5 ++ .../src/entrypoints/auditor/types.ts | 26 -------- .../src/entrypoints/auditor/utils.ts | 63 ++++++++++--------- .../src/entrypoints/scheduler/lib/types.ts | 2 +- 4 files changed, 39 insertions(+), 57 deletions(-) create mode 100644 .changeset/auditor-zod-v4-refactor.md delete mode 100644 packages/backend-defaults/src/entrypoints/auditor/types.ts diff --git a/.changeset/auditor-zod-v4-refactor.md b/.changeset/auditor-zod-v4-refactor.md new file mode 100644 index 0000000000..c1ac7ce0aa --- /dev/null +++ b/.changeset/auditor-zod-v4-refactor.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +Refactored auditor severity log level mappings to use `zod/v4` with schema-driven defaults and type inference. diff --git a/packages/backend-defaults/src/entrypoints/auditor/types.ts b/packages/backend-defaults/src/entrypoints/auditor/types.ts deleted file mode 100644 index 49a6de75ea..0000000000 --- a/packages/backend-defaults/src/entrypoints/auditor/types.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2025 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { z } from 'zod/v3'; - -/** @internal */ -export const severityLogLevelMappingsSchema = z.record( - z.enum(['low', 'medium', 'high', 'critical']), - z.enum(['debug', 'info', 'warn', 'error']), -); - -/** @internal */ -export const CONFIG_ROOT_KEY = 'backend.auditor'; diff --git a/packages/backend-defaults/src/entrypoints/auditor/utils.ts b/packages/backend-defaults/src/entrypoints/auditor/utils.ts index bf1e1f4756..0513aa84b2 100644 --- a/packages/backend-defaults/src/entrypoints/auditor/utils.ts +++ b/packages/backend-defaults/src/entrypoints/auditor/utils.ts @@ -16,8 +16,20 @@ import type { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; -import { z } from 'zod/v3'; -import { CONFIG_ROOT_KEY, severityLogLevelMappingsSchema } from './types'; +import { z } from 'zod/v4'; + +const CONFIG_ROOT_KEY = 'backend.auditor'; + +const logLevel = z.enum(['debug', 'info', 'warn', 'error']); + +const severityLogLevelMappingsSchema = z.object({ + low: logLevel.default('debug'), + medium: logLevel.default('info'), + high: logLevel.default('info'), + critical: logLevel.default('info'), +}); + +type SeverityLogLevelMappings = z.infer; /** * Gets the `backend.auditor.severityLogLevelMappings` configuration. @@ -26,41 +38,32 @@ import { CONFIG_ROOT_KEY, severityLogLevelMappingsSchema } from './types'; * @returns The validated severity-to-log-level mappings. * @throws error - {@link @backstage/errors#InputError} if the mapping configuration is invalid. */ -export function getSeverityLogLevelMappings(config: Config) { +export function getSeverityLogLevelMappings( + config: Config, +): SeverityLogLevelMappings { const auditorConfig = config.getOptionalConfig(CONFIG_ROOT_KEY); - const severityLogLevelMappings = { - low: - auditorConfig?.getOptionalString('severityLogLevelMappings.low') ?? - 'debug', - medium: - auditorConfig?.getOptionalString('severityLogLevelMappings.medium') ?? - 'info', - high: - auditorConfig?.getOptionalString('severityLogLevelMappings.high') ?? - 'info', - critical: - auditorConfig?.getOptionalString('severityLogLevelMappings.critical') ?? - 'info', - } as Required>; + const input = { + low: auditorConfig?.getOptionalString('severityLogLevelMappings.low'), + medium: auditorConfig?.getOptionalString('severityLogLevelMappings.medium'), + high: auditorConfig?.getOptionalString('severityLogLevelMappings.high'), + critical: auditorConfig?.getOptionalString( + 'severityLogLevelMappings.critical', + ), + }; - const res = severityLogLevelMappingsSchema.safeParse( - severityLogLevelMappings, - ); - if (!res.success) { - const key = res.error.issues.at(0)?.path.at(0) as string; - const value = ( - res.error.issues.at(0) as unknown as Record - ).received as string; - const validKeys = ( - res.error.issues.at(0) as unknown as Record - ).options as string[]; + const parsed = severityLogLevelMappingsSchema.safeParse(input); + + if (!parsed.success) { + const issue = parsed.error.issues[0]; + const key = issue.path[0] as keyof typeof input; + const receivedValue = input[key]; throw new InputError( - `The configuration value for 'backend.auditor.severityLogLevelMappings.${key}' was given an invalid value: '${value}'. Expected one of the following valid values: '${validKeys.join( + `The configuration value for '${CONFIG_ROOT_KEY}.severityLogLevelMappings.${key}' was given an invalid value: '${receivedValue}'. Expected one of the following valid values: '${logLevel.options.join( ', ', )}'.`, ); } - return severityLogLevelMappings; + return parsed.data; } diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts index c7f77e7a85..64f7d00899 100644 --- a/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts +++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts @@ -17,7 +17,7 @@ import { JsonObject } from '@backstage/types'; import { CronTime } from 'cron'; import { Duration } from 'luxon'; -import { z } from 'zod/v3'; +import { z } from 'zod/v4'; function isValidOptionalDurationString(d: string | undefined): boolean { try { From 8ec254cd4b87d37a18fe2cab365e84fb526c2772 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 29 Mar 2026 21:47:46 +0200 Subject: [PATCH 051/191] docs: migrate feature docs to new frontend system as primary content Rewrite documentation for TechDocs, Software Templates, Software Catalog, Search, and Kubernetes features to use the new frontend system as the primary installation and configuration instructions. Old frontend system instructions are moved to separate `--old` suffixed files for pages with substantial legacy content, or updated inline for pages with minimal old-system content. Files migrated: - techdocs/getting-started.md - techdocs/how-to-guides.md - software-templates/writing-custom-step-layouts.md - software-templates/writing-custom-field-extensions.md - software-templates/index.md - software-catalog/catalog-customization.md - search/getting-started.md - search/how-to-guides.md - kubernetes/installation.md Signed-off-by: Patrik Oldsberg Made-with: Cursor --- docs/features/kubernetes/installation.md | 32 +- docs/features/search/getting-started--old.md | 351 ++++++ docs/features/search/getting-started.md | 219 +--- docs/features/search/how-to-guides--old.md | 367 ++++++ docs/features/search/how-to-guides.md | 262 +---- .../catalog-customization--old.md | 508 ++++++++ .../software-catalog/catalog-customization.md | 532 +-------- docs/features/software-templates/index.md | 20 +- .../writing-custom-field-extensions--old.md | 332 ++++++ .../writing-custom-field-extensions.md | 200 ++-- .../writing-custom-step-layouts--old.md | 94 ++ .../writing-custom-step-layouts.md | 47 +- .../features/techdocs/getting-started--old.md | 242 ++++ docs/features/techdocs/getting-started.md | 146 +-- docs/features/techdocs/how-to-guides--old.md | 1022 +++++++++++++++++ docs/features/techdocs/how-to-guides.md | 284 +---- docs/getting-started/filter-catalog.md | 2 +- docs/getting-started/viewing-catalog.md | 6 +- 18 files changed, 3269 insertions(+), 1397 deletions(-) create mode 100644 docs/features/search/getting-started--old.md create mode 100644 docs/features/search/how-to-guides--old.md create mode 100644 docs/features/software-catalog/catalog-customization--old.md create mode 100644 docs/features/software-templates/writing-custom-field-extensions--old.md create mode 100644 docs/features/software-templates/writing-custom-step-layouts--old.md create mode 100644 docs/features/techdocs/getting-started--old.md create mode 100644 docs/features/techdocs/how-to-guides--old.md diff --git a/docs/features/kubernetes/installation.md b/docs/features/kubernetes/installation.md index 40b2dc0bcc..b8b9868f6a 100644 --- a/docs/features/kubernetes/installation.md +++ b/docs/features/kubernetes/installation.md @@ -17,32 +17,20 @@ The first step is to add the Kubernetes frontend plugin to your Backstage applic yarn --cwd packages/app add @backstage/plugin-kubernetes ``` -Once the package has been installed, you need to import the plugin in your app by adding the "Kubernetes" tab to the respective catalog pages. +Once installed, the plugin is automatically available in your app through the default feature discovery. It adds a "Kubernetes" tab to entity pages for entities that have Kubernetes resources associated with them. For more details and alternative installation methods, see [installing plugins](../../frontend-system/building-apps/05-installing-plugins.md). -```tsx title="packages/app/src/components/catalog/EntityPage.tsx" -/* highlight-add-next-line */ -import { EntityKubernetesContent } from '@backstage/plugin-kubernetes'; +The Kubernetes tab is shown by default for entities where Kubernetes data is available, based on the entity annotations. You can customize the entity filter for the tab through `app-config.yaml`: -// You can add the tab to any number of pages, the service page is shown as an -// example here -const serviceEntityPage = ( - - {/* other tabs... */} - {/* highlight-add-start */} - - - - {/* highlight-add-end */} - -); +```yaml title="app-config.yaml" +app: + extensions: + - entity-content:kubernetes: + config: + filter: + metadata.annotations.backstage.io/kubernetes-id: + $exists: true ``` -:::note Note - -The optional `refreshIntervalMs` property on the `EntityKubernetesContent` defines the interval in which the content automatically refreshes, if not set this will default to 10 seconds. - -::: - That's it! But now, we need the Kubernetes Backend plugin for the frontend to work. ## Adding Kubernetes Backend plugin diff --git a/docs/features/search/getting-started--old.md b/docs/features/search/getting-started--old.md new file mode 100644 index 0000000000..88dd456a86 --- /dev/null +++ b/docs/features/search/getting-started--old.md @@ -0,0 +1,351 @@ +--- +id: getting-started--old +title: Getting Started with Search (Old Frontend System) +description: How to set up and install Backstage Search +--- + +::::info +This documentation is for Backstage apps that still use the old frontend +system. If your app uses the new frontend system, read the +[current guide](./getting-started.md) instead. +:::: + +Search functions as a plugin to Backstage, so you will need to use Backstage to +use Search. + +If you haven't setup Backstage already, start +[here](../../getting-started/index.md). + +> If you used `npx @backstage/create-app`, and you have a search page defined in +> `packages/app/src/components/search`, skip to +> [`Customizing Search`](#customizing-search) below. + +## Adding Search to the Frontend + +```bash title="From your Backstage root directory" +yarn --cwd packages/app add @backstage/plugin-search @backstage/plugin-search-react +``` + +Create a new `packages/app/src/components/search/SearchPage.tsx` file in your +Backstage app with the following contents: + +```tsx +import { Content, Header, Page } from '@backstage/core-components'; +import { Grid, List, Card, CardContent } from '@material-ui/core'; +import { + SearchBar, + SearchResult, + DefaultResultListItem, + SearchFilter, +} from '@backstage/plugin-search-react'; +import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; + +export const searchPage = ( + +
+ + + + + + + + + + + + + + + + + + {({ results }) => ( + + {results.map(result => { + switch (result.type) { + case 'software-catalog': + return ( + + ); + default: + return ( + + ); + } + })} + + )} + + + + + +); +``` + +Bind the above Search Page to the `/search` route in your +`packages/app/src/App.tsx` file, like this: + +```tsx +import { SearchPage } from '@backstage/plugin-search'; +import { searchPage } from './components/search/SearchPage'; + +const routes = ( + + }> + {searchPage} + + +); +``` + +### Using the Search Modal + +In `Root.tsx`, add the `SidebarSearchModal` component: + +```bash +import { SidebarSearchModal } from '@backstage/plugin-search'; + +export const Root = ({ children }: PropsWithChildren<{}>) => ( + + + + + +... +``` + +For more information about using `Root.tsx`, please see +[the changelog](https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md#0315). + +## Adding Search to the Backend + +Add the following plugins into your backend app: + +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-search-backend @backstage/plugin-search-backend-module-pg @backstage/plugin-search-backend-module-catalog @backstage/plugin-search-backend-module-techdocs +``` + +Then add the following lines: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +// Other plugins... + +/* highlight-add-start */ +// search plugin +backend.add(import('@backstage/plugin-search-backend')); + +// search engines +backend.add(import('@backstage/plugin-search-backend-module-pg')); + +// search collators +backend.add(import('@backstage/plugin-search-backend-module-catalog')); +backend.add(import('@backstage/plugin-search-backend-module-techdocs')); +/* highlight-add-end */ + +backend.start(); +``` + +With the above setup Search will use the [Lunr](https://github.com/olivernn/lunr.js) in-memory Search Engine but if your have Postgres setup as your database then it will use Postgres as your Search Engine. Learn more in the [Search Engines](./search-engines.md) documentation. + +The above also sets up two Collators for you - Catalog and TechDocs - which will index content from these two locations so that you can easily search them. Learn more in the [Collators documentation](./collators.md). + +## Customizing Search + +### Frontend + +The Search Plugin web library (`@backstage/plugin-search-react`) exposes several default filter types as static properties, +including `` and ``. These allow +you to provide values relevant to your Backstage instance that, when selected, +get passed to the backend. + +```tsx {2-5,8-11} + + + + + + +``` + +If you have advanced filter needs, you can specify your own filter component +like this (although new core filter contributions are welcome): + +```tsx +import { useSearch, SearchFilter } from '@backstage/plugin-search-react'; + +const MyCustomFilter = () => { + // Note: filters contain filter data from other filter components. Be sure + // not to clobber other filters' data! + const { filters, setFilters } = useSearch(); + + return (/* ... */); +}; + +// Which could be rendered like this: + +``` + +It's good practice for search results to highlight information that was used to +return it in the first place! The code below highlights how you might specify a +custom result item component, using the `` component as +an example: + +```tsx {7-13} + + {({ results }) => ( + + {results.map(result => { + // result.type is the index type defined by the collator. + switch (result.type) { + case 'software-catalog': + return ( + + ); + // ... + } + })} + + )} + +``` + +> For more advanced customization of the Search frontend, also see how to guides such as [How to implement your own Search API](./how-to-guides.md#how-to-implement-your-own-search-api) and [How to customize search results highlighting styling](./how-to-guides.md#how-to-customize-search-results-highlighting-styling) + +### Backend + +Backstage Search isn't a search engine itself, rather, it provides an interface +between your Backstage instance and a +[Search Engine](./concepts.md#search-engines) of your choice. Currently, we only +support two engines, an in-memory search Engine called Lunr and Elasticsearch. +See [Search Engines](./search-engines.md) documentation for more information how +to configure these in your Backstage instance. + +Backstage Search can be used to power search of anything! Plugins like the +Catalog offer default [collators](./concepts.md#collators) (e.g. +[DefaultCatalogCollator](https://github.com/backstage/backstage/blob/df12cc25aa4934a98bc42ed03c07f64a1a0a9d72/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts)) +which are responsible for providing documents +[to be indexed](./concepts.md#documents-and-indices). You can register any +number of collators with the `IndexBuilder` like this: + +```typescript +const indexBuilder = new IndexBuilder({ logger: env.logger, searchEngine }); + +const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 10 }, + timeout: { minutes: 15 }, + initialDelay: { seconds: 3 }, +}); + +const everyHourSchedule = env.scheduler.createScheduledTaskRunner({ + frequency: { hours: 1 }, + timeout: { minutes: 90 }, + initialDelay: { seconds: 3 }, +}); + +indexBuilder.addCollator({ + schedule: every10MinutesSchedule, + factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { + discovery: env.discovery, + tokenManager: env.tokenManager, + }), +}); + +indexBuilder.addCollator({ + schedule: everyHourSchedule, + factory: new MyCustomCollatorFactory(), +}); +``` + +Backstage Search builds and maintains its index +[on a schedule](./concepts.md#the-scheduler). You can change how often the +indexes are rebuilt for a given type of document. You may want to do this if +your documents are updated more or less frequently. You can do so by configuring +a scheduled `SchedulerServiceTaskRunner` to pass into the `schedule` value, like this: + +```typescript {3} +const every10MinutesSchedule = env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 10 }, + timeout: { minutes: 15 }, + initialDelay: { seconds: 3 }, +}); + +indexBuilder.addCollator({ + schedule: every10MinutesSchedule, + factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { + discovery: env.discovery, + tokenManager: env.tokenManager, + }), +}); +``` + +:::note Note + +if you are using the in-memory Lunr search engine, you probably want to +implement a non-distributed `SchedulerServiceTaskRunner` like the following to ensure consistency +if you're running multiple search backend nodes (alternatively, you can configure +the search plugin to use a non-distributed database such as +[SQLite](../../tutorials/configuring-plugin-databases.md#postgresql-and-sqlite-3)): + +::: + +```typescript +import { + SchedulerServiceTaskRunner, + SchedulerServiceTaskInvocationDefinition, +} from '@backstage/backend-plugin-api'; + +const schedule: SchedulerServiceTaskRunner = { + run: async (task: SchedulerServiceTaskInvocationDefinition) => { + const startRefresh = async () => { + while (!task.signal?.aborted) { + try { + await task.fn(task.signal); + } catch { + // ignore intentionally + } + + await new Promise(resolve => setTimeout(resolve, 600 * 1000)); + } + }; + startRefresh(); + }, +}; + +indexBuilder.addCollator({ + schedule, + factory: DefaultCatalogCollatorFactory.fromConfig(env.config, { + discovery: env.discovery, + tokenManager: env.tokenManager, + }), +}); +``` + +> For more advanced customization of the Search backend, also see how to guides such as [How to index TechDocs documents](./how-to-guides.md#how-to-index-techdocs-documents) and [How to limit what can be searched in the Software Catalog](./how-to-guides.md#how-to-limit-what-can-be-searched-in-the-software-catalog) diff --git a/docs/features/search/getting-started.md b/docs/features/search/getting-started.md index e5029ed35f..ba8ff1c840 100644 --- a/docs/features/search/getting-started.md +++ b/docs/features/search/getting-started.md @@ -4,128 +4,60 @@ title: Getting Started with Search description: How to set up and install Backstage Search --- +::::info +This documentation is written for the new frontend system, which is the default +in new Backstage apps. If your Backstage app still uses the old frontend system, +read the [old frontend system version of this guide](./getting-started--old.md) +instead. +:::: + Search functions as a plugin to Backstage, so you will need to use Backstage to use Search. If you haven't setup Backstage already, start [here](../../getting-started/index.md). -> If you used `npx @backstage/create-app`, and you have a search page defined in -> `packages/app/src/components/search`, skip to -> [`Customizing Search`](#customizing-search) below. - ## Adding Search to the Frontend ```bash title="From your Backstage root directory" yarn --cwd packages/app add @backstage/plugin-search @backstage/plugin-search-react ``` -Create a new `packages/app/src/components/search/SearchPage.tsx` file in your -Backstage app with the following contents: +Once installed, the search plugin is automatically available in your app through +the default feature discovery. It provides a search page at `/search`, a search +navigation item in the sidebar, and a search modal accessible from the sidebar. +For more details and alternative installation methods, see +[installing plugins](../../frontend-system/building-apps/05-installing-plugins.md). -```tsx -import { Content, Header, Page } from '@backstage/core-components'; -import { Grid, List, Card, CardContent } from '@material-ui/core'; -import { - SearchBar, - SearchResult, - DefaultResultListItem, - SearchFilter, -} from '@backstage/plugin-search-react'; -import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; +### Configuring the search page -export const searchPage = ( - -
- - - - - - - - - - - - - - - - - - {({ results }) => ( - - {results.map(result => { - switch (result.type) { - case 'software-catalog': - return ( - - ); - default: - return ( - - ); - } - })} - - )} - - - - - -); +The search page can be configured through `app-config.yaml`. For example, to +disable search result tracking: + +```yaml title="app-config.yaml" +app: + extensions: + - page:search: + config: + noTrack: true ``` -Bind the above Search Page to the `/search` route in your -`packages/app/src/App.tsx` file, like this: +### Search result list items -```tsx -import { SearchPage } from '@backstage/plugin-search'; -import { searchPage } from './components/search/SearchPage'; +The search page automatically discovers and uses search result list item +extensions provided by installed plugins. For example, the catalog plugin +provides a `CatalogSearchResultListItem` and the TechDocs plugin provides a +`TechDocsSearchResultListItem`. These are automatically registered when the +respective plugins are installed. -const routes = ( - - }> - {searchPage} - - -); -``` +You can also install additional search result list item extensions using the +`SearchResultListItemBlueprint` from `@backstage/plugin-search-react/alpha`. -### Using the Search Modal +### Search filters -In `Root.tsx`, add the `SidebarSearchModal` component: - -```bash -import { SidebarSearchModal } from '@backstage/plugin-search'; - -export const Root = ({ children }: PropsWithChildren<{}>) => ( - - - - - -... -``` - -For more information about using `Root.tsx`, please see -[the changelog](https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md#0315). +Similarly, search filter extensions are automatically discovered. You can add +custom filters using the `SearchFilterBlueprint` or +`SearchFilterResultTypeBlueprint` from `@backstage/plugin-search-react/alpha`. ## Adding Search to the Backend @@ -165,70 +97,39 @@ The above also sets up two Collators for you - Catalog and TechDocs - which will ### Frontend -The Search Plugin web library (`@backstage/plugin-search-react`) exposes several default filter types as static properties, -including `` and ``. These allow -you to provide values relevant to your Backstage instance that, when selected, -get passed to the backend. +The search plugin provides extension points for customizing the search +experience through blueprints. You can add custom search result list items, +filters, and result type filters. -```tsx {2-5,8-11} - - - - - - -``` - -If you have advanced filter needs, you can specify your own filter component -like this (although new core filter contributions are welcome): +For example, to create a custom search result list item, use the +`SearchResultListItemBlueprint` from `@backstage/plugin-search-react/alpha`: ```tsx -import { useSearch, SearchFilter } from '@backstage/plugin-search-react'; +import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha'; -const MyCustomFilter = () => { - // Note: filters contain filter data from other filter components. Be sure - // not to clobber other filters' data! - const { filters, setFilters } = useSearch(); - - return (/* ... */); -}; - -// Which could be rendered like this: - +export const MySearchResultListItem = SearchResultListItemBlueprint.make({ + name: 'my-result-item', + params: { + predicate: result => result.type === 'my-custom-type', + component: async () => { + const { MyResultItem } = await import('./components/MyResultItem'); + return MyResultItem; + }, + }, +}); ``` -It's good practice for search results to highlight information that was used to -return it in the first place! The code below highlights how you might specify a -custom result item component, using the `` component as -an example: +Install this in your app by passing it to `createApp`: -```tsx {7-13} - - {({ results }) => ( - - {results.map(result => { - // result.type is the index type defined by the collator. - switch (result.type) { - case 'software-catalog': - return ( - - ); - // ... - } - })} - - )} - +```tsx title="packages/app/src/App.tsx" +import { createApp } from '@backstage/frontend-defaults'; +import { MySearchResultListItem } from './search/MySearchResultListItem'; + +const app = createApp({ + features: [MySearchResultListItem], +}); + +export default app.createRoot(); ``` > For more advanced customization of the Search frontend, also see how to guides such as [How to implement your own Search API](./how-to-guides.md#how-to-implement-your-own-search-api) and [How to customize search results highlighting styling](./how-to-guides.md#how-to-customize-search-results-highlighting-styling) diff --git a/docs/features/search/how-to-guides--old.md b/docs/features/search/how-to-guides--old.md new file mode 100644 index 0000000000..9b0147da8a --- /dev/null +++ b/docs/features/search/how-to-guides--old.md @@ -0,0 +1,367 @@ +--- +id: how-to-guides--old +title: Search How-To guides (Old Frontend System) +sidebar_label: How-To guides +description: Search How To guides +--- + +::::info +This documentation is for Backstage apps that still use the old frontend +system. If your app uses the new frontend system, read the +[current guide](./how-to-guides.md) instead. +:::: + +## How to implement your own Search API + +The Search plugin provides implementation of one primary API by default: the +[SearchApi](https://github.com/backstage/backstage/blob/db2666b980853c281b8fe77905d7639c5d255f13/plugins/search/src/apis.ts#L35), +which is responsible for talking to the search-backend to query search results. + +There may be occasions where you need to implement this API yourself, to +customize it to your own needs - for example if you have your own search backend +that you want to talk to. The purpose of this guide is to walk you through how +to do that in two steps. + +1. Implement the `SearchApi` + [interface](https://github.com/backstage/backstage/blob/db2666b980853c281b8fe77905d7639c5d255f13/plugins/search/src/apis.ts#L31) + according to your needs. + + ```typescript + export class SearchClient implements SearchApi { + // your implementation + } + ``` + +2. Override the API ref `searchApiRef` with your new implemented API in the + `App.tsx` using `ApiFactories`. + [Read more about App APIs](https://backstage.io/docs/api/utility-apis#app-apis). + + ```typescript + const app = createApp({ + apis: [ + // SearchApi + createApiFactory({ + api: searchApiRef, + deps: { discovery: discoveryApiRef }, + factory({ discovery }) { + return new SearchClient({ discoveryApi: discovery }); + }, + }), + ], + }); + ``` + +## How to customize fields in the Software Catalog or TechDocs index + +Sometimes, you might want to have the ability to control which data passes into the search index +in the catalog collator or customize data for a specific kind. You can easily achieve this +by passing an `entityTransformer` callback to the `DefaultCatalogCollatorFactory`. This behavior +is also possible for the `DefaultTechDocsCollatorFactory`. You can either simply amend the default behavior +or even write an entirely new document (which should still follow some required basic structure). + +> `authorization` and `location` cannot be modified via a `entityTransformer`, `location` can be modified only through `locationTemplate`. + +```ts title="packages/backend/src/plugins/search.ts" +const catalogEntityTransformer: CatalogCollatorEntityTransformer = ( + entity: Entity, +) => { + if (entity.kind === 'SomeKind') { + return { + // customize here output for 'SomeKind' kind + }; + } + + return { + // and customize default output + ...defaultCatalogCollatorEntityTransformer(entity), + text: 'my super cool text', + }; +}; + +indexBuilder.addCollator({ + collator: DefaultCatalogCollatorFactory.fromConfig(env.config, { + discovery: env.discovery, + tokenManager: env.tokenManager, + /* highlight-add-next-line */ + entityTransformer: catalogEntityTransformer, + }), +}); + +const techDocsEntityTransformer: TechDocsCollatorEntityTransformer = ( + entity: Entity, +) => { + return { + // add more fields to the index + tags: entity.metadata.tags, + }; +}; + +const techDocsDocumentTransformer: TechDocsCollatorDocumentTransformer = ( + doc: MkSearchIndexDoc, +) => { + return { + // add more fields to the index + bost: doc.boost, + }; +}; + +indexBuilder.addCollator({ + collator: DefaultTechDocsCollatorFactory.fromConfig(env.config, { + discovery: env.discovery, + tokenManager: env.tokenManager, + /* highlight-add-next-line */ + entityTransformer: techDocsEntityTransformer, + /* highlight-add-next-line */ + documentTransformer: techDocsDocumentTransformer, + }), +}); +``` + +## How to customize search results highlighting styling + +The default highlighting styling for matched terms in search results is your +browsers default styles for the `` HTML tag. If you want to customize +how highlighted terms look you can follow Backstage's guide on how to +[Customizing Your App's UI](https://backstage.io/docs/conf/user-interface) +to create an override with your preferred styling. + +For example, using the new MUI V4+V5 unified theming method, the following will result +in highlighted words to be bold & underlined: + +```typescript jsx title=packages/app/src/theme/theme.ts +import { + createBaseThemeOptions, + createUnifiedTheme, + palettes, + UnifiedTheme, +} from '@backstage/theme'; + +export const myLightTheme: UnifiedTheme = createUnifiedTheme({ + ...createBaseThemeOptions({ + palette: palettes.light, + }), + defaultPageTheme: 'home', + components: { + /** @ts-ignore This is temporarily necessary until MUI V5 transition is completed. */ + BackstageHighlightedSearchResultText: { + styleOverrides: { + highlight: { + color: 'inherit', + backgroundColor: 'inherit', + fontWeight: 'bold', + textDecoration: 'underline', + }, + }, + }, + }, +}); +``` + +```typescript jsx title= packages/app/src/App.tsx + +const app : BackstageApp = createApp({ + ... + themes: [{ + id: 'my-light-theme', + title: 'Light Theme', + variant: 'light', + icon: , + Provider: ({ children }) => () + }] +}); +``` + +Obviously if you wanted a dark theme, you would need to provide that as well. + +## How to render search results using extensions + +Extensions for search results let you customize components used to render search result items, It is possible to provide your own search result item extensions or use the ones provided by plugin packages. + +### 1. Providing an extension in your plugin package + +> Note: You must use the `plugin.provide()` function to make a search item renderer available. Unlike rendering a list in a standard MUI Table or similar, you cannot simply provide +> a rendering function to the `` component. + +Using the example below, you can provide an extension to be used as a search result item: + +```tsx title="plugins/your-plugin/src/plugin.ts" +import { createPlugin } from '@backstage/core-plugin-api'; +import { createSearchResultListItemExtension } from '@backstage/plugin-search-react'; + +const plugin = createPlugin({ id: 'YOUR_PLUGIN_ID' }); + +export const YourSearchResultListItemExtension = plugin.provide( + createSearchResultListItemExtension({ + name: 'YourSearchResultListItem', + component: () => + import('./components').then(m => m.YourSearchResultListItem), + }), +); +``` + +If your list item accept props, you can extend the `SearchResultListItemExtensionProps` with your component specific props: + +```tsx +export const YourSearchResultListItemExtension: ( + props: SearchResultListItemExtensionProps, +) => JSX.Element | null = plugin.provide( + createSearchResultListItemExtension({ + name: 'YourSearchResultListItem', + component: () => + import('./components').then(m => m.YourSearchResultListItem), + }), +); +``` + +Additionally, you can define a predicate function that receives a result and returns whether your extension should be used to render it or not: + +```tsx title="plugins/your-plugin/src/plugin.ts" +import { createPlugin } from '@backstage/core-plugin-api'; +import { createSearchResultListItemExtension } from '@backstage/plugin-search-react'; + +const plugin = createPlugin({ id: 'YOUR_PLUGIN_ID' }); + +export const YourSearchResultListItemExtension = plugin.provide( + createSearchResultListItemExtension({ + name: 'YourSearchResultListItem', + component: () => + import('./components').then(m => m.YourSearchResultListItem), + // Only results matching your type will be rendered by this extension + predicate: result => result.type === 'YOUR_RESULT_TYPE', + }), +); +``` + +Remember to export your new extension via your plugin's `index.ts` so that it is available from within your app: + +```tsx title="plugins/your-plugin/src/index.ts" +export { YourSearchResultListItem } from './plugin.ts'; +``` + +For more details, see the [createSearchResultListItemExtension](https://backstage.io/api/stable/functions/_backstage_plugin-search-react.index.createSearchResultListItemExtension.html) API reference. + +### 2. Custom search result extension in the SearchPage + +Once you have exposed your item renderer via the `plugin.provide()` function, you can now override the default search item renderers and tell the `` component +which renderers to use. Note that the order of the renderers matters! The first one that matches via its predicate function will be used. + +Here is an example of customizing your `SearchPage`: + +```tsx title="packages/app/src/components/searchPage.tsx" +import { Grid, Paper } from '@material-ui/core'; +import BuildIcon from '@material-ui/icons/Build'; + +import { + Page, + Header, + Content, + DocsIcon, + CatalogIcon, +} from '@backstage/core-components'; +import { SearchBar, SearchResult } from '@backstage/plugin-search-react'; + +// Your search result item extension +import { YourSearchResultListItem } from '@backstage/your-plugin'; + +// Extensions provided by other plugin developers +import { ToolSearchResultListItem } from '@backstage/plugin-explore'; +import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; +import { CatalogSearchResultListItem } from '@internal/plugin-catalog-customized'; + +// This example omits other components, like filter and pagination +const SearchPage = () => ( + +
+ + + + + + + + + + + } /> + } /> + } /> + + + + + +); + +export const searchPage = ; +``` + +> **Important**: A default result item extension (one that does not have a predicate) should be placed as the last child, so it can be used only when no other extensions match the result being rendered. +> If a non-default extension is specified, the `DefaultResultListItem` component will be used. + +### 2. Custom search result extension in the SidebarSearchModal + +You may be using the SidebarSearchModal component. In this case, you can customize the search items in this component as follows: + +```tsx title="packages/app/src/components/Root/Root.tsx" +import { SidebarSearchModal } from '@backstage/plugin-search'; +... +export const Root = ({ children }: PropsWithChildren<{}>) => { + const styles = useStyles(); + + return + + ... + } />, + /* Provide an existing search item renderer */ + } /> + ]} /> + ... + + {children} + ; +}; +``` + +### 3. Custom search result extension in a custom SearchModal + +Assuming you have completely customized your SearchModal, here's an example that renders results with extensions: + +```tsx title="packages/app/src/components/searchModal.tsx" +import { DialogContent, DialogTitle, Paper } from '@material-ui/core'; +import BuildIcon from '@material-ui/icons/Build'; + +import { DocsIcon, CatalogIcon } from '@backstage/core-components'; +import { SearchBar, SearchResult } from '@backstage/plugin-search-react'; + +// Your search result item extension +import { YourSearchResultListItem } from '@backstage/your-plugin'; + +// Extensions provided by other plugin developers +import { ToolSearchResultListItem } from '@backstage/plugin-explore'; +import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; +import { CatalogSearchResultListItem } from '@internal/plugin-catalog-customized'; + +export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => ( + <> + + + + + + + + } /> + } /> + } /> + {/* As a "default" extension, it does not define a predicate function, + so it must be the last child to render results that do not match the above extensions */} + + + + +); +``` + +There are other more specific search results layout components that also accept result item extensions, check their documentation: [SearchResultList](https://backstage.io/storybook/?path=/story/plugins-search-searchresultlist--with-result-item-extensions) and [SearchResultGroup](https://backstage.io/storybook/?path=/story/plugins-search-searchresultgroup--with-result-item-extensions). diff --git a/docs/features/search/how-to-guides.md b/docs/features/search/how-to-guides.md index 018c70479d..53f838898c 100644 --- a/docs/features/search/how-to-guides.md +++ b/docs/features/search/how-to-guides.md @@ -5,6 +5,13 @@ sidebar_label: How-To guides description: Search How To guides --- +::::info +This documentation is written for the new frontend system, which is the default +in new Backstage apps. If your Backstage app still uses the old frontend system, +read the [old frontend system version of this guide](./how-to-guides--old.md) +instead. +:::: + ## How to implement your own Search API The Search plugin provides implementation of one primary API by default: the @@ -26,24 +33,9 @@ to do that in two steps. } ``` -2. Override the API ref `searchApiRef` with your new implemented API in the - `App.tsx` using `ApiFactories`. - [Read more about App APIs](https://backstage.io/docs/api/utility-apis#app-apis). - - ```typescript - const app = createApp({ - apis: [ - // SearchApi - createApiFactory({ - api: searchApiRef, - deps: { discovery: discoveryApiRef }, - factory({ discovery }) { - return new SearchClient({ discoveryApi: discovery }); - }, - }), - ], - }); - ``` +2. Override the default API extension by creating a custom API extension using + `createApiExtension` from `@backstage/frontend-plugin-api`, and install it + in your app. See the [Utility APIs](../../frontend-system/utility-apis/01-index.md) documentation for details on how to create and install custom API extensions. ## How to customize fields in the Software Catalog or TechDocs index @@ -119,7 +111,7 @@ how highlighted terms look you can follow Backstage's guide on how to [Customizing Your App's UI](https://backstage.io/docs/conf/user-interface) to create an override with your preferred styling. -For example, using the new MUI V4+V5 unified theming method, the following will result +For example, using the unified theming method, the following will result in highlighted words to be bold & underlined: ```typescript jsx title=packages/app/src/theme/theme.ts @@ -151,211 +143,59 @@ export const myLightTheme: UnifiedTheme = createUnifiedTheme({ }); ``` -```typescript jsx title= packages/app/src/App.tsx - -const app : BackstageApp = createApp({ - ... - themes: [{ - id: 'my-light-theme', - title: 'Light Theme', - variant: 'light', - icon: , - Provider: ({ children }) => () - }] -}); -``` - -Obviously if you wanted a dark theme, you would need to provide that as well. +Custom themes are installed as extensions in the new frontend system. See the +[theming documentation](../../frontend-system/building-apps/02-configuring-extensions.md) +for details on how to install custom themes. ## How to render search results using extensions -Extensions for search results let you customize components used to render search result items, It is possible to provide your own search result item extensions or use the ones provided by plugin packages. +Extensions for search results let you customize components used to render +search result items. It is possible to provide your own search result item +extensions or use the ones provided by plugin packages. -### 1. Providing an extension in your plugin package +### Providing a search result list item extension -> Note: You must use the `plugin.provide()` function to make a search item renderer available. Unlike rendering a list in a standard MUI Table or similar, you cannot simply provide -> a rendering function to the `` component. +In the new frontend system, search result list item extensions are created +using the `SearchResultListItemBlueprint` from +`@backstage/plugin-search-react/alpha`: -Using the example below, you can provide an extension to be used as a search result item: +```tsx title="plugins/your-plugin/src/extensions.ts" +import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha'; -```tsx title="plugins/your-plugin/src/plugin.ts" -import { createPlugin } from '@backstage/core-plugin-api'; -import { createSearchResultListItemExtension } from '@backstage/plugin-search-react'; - -const plugin = createPlugin({ id: 'YOUR_PLUGIN_ID' }); - -export const YourSearchResultListItemExtension = plugin.provide( - createSearchResultListItemExtension({ - name: 'YourSearchResultListItem', - component: () => - import('./components').then(m => m.YourSearchResultListItem), - }), -); -``` - -If your list item accept props, you can extend the `SearchResultListItemExtensionProps` with your component specific props: - -```tsx -export const YourSearchResultListItemExtension: ( - props: SearchResultListItemExtensionProps, -) => JSX.Element | null = plugin.provide( - createSearchResultListItemExtension({ - name: 'YourSearchResultListItem', - component: () => - import('./components').then(m => m.YourSearchResultListItem), - }), -); -``` - -Additionally, you can define a predicate function that receives a result and returns whether your extension should be used to render it or not: - -```tsx title="plugins/your-plugin/src/plugin.ts" -import { createPlugin } from '@backstage/core-plugin-api'; -import { createSearchResultListItemExtension } from '@backstage/plugin-search-react'; - -const plugin = createPlugin({ id: 'YOUR_PLUGIN_ID' }); - -export const YourSearchResultListItemExtension = plugin.provide( - createSearchResultListItemExtension({ - name: 'YourSearchResultListItem', - component: () => - import('./components').then(m => m.YourSearchResultListItem), - // Only results matching your type will be rendered by this extension +export const YourSearchResultListItem = SearchResultListItemBlueprint.make({ + name: 'your-result-item', + params: { predicate: result => result.type === 'YOUR_RESULT_TYPE', - }), -); + component: async () => { + const { YourSearchResultListItem } = await import('./components'); + return YourSearchResultListItem; + }, + }, +}); ``` -Remember to export your new extension via your plugin's `index.ts` so that it is available from within your app: +The extension is then exported from your plugin's alpha entry point and +automatically discovered when the plugin is installed. -```tsx title="plugins/your-plugin/src/index.ts" -export { YourSearchResultListItem } from './plugin.ts'; +If you need to provide a search result list item extension from your app +rather than a plugin, you can install it directly in `createApp`: + +```tsx title="packages/app/src/App.tsx" +import { createApp } from '@backstage/frontend-defaults'; +import { YourSearchResultListItem } from './search/YourSearchResultListItem'; + +const app = createApp({ + features: [YourSearchResultListItem], +}); + +export default app.createRoot(); ``` -For more details, see the [createSearchResultListItemExtension](https://backstage.io/api/stable/functions/_backstage_plugin-search-react.index.createSearchResultListItemExtension.html) API reference. +### Search result item ordering -### 2. Custom search result extension in the SearchPage - -Once you have exposed your item renderer via the `plugin.provide()` function, you can now override the default search item renderers and tell the `` component -which renderers to use. Note that the order of the renderers matters! The first one that matches via its predicate function will be used. - -Here is an example of customizing your `SearchPage`: - -```tsx title="packages/app/src/components/searchPage.tsx" -import { Grid, Paper } from '@material-ui/core'; -import BuildIcon from '@material-ui/icons/Build'; - -import { - Page, - Header, - Content, - DocsIcon, - CatalogIcon, -} from '@backstage/core-components'; -import { SearchBar, SearchResult } from '@backstage/plugin-search-react'; - -// Your search result item extension -import { YourSearchResultListItem } from '@backstage/your-plugin'; - -// Extensions provided by other plugin developers -import { ToolSearchResultListItem } from '@backstage/plugin-explore'; -import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; -import { CatalogSearchResultListItem } from '@internal/plugin-catalog-customized'; - -// This example omits other components, like filter and pagination -const SearchPage = () => ( - -
- - - - - - - - - - - } /> - } /> - } /> - - - - - -); - -export const searchPage = ; -``` - -> **Important**: A default result item extension (one that does not have a predicate) should be placed as the last child, so it can be used only when no other extensions match the result being rendered. -> If a non-default extension is specified, the `DefaultResultListItem` component will be used. - -### 2. Custom search result extension in the SidebarSearchModal - -You may be using the SidebarSearchModal component. In this case, you can customize the search items in this component as follows: - -```tsx title="packages/app/src/components/Root/Root.tsx" -import { SidebarSearchModal } from '@backstage/plugin-search'; -... -export const Root = ({ children }: PropsWithChildren<{}>) => { - const styles = useStyles(); - - return - - ... - } />, - /* Provide an existing search item renderer */ - } /> - ]} /> - ... - - {children} - ; -}; -``` - -### 3. Custom search result extension in a custom SearchModal - -Assuming you have completely customized your SearchModal, here's an example that renders results with extensions: - -```tsx title="packages/app/src/components/searchModal.tsx" -import { DialogContent, DialogTitle, Paper } from '@material-ui/core'; -import BuildIcon from '@material-ui/icons/Build'; - -import { DocsIcon, CatalogIcon } from '@backstage/core-components'; -import { SearchBar, SearchResult } from '@backstage/plugin-search-react'; - -// Your search result item extension -import { YourSearchResultListItem } from '@backstage/your-plugin'; - -// Extensions provided by other plugin developers -import { ToolSearchResultListItem } from '@backstage/plugin-explore'; -import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; -import { CatalogSearchResultListItem } from '@internal/plugin-catalog-customized'; - -export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => ( - <> - - - - - - - - } /> - } /> - } /> - {/* As a "default" extension, it does not define a predicate function, - so it must be the last child to render results that do not match the above extensions */} - - - - -); -``` +When multiple search result list item extensions are installed, the search page +uses them to render results based on their predicate functions. The first +extension whose predicate matches a given result is used to render it. Extensions +without a predicate act as fallback renderers and should be ordered last. There are other more specific search results layout components that also accept result item extensions, check their documentation: [SearchResultList](https://backstage.io/storybook/?path=/story/plugins-search-searchresultlist--with-result-item-extensions) and [SearchResultGroup](https://backstage.io/storybook/?path=/story/plugins-search-searchresultgroup--with-result-item-extensions). diff --git a/docs/features/software-catalog/catalog-customization--old.md b/docs/features/software-catalog/catalog-customization--old.md new file mode 100644 index 0000000000..c4ce27dc9e --- /dev/null +++ b/docs/features/software-catalog/catalog-customization--old.md @@ -0,0 +1,508 @@ +--- +id: catalog-customization--old +title: Catalog Customization (Old Frontend System) +description: How to add custom filters or interface elements to the Backstage software catalog +--- + +::::info +This documentation is for Backstage apps that still use the old frontend +system. If your app uses the new frontend system, read the +[current guide](./catalog-customization.md) instead. +:::: + +The Backstage software catalog comes with a default `CatalogIndexPage` to filter and find catalog entities. This is already set up by default by `@backstage/create-app`. If you want to change the default index page - to set the initially selected filter, adjust columns, add actions, or to add a custom filter to the catalog - the following sections will show you how. + +## Pagination + +Initial support for pagination of the `CatalogIndexPage` was added in v1.21.0 of Backstage, so make sure you are on that version or newer to use this feature. To enable pagination you simply need to pass in the `pagination` prop like this: + +```tsx title="packages/app/src/App.tsx" +} /> +``` + +## Initially Selected Filter + +By default, the initially selected filter defaults to Owned. If you are still building up your catalog this may show an empty list to start. If you would prefer this to show All as the default, here's how you can make that change: + +```tsx title="packages/app/src/App.tsx" +} +/> +``` + +Possible options are: owned, starred, or all + +## Initially Selected Kind + +By default, the initially selected Kind when viewing the Catalog is Component, but you may have reasons that you want this to be different. Let's say at your Organization they would like it to always default to Domain, here's how you would do that: + +```tsx title="packages/app/src/App.tsx" +} /> +``` + +Possible options are all the [default Kinds](system-model.md) as well as any custom Kinds that you have added. + +## Owner Picker Mode + +The Owner filter by default will only contain a list of Users and/or Groups that actually own an entity in the Catalog, now you may have reason to change this. Here's how: + +```tsx title="packages/app/src/App.tsx" +} /> +``` + +Possible options are: owners-only or all + +## Table Options + +The tables used within Backstage are built on top of [`@material-table/core`](https://material-table-core.github.io/) and the `CatalogIndexPage` has a `tableOptions` prop that allows you to customize the underlying table to a certain extent, but there are some hard coded Backstage settings that can't be changed. Here's an example of how to use this prop to disable the search filter field in the table's header: + +```tsx title="packages/app/src/App.tsx" +} +/> +``` + +There are many options that can be set using `tableOptions`, the full list of settings can be found in the [`@material-table/core` `Options` interface](https://github.com/material-table-core/core/blob/v3.1.0/types/index.d.ts#L323) (this link goes to `v3.1.0` of `@material-table/core` as that is the version currently used by Backstage). + +## Customize Columns + +The columns you see in the `CatalogIndexPage` were selected to be a good starting point for most, but there may be cases where you would like to add or remove columns from existing or custom Kinds. + +### Adding a column to an existing Kind + +Suppose we want to add a new User Email column to the `User` kind in the Catalog. We can do this by overriding the `columns` that we pass into the `CatalogIndexPage` component in our `App.tsx`. First, we need to match the entity kind that we want to override, and then define the columns to show: + +```tsx title="packages/app/src/App.tsx" +{/* prettier-ignore */ /* highlight-add-start */} +const myColumnsFunc: CatalogTableColumnsFunc = entityListContext => { + if (entityListContext.filters.kind?.value === 'user') { + return [ + // Render existing columns + ...CatalogTable.defaultColumnsFunc(entityListContext), + // Add new columns here + ]; + } + + return CatalogTable.defaultColumnsFunc(entityListContext); +}; +{/* prettier-ignore */ /* highlight-add-end */} +``` + +Then, we can implement the `createUserEmailColumn` function and add it to the list of columns. `field` is used to access the data from the entity, while `render` lets us customize how we display the data: + +```tsx title="packages/app/src/App.tsx" +{/* highlight-add-start */} +const createUserEmailColumn = (): TableColumn => ({ + title: 'User Email', + field: 'entity.spec.profile.email', + render: ({ entity }) => ( + + ), +}); +{/* highlight-add-end */} + +const myColumnsFunc: CatalogTableColumnsFunc = entityListContext => { + if (entityListContext.filters.kind?.value === 'user') { + return [ + // Render existing columns + ...CatalogTable.defaultColumnsFunc(entityListContext), + // Add new columns here + {/* highlight-add-next-line */} + createUserEmailColumn(), + ]; + } + + return CatalogTable.defaultColumnsFunc(entityListContext); +}; +``` + +Finally, we can pass the `myColumnsFunc` to the `CatalogIndexPage` component: + +```tsx title="packages/app/src/App.tsx" +const routes = ( + + + } + /> + {/* Other routes */} + +) +``` + +### Adding columns to a custom or specific Kind + +Another use case for customization is when adding a custom `Kind`. This feature is available in Backstage >= `v1.23.0`. For example: + +```tsx title="packages/app/src/App.tsx" +import { + CatalogEntityPage, + CatalogIndexPage, + catalogPlugin, + {/* highlight-add-start */} + CatalogTable, + CatalogTableColumnsFunc, + {/* highlight-add-end */} +} from '@backstage/plugin-catalog'; + +{/* highlight-add-start */} +const myColumnsFunc: CatalogTableColumnsFunc = entityListContext => { + if (entityListContext.filters.kind?.value === 'MyKind') { + return [ + CatalogTable.columns.createNameColumn(), + CatalogTable.columns.createOwnerColumn(), + ]; + } + + return CatalogTable.defaultColumnsFunc(entityListContext); +}; +{/* highlight-add-end */} + +{/* highlight-remove-next-line */} +} /> +{/* highlight-add-next-line */} +} /> +``` + +:::note Note + +In the examples above, the contents of the files have been shortened for simplicity. + +::: + +## Customize Actions + +The `CatalogIndexPage` comes with three default actions - view, edit, and star. You might want to add more. + +To do this, first you'll need to add `@mui/utils` to your `packages/app/package.json`: + +```sh +yarn --cwd packages/app add @mui/utils +``` + +Then you'll do the following: + +```tsx title="packages/app/src/App.tsx" +import { + AlertDisplay, + OAuthRequestDialog, + SignInPage, + {/* highlight-add-next-line */} + TableProps, +} from '@backstage/core-components'; + +import { + CatalogEntityPage, + CatalogIndexPage, + {/* highlight-add-next-line */} + CatalogTableRow, + catalogPlugin, +} from '@backstage/plugin-catalog'; + +{/* highlight-add-start */} +import { Typography } from '@material-ui/core'; +import OpenInNew from '@material-ui/icons/OpenInNew'; +import { visuallyHidden } from '@mui/utils'; +{/* highlight-add-end */} + +{/* highlight-add-start */} +const customActions: TableProps['actions'] = [ + ({ entity }) => { + const url = 'https://backstage.io/'; + const title = `View - ${entity.metadata.name}`; + + return { + icon: () => ( + <> + {title} + + + ), + tooltip: title, + disabled: !url, + onClick: () => { + if (!url) return; + window.open(url, '_blank'); + }, + }; + }, +]; +{/* highlight-add-end */} + +{/* highlight-remove-next-line */} +} /> +{/* highlight-add-next-line */} +} /> +``` + +:::note Note + +In the example above, the contents of `App.tsx` has been shortened for simplicity. + +::: + +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). + +## Customize Filters + +There are various ways to customize filters: adjusting the existing filters with props, adding or removing default filters, creating brand-new custom filters, etc. The following sections cover these cases: + +### Default Filter Props + +There are a set of default filters that you can use, which surface all the props mentioned earlier in this document. Here's how they can be used: + +```tsx title="packages/app/src/App.tsx" +import { DefaultFilters } from '@backstage/plugin-catalog-react'; + + + + + } + /> + } +/>; +``` + +### Removing Default Filters + +If you have reasons not to use the Lifecycle, Tag, and Processing Status filters, here's an example of how to remove them: + +```tsx title="packages/app/src/App.tsx" +import { + EntityKindPicker, + EntityTypePicker, + UserListPicker, + EntityOwnerPicker, + EntityNamespacePicker, +} from '@backstage/plugin-catalog-react'; + + + + + + + + + } + /> + } +/>; +``` + +### Custom Filters + +You can add custom filters. For example, suppose that we want to allow filtering by a custom annotation added to entities, `company.com/security-tier`. Here is how we can build a filter to support that need. + +First we need to create a new filter that implements the `EntityFilter` interface: + +```ts +import { EntityFilter } from '@backstage/plugin-catalog-react'; +import { Entity } from '@backstage/catalog-model'; + +class EntitySecurityTierFilter implements EntityFilter { + constructor(readonly values: string[]) {} + filterEntity(entity: Entity): boolean { + const tier = entity.metadata.annotations?.['company.com/security-tier']; + return tier !== undefined && this.values.includes(tier); + } +} +``` + +The `EntityFilter` interface permits backend filters, which are passed along to the `catalog-backend` - or frontend filters, which are applied after entities are loaded from the backend. + +We'll use this filter to extend the default filters in a type-safe way. Let's create the custom filter shape extending the default somewhere alongside this filter: + +```ts +export type CustomFilters = DefaultEntityFilters & { + securityTiers?: EntitySecurityTierFilter; +}; +``` + +To control this filter, we can create a React component that shows checkboxes for the security tiers. This component will make use of the `useEntityList` hook, which accepts this extended filter type as a [generic](https://www.typescriptlang.org/docs/handbook/2/generics.html) parameter: + +```tsx +export const EntitySecurityTierPicker = () => { + // The securityTiers key is recognized due to the CustomFilter generic + const { + filters: { securityTiers }, + updateFilters, + } = useEntityList(); + + // Toggles the value, depending on whether it's already selected + function onChange(value: string) { + const newTiers = securityTiers?.values.includes(value) + ? securityTiers.values.filter(tier => tier !== value) + : [...(securityTiers?.values ?? []), value]; + updateFilters({ + securityTiers: newTiers.length + ? new EntitySecurityTierFilter(newTiers) + : undefined, + }); + } + + const tierOptions = ['1', '2', '3']; + return ( + + Security Tier + + {tierOptions.map(tier => ( + onChange(tier)} + /> + } + label={`Tier ${tier}`} + /> + ))} + + + ); +}; +``` + +Now we can add the component to `CatalogIndexPage`: + +```tsx title="packages/app/src/App.tsx" +{/* prettier-ignore */ /* highlight-add-start */} +import { DefaultFilters } from '@backstage/plugin-catalog-react'; +{/* prettier-ignore */ /* highlight-add-end */} + +const routes = ( + + + {/* highlight-remove-next-line */} + } /> + {/* highlight-add-start */} + + + + + } + /> + } + /> + {/* highlight-add-end */} + {/* ... */} + +); +``` + +The same method can be used to customize the _default_ filters with a different interface - for such usage, the generic argument isn't needed since the filter shape remains the same as the default. + +## Advanced Customization + +For those where none of the above fits their needs you can take the option of creating a fully custom `CatalogIndexPage`. + +```tsx title="packages/app/src/components/catalog/CustomCatalogIndex.tsx" +import { + PageWithHeader, + Content, + ContentHeader, + SupportButton, +} from '@backstage/core-components'; +import { useApi, configApiRef } from '@backstage/core-plugin-api'; +import { CatalogTable } from '@backstage/plugin-catalog'; +import { + EntityListProvider, + CatalogFilterLayout, + EntityKindPicker, + EntityLifecyclePicker, + EntityNamespacePicker, + EntityOwnerPicker, + EntityProcessingStatusPicker, + EntityTagPicker, + EntityTypePicker, + UserListPicker, +} from '@backstage/plugin-catalog-react'; + +export const CustomCatalogPage = () => { + const orgName = + useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage'; + + return ( + + + + All your software catalog entities + + + + + + + + + + + + + + + + + + + + + ); +}; +``` + +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 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: + +```tsx title="packages/app/src/App.tsx" +const routes = ( + + + {/* highlight-remove-next-line */} + } /> + {/* highlight-add-start */} + }> + + + {/* highlight-add-end */} + {/* ... */} + +); +``` diff --git a/docs/features/software-catalog/catalog-customization.md b/docs/features/software-catalog/catalog-customization.md index 2e984d2942..17a2cd18c9 100644 --- a/docs/features/software-catalog/catalog-customization.md +++ b/docs/features/software-catalog/catalog-customization.md @@ -4,512 +4,66 @@ title: Catalog Customization description: How to add custom filters or interface elements to the Backstage software catalog --- -The Backstage software catalog comes with a default `CatalogIndexPage` to filter and find catalog entities. This is already set up by default by `@backstage/create-app`. If you want to change the default index page - to set the initially selected filter, adjust columns, add actions, or to add a custom filter to the catalog - the following sections will show you how. +::::info +This documentation is written for the new frontend system, which is the default +in new Backstage apps. If your Backstage app still uses the old frontend system, +read the [old frontend system version of this guide](./catalog-customization--old.md) +instead. +:::: -## Pagination +The Backstage software catalog comes with a default catalog index page and entity pages that are highly configurable through `app-config.yaml`. This guide covers how to customize the catalog in the new frontend system. -Initial support for pagination of the `CatalogIndexPage` was added in v1.21.0 of Backstage, so make sure you are on that version or newer to use this feature. To enable pagination you simply need to pass in the `pagination` prop like this: +## Catalog index page -```tsx title="packages/app/src/App.tsx" -} /> +The catalog index page can be configured through extensions in `app-config.yaml`. For example, to enable pagination: + +```yaml title="app-config.yaml" +app: + extensions: + - page:catalog: + config: + pagination: true ``` -## Initially Selected Filter +You can also configure pagination with additional options: -By default, the initially selected filter defaults to Owned. If you are still building up your catalog this may show an empty list to start. If you would prefer this to show All as the default, here's how you can make that change: - -```tsx title="packages/app/src/App.tsx" -} -/> +```yaml title="app-config.yaml" +app: + extensions: + - page:catalog: + config: + pagination: + mode: offset + limit: 20 ``` -Possible options are: owned, starred, or all +### Catalog filters -## Initially Selected Kind +The catalog index page includes a set of default filters (kind, type, owner, lifecycle, tag, namespace, processing status). These filters can be configured through extensions. For example, to set the initial kind filter: -By default, the initially selected Kind when viewing the Catalog is Component, but you may have reasons that you want this to be different. Let's say at your Organization they would like it to always default to Domain, here's how you would do that: - -```tsx title="packages/app/src/App.tsx" -} /> +```yaml title="app-config.yaml" +app: + extensions: + - catalog-filter:catalog/kind: + config: + initialFilter: domain ``` -Possible options are all the [default Kinds](system-model.md) as well as any custom Kinds that you have added. +To set the initial list filter to "all" instead of "owned": -## Owner Picker Mode - -The Owner filter by default will only contain a list of Users and/or Groups that actually own an entity in the Catalog, now you may have reason to change this. Here's how: - -```tsx title="packages/app/src/App.tsx" -} /> +```yaml title="app-config.yaml" +app: + extensions: + - catalog-filter:catalog/list: + config: + initialFilter: all ``` -Possible options are: owners-only or all +### Custom filters -## Table Options +You can create custom catalog filters using the `CatalogFilterBlueprint` from `@backstage/plugin-catalog-react/alpha`. See the [extension overrides](../../frontend-system/building-apps/03-extension-overrides.md) documentation for details on how to install custom extensions. -The tables used within Backstage are built on top of [`@material-table/core`](https://material-table-core.github.io/) and the `CatalogIndexPage` has a `tableOptions` prop that allows you to customize the underlying table to a certain extent, but there are some hard coded Backstage settings that can't be changed. Here's an example of how to use this prop to disable the search filter field in the table's header: - -```tsx title="packages/app/src/App.tsx" -} -/> -``` - -There are many options that can be set using `tableOptions`, the full list of settings can be found in the [`@material-table/core` `Options` interface](https://github.com/material-table-core/core/blob/v3.1.0/types/index.d.ts#L323) (this link goes to `v3.1.0` of `@material-table/core` as that is the version currently used by Backstage). - -## Customize Columns - -The columns you see in the `CatalogIndexPage` were selected to be a good starting point for most, but there may be cases where you would like to add or remove columns from existing or custom Kinds. - -### Adding a column to an existing Kind - -Suppose we want to add a new User Email column to the `User` kind in the Catalog. We can do this by overriding the `columns` that we pass into the `CatalogIndexPage` component in our `App.tsx`. First, we need to match the entity kind that we want to override, and then define the columns to show: - -```tsx title="packages/app/src/App.tsx" -{/* prettier-ignore */ /* highlight-add-start */} -const myColumnsFunc: CatalogTableColumnsFunc = entityListContext => { - if (entityListContext.filters.kind?.value === 'user') { - return [ - // Render existing columns - ...CatalogTable.defaultColumnsFunc(entityListContext), - // Add new columns here - ]; - } - - return CatalogTable.defaultColumnsFunc(entityListContext); -}; -{/* prettier-ignore */ /* highlight-add-end */} -``` - -Then, we can implement the `createUserEmailColumn` function and add it to the list of columns. `field` is used to access the data from the entity, while `render` lets us customize how we display the data: - -```tsx title="packages/app/src/App.tsx" -{/* highlight-add-start */} -const createUserEmailColumn = (): TableColumn => ({ - title: 'User Email', - field: 'entity.spec.profile.email', - render: ({ entity }) => ( - - ), -}); -{/* highlight-add-end */} - -const myColumnsFunc: CatalogTableColumnsFunc = entityListContext => { - if (entityListContext.filters.kind?.value === 'user') { - return [ - // Render existing columns - ...CatalogTable.defaultColumnsFunc(entityListContext), - // Add new columns here - {/* highlight-add-next-line */} - createUserEmailColumn(), - ]; - } - - return CatalogTable.defaultColumnsFunc(entityListContext); -}; -``` - -Finally, we can pass the `myColumnsFunc` to the `CatalogIndexPage` component: - -```tsx title="packages/app/src/App.tsx" -const routes = ( - - - } - /> - {/* Other routes */} - -) -``` - -### Adding columns to a custom or specific Kind - -Another use case for customization is when adding a custom `Kind`. This feature is available in Backstage >= `v1.23.0`. For example: - -```tsx title="packages/app/src/App.tsx" -import { - CatalogEntityPage, - CatalogIndexPage, - catalogPlugin, - {/* highlight-add-start */} - CatalogTable, - CatalogTableColumnsFunc, - {/* highlight-add-end */} -} from '@backstage/plugin-catalog'; - -{/* highlight-add-start */} -const myColumnsFunc: CatalogTableColumnsFunc = entityListContext => { - if (entityListContext.filters.kind?.value === 'MyKind') { - return [ - CatalogTable.columns.createNameColumn(), - CatalogTable.columns.createOwnerColumn(), - ]; - } - - return CatalogTable.defaultColumnsFunc(entityListContext); -}; -{/* highlight-add-end */} - -{/* highlight-remove-next-line */} -} /> -{/* highlight-add-next-line */} -} /> -``` - -:::note Note - -In the examples above, the contents of the files have been shortened for simplicity. - -::: - -## Customize Actions - -The `CatalogIndexPage` comes with three default actions - view, edit, and star. You might want to add more. - -To do this, first you'll need to add `@mui/utils` to your `packages/app/package.json`: - -```sh -yarn --cwd packages/app add @mui/utils -``` - -Then you'll do the following: - -```tsx title="packages/app/src/App.tsx" -import { - AlertDisplay, - OAuthRequestDialog, - SignInPage, - {/* highlight-add-next-line */} - TableProps, -} from '@backstage/core-components'; - -import { - CatalogEntityPage, - CatalogIndexPage, - {/* highlight-add-next-line */} - CatalogTableRow, - catalogPlugin, -} from '@backstage/plugin-catalog'; - -{/* highlight-add-start */} -import { Typography } from '@material-ui/core'; -import OpenInNew from '@material-ui/icons/OpenInNew'; -import { visuallyHidden } from '@mui/utils'; -{/* highlight-add-end */} - -{/* highlight-add-start */} -const customActions: TableProps['actions'] = [ - ({ entity }) => { - const url = 'https://backstage.io/'; - const title = `View - ${entity.metadata.name}`; - - return { - icon: () => ( - <> - {title} - - - ), - tooltip: title, - disabled: !url, - onClick: () => { - if (!url) return; - window.open(url, '_blank'); - }, - }; - }, -]; -{/* highlight-add-end */} - -{/* highlight-remove-next-line */} -} /> -{/* highlight-add-next-line */} -} /> -``` - -:::note Note - -In the example above, the contents of `App.tsx` has been shortened for simplicity. - -::: - -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). - -## Customize Filters - -There are various ways to customize filters: adjusting the existing filters with props, adding or removing default filters, creating brand-new custom filters, etc. The following sections cover these cases: - -### Default Filter Props - -There are a set of default filters that you can use, which surface all the props mentioned earlier in this document. Here's how they can be used: - -```tsx title="packages/app/src/App.tsx" -import { DefaultFilters } from '@backstage/plugin-catalog-react'; - - - - - } - /> - } -/>; -``` - -### Removing Default Filters - -If you have reasons not to use the Lifecycle, Tag, and Processing Status filters, here's an example of how to remove them: - -```tsx title="packages/app/src/App.tsx" -import { - EntityKindPicker, - EntityTypePicker, - UserListPicker, - EntityOwnerPicker, - EntityNamespacePicker, -} from '@backstage/plugin-catalog-react'; - - - - - - - - - } - /> - } -/>; -``` - -### Custom Filters - -You can add custom filters. For example, suppose that we want to allow filtering by a custom annotation added to entities, `company.com/security-tier`. Here is how we can build a filter to support that need. - -First we need to create a new filter that implements the `EntityFilter` interface: - -```ts -import { EntityFilter } from '@backstage/plugin-catalog-react'; -import { Entity } from '@backstage/catalog-model'; - -class EntitySecurityTierFilter implements EntityFilter { - constructor(readonly values: string[]) {} - filterEntity(entity: Entity): boolean { - const tier = entity.metadata.annotations?.['company.com/security-tier']; - return tier !== undefined && this.values.includes(tier); - } -} -``` - -The `EntityFilter` interface permits backend filters, which are passed along to the `catalog-backend` - or frontend filters, which are applied after entities are loaded from the backend. - -We'll use this filter to extend the default filters in a type-safe way. Let's create the custom filter shape extending the default somewhere alongside this filter: - -```ts -export type CustomFilters = DefaultEntityFilters & { - securityTiers?: EntitySecurityTierFilter; -}; -``` - -To control this filter, we can create a React component that shows checkboxes for the security tiers. This component will make use of the `useEntityList` hook, which accepts this extended filter type as a [generic](https://www.typescriptlang.org/docs/handbook/2/generics.html) parameter: - -```tsx -export const EntitySecurityTierPicker = () => { - // The securityTiers key is recognized due to the CustomFilter generic - const { - filters: { securityTiers }, - updateFilters, - } = useEntityList(); - - // Toggles the value, depending on whether it's already selected - function onChange(value: string) { - const newTiers = securityTiers?.values.includes(value) - ? securityTiers.values.filter(tier => tier !== value) - : [...(securityTiers?.values ?? []), value]; - updateFilters({ - securityTiers: newTiers.length - ? new EntitySecurityTierFilter(newTiers) - : undefined, - }); - } - - const tierOptions = ['1', '2', '3']; - return ( - - Security Tier - - {tierOptions.map(tier => ( - onChange(tier)} - /> - } - label={`Tier ${tier}`} - /> - ))} - - - ); -}; -``` - -Now we can add the component to `CatalogIndexPage`: - -```tsx title="packages/app/src/App.tsx" -{/* prettier-ignore */ /* highlight-add-start */} -import { DefaultFilters } from '@backstage/plugin-catalog-react'; -{/* prettier-ignore */ /* highlight-add-end */} - -const routes = ( - - - {/* highlight-remove-next-line */} - } /> - {/* highlight-add-start */} - - - - - } - /> - } - /> - {/* highlight-add-end */} - {/* ... */} - -); -``` - -The same method can be used to customize the _default_ filters with a different interface - for such usage, the generic argument isn't needed since the filter shape remains the same as the default. - -## Advanced Customization - -For those where none of the above fits their needs you can take the option of creating a fully custom `CatalogIndexPage`. - -```tsx title="packages/app/src/components/catalog/CustomCatalogIndex.tsx" -import { - PageWithHeader, - Content, - ContentHeader, - SupportButton, -} from '@backstage/core-components'; -import { useApi, configApiRef } from '@backstage/core-plugin-api'; -import { CatalogTable } from '@backstage/plugin-catalog'; -import { - EntityListProvider, - CatalogFilterLayout, - EntityKindPicker, - EntityLifecyclePicker, - EntityNamespacePicker, - EntityOwnerPicker, - EntityProcessingStatusPicker, - EntityTagPicker, - EntityTypePicker, - UserListPicker, -} from '@backstage/plugin-catalog-react'; - -export const CustomCatalogPage = () => { - const orgName = - useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage'; - - return ( - - - - All your software catalog entities - - - - - - - - - - - - - - - - - - - - - ); -}; -``` - -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 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: - -```tsx title="packages/app/src/App.tsx" -const routes = ( - - - {/* highlight-remove-next-line */} - } /> - {/* highlight-add-start */} - }> - - - {/* highlight-add-end */} - {/* ... */} - -); -``` - -## New Frontend System - -This section of the documentation explains how to create and configure catalog extensions in the [new frontend system](../../frontend-system/index.md). - -:::warning Warning - -This section is a work in progress. - -::: +## Entity page ### Entity filters diff --git a/docs/features/software-templates/index.md b/docs/features/software-templates/index.md index 1b36fcb2dd..f2465c735e 100644 --- a/docs/features/software-templates/index.md +++ b/docs/features/software-templates/index.md @@ -85,25 +85,9 @@ Once the template has finished running, and from the screenshot above, when its There could be situations where you would like to disable the `Register Existing Component` button for your users. -To do so, you need to explicitly disable the default route binding from the `scaffolderPlugin.registerComponent` to the Catalog Import page. +To do so, you can disable the route binding in your `app-config.yaml`: -This can be done in `backstage/packages/app/src/App.tsx`: - -```diff - const app = createApp({ - apis, - bindRoutes({ bind }) { - bind(scaffolderPlugin.externalRoutes, { -+ registerComponent: false, -- registerComponent: catalogImportPlugin.routes.importPage, - viewTechDoc: techdocsPlugin.routes.docRoot, - }); -}) -``` - -OR in `app-config.yaml`: - -```yaml +```yaml title="app-config.yaml" app: routes: bindings: diff --git a/docs/features/software-templates/writing-custom-field-extensions--old.md b/docs/features/software-templates/writing-custom-field-extensions--old.md new file mode 100644 index 0000000000..2ba95c8602 --- /dev/null +++ b/docs/features/software-templates/writing-custom-field-extensions--old.md @@ -0,0 +1,332 @@ +--- +id: writing-custom-field-extensions--old +title: Writing Custom Field Extensions (Old Frontend System) +description: How to write your own field extensions +--- + +::::info +This documentation is for Backstage apps that still use the old frontend +system. If your app uses the new frontend system, read the +[current guide](./writing-custom-field-extensions.md) instead. +:::: + +Collecting input from the user is a very large part of the scaffolding process +and Software Templates as a whole. Sometimes the built in components and fields +just aren't good enough, and sometimes you want to enrich the form that the +users sees with better inputs that fit better. + +This is where `Custom Field Extensions` come in. + +With them you can show your own `React` Components and use them to control the +state of the JSON schema, as well as provide your own validation functions to +validate the data too. + +## Creating a Field Extension + +Field extensions are a way to combine an ID, a `React` Component and a +`validation` function together in a modular way that you can then use to pass to +the `Scaffolder` frontend plugin in your own `App.tsx`. + +You can create your own Field Extension by using the +[`createScaffolderFieldExtension`](https://backstage.io/api/stable/variables/_backstage_plugin-scaffolder.index.createScaffolderFieldExtension.html) +`API` like below. + +As an example, we will create a component that validates whether a string is in the `Kebab-case` pattern: + +```tsx +//packages/app/src/scaffolder/ValidateKebabCase/ValidateKebabCaseExtension.tsx +import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react'; +import type { FieldValidation } from '@rjsf/utils'; +import FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import Input from '@material-ui/core/Input'; +import InputLabel from '@material-ui/core/InputLabel'; +/* + This is the actual component that will get rendered in the form +*/ +export const ValidateKebabCase = ({ + onChange, + rawErrors, + required, + formData, +}: FieldExtensionComponentProps) => { + return ( + 0 && !formData} + > + Name + onChange(e.target?.value)} + /> + + Use only letters, numbers, hyphens and underscores + + + ); +}; + +/* + This is a validation function that will run when the form is submitted. + You will get the value from the `onChange` handler before as the value here to make sure that the types are aligned\ +*/ + +export const validateKebabCaseValidation = ( + value: string, + validation: FieldValidation, +) => { + const kebabCase = /^[a-z0-9-_]+$/g.test(value); + + if (kebabCase === false) { + validation.addError( + `Only use letters, numbers, hyphen ("-") and underscore ("_").`, + ); + } +}; +``` + +```tsx +// packages/app/src/scaffolder/ValidateKebabCase/extensions.ts + +/* + This is where the magic happens and creates the custom field extension. + + Note that if you're writing extensions part of a separate plugin, + then please use `scaffolderPlugin.provide` from there instead and export it part of your `plugin.ts` rather than re-using the `scaffolder.plugin`. +*/ + +import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; +import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; +import { + ValidateKebabCase, + validateKebabCaseValidation, +} from './ValidateKebabCaseExtension'; + +export const ValidateKebabCaseFieldExtension = scaffolderPlugin.provide( + createScaffolderFieldExtension({ + name: 'ValidateKebabCase', + component: ValidateKebabCase, + validation: validateKebabCaseValidation, + }), +); +``` + +```tsx +// packages/app/src/scaffolder/ValidateKebabCase/index.ts + +export { ValidateKebabCaseFieldExtension } from './extensions'; +``` + +Once all these files are in place, you then need to provide your custom +extension to the `scaffolder` plugin. + +You do this in `packages/app/src/App.tsx`. You need to provide the +`customFieldExtensions` as children to the `ScaffolderPage`. + +```tsx +const routes = ( + + ... + } /> + ... + +); +``` + +Should look something like this instead: + +```tsx +import { ValidateKebabCaseFieldExtension } from './scaffolder/ValidateKebabCase'; +import { ScaffolderFieldExtensions } from '@backstage/plugin-scaffolder-react'; + +const routes = ( + + ... + }> + + + + + ... + +); +``` + +### Async Validation Function + +A validation function can be asynchronous and use [Utility APIs](https://backstage.io/docs/api/utility-apis/) via the `ApiHolder` in the [field validation context](https://backstage.io/api/stable/types/_backstage_plugin-scaffolder-react.index.CustomFieldValidator.html). The example below uses the `catalogApiRef` to check if the submitted value (in this scenario an entity ref) exists in the catalog. + +```tsx +import { FieldValidation } from '@rjsf/utils'; +import { ApiHolder } from '@backstage/core-plugin-api'; +import { catalogApiRef } from '@backstage/plugin-catalog-react'; + +/* + This validation function checks if the submitted entity ref value is present in the catalog. +*/ + +export const customFieldExtensionValidator = async ( + value: string, + validation: FieldValidation, + context: { apiHolder: ApiHolder }, +) => { + const catalogApi = context.apiHolder.get(catalogApiRef); + + if ((await catalogApi?.getEntityByRef(value)) === undefined) { + validation.addError('Entity not found'); + } +}; +``` + +## Using the Custom Field Extension + +Once it's been passed to the `ScaffolderPage` you should now be able to use the +`ui:field` property in your templates to point it to the name of the +`customFieldExtension` that you registered. + +Something like this: + +```yaml +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: Test template + title: Test template with custom extension + description: Test template +spec: + parameters: + - title: Fill in some steps + required: + - name + properties: + name: + title: Name + type: string + description: My custom name for the component + ui:field: ValidateKebabCase + steps: + [...] +``` + +## Access Data from other Fields + +Custom fields extensions can read data from other fields in the form via the form context. This +is something that we discourage due to the coupling that it creates, but is sometimes still +the most sensible solution. + +```tsx +const CustomFieldExtensionComponent = (props: FieldExtensionComponentProps) => { + const { formData } = props.formContext; + ... +}; + +const CustomFieldExtension = scaffolderPlugin.provide( + createScaffolderFieldExtension({ + name: ..., + component: CustomFieldExtensionComponent, + validation: ... + }) +); +``` + +## Previewing Custom Field Extensions + +You can preview custom field extensions you write in the Backstage UI using the Custom Field Explorer +(accessible via the `/create/edit` route by default): + +![Custom Field Explorer](../../assets/software-templates/custom-field-explorer.png) + +In order to make your new custom field extension available in the explorer you will have to define a +JSON schema that describes the input/output types on your field like in the following example: + +```tsx +//packages/app/src/scaffolder/MyCustomExtensionWithOptions/MyCustomExtensionWithOptions.tsx +export const MyCustomExtensionWithOptionsSchema = { + uiOptions: { + type: 'object', + properties: { + focused: { + description: 'Whether to focus this field', + type: 'boolean', + }, + }, + }, + returnValue: { type: 'string' }, +}; + +export const MyCustomExtensionWithOptions = ({ + onChange, + rawErrors, + required, + formData, +}: FieldExtensionComponentProps) => { + return ( + 0 && !formData} + onChange={onChange} + focused={focused} + /> + ); +}; +``` + +```tsx +// packages/app/src/scaffolder/MyCustomExtensionWithOptions/extensions.ts +... +import { MyCustomExtensionWithOptions, MyCustomExtensionWithOptionsSchema } from './MyCustomExtensionWithOptions'; + +export const MyCustomFieldWithOptionsExtension = scaffolderPlugin.provide( + createScaffolderFieldExtension({ + name: 'MyCustomExtensionWithOptions', + component: MyCustomExtensionWithOptions, + schema: MyCustomExtensionWithOptionsSchema, + }), +); +``` + +We recommend using a library like [zod](https://github.com/colinhacks/zod) to define your schema +and the provided `makeFieldSchemaFromZod` helper utility function to generate both the JSON schema +and type for your field props to preventing having to duplicate the definitions: + +```tsx +//packages/app/src/scaffolder/MyCustomExtensionWithOptions/MyCustomExtensionWithOptions.tsx +... +import { z } from 'zod/v3'; +import { makeFieldSchemaFromZod } from '@backstage/plugin-scaffolder'; + +const MyCustomExtensionWithOptionsFieldSchema = makeFieldSchemaFromZod( + z.string(), + z.object({ + focused: z + .boolean() + .optional() + .describe('Whether to focus this field'), + }), +); + +export const MyCustomExtensionWithOptionsSchema = MyCustomExtensionWithOptionsFieldSchema.schema; + +type MyCustomExtensionWithOptionsProps = typeof MyCustomExtensionWithOptionsFieldSchema.type; + +export const MyCustomExtensionWithOptions = ({ + onChange, + rawErrors, + required, + formData, +}: MyCustomExtensionWithOptionsProps) => { + return ( + 0 && !formData} + onChange={onChange} + focused={focused} + /> + ); +}; +``` diff --git a/docs/features/software-templates/writing-custom-field-extensions.md b/docs/features/software-templates/writing-custom-field-extensions.md index f2e8486a07..cca45422cb 100644 --- a/docs/features/software-templates/writing-custom-field-extensions.md +++ b/docs/features/software-templates/writing-custom-field-extensions.md @@ -4,6 +4,13 @@ title: Writing Custom Field Extensions description: How to write your own field extensions --- +::::info +This documentation is written for the new frontend system, which is the default +in new Backstage apps. If your Backstage app still uses the old frontend system, +read the [old frontend system version of this guide](./writing-custom-field-extensions--old.md) +instead. +:::: + Collecting input from the user is a very large part of the scaffolding process and Software Templates as a whole. Sometimes the built in components and fields just aren't good enough, and sometimes you want to enrich the form that the @@ -18,26 +25,26 @@ validate the data too. ## Creating a Field Extension Field extensions are a way to combine an ID, a `React` Component and a -`validation` function together in a modular way that you can then use to pass to -the `Scaffolder` frontend plugin in your own `App.tsx`. +`validation` function together in a modular way that you can then register as +an extension in your Backstage app. -You can create your own Field Extension by using the -[`createScaffolderFieldExtension`](https://backstage.io/api/stable/variables/_backstage_plugin-scaffolder.index.createScaffolderFieldExtension.html) -`API` like below. +You can create your own field extension by using the `FormFieldBlueprint` from +`@backstage/plugin-scaffolder-react/alpha` together with `createFormField`, which +types the component, validation, and optional schema. -As an example, we will create a component that validates whether a string is in the `Kebab-case` pattern: +As an example, we will create a component that validates whether a string is in the `Kebab-case` pattern. + +First, create the component and validation function: ```tsx -//packages/app/src/scaffolder/ValidateKebabCase/ValidateKebabCaseExtension.tsx +// packages/app/src/scaffolder/ValidateKebabCase/ValidateKebabCaseExtension.tsx import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react'; import type { FieldValidation } from '@rjsf/utils'; import FormControl from '@material-ui/core/FormControl'; import FormHelperText from '@material-ui/core/FormHelperText'; import Input from '@material-ui/core/Input'; import InputLabel from '@material-ui/core/InputLabel'; -/* - This is the actual component that will get rendered in the form -*/ + export const ValidateKebabCase = ({ onChange, rawErrors, @@ -63,11 +70,6 @@ export const ValidateKebabCase = ({ ); }; -/* - This is a validation function that will run when the form is submitted. - You will get the value from the `onChange` handler before as the value here to make sure that the types are aligned\ -*/ - export const validateKebabCaseValidation = ( value: string, validation: FieldValidation, @@ -82,71 +84,50 @@ export const validateKebabCaseValidation = ( }; ``` +Then, create the extension using `FormFieldBlueprint` and `createFormField`: + ```tsx // packages/app/src/scaffolder/ValidateKebabCase/extensions.ts - -/* - This is where the magic happens and creates the custom field extension. - - Note that if you're writing extensions part of a separate plugin, - then please use `scaffolderPlugin.provide` from there instead and export it part of your `plugin.ts` rather than re-using the `scaffolder.plugin`. -*/ - -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { createScaffolderFieldExtension } from '@backstage/plugin-scaffolder-react'; +import { + FormFieldBlueprint, + createFormField, +} from '@backstage/plugin-scaffolder-react/alpha'; import { ValidateKebabCase, validateKebabCaseValidation, } from './ValidateKebabCaseExtension'; -export const ValidateKebabCaseFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: 'ValidateKebabCase', - component: ValidateKebabCase, - validation: validateKebabCaseValidation, - }), -); +export const ValidateKebabCaseFieldExtension = FormFieldBlueprint.make({ + name: 'validate-kebab-case', + params: { + field: () => + Promise.resolve( + createFormField({ + name: 'ValidateKebabCase', + component: ValidateKebabCase, + validation: validateKebabCaseValidation, + }), + ), + }, +}); ``` ```tsx // packages/app/src/scaffolder/ValidateKebabCase/index.ts - export { ValidateKebabCaseFieldExtension } from './extensions'; ``` -Once all these files are in place, you then need to provide your custom -extension to the `scaffolder` plugin. +Once the extension is created, install it in your app by passing it to `createApp`: -You do this in `packages/app/src/App.tsx`. You need to provide the -`customFieldExtensions` as children to the `ScaffolderPage`. - -```tsx -const routes = ( - - ... - } /> - ... - -); -``` - -Should look something like this instead: - -```tsx +```tsx title="packages/app/src/App.tsx" +import { createApp } from '@backstage/frontend-defaults'; import { ValidateKebabCaseFieldExtension } from './scaffolder/ValidateKebabCase'; -import { ScaffolderFieldExtensions } from '@backstage/plugin-scaffolder-react'; -const routes = ( - - ... - }> - - - - - ... - -); +const app = createApp({ + features: [ValidateKebabCaseFieldExtension], +}); + +export default app.createRoot(); ``` ### Async Validation Function @@ -158,10 +139,6 @@ import { FieldValidation } from '@rjsf/utils'; import { ApiHolder } from '@backstage/core-plugin-api'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; -/* - This validation function checks if the submitted entity ref value is present in the catalog. -*/ - export const customFieldExtensionValidator = async ( value: string, validation: FieldValidation, @@ -177,11 +154,8 @@ export const customFieldExtensionValidator = async ( ## Using the Custom Field Extension -Once it's been passed to the `ScaffolderPage` you should now be able to use the -`ui:field` property in your templates to point it to the name of the -`customFieldExtension` that you registered. - -Something like this: +Once registered, you can use the `ui:field` property in your templates to +reference the name of the custom field extension: ```yaml apiVersion: scaffolder.backstage.io/v1beta3 @@ -212,18 +186,30 @@ is something that we discourage due to the coupling that it creates, but is some the most sensible solution. ```tsx +import { + FormFieldBlueprint, + createFormField, +} from '@backstage/plugin-scaffolder-react/alpha'; +import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react'; + const CustomFieldExtensionComponent = (props: FieldExtensionComponentProps) => { const { formData } = props.formContext; ... }; -const CustomFieldExtension = scaffolderPlugin.provide( - createScaffolderFieldExtension({ - name: ..., - component: CustomFieldExtensionComponent, - validation: ... - }) -); +const CustomFieldExtension = FormFieldBlueprint.make({ + name: 'custom-field', + params: { + field: () => + Promise.resolve( + createFormField({ + name: 'custom-field', + component: CustomFieldExtensionComponent, + validation: ..., + }), + ), + }, +}); ``` ## Previewing Custom Field Extensions @@ -237,7 +223,10 @@ In order to make your new custom field extension available in the explorer you w JSON schema that describes the input/output types on your field like in the following example: ```tsx -//packages/app/src/scaffolder/MyCustomExtensionWithOptions/MyCustomExtensionWithOptions.tsx +// packages/app/src/scaffolder/MyCustomExtensionWithOptions/MyCustomExtensionWithOptions.tsx +import FormControl from '@material-ui/core/FormControl'; +import { FieldExtensionComponentProps } from '@backstage/plugin-scaffolder-react'; + export const MyCustomExtensionWithOptionsSchema = { uiOptions: { type: 'object', @@ -256,7 +245,10 @@ export const MyCustomExtensionWithOptions = ({ rawErrors, required, formData, + uiSchema, }: FieldExtensionComponentProps) => { + const focused = uiSchema['ui:options']?.focused; + return ( + Promise.resolve( + createFormField({ + name: 'MyCustomExtensionWithOptions', + component: MyCustomExtensionWithOptions, + schema: MyCustomExtensionWithOptionsSchema, + }), + ), + }, +}); ``` We recommend using a library like [zod](https://github.com/colinhacks/zod) to define your schema @@ -288,31 +292,33 @@ and the provided `makeFieldSchemaFromZod` helper utility function to generate bo and type for your field props to preventing having to duplicate the definitions: ```tsx -//packages/app/src/scaffolder/MyCustomExtensionWithOptions/MyCustomExtensionWithOptions.tsx -... +// packages/app/src/scaffolder/MyCustomExtensionWithOptions/MyCustomExtensionWithOptions.tsx +import FormControl from '@material-ui/core/FormControl'; import { z } from 'zod/v3'; import { makeFieldSchemaFromZod } from '@backstage/plugin-scaffolder'; const MyCustomExtensionWithOptionsFieldSchema = makeFieldSchemaFromZod( z.string(), z.object({ - focused: z - .boolean() - .optional() - .describe('Whether to focus this field'), + focused: z.boolean().optional().describe('Whether to focus this field'), }), ); -export const MyCustomExtensionWithOptionsSchema = MyCustomExtensionWithOptionsFieldSchema.schema; +export const MyCustomExtensionWithOptionsSchema = + MyCustomExtensionWithOptionsFieldSchema.schema; -type MyCustomExtensionWithOptionsProps = typeof MyCustomExtensionWithOptionsFieldSchema.type; +type MyCustomExtensionWithOptionsProps = + typeof MyCustomExtensionWithOptionsFieldSchema.type; export const MyCustomExtensionWithOptions = ({ onChange, rawErrors, required, formData, + uiSchema, }: MyCustomExtensionWithOptionsProps) => { + const focused = uiSchema['ui:options']?.focused; + return ( { + const mid = Math.ceil(properties.length / 2); + + return ( + <> +

{title}

+

In two column layout!!

+ + {properties.slice(0, mid).map(prop => ( + + {prop.content} + + ))} + {properties.slice(mid).map(prop => ( + + {prop.content} + + ))} + + {description} + + ); +}; + +export const TwoColumnLayout = scaffolderPlugin.provide( + createScaffolderLayout({ + name: 'TwoColumn', + component: TwoColumn, + }), +); +``` + +After you have registered your component as a custom layout then you need to provide the `layouts` to the `ScaffolderPage`: + +```tsx +import { MyCustomFieldExtension } from './scaffolder/MyCustomExtension'; +import { TwoColumnLayout } from './components/scaffolder/customScaffolderLayouts'; + +const routes = ( + + ... + }> + + + + + ... + +); +``` + +## Using the custom step layout + +Any component that has been passed to the `ScaffolderPage` as children of the `ScaffolderLayouts` component can be used as a `ui:ObjectFieldTemplate` in your template file: + +```yaml +parameters: + - title: Fill in some steps + ui:ObjectFieldTemplate: TwoColumn +``` diff --git a/docs/features/software-templates/writing-custom-step-layouts.md b/docs/features/software-templates/writing-custom-step-layouts.md index ad766270f2..82858626d6 100644 --- a/docs/features/software-templates/writing-custom-step-layouts.md +++ b/docs/features/software-templates/writing-custom-step-layouts.md @@ -4,6 +4,13 @@ title: Writing custom step layouts description: How to override the default step form layout --- +::::info +This documentation is written for the new frontend system, which is the default +in new Backstage apps. If your Backstage app still uses the old frontend system, +read the [old frontend system version of this guide](./writing-custom-step-layouts--old.md) +instead. +:::: + Every form in each step rendered in the frontend uses the default form layout from [react-jsonschema-form](https://rjsf-team.github.io/react-jsonschema-form/docs/). It is possible to override this behaviour by supplying a `ui:ObjectFieldTemplate` property for a particular step: ```yaml @@ -16,14 +23,12 @@ This is the same [field](https://rjsf-team.github.io/react-jsonschema-form/docs/ ## Registering a React component as a custom step layout -The [createScaffolderLayout](https://backstage.io/api/stable/functions/_backstage_plugin-scaffolder-react.index.createScaffolderLayout.html) function is used to mark a component as a custom step layout: +In the new frontend system, custom step layouts can be registered by creating a scaffolder module plugin that provides the layout through an extension override. Create a new plugin module: -```tsx -import { scaffolderPlugin } from '@backstage/plugin-scaffolder'; -import { - createScaffolderLayout, - LayoutTemplate, -} from '@backstage/plugin-scaffolder-react'; +```tsx title="packages/app/src/scaffolder/customLayouts.tsx" +import { createFrontendModule } from '@backstage/frontend-plugin-api'; +import scaffolderPlugin from '@backstage/plugin-scaffolder/alpha'; +import { LayoutTemplate } from '@backstage/plugin-scaffolder-react'; import { Grid } from '@material-ui/core'; const TwoColumn: LayoutTemplate = ({ properties, description, title }) => { @@ -49,37 +54,15 @@ const TwoColumn: LayoutTemplate = ({ properties, description, title }) => { ); }; - -export const TwoColumnLayout = scaffolderPlugin.provide( - createScaffolderLayout({ - name: 'TwoColumn', - component: TwoColumn, - }), -); ``` -After you have registered your component as a custom layout then you need to provide the `layouts` to the `ScaffolderPage`: +Use `createScaffolderLayout` from `@backstage/plugin-scaffolder-react` and `scaffolderPlugin.provide` from `@backstage/plugin-scaffolder` to register the layout under the name `TwoColumn`, then install it through a frontend module using `createFrontendModule` together with `scaffolderPlugin.withOverrides` from `@backstage/plugin-scaffolder/alpha`, following the patterns described in the extension overrides guide. -```tsx -import { MyCustomFieldExtension } from './scaffolder/MyCustomExtension'; -import { TwoColumnLayout } from './components/scaffolder/customScaffolderLayouts'; - -const routes = ( - - ... - }> - - - - - ... - -); -``` +For details on how to override and extend extensions in the new frontend system, see the [extension overrides](../../frontend-system/architecture/25-extension-overrides.md) documentation. ## Using the custom step layout -Any component that has been passed to the `ScaffolderPage` as children of the `ScaffolderLayouts` component can be used as a `ui:ObjectFieldTemplate` in your template file: +Once the layout is registered, it can be used as a `ui:ObjectFieldTemplate` in your template file: ```yaml parameters: diff --git a/docs/features/techdocs/getting-started--old.md b/docs/features/techdocs/getting-started--old.md new file mode 100644 index 0000000000..869f685cd2 --- /dev/null +++ b/docs/features/techdocs/getting-started--old.md @@ -0,0 +1,242 @@ +--- +id: getting-started--old +title: Getting Started (Old Frontend System) +description: Getting Started Documentation +--- + +::::info +This documentation is for Backstage apps that still use the old frontend +system. If your app uses the new frontend system, read the +[current guide](./getting-started.md) instead. +:::: + +TechDocs functions as a plugin in Backstage and ships with it installed out of the box, so you will need to use Backstage to use TechDocs. + +If you haven't setup Backstage already, start [here](../../getting-started/index.md). + +## Adding TechDocs frontend plugin + +The first step is to add the TechDocs plugin to your Backstage application. +Navigate to your new Backstage application directory. And then to your +`packages/app` directory, and install the `@backstage/plugin-techdocs` package. + +```bash title="From your Backstage root directory" +yarn --cwd packages/app add @backstage/plugin-techdocs +``` + +Once the package has been installed, you need to import the plugin in your app. + +In `packages/app/src/App.tsx`, import `TechDocsPage` and add the following to +`FlatRoutes`: + +```tsx title="packages/app/src/App.tsx" +import { + DefaultTechDocsHome, + TechDocsIndexPage, + TechDocsReaderPage, +} from '@backstage/plugin-techdocs'; + +const AppRoutes = () => { + + {/* ... other plugin routes */} + }> + + + } + /> + ; +}; +``` + +It would be nice to decorate your pages with something else... Having a link that redirects you to a new issue page when you highlight text in your documentation would be really cool, right? Let's learn how to do this using the TechDocs Addon Framework! + +With the [TechDocs Addon framework](https://backstage.io/docs/features/techdocs/addons#installing-and-using-addons), you can render React components in documentation pages and these Addons can be provided by any Backstage plugin. The framework is exported by the [@backstage/plugin-techdocs-react](https://www.npmjs.com/package/@backstage/plugin-techdocs-react) package and there is a `` Addon in the [@backstage/plugin-techdocs-module-addons-contrib](https://www.npmjs.com/package/@backstage/plugin-techdocs-module-addons-contrib) package for you to use once you have these two dependencies installed: + +```tsx +import { + DefaultTechDocsHome, + TechDocsIndexPage, + TechDocsReaderPage, +} from '@backstage/plugin-techdocs'; +/* highlight-add-start */ +import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; +import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; +/* highlight-add-end */ + +const AppRoutes = () => { + + {/* ... other plugin routes */} + }> + + + } + > + {/* highlight-add-start */} + + + + {/* highlight-add-end */} + + ; +}; +``` + +I know, you're curious to see how it looks, aren't you? See the image below: + + + +![TechDocs Report Issue Add-on](../../assets/techdocs/report-issue-addon.png) + +By clicking the open new issue button, you will be redirected to the new issue page according to the source code provider you are using: + + + +![TechDocs Report Issue Template](../../assets/techdocs/report-issue-template.png) + +That's it! Now, we need the TechDocs Backend plugin for the frontend to work. + +## Adding TechDocs Backend plugin + +First we need to install the `@backstage/plugin-techdocs-backend` package. + +```bash title="From your Backstage root directory" +yarn --cwd packages/backend add @backstage/plugin-techdocs-backend +``` + +Then in your backend `index.ts` you will add the following line. + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +// Other plugins... + +/* highlight-add-start */ +backend.add(import('@backstage/plugin-techdocs-backend')); +/* highlight-add-end */ + +backend.start(); +``` + +That's it! TechDocs frontend and backend have now been added to your Backstage +app. Now let us tweak some configurations to suit your needs. + +## Setting the configuration + +**See [TechDocs Configuration Options](configuration.md) for complete +configuration reference.** + +### Should TechDocs Backend generate docs? + +```yaml +techdocs: + builder: 'local' +``` + +Note that we recommend generating docs on CI/CD instead. Read more in the +"Basic" and "Recommended" sections of the +[TechDocs Architecture](architecture.md). But if you want to get started quickly +set `techdocs.builder` to `'local'` so that TechDocs Backend is responsible for +generating documentation sites. If set to `'external'`, Backstage will assume +that the sites are being generated on each entity's CI/CD pipeline, and are +being stored in a storage somewhere. + +When `techdocs.builder` is set to `'external'`, TechDocs becomes more or less a +read-only experience where it serves static files from a storage containing all +the generated documentation. + +### Choosing storage (publisher) + +TechDocs needs to know where to store generated documentation sites and where to +fetch the sites from. This is managed by a +[Publisher](./concepts.md#techdocs-publisher). Examples: Google Cloud Storage, +Amazon S3, or local filesystem of Backstage server. + +It is okay to use the local filesystem in a "basic" setup when you are trying +out Backstage for the first time. At a later time, review +[Using Cloud Storage](./using-cloud-storage.md). + +```yaml +techdocs: + builder: 'local' + publisher: + type: 'local' +``` + +### Disabling Docker in Docker situation (Optional) + +You can skip this if your `techdocs.builder` is set to `'external'`. + +The TechDocs Backend plugin runs a docker container with mkdocs installed to +generate the frontend of the docs from source files (Markdown). If you are +deploying Backstage using Docker, this will mean that your Backstage Docker +container will try to run another Docker container for TechDocs Backend. + +To avoid this problem, we have a configuration available. You can set a value in +your `app-config.yaml` that tells the techdocs generator if it should run the +`local` mkdocs or run it from `docker`. This defaults to running as `docker` if +no config is provided. + +```yaml +techdocs: + builder: 'local' + publisher: + type: 'local' + generator: + runIn: local +``` + +Setting `generator.runIn` to `local` means you will have to make sure your +environment is compatible with techdocs. + +You will have to install the `mkdocs` and `mkdocs-techdocs-core` package from +pip, optionally also `graphviz` and `plantuml` from your OS package manager (e.g. +apt). + +You can do so by including the following lines right above `USER node` of your +`Dockerfile`: + +```Dockerfile +RUN apt-get update && \ + apt-get install -y python3 python3-pip python3-venv && \ + rm -rf /var/lib/apt/lists/* + +ENV VIRTUAL_ENV=/opt/venv +RUN python3 -m venv $VIRTUAL_ENV +ENV PATH="$VIRTUAL_ENV/bin:$PATH" + +RUN pip3 install mkdocs-techdocs-core +``` + +Please be aware that the version requirement could change, you need to check our +[`Dockerfile`](https://github.com/backstage/techdocs-container/blob/main/Dockerfile) +and make sure to match with it. + +On a Debian-based Docker container, Python packages must be either installed using +the OS package manager or within a virtual environment (see the +[related PEP](https://peps.python.org/pep-0668/)). Alternative is to use e.g. +[pipx](https://pypa.github.io/pipx/) for installing Python packages in an isolated +environment. + +The above Dockerfile snippet installs the latest `mkdocs-techdoc-core` package. +Version numbers can be found in the corresponding +[changelog](https://github.com/backstage/mkdocs-techdocs-core#changelog). In +case you want to pin the version, use the example below: + +```Dockerfile +RUN pip3 install mkdocs-techdocs-core==1.2.3 +``` + +Note: We recommend Python version 3.11 or higher. + +> Caveat: Please install the `mkdocs-techdocs-core` package after all other +> Python packages. The order is important to make sure we get correct version of +> some of the dependencies. + +## Additional reading + +- [Creating and publishing your docs](creating-and-publishing.md) +- [Back to README](README.md) diff --git a/docs/features/techdocs/getting-started.md b/docs/features/techdocs/getting-started.md index 76c231ff0e..862d14eb4e 100644 --- a/docs/features/techdocs/getting-started.md +++ b/docs/features/techdocs/getting-started.md @@ -4,6 +4,13 @@ title: Getting Started description: Getting Started Documentation --- +::::info +This documentation is written for the new frontend system, which is the default +in new Backstage apps. If your Backstage app still uses the old frontend system, +read the [old frontend system version of this guide](./getting-started--old.md) +instead. +:::: + TechDocs functions as a plugin in Backstage and ships with it installed out of the box, so you will need to use Backstage to use TechDocs. If you haven't setup Backstage already, start [here](../../getting-started/index.md). @@ -11,88 +18,37 @@ If you haven't setup Backstage already, start [here](../../getting-started/index ## Adding TechDocs frontend plugin The first step is to add the TechDocs plugin to your Backstage application. -Navigate to your new Backstage application directory. And then to your -`packages/app` directory, and install the `@backstage/plugin-techdocs` package. ```bash title="From your Backstage root directory" yarn --cwd packages/app add @backstage/plugin-techdocs ``` -Once the package has been installed, you need to import the plugin in your app. +Once installed, the plugin is automatically available in your app through the default feature discovery. For more details and alternative installation methods, see [installing plugins](../../frontend-system/building-apps/05-installing-plugins.md). -In `packages/app/src/App.tsx`, import `TechDocsPage` and add the following to -`FlatRoutes`: +The plugin provides a docs index page at `/docs` and a reader page for individual documentation sites, along with a "Docs" navigation item in the sidebar and a documentation tab on entity pages. -```tsx title="packages/app/src/App.tsx" -import { - DefaultTechDocsHome, - TechDocsIndexPage, - TechDocsReaderPage, -} from '@backstage/plugin-techdocs'; +## Using TechDocs Addons -const AppRoutes = () => { - - {/* ... other plugin routes */} - }> - - - } - /> - ; -}; +The TechDocs Addon framework lets you render React components in documentation pages. Addons are provided as separate plugin packages that are automatically discovered when installed. + +For example, to add the Report Issue addon: + +```bash title="From your Backstage root directory" +yarn --cwd packages/app add @backstage/plugin-techdocs-module-addons-contrib ``` -It would be nice to decorate your pages with something else... Having a link that redirects you to a new issue page when you highlight text in your documentation would be really cool, right? Let's learn how to do this using the TechDocs Addon Framework! - -With the [TechDocs Addon framework](https://backstage.io/docs/features/techdocs/addons#installing-and-using-addons), you can render React components in documentation pages and these Addons can be provided by any Backstage plugin. The framework is exported by the [@backstage/plugin-techdocs-react](https://www.npmjs.com/package/@backstage/plugin-techdocs-react) package and there is a `` Addon in the [@backstage/plugin-techdocs-module-addons-contrib](https://www.npmjs.com/package/@backstage/plugin-techdocs-module-addons-contrib) package for you to use once you have these two dependencies installed: - -```tsx -import { - DefaultTechDocsHome, - TechDocsIndexPage, - TechDocsReaderPage, -} from '@backstage/plugin-techdocs'; -/* highlight-add-start */ -import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; -import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; -/* highlight-add-end */ - -const AppRoutes = () => { - - {/* ... other plugin routes */} - }> - - - } - > - {/* highlight-add-start */} - - - - {/* highlight-add-end */} - - ; -}; -``` - -I know, you're curious to see how it looks, aren't you? See the image below: +Once installed, the addon is automatically active. You can see it in action when you highlight text in your documentation: ![TechDocs Report Issue Add-on](../../assets/techdocs/report-issue-addon.png) -By clicking the open new issue button, you will be redirected to the new issue page according to the source code provider you are using: +By clicking the open new issue button, you are redirected to the new issue page according to the source code provider you are using: ![TechDocs Report Issue Template](../../assets/techdocs/report-issue-template.png) -That's it! Now, we need the TechDocs Backend plugin for the frontend to work. - ## Adding TechDocs Backend plugin First we need to install the `@backstage/plugin-techdocs-backend` package. @@ -115,13 +71,11 @@ backend.add(import('@backstage/plugin-techdocs-backend')); backend.start(); ``` -That's it! TechDocs frontend and backend have now been added to your Backstage -app. Now let us tweak some configurations to suit your needs. +That's it! TechDocs frontend and backend have now been added to your Backstage app. Now let us tweak some configurations to suit your needs. ## Setting the configuration -**See [TechDocs Configuration Options](configuration.md) for complete -configuration reference.** +**See [TechDocs Configuration Options](configuration.md) for complete configuration reference.** ### Should TechDocs Backend generate docs? @@ -130,28 +84,15 @@ techdocs: builder: 'local' ``` -Note that we recommend generating docs on CI/CD instead. Read more in the -"Basic" and "Recommended" sections of the -[TechDocs Architecture](architecture.md). But if you want to get started quickly -set `techdocs.builder` to `'local'` so that TechDocs Backend is responsible for -generating documentation sites. If set to `'external'`, Backstage will assume -that the sites are being generated on each entity's CI/CD pipeline, and are -being stored in a storage somewhere. +Note that we recommend generating docs on CI/CD instead. Read more in the "Basic" and "Recommended" sections of the [TechDocs Architecture](architecture.md). But if you want to get started quickly set `techdocs.builder` to `'local'` so that TechDocs Backend is responsible for generating documentation sites. If set to `'external'`, Backstage will assume that the sites are being generated on each entity's CI/CD pipeline, and are being stored in a storage somewhere. -When `techdocs.builder` is set to `'external'`, TechDocs becomes more or less a -read-only experience where it serves static files from a storage containing all -the generated documentation. +When `techdocs.builder` is set to `'external'`, TechDocs becomes more or less a read-only experience where it serves static files from a storage containing all the generated documentation. ### Choosing storage (publisher) -TechDocs needs to know where to store generated documentation sites and where to -fetch the sites from. This is managed by a -[Publisher](./concepts.md#techdocs-publisher). Examples: Google Cloud Storage, -Amazon S3, or local filesystem of Backstage server. +TechDocs needs to know where to store generated documentation sites and where to fetch the sites from. This is managed by a [Publisher](./concepts.md#techdocs-publisher). Examples: Google Cloud Storage, Amazon S3, or local filesystem of Backstage server. -It is okay to use the local filesystem in a "basic" setup when you are trying -out Backstage for the first time. At a later time, review -[Using Cloud Storage](./using-cloud-storage.md). +It is okay to use the local filesystem in a "basic" setup when you are trying out Backstage for the first time. At a later time, review [Using Cloud Storage](./using-cloud-storage.md). ```yaml techdocs: @@ -164,15 +105,9 @@ techdocs: You can skip this if your `techdocs.builder` is set to `'external'`. -The TechDocs Backend plugin runs a docker container with mkdocs installed to -generate the frontend of the docs from source files (Markdown). If you are -deploying Backstage using Docker, this will mean that your Backstage Docker -container will try to run another Docker container for TechDocs Backend. +The TechDocs Backend plugin runs a docker container with mkdocs installed to generate the frontend of the docs from source files (Markdown). If you are deploying Backstage using Docker, this will mean that your Backstage Docker container will try to run another Docker container for TechDocs Backend. -To avoid this problem, we have a configuration available. You can set a value in -your `app-config.yaml` that tells the techdocs generator if it should run the -`local` mkdocs or run it from `docker`. This defaults to running as `docker` if -no config is provided. +To avoid this problem, we have a configuration available. You can set a value in your `app-config.yaml` that tells the techdocs generator if it should run the `local` mkdocs or run it from `docker`. This defaults to running as `docker` if no config is provided. ```yaml techdocs: @@ -183,15 +118,11 @@ techdocs: runIn: local ``` -Setting `generator.runIn` to `local` means you will have to make sure your -environment is compatible with techdocs. +Setting `generator.runIn` to `local` means you will have to make sure your environment is compatible with techdocs. -You will have to install the `mkdocs` and `mkdocs-techdocs-core` package from -pip, optionally also `graphviz` and `plantuml` from your OS package manager (e.g. -apt). +You will have to install the `mkdocs` and `mkdocs-techdocs-core` package from pip, optionally also `graphviz` and `plantuml` from your OS package manager (e.g. apt). -You can do so by including the following lines right above `USER node` of your -`Dockerfile`: +You can do so by including the following lines right above `USER node` of your `Dockerfile`: ```Dockerfile RUN apt-get update && \ @@ -205,20 +136,11 @@ ENV PATH="$VIRTUAL_ENV/bin:$PATH" RUN pip3 install mkdocs-techdocs-core ``` -Please be aware that the version requirement could change, you need to check our -[`Dockerfile`](https://github.com/backstage/techdocs-container/blob/main/Dockerfile) -and make sure to match with it. +Please be aware that the version requirement could change, you need to check our [`Dockerfile`](https://github.com/backstage/techdocs-container/blob/main/Dockerfile) and make sure to match with it. -On a Debian-based Docker container, Python packages must be either installed using -the OS package manager or within a virtual environment (see the -[related PEP](https://peps.python.org/pep-0668/)). Alternative is to use e.g. -[pipx](https://pypa.github.io/pipx/) for installing Python packages in an isolated -environment. +On a Debian-based Docker container, Python packages must be either installed using the OS package manager or within a virtual environment (see the [related PEP](https://peps.python.org/pep-0668/)). Alternative is to use e.g. [pipx](https://pypa.github.io/pipx/) for installing Python packages in an isolated environment. -The above Dockerfile snippet installs the latest `mkdocs-techdoc-core` package. -Version numbers can be found in the corresponding -[changelog](https://github.com/backstage/mkdocs-techdocs-core#changelog). In -case you want to pin the version, use the example below: +The above Dockerfile snippet installs the latest `mkdocs-techdoc-core` package. Version numbers can be found in the corresponding [changelog](https://github.com/backstage/mkdocs-techdocs-core#changelog). In case you want to pin the version, use the example below: ```Dockerfile RUN pip3 install mkdocs-techdocs-core==1.2.3 @@ -226,9 +148,7 @@ RUN pip3 install mkdocs-techdocs-core==1.2.3 Note: We recommend Python version 3.11 or higher. -> Caveat: Please install the `mkdocs-techdocs-core` package after all other -> Python packages. The order is important to make sure we get correct version of -> some of the dependencies. +> Caveat: Please install the `mkdocs-techdocs-core` package after all other Python packages. The order is important to make sure we get correct version of some of the dependencies. ## Additional reading diff --git a/docs/features/techdocs/how-to-guides--old.md b/docs/features/techdocs/how-to-guides--old.md new file mode 100644 index 0000000000..19674bb808 --- /dev/null +++ b/docs/features/techdocs/how-to-guides--old.md @@ -0,0 +1,1022 @@ +--- +id: how-to-guides--old +title: TechDocs How-To guides (Old Frontend System) +sidebar_label: How-To guides +description: TechDocs How-To guides related to TechDocs +--- + +::::info +This documentation is for Backstage apps that still use the old frontend +system. If your app uses the new frontend system, read the +[current guide](./how-to-guides.md) instead. +:::: + +## How to migrate from TechDocs Basic to Recommended deployment approach? + +The main difference between TechDocs Basic and Recommended deployment approach +is where the docs are generated and stored. In Basic or the out-of-the-box +setup, docs are generated and stored at the server running your Backstage +instance. But the recommended setup is to generate docs on CI/CD and store the +generated sites to an external storage (e.g. AWS S3 or GCS). TechDocs in your +Backstage instance should turn into read-only mode. Read more details and the +benefits in the [TechDocs Architecture](architecture.md). + +Here are the steps needed to switch from the Basic to Recommended setup - + +### 1. Prepare a cloud storage + +Choose a cloud storage provider like AWS, Google Cloud or Microsoft Azure. +Follow the detailed instructions for +[using cloud storage](using-cloud-storage.md) in TechDocs. + +### 2. Publish to storage from CI/CD + +Start publishing your TechDocs sites from the CI/CD workflow of each repository +containing the source markdown files. Read the detailed instructions for +[configuring CI/CD](configuring-ci-cd.md). + +### 3. Switch TechDocs to read-only mode + +In your Backstage instance's `app-config.yaml`, set `techdocs.builder` from +`'local'` to `'external'`. By doing this, TechDocs will not try to generate +docs. Look at [TechDocs configuration](configuration.md) for reference. + +## How to understand techdocs-ref annotation values + +If TechDocs is configured to generate docs, it will first download source files +based on the value of the `backstage.io/techdocs-ref` annotation defined in the +Entity's `catalog-info.yaml` file. This is also called the +[Prepare](./concepts.md#techdocs-preparer) step. + +We strongly recommend that the `backstage.io/techdocs-ref` annotation in each +documented catalog entity's `catalog-info.yaml` be set to `dir:.` in almost all +situations. This is because TechDocs is aligned with the "docs like code" +philosophy, whereby documentation should be authored and managed alongside the +source code of the underlying software itself. + +When you see `dir:.`, you can translate it to mean: + +- That the documentation source code lives in the same location as the + `catalog-info.yaml` file. +- That, in particular, the `mkdocs.yml` file is a sibling of `catalog-info.yaml` + (meaning, it is in the same directory) +- And that all of the source content of the documentation would be available if + one were to download the directory containing those two files (as well as all + sub-directories). + +The directory tree of the entity would look something like this: + +``` +├── catalog-info.yaml +├── mkdocs.yml +└── docs + └── index.md +``` + +If, for example, you wanted to keep a lean root directory, you could place your +`mkdocs.yml` file in a subdirectory and update the `backstage.io/techdocs-ref` +annotation value accordingly, e.g. to `dir:./sub-folder`: + +``` +├── catalog-info.yaml +└── sub-folder + ├── mkdocs.yml + └── docs + └── index.md +``` + +In rare situations where your TechDocs source content is managed and stored in a +location completely separate from your `catalog-info.yaml`, you can instead +specify a URL location reference, the exact value of which will vary based on +the source code hosting provider. Notice that instead of the `dir:` prefix, the +`url:` prefix is used instead. For example: + +- **GitHub**: `url:https://githubhost.com/org/repo/tree/` +- **GitLab**: `url:https://gitlabhost.com/org/repo/tree/` +- **Bitbucket**: `url:https://bitbuckethost.com/project/repo/src/` +- **Azure**: `url:https://azurehost.com/organization/project/_git/repository` + +Note, just as it's possible to specify a subdirectory with the `dir:` prefix, +you can also provide a path to a non-root directory inside the repository which +contains the `mkdocs.yml` file and `docs/` directory. It is important that it is +suffixed with a '/' in order for relative path resolution to work consistently. + +e.g. +`url:https://github.com/backstage/backstage/tree/master/plugins/techdocs-backend/examples/documented-component/` + +### Why is URL Reader faster than a git clone? + +URL Reader uses the source code hosting provider to download a zip or tarball of +the repository. The archive does not have any git history attached to it. Also +it is a compressed file. Hence the file size is significantly smaller than how +much data git clone has to transfer. + +## How to customize the TechDocs home page? + +TechDocs uses a composability pattern similar to the Search and Catalog plugins +in Backstage. While a default table experience, similar to the one provided by +the Catalog plugin, is made available for ease-of-use, it's possible for you to +provide a completely custom experience, tailored to the needs of your +organization. For example, TechDocs comes with an alternative grid based layout +(``) and panel layout (`TechDocsCustomHome`). + +This is done in your `app` package. By default, you might see something like +this in your `App.tsx`: + +```tsx +const AppRoutes = () => { + + }> + + + ; +}; +``` + +### Using TechDocsCustomHome + +You can easily customize the TechDocs home page using TechDocs panel layout +(``). + +Modify your `App.tsx` as follows: + +```tsx +import { Fragment, PropsWithChildren } from 'react'; +import { TechDocsCustomHome } from '@backstage/plugin-techdocs'; +//... + +const options = { emptyRowsWhenPaging: false }; +const linkDestination = (entity: Entity): string | undefined => { + return entity.metadata.annotations?.['external-docs']; +}; +const techDocsTabsConfig = [ + { + label: 'Recommended Documentation', + panels: [ + { + title: 'Golden Path', + description: 'Documentation about standards to follow', + panelType: 'DocsCardGrid', + panelProps: { CustomHeader: () => }, + filterPredicate: entity => + entity?.metadata?.tags?.includes('golden-path') ?? false, + }, + { + title: 'Recommended', + description: 'Useful documentation', + panelType: 'InfoCardGrid', + panelProps: { + CustomHeader: () => + linkDestination: linkDestination, + }, + filterPredicate: entity => + entity?.metadata?.tags?.includes('recommended') ?? false, + }, + ], + }, + { + label: 'Browse All', + panels: [ + { + description: 'Browse all docs', + filterPredicate: filterEntity, + panelType: 'TechDocsIndexPage', + title: 'All', + panelProps: { PageWrapper: Fragment, CustomHeader: Fragment, options: options }, + }, + ], + }, +]; +const docsFilter = { + kind: ['Location', 'Resource', 'Component'], + 'metadata.annotations.featured-docs': CATALOG_FILTER_EXISTS, +} +const customPageWrapper = ({ children }: PropsWithChildren<{}>) => + ({children}) +const AppRoutes = () => { + + + } + /> + ; +}; +``` + +### Building a Custom home page + +But you can replace `` with any React component, which +will be rendered in its place. Most likely, you would want to create and +maintain such a component in a new directory at +`packages/app/src/components/techdocs`, and import and use it in `App.tsx`: + +For example, you can define the following Custom home page component: + +```tsx +import { ReactNode } from 'react'; + +import { Content } from '@backstage/core-components'; +import { + CatalogFilterLayout, + EntityOwnerPicker, + EntityTagPicker, + UserListPicker, + EntityListProvider, +} from '@backstage/plugin-catalog-react'; +import { + TechDocsPageWrapper, + TechDocsPicker, +} from '@backstage/plugin-techdocs'; +import { Entity } from '@backstage/catalog-model'; + +import { EntityListDocsGrid } from '@backstage/plugin-techdocs'; + +export type CustomTechDocsHomeProps = { + groups?: Array<{ + title: ReactNode; + filterPredicate: ((entity: Entity) => boolean) | string; + }>; +}; + +export const CustomTechDocsHome = ({ groups }: CustomTechDocsHomeProps) => { + return ( + + + + + + + + + + + + + + + + + + ); +}; +``` + +Then you can add the following to your `App.tsx`: + +```tsx +import { CustomTechDocsHome } from './components/techdocs/CustomTechDocsHome'; +// ... +const AppRoutes = () => { + + }> + + entity?.metadata?.tags?.includes('recommended') ?? false, + }, + { + title: 'My Docs', + filterPredicate: 'ownedByUser', + }, + ]} + /> + + ; +}; +``` + +## How to customize the TechDocs reader page? + +Similar to how it is possible to customize the TechDocs Home, it is also +possible to customize the TechDocs Reader Page. It is done in your `app` +package. By default, you might see something like this in your `App.tsx`: + +```tsx +const AppRoutes = () => { + }> + {techDocsPage} + ; +}; +``` + +The `techDocsPage` is a default techdocs reader page which lives in +`packages/app/src/components/techdocs`. It includes the following without you +having to set anything up. + +```tsx + + + + + +``` + +If you would like to compose your own `techDocsPage`, you can do so by replacing +the children of TechDocsPage with something else. Maybe you are _just_ +interested in replacing the Header: + +```tsx + +
+ + +``` + +Or maybe you want to disable the in-context search + +```tsx + +
+ + +``` + +Or maybe you want to replace the entire TechDocs Page. + +```tsx + +
+ +

my own content

+
+ +``` + +## How to migrate from TechDocs Alpha to Beta + +> This guide only applies to the "recommended" TechDocs deployment method (where +> an external storage provider and external CI/CD is used). If you use the +> "basic" or "out-of-the-box" setup, you can stop here! No action needed. + +For the purposes of this guide, TechDocs Beta version is defined as: + +- **TechDocs Plugin**: At least `v0.11.0` +- **TechDocs Backend Plugin**: At least `v0.10.0` +- **TechDocs CLI**: At least `v0.7.0` + +The beta version of TechDocs made a breaking change to the way TechDocs content +was accessed and stored, allowing pages to be accessed with case-insensitive +entity triplet paths (e.g. `/docs/namespace/kind/name` whereas in prior +versions, they could only be accessed at `/docs/namespace/Kind/name`). In order +to enable this change, documentation has to be stored in an external storage +provider using an object key whose entity triplet is lower-cased. + +New installations of TechDocs since the beta version will work fine with no +action, but for those who were running TechDocs prior to this version, a +migration will need to be performed so that all existing content in your storage +bucket matches this lower-case entity triplet expectation. + +1. **Ensure you have the right permissions on your storage provider**: In order + to migrate files in your storage provider, the `techdocs-cli` needs to be + able to read/copy/rename/move/delete files. The exact instructions vary by + storage provider, but check the [using cloud storage][using-cloud-storage] + page for details. + +2. **Run a non-destructive migration of files**: Ensure you have the latest + version of `techdocs-cli` installed. Then run the following command, using + the details relevant for your provider / configuration. This will copy all + files from, e.g. `namespace/Kind/name/index.html` to + `namespace/kind/name/index.html`, without removing the original files. + +```sh +techdocs-cli migrate --publisher-type --storage-name --verbose +``` + +3. **Deploy the updated versions of the TechDocs plugins**: Once the migration + above has been run, you can deploy the beta versions of the TechDocs backend + and frontend plugins to your Backstage instance. + +4. **Verify that your TechDocs sites are still loading/accessible**: Try + accessing a TechDocs site using different entity-triplet case variants, e.g. + `/docs/namespace/KIND/name` or `/docs/namespace/kind/name`. Your TechDocs + site should load regardless of the URL path casing you use. + +5. **Clean up the old objects from storage**: Once you've verified that your + TechDocs site is accessible, you can clean up your storage bucket by + re-running the `migrate` command on the TechDocs CLI, but with an additional + `removeOriginal` flag passed: + +```sh +techdocs-cli migrate --publisher-type --storage-name --removeOriginal --verbose +``` + +6. **Update your CI/CD pipelines to use the beta version of the TechDocs CLI**: + Finally, you can update all of your CI/CD pipelines to use at least v0.x.y of + the TechDocs CLI, ensuring that all sites are published to the new, + lower-cased entity triplet paths going forward. + +If you encounter problems running this migration, please [report the +issue][beta-migrate-bug]. You can temporarily revert to pre-beta storage +expectations with a configuration change: + +```yaml +techdocs: + legacyUseCaseSensitiveTripletPaths: true +``` + +[beta-migrate-bug]: +https://github.com/backstage/backstage/issues/new?assignees=&labels=bug&template=bug_template.md&title=[TechDocs]%20Unable%20to%20run%20beta%20migration +[using-cloud-storage]: ./using-cloud-storage.md + +## How to implement your own TechDocs APIs + +The TechDocs plugin provides implementations of two primary APIs by default: the +[TechDocsStorageApi](https://github.com/backstage/backstage/blob/55114cfeb7045e3e5eeeaf67546b58964f4adcc7/plugins/techdocs/src/api.ts#L33), +which is responsible for talking to TechDocs storage to fetch files to render, +and +[TechDocsApi](https://github.com/backstage/backstage/blob/55114cfeb7045e3e5eeeaf67546b58964f4adcc7/plugins/techdocs/src/api.ts#L49), +which is responsible for talking to techdocs-backend. + +There may be occasions where you need to implement these two APIs yourself, to +customize them to your own needs. The purpose of this guide is to walk you +through how to do that in two steps. + +1. Implement the `TechDocsStorageApi` and `TechDocsApi` interfaces according to + your needs. + +```typescript +export class TechDocsCustomStorageApi implements TechDocsStorageApi { + // your implementation +} + +export class TechDocsCustomApiClient implements TechDocsApi { + // your implementation +} +``` + +2. Override the API refs `techdocsStorageApiRef` and `techdocsApiRef` with your + new implemented APIs in the `App.tsx` using `ApiFactories`. + [Read more about App APIs](https://backstage.io/docs/api/utility-apis#app-apis). + +```typescript +const app = createApp({ + apis: [ + // TechDocsStorageApi + createApiFactory({ + api: techdocsStorageApiRef, + deps: { discoveryApi: discoveryApiRef, configApi: configApiRef }, + factory({ discoveryApi, configApi }) { + return new TechDocsCustomStorageApi({ discoveryApi, configApi }); + }, + }), + // TechDocsApi + createApiFactory({ + api: techdocsApiRef, + deps: { discoveryApi: discoveryApiRef }, + factory({ discoveryApi }) { + return new TechDocsCustomApiClient({ discoveryApi }); + }, + }), + ], +}); +``` + +## How to add the documentation setup to your software templates + +[Software Templates](https://backstage.io/docs/features/software-templates/) +in Backstage is a tool that can help your users to create new components out of +already configured templates. It comes with a set of default templates to use, +but you can also +[add your own templates](https://backstage.io/docs/features/software-templates/adding-templates). + +If you have your own templates set up, we highly recommend that you include the +required setup for TechDocs in those templates. When creating a new component, +your users will then get a TechDocs site up and running automatically, ready for +them to start writing technical documentation. + +The purpose of this how-to guide is to walk you through how to add the required +configuration and some default markdown files to your new template. You can use +the +[react-ssr-template](https://github.com/backstage/software-templates/tree/main/scaffolder-templates/react-ssr-template) +as a reference when walking through the steps. + +Prerequisites: + +- An existing software template including a `template.yaml` together with a + skeleton folder including at least a `catalog-info.yaml`. + +1. Update your component's entity description by adding the following lines to + the `catalog-info.yaml` in your skeleton folder. + +```yaml +annotations: + backstage.io/techdocs-ref: dir:. +``` + +The +[`backstage.io/techdocs-ref` annotation](../software-catalog/well-known-annotations.md#backstageiotechdocs-ref) +is used by TechDocs to download the documentation source files for generating an +entity's TechDocs site. + +2. Create an `mkdocs.yml` file in the root of your skeleton folder with the + following content: + +```yaml +site_name: ${{values.component_id}} +site_description: ${{values.description}} + +nav: + - Introduction: index.md + +plugins: + - techdocs-core +``` + +3. Create a `/docs` folder in the skeleton folder with at least an `index.md` + file in it. + +The `docs/index.md` can for example have the following content: + +```markdown +# ${{ values.component_id }} + +${{ values.description }} + +## Getting started + +Start writing your documentation by adding more markdown (.md) files to this +folder (/docs) or replace the content in this file. +``` + +:::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! + +### Prevent download of Google fonts + +If your Backstage instance does not have internet access, the generation will fail. TechDocs tries to download the Roboto font from Google. You can disable it by adding the following lines to mkdocs.yaml: + +```yaml +theme: + name: material + font: false +``` + +:::note Note + +The addition `name: material` is necessary. Otherwise it will not work + +::: + +## How to enable iframes in TechDocs + +TechDocs uses the [DOMPurify](https://github.com/cure53/DOMPurify) library to +sanitize HTML and prevent XSS attacks. + +It's possible to allow some iframes based on a list of allowed hosts. To do +this, add the allowed hosts in the `techdocs.sanitizer.allowedIframeHosts` +configuration of your `app-config.yaml`. + +For example: + +```yaml +techdocs: + sanitizer: + allowedIframeHosts: + - drive.google.com +``` + +This way, all iframes where the host in the src attribute is in the +`sanitizer.allowedIframeHosts` list will be displayed. + +## How to enable custom elements in TechDocs + +TechDocs uses the [DOMPurify](https://github.com/cure53/DOMPurify) library to +sanitize HTML and prevent XSS attacks. + +It's possible to allow custom elements based on a list of allowed patterns. To do +this, add the allowed elements and attributes in the `techdocs.sanitizer.allowedCustomElementTagNameRegExp` +and `allowedCustomElementAttributeNameRegExp` configuration of your `app-config.yaml`. + +For example: + +```yaml +techdocs: + sanitizer: + allowedCustomElementTagNameRegExp: '^backstage-', + allowedCustomElementAttributeNameRegExp: 'attribute1|attribute2', +``` + +This way, custom element like `` will be allowed in the result HTML. + +## How to allow additional URI protocols in TechDocs + +TechDocs uses the [DOMPurify](https://github.com/cure53/DOMPurify) library to +sanitize HTML and prevent XSS attacks. + +It's possible to allow additional URI protocols based on a list of protocols. To do +this, add the allowed protocols in the `techdocs.sanitizer.additionalAllowedURIProtocols` +and `additionalAllowedURIProtocols` configuration of your `app-config.yaml`. + +For example: + +```yaml +techdocs: + sanitizer: + additionalAllowedURIProtocols: ["vscode"], +``` + +This way, links like `VSCode Settings` will be allowed in the result HTML + +## How to render PlantUML diagram in TechDocs + +PlantUML allows you to create diagrams from plain text language. Each diagram description begins with the keyword - (@startXYZ and @endXYZ, depending on the kind of diagram). For UML Diagrams, Keywords @startuml & @enduml should be used. Further details for all types of diagrams can be found at [PlantUML Language Reference Guide](https://plantuml.com/guide). + +### UML Diagram Details:- + +#### Embedded PlantUML Diagram Example + +Here, the markdown file itself contains the diagram description. + +````md +```plantuml +@startuml +title Login Sequence + ComponentA->ComponentB: Login Request + note right of ComponentB: ComponentB logs message + ComponentB->ComponentA: Login Response +@enduml +``` +```` + +#### Referenced PlantUML Diagram Example + +Here, the markdown file refers to another file (`*.puml` or `*.pu`) which contains the diagram description. + +````md +```plantuml +!include umldiagram.puml +``` +```` + +Note: To refer external diagram files, we need to include the diagrams directory in the path. Please refer [`Dockerfile`](https://github.com/backstage/techdocs-container/blob/main/Dockerfile) for details. + +## How to add Mermaid support in TechDocs + +There are a few options for adding Mermaid support in TechDocs: using [Kroki](https://kroki.io) or [markdown-inline-mermaid](https://github.com/johanneswuerbach/markdown-inline-mermaid) to generate the diagrams at build time, or the [`backstage-plugin-techdocs-addon-mermaid`](https://github.com/johanneswuerbach/backstage-plugin-techdocs-addon-mermaid) plugin to generate the diagram in the browser. We currently use `backstage-plugin-techdocs-addon-mermaid` plugin for the [Mermaid example on the Demo site](https://demo.backstage.io/docs/default/component/backstage-demo/examples/mermaid/). + +### Using Kroki + +To add `Mermaid` support in TechDocs, you can use [`kroki`](https://kroki.io) +that creates diagrams from Textual descriptions. It is a single rendering +gateway for all popular diagrams-as-a-code tools. It supports an enormous number +of diagram types. + +1. **Create and Publish Docker image:** Create the Docker image from the + following `Dockerfile` and publish it to DockerHub. + +```docker +FROM python:3.10-alpine + +RUN apk update && apk --no-cache add gcc musl-dev openjdk11-jdk curl graphviz ttf-dejavu fontconfig + +RUN pip install --upgrade pip && pip install mkdocs-techdocs-core==1.2.0 + +RUN pip install mkdocs-kroki-plugin + +ENTRYPOINT [ "mkdocs" ] +``` + +Create a repository in your DockerHub and run the below command in the same +folder where your `Dockerfile` is present: + +```shell +docker build . -t dockerHub_Username/repositoryName:tagName +``` + +Once the docker image is ready, push it to DockerHub. + +2. **Update app-config.yaml:** So that when your app generates TechDocs, it will + pull your docker image from DockerHub. + +```python +techdocs: + builder: 'local' # Alternatives - 'external' + generator: + runIn: 'docker' # Alternatives - 'local' + dockerImage: dockerHub_Username/repositoryName:tagName + pullImage: true + publisher: + type: 'local' # Alternatives - 'googleGcs' or 'awsS3'. Read documentation for using alternatives. +``` + +3. **Add the `kroki` plugin in `mkdocs.yml`:** + +```yml +plugins: + - techdocs-core + - kroki +``` + +:::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:** + +````md +```kroki-mermaid +sequenceDiagram +GitLab->>Kroki: Request rendering +Kroki->>Mermaid: Request rendering +Mermaid-->>Kroki: Image +Kroki-->>GitLab: Image +``` +```` + +Done! Now you have a support of the following diagrams along with mermaid: + +- `PlantUML` +- `BlockDiag` +- `BPMN` +- `ByteField` +- `SeqDiag` +- `ActDiag` +- `NwDiag` +- `PacketDiag` +- `RackDiag` +- `C4 with PlantUML` +- `Ditaa` +- `Erd` +- `Excalidraw` +- `GraphViz` +- `Nomnoml` +- `Pikchr` +- `Svgbob` +- `UMlet` +- `Vega` +- `Vega-Lite` +- `WaveDrom` + +### Using `markdown-inline-mermaid` + +To use `markdown-inline-mermaid` to generate your Mermaid diagrams in TechDocs you'll need to do the following: + +1. In your Dockerfile you will need to make sure you install `markdown-inline-mermaid` and its dependencies, you will also need to install the `@mermaid-js/mermaid-cli`: + + ```dockerfile title="Dockerfile" + RUN apt-get install -y chromium + RUN pip3 install mkdocs-techdocs-core markdown-inline-mermaid + RUN npm install -g @mermaid-js/mermaid-cli + ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium + ``` + +2. Now in your `mkdocs.yml` file you will need to add the following section (this is at the root level like `plugins` which you should already have): + + ```yaml title="mkdocs.yml" + markdown_extensions: + - markdown_inline_mermaid + ``` + +3. With this in place you can now add Mermaid diagrams in your Markdown files like this: + + ````md + ```mermaid + sequenceDiagram + Alice->>John: Hello John, how are you? + John-->>Alice: Great! + Alice-)John: See you later! + ``` + ```` + +### Using the `backstage-plugin-techdocs-addon-mermaid` plugin + +Please follow the [Getting Started](https://github.com/johanneswuerbach/backstage-plugin-techdocs-addon-mermaid?tab=readme-ov-file#getting-started) instructions in the plugin's README. + +## How to implement a hybrid build strategy + +One limitation of the [Recommended deployment](./architecture.md#recommended-deployment) is that +the experience for users requires modifying their CI/CD process to publish +their TechDocs. For some users, this may be unnecessary, and provides a barrier +to entry for onboarding users to Backstage. However, a purely local TechDocs +build restricts TechDocs creators to using the tooling provided in Backstage, +as well as the plugins and features provided in the Backstage-included `mkdocs` +installation. + +To accommodate both of these use-cases, users can implement a custom [Build Strategy](./concepts.md#techdocs-build-strategy) +with logic to encode which TechDocs should be built locally, and which will be +built externally. + +To achieve this hybrid build model: + +1. In your Backstage instance's `app-config.yaml`, set `techdocs.builder` to + `'local'`. This ensures that Backstage will build docs for users who want the + 'out-of-the-box' experience. +2. Configure external storage of TechDocs as normal for a production deployment. + This allows Backstage to publish documentation to your storage, as well as + allowing other users to publish documentation from their CI/CD pipelines. +3. Create a custom build strategy, that implements the `DocsBuildStrategy` interface, + and which implements your custom logic for determining whether to build docs for + a given entity. + For example, to only build docs when an entity has the `company.com/techdocs-builder` + annotation set to `'local'`: + + ```typescript + export class AnnotationBasedBuildStrategy { + private readonly config: Config; + + constructor(config: Config) { + this.config = config; + } + + async shouldBuild(_: Entity): Promise { + return ( + this.entity.metadata?.annotations?.['company.com/techdocs-builder'] === + 'local' + ); + } + } + ``` + +4. Pass an instance of this Build Strategy as the `docsBuildStrategy` parameter of the + TechDocs backend `createRouter` method. + +Users should now be able to choose to have their documentation built and published by +the TechDocs backend by adding the `company.com/techdocs-builder` annotation to their +entity. If the value of this annotation is `'local'`, the TechDocs backend will build +and publish the documentation for them. If the value of the `company.com/techdocs-builder` +annotation is anything other than `'local'`, the user is responsible for publishing +documentation to the appropriate location in the TechDocs external storage. + +### Hybrid build strategy using the Backend System + +To setup a hybrid build strategy using the Backend System you'll follow the same steps as above but for Step 4 you will need to do the following: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { + DocsBuildStrategy, + techdocsBuildsExtensionPoint, +} from '@backstage/plugin-techdocs-node'; + +const techdocsCustomBuildStrategy = createBackendModule({ + pluginId: 'techdocs', + moduleId: 'customBuildStrategy', + register(env) { + env.registerInit({ + deps: { + techdocs: techdocsBuildsExtensionPoint, + }, + async init({ techdocs }) { + const docsBuildStrategy: DocsBuildStrategy = { + shouldBuild: async params => + params.entity.metadata?.annotations?.[ + 'demo.backstage.io/techdocs-builder' + ] === 'local', + }; + + techdocs.setBuildStrategy(docsBuildStrategy); + }, + }); + }, +}); + +// Other plugins... + +/* highlight-add-start */ +backend.add(import('@backstage/plugin-techdocs-backend')); +backend.add(techdocsCustomBuildStrategy); +/* highlight-add-end */ + +backend.start(); +``` + +:::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? + +The default plugin [mkdocs-techdocs-core](https://github.com/backstage/mkdocs-techdocs-core) provides a set of plugins that can be viewed as the minimum required plugins to enable TechDocs. Your organization might have needs beyond the core set though, here is the recommended way to enable other plugins. + +### Install the plugin + +#### With CI generation + +If you generate the HTML files in CI using `@techdocs/cli`, you need to install the desired mkdocs plugin in the runtime where the cli is being executed. This might be e.g. a docker image or a Jenkins node. Use the `--no-docker` flag with the cli to pick up the plugin you just installed. + +#### With local generation + +Create a new Docker image that extends [spotify/techdocs](https://github.com/backstage/techdocs-container), roughly: + +```Dockerfile +FROM spotify/techdocs: + +pip install +... +``` + +Then publish the image and use it in your config under the `techdocs.generator.dockerImage` [key](https://github.com/backstage/techdocs-container). + +### Specify the plugin in the mkdocs config + +To use the plugin, it has to be listed in the `mkdocs.yaml` file. You can either add the plugin to your applicable files, or specify defaults. + +To make a mkdocs plugin available for all your TechDocs components you can either list it in the `techdocs.generator.mkdocs.defaultPlugins` [config](https://github.com/backstage/backstage/blob/master/plugins/techdocs-backend/config.d.ts#L64C14-L64C14), or use the `--defaultPlugin` [cli option](https://backstage.io/docs/features/techdocs/cli#generate-techdocs-site-from-a-documentation-project) depending on your setup. + +## Reference another components TechDocs + +In systems where you might have multiple entities for example a System with a Website and an API, when served from a Monorepo you might want to keep the TechDocs in one location in the repository. + +In this case you can add the `backstage.io/techdocs-entity` annotation and point to the owners `entityRef` and use its TechDocs. This allows the Subcomponents to read the parents docs, filling the TechDocs link on the `AboutCard` element and the Techdocs tab + +```yaml +apiVersion: backstage.io/v1alpha1 +kind: System +metadata: + name: example + namespace: default + title: Example + description: This is the parent entity + annotations: + backstage.io/techdocs-ref: dir:. + +--- +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: example-platform + title: Example Application Platform + namespace: default + description: This is the child entity + annotations: + backstage.io/techdocs-entity: system:default/example +``` + +### Deep linking into TechDocs + +The `backstage.io/techdocs-entity-path` annotation can be use to deep link into a specific page within the components TechDocs. +This can be used in conjunction with `backstage.io/techdocs-entity` or standalone. + +```yaml +apiVersion: backstage.io/v1alpha1 +kind: System +metadata: + name: example + namespace: default + title: Example + description: This is the parent entity + annotations: + backstage.io/techdocs-ref: dir:. + +--- +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: example-platfrom + title: Example Application Platform + namespace: default + description: This is the child entity + annotations: + backstage.io/techdocs-entity: system:default/example + backstage.io/techdocs-entity-path: /path/to/component/docs +``` + +## How to resolve broken links from moved or renamed pages in your documentation site + +TechDocs supports using the [mkdocs-redirects](https://github.com/mkdocs/mkdocs-redirects/tree/master) plugin to create a redirect map for any TechDocs site. This allows broken links from renamed or moved pages in your site to be redirected to their specified replacement. +TechDocs will notify the user that the page they are trying to access is no longer maintained. Then, they will be redirected. External site redirects are not supported. If an external redirect is provided, the user will instead be redirected to the index page of the documentation site. + +## Create download links for static assets + +You may want to make files available for download by your users such as PDF +documents, images, or code templates. Download links for files included in your +docs directory can be made by adding `{: download }` after a markdown link. + +``` +[Link text](https://example.com/foo.jpg){: download } +``` + +The user's browser will download the file as `download.jpg` when the link is +clicked. + +Specify a file name to control the name the file will be given when it is +downloaded: + +``` +[Link text](https://example.com/foo.jpg){: download="foo.jpg" } +``` diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 33faf44841..2c42910e47 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -5,6 +5,13 @@ sidebar_label: How-To guides description: TechDocs How-To guides related to TechDocs --- +::::info +This documentation is written for the new frontend system, which is the default +in new Backstage apps. If your Backstage app still uses the old frontend system, +read the [old frontend system version of this guide](./how-to-guides--old.md) +instead. +:::: + ## How to migrate from TechDocs Basic to Recommended deployment approach? The main difference between TechDocs Basic and Recommended deployment approach @@ -108,241 +115,37 @@ much data git clone has to transfer. ## How to customize the TechDocs home page? TechDocs uses a composability pattern similar to the Search and Catalog plugins -in Backstage. While a default table experience, similar to the one provided by -the Catalog plugin, is made available for ease-of-use, it's possible for you to -provide a completely custom experience, tailored to the needs of your -organization. For example, TechDocs comes with an alternative grid based layout -(``) and panel layout (`TechDocsCustomHome`). +in Backstage. The default TechDocs home page provides a table experience +similar to the one provided by the Catalog plugin. TechDocs also comes with +an alternative grid based layout and panel layout. -This is done in your `app` package. By default, you might see something like -this in your `App.tsx`: - -```tsx -const AppRoutes = () => { - - }> - - - ; -}; -``` - -### Using TechDocsCustomHome - -You can easily customize the TechDocs home page using TechDocs panel layout -(``). - -Modify your `App.tsx` as follows: - -```tsx -import { Fragment, PropsWithChildren } from 'react'; -import { TechDocsCustomHome } from '@backstage/plugin-techdocs'; -//... - -const options = { emptyRowsWhenPaging: false }; -const linkDestination = (entity: Entity): string | undefined => { - return entity.metadata.annotations?.['external-docs']; -}; -const techDocsTabsConfig = [ - { - label: 'Recommended Documentation', - panels: [ - { - title: 'Golden Path', - description: 'Documentation about standards to follow', - panelType: 'DocsCardGrid', - panelProps: { CustomHeader: () => }, - filterPredicate: entity => - entity?.metadata?.tags?.includes('golden-path') ?? false, - }, - { - title: 'Recommended', - description: 'Useful documentation', - panelType: 'InfoCardGrid', - panelProps: { - CustomHeader: () => - linkDestination: linkDestination, - }, - filterPredicate: entity => - entity?.metadata?.tags?.includes('recommended') ?? false, - }, - ], - }, - { - label: 'Browse All', - panels: [ - { - description: 'Browse all docs', - filterPredicate: filterEntity, - panelType: 'TechDocsIndexPage', - title: 'All', - panelProps: { PageWrapper: Fragment, CustomHeader: Fragment, options: options }, - }, - ], - }, -]; -const docsFilter = { - kind: ['Location', 'Resource', 'Component'], - 'metadata.annotations.featured-docs': CATALOG_FILTER_EXISTS, -} -const customPageWrapper = ({ children }: PropsWithChildren<{}>) => - ({children}) -const AppRoutes = () => { - - - } - /> - ; -}; -``` - -### Building a Custom home page - -But you can replace `` with any React component, which -will be rendered in its place. Most likely, you would want to create and -maintain such a component in a new directory at -`packages/app/src/components/techdocs`, and import and use it in `App.tsx`: - -For example, you can define the following Custom home page component: - -```tsx -import { ReactNode } from 'react'; - -import { Content } from '@backstage/core-components'; -import { - CatalogFilterLayout, - EntityOwnerPicker, - EntityTagPicker, - UserListPicker, - EntityListProvider, -} from '@backstage/plugin-catalog-react'; -import { - TechDocsPageWrapper, - TechDocsPicker, -} from '@backstage/plugin-techdocs'; -import { Entity } from '@backstage/catalog-model'; - -import { EntityListDocsGrid } from '@backstage/plugin-techdocs'; - -export type CustomTechDocsHomeProps = { - groups?: Array<{ - title: ReactNode; - filterPredicate: ((entity: Entity) => boolean) | string; - }>; -}; - -export const CustomTechDocsHome = ({ groups }: CustomTechDocsHomeProps) => { - return ( - - - - - - - - - - - - - - - - - - ); -}; -``` - -Then you can add the following to your `App.tsx`: - -```tsx -import { CustomTechDocsHome } from './components/techdocs/CustomTechDocsHome'; -// ... -const AppRoutes = () => { - - }> - - entity?.metadata?.tags?.includes('recommended') ?? false, - }, - { - title: 'My Docs', - filterPredicate: 'ownedByUser', - }, - ]} - /> - - ; -}; -``` +Customization of the TechDocs home page in the new frontend system is done by +overriding the default page extension. For details on how to override +extensions, see the [extension overrides](../../frontend-system/architecture/25-extension-overrides.md) documentation. ## How to customize the TechDocs reader page? -Similar to how it is possible to customize the TechDocs Home, it is also -possible to customize the TechDocs Reader Page. It is done in your `app` -package. By default, you might see something like this in your `App.tsx`: +The TechDocs reader page can be configured through `app-config.yaml`. For +example, you can disable the in-context search or the header: -```tsx -const AppRoutes = () => { - }> - {techDocsPage} - ; -}; +```yaml title="app-config.yaml" +app: + extensions: + - page:techdocs/reader: + config: + withoutSearch: true ``` -The `techDocsPage` is a default techdocs reader page which lives in -`packages/app/src/components/techdocs`. It includes the following without you -having to set anything up. - -```tsx - - - - - +```yaml title="app-config.yaml" +app: + extensions: + - page:techdocs/reader: + config: + withoutHeader: true ``` -If you would like to compose your own `techDocsPage`, you can do so by replacing -the children of TechDocsPage with something else. Maybe you are _just_ -interested in replacing the Header: - -```tsx - -
- - -``` - -Or maybe you want to disable the in-context search - -```tsx - -
- - -``` - -Or maybe you want to replace the entire TechDocs Page. - -```tsx - -
- -

my own content

-
- -``` +For more advanced customization of the reader page, you can override the page +extension. See the [extension overrides](../../frontend-system/architecture/25-extension-overrides.md) documentation for details. ## How to migrate from TechDocs Alpha to Beta @@ -446,32 +249,9 @@ export class TechDocsCustomApiClient implements TechDocsApi { } ``` -2. Override the API refs `techdocsStorageApiRef` and `techdocsApiRef` with your - new implemented APIs in the `App.tsx` using `ApiFactories`. - [Read more about App APIs](https://backstage.io/docs/api/utility-apis#app-apis). - -```typescript -const app = createApp({ - apis: [ - // TechDocsStorageApi - createApiFactory({ - api: techdocsStorageApiRef, - deps: { discoveryApi: discoveryApiRef, configApi: configApiRef }, - factory({ discoveryApi, configApi }) { - return new TechDocsCustomStorageApi({ discoveryApi, configApi }); - }, - }), - // TechDocsApi - createApiFactory({ - api: techdocsApiRef, - deps: { discoveryApi: discoveryApiRef }, - factory({ discoveryApi }) { - return new TechDocsCustomApiClient({ discoveryApi }); - }, - }), - ], -}); -``` +2. Override the default API extensions by creating custom API extensions using + `createApiExtension` from `@backstage/frontend-plugin-api`, and install them + in your app. See the [Utility APIs](../../frontend-system/utility-apis/01-index.md) documentation for details on how to create and install custom API extensions. ## How to add the documentation setup to your software templates diff --git a/docs/getting-started/filter-catalog.md b/docs/getting-started/filter-catalog.md index 3e0de8cdeb..2488b4caab 100644 --- a/docs/getting-started/filter-catalog.md +++ b/docs/getting-started/filter-catalog.md @@ -8,7 +8,7 @@ Audience: All ## Overview -The Catalog can be filtered by any combination of owner, kind, type, lifecycle, processing status, namespace, and name. [Customize Filters](../features/software-catalog/catalog-customization.md#customize-filters) provides information on how to modify the available filter criteria. +The Catalog can be filtered by any combination of owner, kind, type, lifecycle, processing status, namespace, and name. [Customize Filters](../features/software-catalog/catalog-customization--old.md#customize-filters) provides information on how to modify the available filter criteria. ![Catalog filter options](../assets/uiguide/catalog-filter-options.png) diff --git a/docs/getting-started/viewing-catalog.md b/docs/getting-started/viewing-catalog.md index acfa470cc2..3307a863da 100644 --- a/docs/getting-started/viewing-catalog.md +++ b/docs/getting-started/viewing-catalog.md @@ -31,7 +31,7 @@ Initially, the Catalog displays registered entities matching the following filte - `Processing Status` - normal - `Namespace` - The ID of a [namespace](../features/software-catalog/descriptor-format.md#namespace-optional) to which the entity belongs -You can change the initial setting for the [Owner](../features/software-catalog/catalog-customization.md#initially-selected-filter) and [Kind](../features/software-catalog/catalog-customization.md#initially-selected-kind) filters. +You can change the initial setting for the [Owner](../features/software-catalog/catalog-customization--old.md#initially-selected-filter) and [Kind](../features/software-catalog/catalog-customization--old.md#initially-selected-kind) filters. ## Informational columns for each entity @@ -55,7 +55,7 @@ For each kind of entity, a set of columns display information regarding the enti - `Tags` - an optional field that can be used for searching - `Actions` - see [Catalog Actions](#catalog-actions) -You can modify the columns associated with each kind of entity, following the instructions in [Customize Columns](../features/software-catalog/catalog-customization.md#customize-columns). +You can modify the columns associated with each kind of entity, following the instructions in [Customize Columns](../features/software-catalog/catalog-customization--old.md#customize-columns). ## Catalog Actions @@ -69,7 +69,7 @@ From left to right, the actions are: - Edit - Edit the `catalog-info.yaml` file that defines the entity. See [Updating a Component](../getting-started/update-a-component.md) - Star - Designate the entity as a favorite. You can [filter](../getting-started/filter-catalog.md) the catalog for starred entities. -[Customize Actions](../features/software-catalog/catalog-customization.md#customize-actions) describes how you can modify the actions that are displayed. +[Customize Actions](../features/software-catalog/catalog-customization--old.md#customize-actions) describes how you can modify the actions that are displayed. ## Viewing entity details From 93cb19813fb3e5a3a7c8f0988ee4445958276dc4 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 29 Mar 2026 22:40:19 +0200 Subject: [PATCH 052/191] fix: correct broken link to extension overrides documentation Signed-off-by: Patrik Oldsberg Made-with: Cursor --- docs/features/software-catalog/catalog-customization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-catalog/catalog-customization.md b/docs/features/software-catalog/catalog-customization.md index 17a2cd18c9..f9ae7c3a4e 100644 --- a/docs/features/software-catalog/catalog-customization.md +++ b/docs/features/software-catalog/catalog-customization.md @@ -61,7 +61,7 @@ app: ### Custom filters -You can create custom catalog filters using the `CatalogFilterBlueprint` from `@backstage/plugin-catalog-react/alpha`. See the [extension overrides](../../frontend-system/building-apps/03-extension-overrides.md) documentation for details on how to install custom extensions. +You can create custom catalog filters using the `CatalogFilterBlueprint` from `@backstage/plugin-catalog-react/alpha`. See the [extension overrides](../../frontend-system/architecture/25-extension-overrides.md) documentation for details on how to install custom extensions. ## Entity page From c9132c223cc3e5ea157928c2f05ca18222280c2f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 29 Mar 2026 23:04:08 +0200 Subject: [PATCH 053/191] docs: improve new frontend system docs for techdocs, scaffolder, and catalog Expand the TechDocs home page customization section with concrete examples showing how to override the page:techdocs extension using a frontend module. Replace the incorrect scaffolder custom step layouts guide with a note that this feature is not yet supported in the new frontend system. Restore missing catalog customization docs for columns, actions, table options, removing filters, and fully custom catalog pages to ensure parity with the old frontend system guide. Signed-off-by: Patrik Oldsberg Made-with: Cursor --- .../software-catalog/catalog-customization.md | 144 +++++++++++++++++- .../writing-custom-step-layouts.md | 65 +------- docs/features/techdocs/how-to-guides.md | 48 +++++- 3 files changed, 197 insertions(+), 60 deletions(-) diff --git a/docs/features/software-catalog/catalog-customization.md b/docs/features/software-catalog/catalog-customization.md index f9ae7c3a4e..e148c6da08 100644 --- a/docs/features/software-catalog/catalog-customization.md +++ b/docs/features/software-catalog/catalog-customization.md @@ -61,7 +61,149 @@ app: ### Custom filters -You can create custom catalog filters using the `CatalogFilterBlueprint` from `@backstage/plugin-catalog-react/alpha`. See the [extension overrides](../../frontend-system/architecture/25-extension-overrides.md) documentation for details on how to install custom extensions. +You can create custom catalog filters using the `CatalogFilterBlueprint` from `@backstage/plugin-catalog-react/alpha`. For example, to add a custom security tier filter: + +```tsx title="packages/app/src/catalog/SecurityTierFilter.tsx" +import { CatalogFilterBlueprint } from '@backstage/plugin-catalog-react/alpha'; + +export const securityTierFilter = CatalogFilterBlueprint.make({ + name: 'security-tier', + params: { + loader: async () => { + const { EntitySecurityTierPicker } = await import( + './EntitySecurityTierPicker' + ); + return ; + }, + }, +}); +``` + +Then install it as a frontend module: + +```tsx title="packages/app/src/catalog/catalogCustomizations.tsx" +import { createFrontendModule } from '@backstage/frontend-plugin-api'; +import { securityTierFilter } from './SecurityTierFilter'; + +export default createFrontendModule({ + pluginId: 'catalog', + extensions: [securityTierFilter], +}); +``` + +### Removing default filters + +Default filters can be disabled through `app-config.yaml` by setting them to `false`: + +```yaml title="app-config.yaml" +app: + extensions: + - catalog-filter:catalog/lifecycle: false + - catalog-filter:catalog/tag: false + - catalog-filter:catalog/processing-status: false +``` + +## Customizing columns, actions, and table options + +In the old frontend system, customizing the catalog table columns, row actions, +and table options was done by passing props directly to the `CatalogIndexPage` +component. In the new frontend system, these customizations are done by +overriding the `page:catalog` extension. + +For example, to customize the catalog index page with custom columns or actions, +you can override the page extension using a frontend module: + +```tsx title="packages/app/src/catalog/customCatalogPage.tsx" +import { + PageBlueprint, + createFrontendModule, +} from '@backstage/frontend-plugin-api'; + +const customCatalogPage = PageBlueprint.make({ + params: { + path: '/catalog', + loader: () => + import('./CustomCatalogPage').then(m => ), + }, +}); + +export default createFrontendModule({ + pluginId: 'catalog', + extensions: [customCatalogPage], +}); +``` + +Inside your custom catalog page component you have full control over the table +columns, actions, and options. You can compose a page using components from +`@backstage/plugin-catalog` and `@backstage/plugin-catalog-react`: + +```tsx title="packages/app/src/catalog/CustomCatalogPage.tsx" +import { + PageWithHeader, + Content, + ContentHeader, + SupportButton, +} from '@backstage/core-components'; +import { useApi, configApiRef } from '@backstage/core-plugin-api'; +import { CatalogTable } from '@backstage/plugin-catalog'; +import { + EntityListProvider, + CatalogFilterLayout, + EntityKindPicker, + EntityLifecyclePicker, + EntityNamespacePicker, + EntityOwnerPicker, + EntityProcessingStatusPicker, + EntityTagPicker, + EntityTypePicker, + UserListPicker, +} from '@backstage/plugin-catalog-react'; + +export const CustomCatalogPage = () => { + const orgName = + useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage'; + + return ( + + + + All your software catalog entities + + + + + + + + + + + + + + + + + + + + + ); +}; +``` + +:::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. + +::: + +For more details on extension overrides and the different override patterns +available, see the [extension overrides](../../frontend-system/architecture/25-extension-overrides.md) documentation. ## Entity page diff --git a/docs/features/software-templates/writing-custom-step-layouts.md b/docs/features/software-templates/writing-custom-step-layouts.md index 82858626d6..03e496617d 100644 --- a/docs/features/software-templates/writing-custom-step-layouts.md +++ b/docs/features/software-templates/writing-custom-step-layouts.md @@ -11,61 +11,12 @@ read the [old frontend system version of this guide](./writing-custom-step-layou instead. :::: -Every form in each step rendered in the frontend uses the default form layout from [react-jsonschema-form](https://rjsf-team.github.io/react-jsonschema-form/docs/). It is possible to override this behaviour by supplying a `ui:ObjectFieldTemplate` property for a particular step: +:::caution +Custom step layouts are not yet supported in the new frontend system. The +scaffolder plugin does not provide an extension blueprint or input for +registering custom layouts in the new system. -```yaml -parameters: - - title: Fill in some steps - ui:ObjectFieldTemplate: TwoColumn -``` - -This is the same [field](https://rjsf-team.github.io/react-jsonschema-form/docs/advanced-customization/custom-templates#objectfieldtemplate) used by [react-jsonschema-form](https://rjsf-team.github.io/react-jsonschema-form/docs/) but we need to add a couple of steps to ensure that the string value of `TwoColumn` above is resolved to a react component. - -## Registering a React component as a custom step layout - -In the new frontend system, custom step layouts can be registered by creating a scaffolder module plugin that provides the layout through an extension override. Create a new plugin module: - -```tsx title="packages/app/src/scaffolder/customLayouts.tsx" -import { createFrontendModule } from '@backstage/frontend-plugin-api'; -import scaffolderPlugin from '@backstage/plugin-scaffolder/alpha'; -import { LayoutTemplate } from '@backstage/plugin-scaffolder-react'; -import { Grid } from '@material-ui/core'; - -const TwoColumn: LayoutTemplate = ({ properties, description, title }) => { - const mid = Math.ceil(properties.length / 2); - - return ( - <> -

{title}

-

In two column layout!!

- - {properties.slice(0, mid).map(prop => ( - - {prop.content} - - ))} - {properties.slice(mid).map(prop => ( - - {prop.content} - - ))} - - {description} - - ); -}; -``` - -Use `createScaffolderLayout` from `@backstage/plugin-scaffolder-react` and `scaffolderPlugin.provide` from `@backstage/plugin-scaffolder` to register the layout under the name `TwoColumn`, then install it through a frontend module using `createFrontendModule` together with `scaffolderPlugin.withOverrides` from `@backstage/plugin-scaffolder/alpha`, following the patterns described in the extension overrides guide. - -For details on how to override and extend extensions in the new frontend system, see the [extension overrides](../../frontend-system/architecture/25-extension-overrides.md) documentation. - -## Using the custom step layout - -Once the layout is registered, it can be used as a `ui:ObjectFieldTemplate` in your template file: - -```yaml -parameters: - - title: Fill in some steps - ui:ObjectFieldTemplate: TwoColumn -``` +If you need custom step layouts, you can continue using the +[old frontend system](./writing-custom-step-layouts--old.md) approach with +`createScaffolderLayout` and the `ScaffolderLayouts` component. +::: diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 2c42910e47..e3433e558b 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -120,8 +120,52 @@ similar to the one provided by the Catalog plugin. TechDocs also comes with an alternative grid based layout and panel layout. Customization of the TechDocs home page in the new frontend system is done by -overriding the default page extension. For details on how to override -extensions, see the [extension overrides](../../frontend-system/architecture/25-extension-overrides.md) documentation. +overriding the `page:techdocs` extension. The TechDocs home page is a standard +page extension created using `PageBlueprint`, which means you can override it +just like any other page extension. + +The simplest approach is to create a frontend module that provides a replacement +page extension with the same extension ID. Since the TechDocs page extension has +the ID `page:techdocs`, you can override it by creating a new page extension +under the `techdocs` plugin namespace: + +```tsx title="packages/app/src/techdocs/TechDocsHomePage.tsx" +import { + PageBlueprint, + createFrontendModule, +} from '@backstage/frontend-plugin-api'; + +const customTechDocsPage = PageBlueprint.make({ + params: { + path: '/docs', + loader: () => + import('./CustomTechDocsHome').then(m => ), + }, +}); + +export default createFrontendModule({ + pluginId: 'techdocs', + extensions: [customTechDocsPage], +}); +``` + +Then install the module in your app: + +```tsx title="packages/app/src/App.tsx" +import { createApp } from '@backstage/frontend-defaults'; +import customTechDocsModule from './techdocs/TechDocsHomePage'; + +const app = createApp({ + features: [customTechDocsModule], +}); + +export default app.createRoot(); +``` + +You can also use the `.override(...)` method on the original extension if you +want to customize the existing page without fully replacing it. For more details +on extension overrides and the different override patterns available, see the +[extension overrides](../../frontend-system/architecture/25-extension-overrides.md) documentation. ## How to customize the TechDocs reader page? From c16c5084d05c9ff34b1cd99cc7afcb23c105dd86 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Fri, 27 Mar 2026 18:12:06 +0000 Subject: [PATCH 054/191] cli-module-build: pack packages in batches inside createDistWorkspace When passing --always-yarn-pack, we previously packed all packages in parallel. Since package.json files are rewritten during packing, this could cause intermittent "No local workspace found for this range" failures. To fix this, we now pack packages in batches, starting with the ones that have no workspace dependencies and expanding out to include packages whose dependencies have already been packed. Signed-off-by: MT Lewis --- .changeset/soft-beers-bathe.md | 5 + .../packager/computeTopologicalLayers.test.ts | 139 ++++++++++++++++++ .../lib/packager/computeTopologicalLayers.ts | 63 ++++++++ .../src/lib/packager/createDistWorkspace.ts | 28 +++- 4 files changed, 227 insertions(+), 8 deletions(-) create mode 100644 .changeset/soft-beers-bathe.md create mode 100644 packages/cli-module-build/src/lib/packager/computeTopologicalLayers.test.ts create mode 100644 packages/cli-module-build/src/lib/packager/computeTopologicalLayers.ts diff --git a/.changeset/soft-beers-bathe.md b/.changeset/soft-beers-bathe.md new file mode 100644 index 0000000000..ae2b8a53a3 --- /dev/null +++ b/.changeset/soft-beers-bathe.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli-module-build': patch +--- + +When building dist-workspaces with --always-pack, batch `yarn pack` operations to avoid packing packages and their dependencies simultaneously. diff --git a/packages/cli-module-build/src/lib/packager/computeTopologicalLayers.test.ts b/packages/cli-module-build/src/lib/packager/computeTopologicalLayers.test.ts new file mode 100644 index 0000000000..055e481d79 --- /dev/null +++ b/packages/cli-module-build/src/lib/packager/computeTopologicalLayers.test.ts @@ -0,0 +1,139 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PackageGraphNode } from '@backstage/cli-node'; +import { computeTopologicalLayers } from './computeTopologicalLayers'; + +function makeNode( + name: string, + deps: PackageGraphNode[] = [], +): PackageGraphNode { + return { + name, + dir: `/packages/${name}`, + packageJson: { name, version: '1.0.0' }, + allLocalDependencies: new Map(), + publishedLocalDependencies: new Map(deps.map(d => [d.name, d])), + localDependencies: new Map(), + localDevDependencies: new Map(), + localOptionalDependencies: new Map(), + allLocalDependents: new Map(), + publishedLocalDependents: new Map(), + localDependents: new Map(), + localDevDependents: new Map(), + localOptionalDependents: new Map(), + } as PackageGraphNode; +} + +describe('computeTopologicalLayers', () => { + it('returns an empty array for no packages', () => { + expect(computeTopologicalLayers([])).toEqual([]); + }); + + it('returns a single layer when packages have no dependencies', () => { + const a = makeNode('a'); + const b = makeNode('b'); + const c = makeNode('c'); + + const layers = computeTopologicalLayers([a, b, c]); + expect(layers).toHaveLength(1); + expect(layers[0]).toEqual(expect.arrayContaining([a, b, c])); + }); + + it('separates packages into layers based on dependency order', () => { + const a = makeNode('a'); + const b = makeNode('b', [a]); + const c = makeNode('c', [b]); + + const layers = computeTopologicalLayers([a, b, c]); + expect(layers).toHaveLength(3); + expect(layers[0]).toEqual([a]); + expect(layers[1]).toEqual([b]); + expect(layers[2]).toEqual([c]); + }); + + it('groups independent packages into the same layer', () => { + // a + // / \ + // b c + // \ / + // d + const a = makeNode('a'); + const b = makeNode('b', [a]); + const c = makeNode('c', [a]); + const d = makeNode('d', [b, c]); + + const layers = computeTopologicalLayers([a, b, c, d]); + expect(layers).toHaveLength(3); + expect(layers[0]).toEqual([a]); + expect(layers[1]).toEqual(expect.arrayContaining([b, c])); + expect(layers[1]).toHaveLength(2); + expect(layers[2]).toEqual([d]); + }); + + it('falls back to a single layer on circular dependencies', () => { + const a = makeNode('a'); + const b = makeNode('b'); + // Create a cycle: a -> b -> a + a.publishedLocalDependencies.set('b', b); + b.publishedLocalDependencies.set('a', a); + + const layers = computeTopologicalLayers([a, b]); + // Should still return all packages rather than hanging or throwing + expect(layers).toHaveLength(1); + expect(layers[0]).toEqual(expect.arrayContaining([a, b])); + }); + + it('handles a partial cycle with non-cyclic packages separated into earlier layers', () => { + const a = makeNode('a'); + const b = makeNode('b', [a]); + const c = makeNode('c'); + // Create cycle: b -> c -> b (but a is not in the cycle) + b.publishedLocalDependencies.set('c', c); + c.publishedLocalDependencies.set('b', b); + + const layers = computeTopologicalLayers([a, b, c]); + // a has no deps, so it goes in layer 0 + expect(layers[0]).toEqual([a]); + // b and c form a cycle, so they are dumped together + expect(layers[1]).toEqual(expect.arrayContaining([b, c])); + expect(layers).toHaveLength(2); + }); + + it('produces correct layers regardless of input order', () => { + const a = makeNode('a'); + const b = makeNode('b', [a]); + const c = makeNode('c', [a]); + + // Reverse input order + const layers = computeTopologicalLayers([c, b, a]); + expect(layers).toHaveLength(2); + expect(layers[0]).toEqual([a]); + expect(layers[1]).toEqual(expect.arrayContaining([b, c])); + }); + + it('only considers publishedLocalDependencies, not other dependency types', () => { + const a = makeNode('a'); + const b = makeNode('b'); + // b has a as a dev dependency but not a published dependency + b.localDevDependencies.set('a', a); + + const layers = computeTopologicalLayers([a, b]); + // Both should be in the same layer since published deps are empty + expect(layers).toHaveLength(1); + expect(layers[0]).toEqual(expect.arrayContaining([a, b])); + }); +}); diff --git a/packages/cli-module-build/src/lib/packager/computeTopologicalLayers.ts b/packages/cli-module-build/src/lib/packager/computeTopologicalLayers.ts new file mode 100644 index 0000000000..0a0e78eb8a --- /dev/null +++ b/packages/cli-module-build/src/lib/packager/computeTopologicalLayers.ts @@ -0,0 +1,63 @@ +/* + * Copyright 2026 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PackageGraphNode } from '@backstage/cli-node'; + +/** + * Groups packages into topological layers based on their published local + * dependencies. Packages in the same layer have no inter-dependencies and + * can be safely packed in parallel. Each successive layer depends only on + * packages in earlier (already-packed) layers. + * + * If a dependency cycle is detected the remaining packages are returned as + * a single final layer — this matches the previous behaviour of packing + * everything in parallel and avoids blocking the build entirely. + */ +export function computeTopologicalLayers( + packages: PackageGraphNode[], +): PackageGraphNode[][] { + const remaining = new Map(packages.map(p => [p.name, p])); + const layers: PackageGraphNode[][] = []; + + while (remaining.size > 0) { + const layer: PackageGraphNode[] = []; + + for (const [, pkg] of remaining) { + // A package is ready when none of its published local deps are still + // waiting to be packed. + const blocked = Array.from(pkg.publishedLocalDependencies.keys()).some( + dep => remaining.has(dep), + ); + if (!blocked) { + layer.push(pkg); + } + } + + if (layer.length === 0) { + // Circular dependency — fall back to packing everything remaining + // together, accepting the (pre-existing) race risk for this cycle. + layers.push(Array.from(remaining.values())); + break; + } + + for (const pkg of layer) { + remaining.delete(pkg.name); + } + layers.push(layer); + } + + return layers; +} diff --git a/packages/cli-module-build/src/lib/packager/createDistWorkspace.ts b/packages/cli-module-build/src/lib/packager/createDistWorkspace.ts index 72a06af69f..61726a6098 100644 --- a/packages/cli-module-build/src/lib/packager/createDistWorkspace.ts +++ b/packages/cli-module-build/src/lib/packager/createDistWorkspace.ts @@ -26,6 +26,7 @@ import * as tar from 'tar'; import partition from 'lodash/partition'; import { run, targetPaths } from '@backstage/cli-common'; +import { computeTopologicalLayers } from './computeTopologicalLayers'; import { dependencies as cliDependencies, devDependencies as cliDevDependencies, @@ -347,8 +348,14 @@ async function moveToDistWorkspace( // Old flow is below, which calls `yarn pack` and extracts the tarball - async function pack(target: PackageGraphNode, archive: string) { + let archiveIndex = 0; + + async function pack( + target: PackageGraphNode, + archive: string = `temp-package-${archiveIndex++}.tgz`, + ) { logger.log(`Repacking ${target.name} into dist workspace`); + const absoluteOutputPath = resolvePath( workspaceDir, relativePath(targetPaths.rootDir, target.dir), @@ -392,13 +399,18 @@ async function moveToDistWorkspace( await pack(target, `temp-package.tgz`); } - // Repacking in parallel is much faster and safe for all packages outside of the Backstage repo - await runConcurrentTasks({ - items: safePackages.map((target, index) => ({ target, index })), - worker: async ({ target, index }) => { - await pack(target, `temp-package-${index}.tgz`); - }, - }); + // Pack safe packages in topological layers so that a package is never + // packed concurrently with its own dependencies. `yarn pack` temporarily + // rewrites each package's package.json to resolve workspace:^ references; + // packing a package while a dependency's manifest is mid-rewrite causes + // intermittent "No local workspace found for this range" failures. + const layers = computeTopologicalLayers(safePackages); + for (const layer of layers) { + await runConcurrentTasks({ + items: layer, + worker: pack, + }); + } } /** From 561f555fb1e722c609ba0079a697a48a52cc4975 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 30 Mar 2026 12:42:11 +0200 Subject: [PATCH 055/191] docs: address copilot review feedback Fix links in viewing-catalog.md and filter-catalog.md to point to the new frontend system catalog-customization.md instead of the old guide. Fix the Kubernetes entity content extension ID to use the correct `entity-content:kubernetes/kubernetes` format. Wrap raw extension definitions in `createFrontendModule` in search and scaffolder docs. Correct TechDocs addon installation instructions to explicitly install addon modules rather than claiming auto-discovery. Signed-off-by: Patrik Oldsberg Made-with: Cursor --- docs/features/kubernetes/installation.md | 2 +- docs/features/search/getting-started.md | 16 +++++++++++--- docs/features/search/how-to-guides.md | 16 +++++++++++--- .../writing-custom-field-extensions.md | 16 +++++++++++--- docs/features/techdocs/getting-started.md | 21 ++++++++++++++++--- docs/getting-started/filter-catalog.md | 2 +- docs/getting-started/viewing-catalog.md | 6 +++--- 7 files changed, 62 insertions(+), 17 deletions(-) diff --git a/docs/features/kubernetes/installation.md b/docs/features/kubernetes/installation.md index b8b9868f6a..18352e78ad 100644 --- a/docs/features/kubernetes/installation.md +++ b/docs/features/kubernetes/installation.md @@ -24,7 +24,7 @@ The Kubernetes tab is shown by default for entities where Kubernetes data is ava ```yaml title="app-config.yaml" app: extensions: - - entity-content:kubernetes: + - entity-content:kubernetes/kubernetes: config: filter: metadata.annotations.backstage.io/kubernetes-id: diff --git a/docs/features/search/getting-started.md b/docs/features/search/getting-started.md index ba8ff1c840..6e4789becc 100644 --- a/docs/features/search/getting-started.md +++ b/docs/features/search/getting-started.md @@ -119,14 +119,24 @@ export const MySearchResultListItem = SearchResultListItemBlueprint.make({ }); ``` -Install this in your app by passing it to `createApp`: +Install this in your app by wrapping it in a frontend module and passing it to `createApp`: + +```tsx title="packages/app/src/search/searchModule.ts" +import { createFrontendModule } from '@backstage/frontend-plugin-api'; +import { MySearchResultListItem } from './MySearchResultListItem'; + +export const searchCustomizations = createFrontendModule({ + pluginId: 'search', + extensions: [MySearchResultListItem], +}); +``` ```tsx title="packages/app/src/App.tsx" import { createApp } from '@backstage/frontend-defaults'; -import { MySearchResultListItem } from './search/MySearchResultListItem'; +import { searchCustomizations } from './search/searchModule'; const app = createApp({ - features: [MySearchResultListItem], + features: [searchCustomizations], }); export default app.createRoot(); diff --git a/docs/features/search/how-to-guides.md b/docs/features/search/how-to-guides.md index 53f838898c..8c0f430886 100644 --- a/docs/features/search/how-to-guides.md +++ b/docs/features/search/how-to-guides.md @@ -178,14 +178,24 @@ The extension is then exported from your plugin's alpha entry point and automatically discovered when the plugin is installed. If you need to provide a search result list item extension from your app -rather than a plugin, you can install it directly in `createApp`: +rather than a plugin, wrap it in a frontend module and pass it to `createApp`: + +```tsx title="packages/app/src/search/searchModule.ts" +import { createFrontendModule } from '@backstage/frontend-plugin-api'; +import { YourSearchResultListItem } from './YourSearchResultListItem'; + +export const searchCustomizations = createFrontendModule({ + pluginId: 'search', + extensions: [YourSearchResultListItem], +}); +``` ```tsx title="packages/app/src/App.tsx" import { createApp } from '@backstage/frontend-defaults'; -import { YourSearchResultListItem } from './search/YourSearchResultListItem'; +import { searchCustomizations } from './search/searchModule'; const app = createApp({ - features: [YourSearchResultListItem], + features: [searchCustomizations], }); export default app.createRoot(); diff --git a/docs/features/software-templates/writing-custom-field-extensions.md b/docs/features/software-templates/writing-custom-field-extensions.md index cca45422cb..b63d29da7a 100644 --- a/docs/features/software-templates/writing-custom-field-extensions.md +++ b/docs/features/software-templates/writing-custom-field-extensions.md @@ -117,14 +117,24 @@ export const ValidateKebabCaseFieldExtension = FormFieldBlueprint.make({ export { ValidateKebabCaseFieldExtension } from './extensions'; ``` -Once the extension is created, install it in your app by passing it to `createApp`: +Once the extension is created, install it in your app by wrapping it in a frontend module and passing it to `createApp`: + +```tsx title="packages/app/src/scaffolder/scaffolderModule.ts" +import { createFrontendModule } from '@backstage/frontend-plugin-api'; +import { ValidateKebabCaseFieldExtension } from './ValidateKebabCase'; + +export const scaffolderCustomizations = createFrontendModule({ + pluginId: 'scaffolder', + extensions: [ValidateKebabCaseFieldExtension], +}); +``` ```tsx title="packages/app/src/App.tsx" import { createApp } from '@backstage/frontend-defaults'; -import { ValidateKebabCaseFieldExtension } from './scaffolder/ValidateKebabCase'; +import { scaffolderCustomizations } from './scaffolder/scaffolderModule'; const app = createApp({ - features: [ValidateKebabCaseFieldExtension], + features: [scaffolderCustomizations], }); export default app.createRoot(); diff --git a/docs/features/techdocs/getting-started.md b/docs/features/techdocs/getting-started.md index 862d14eb4e..d9706c1de8 100644 --- a/docs/features/techdocs/getting-started.md +++ b/docs/features/techdocs/getting-started.md @@ -29,15 +29,30 @@ The plugin provides a docs index page at `/docs` and a reader page for individua ## Using TechDocs Addons -The TechDocs Addon framework lets you render React components in documentation pages. Addons are provided as separate plugin packages that are automatically discovered when installed. +The TechDocs Addon framework lets you render React components in documentation pages. Addons are provided as separate plugin modules. -For example, to add the Report Issue addon: +For example, to add the Report Issue addon, first install the package: ```bash title="From your Backstage root directory" yarn --cwd packages/app add @backstage/plugin-techdocs-module-addons-contrib ``` -Once installed, the addon is automatically active. You can see it in action when you highlight text in your documentation: +Then install the addon module in your app: + +```tsx title="packages/app/src/App.tsx" +import { createApp } from '@backstage/frontend-defaults'; +import { techDocsReportIssueAddonModule } from '@backstage/plugin-techdocs-module-addons-contrib/alpha'; + +const app = createApp({ + features: [techDocsReportIssueAddonModule], +}); + +export default app.createRoot(); +``` + +The same package also provides `techDocsExpandableNavigationAddonModule`, `techDocsTextSizeAddonModule`, and `techDocsLightBoxAddonModule`. + +You can see the Report Issue addon in action when you highlight text in your documentation: diff --git a/docs/getting-started/filter-catalog.md b/docs/getting-started/filter-catalog.md index 2488b4caab..bdadca3ae7 100644 --- a/docs/getting-started/filter-catalog.md +++ b/docs/getting-started/filter-catalog.md @@ -8,7 +8,7 @@ Audience: All ## Overview -The Catalog can be filtered by any combination of owner, kind, type, lifecycle, processing status, namespace, and name. [Customize Filters](../features/software-catalog/catalog-customization--old.md#customize-filters) provides information on how to modify the available filter criteria. +The Catalog can be filtered by any combination of owner, kind, type, lifecycle, processing status, namespace, and name. [Catalog filters](../features/software-catalog/catalog-customization.md#catalog-filters) provides information on how to modify the available filter criteria. ![Catalog filter options](../assets/uiguide/catalog-filter-options.png) diff --git a/docs/getting-started/viewing-catalog.md b/docs/getting-started/viewing-catalog.md index 3307a863da..464edd8601 100644 --- a/docs/getting-started/viewing-catalog.md +++ b/docs/getting-started/viewing-catalog.md @@ -31,7 +31,7 @@ Initially, the Catalog displays registered entities matching the following filte - `Processing Status` - normal - `Namespace` - The ID of a [namespace](../features/software-catalog/descriptor-format.md#namespace-optional) to which the entity belongs -You can change the initial setting for the [Owner](../features/software-catalog/catalog-customization--old.md#initially-selected-filter) and [Kind](../features/software-catalog/catalog-customization--old.md#initially-selected-kind) filters. +You can change the initial setting for the [Owner](../features/software-catalog/catalog-customization.md#catalog-filters) and [Kind](../features/software-catalog/catalog-customization.md#catalog-filters) filters. ## Informational columns for each entity @@ -55,7 +55,7 @@ For each kind of entity, a set of columns display information regarding the enti - `Tags` - an optional field that can be used for searching - `Actions` - see [Catalog Actions](#catalog-actions) -You can modify the columns associated with each kind of entity, following the instructions in [Customize Columns](../features/software-catalog/catalog-customization--old.md#customize-columns). +You can modify the columns associated with each kind of entity, following the instructions in [Customizing columns, actions, and table options](../features/software-catalog/catalog-customization.md#customizing-columns-actions-and-table-options). ## Catalog Actions @@ -69,7 +69,7 @@ From left to right, the actions are: - Edit - Edit the `catalog-info.yaml` file that defines the entity. See [Updating a Component](../getting-started/update-a-component.md) - Star - Designate the entity as a favorite. You can [filter](../getting-started/filter-catalog.md) the catalog for starred entities. -[Customize Actions](../features/software-catalog/catalog-customization--old.md#customize-actions) describes how you can modify the actions that are displayed. +[Customizing columns, actions, and table options](../features/software-catalog/catalog-customization.md#customizing-columns-actions-and-table-options) describes how you can modify the actions that are displayed. ## Viewing entity details From c368cf3db7b8674583b24f2df814bdbc4e056d6d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 10:42:34 +0000 Subject: [PATCH 056/191] chore(deps): update dependency @types/use-sync-external-store to v1 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .changeset/renovate-9d44e41.md | 5 +++++ packages/ui/package.json | 2 +- yarn.lock | 9 ++++++++- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 .changeset/renovate-9d44e41.md diff --git a/.changeset/renovate-9d44e41.md b/.changeset/renovate-9d44e41.md new file mode 100644 index 0000000000..0fd0cb5306 --- /dev/null +++ b/.changeset/renovate-9d44e41.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Updated dependency `@types/use-sync-external-store` to `^1.0.0`. diff --git a/packages/ui/package.json b/packages/ui/package.json index 6d99d851b7..322bfbb181 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -62,7 +62,7 @@ "@backstage/cli": "workspace:^", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", - "@types/use-sync-external-store": "^0.0.6", + "@types/use-sync-external-store": "^1.0.0", "eslint-plugin-storybook": "^10.3.3", "glob": "^11.0.1", "globals": "^17.0.0", diff --git a/yarn.lock b/yarn.lock index aa6bd6204d..802d200f11 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7944,7 +7944,7 @@ __metadata: "@tanstack/react-table": "npm:^8.21.3" "@types/react": "npm:^18.0.0" "@types/react-dom": "npm:^18.0.0" - "@types/use-sync-external-store": "npm:^0.0.6" + "@types/use-sync-external-store": "npm:^1.0.0" clsx: "npm:^2.1.1" eslint-plugin-storybook: "npm:^10.3.3" glob: "npm:^11.0.1" @@ -22405,6 +22405,13 @@ __metadata: languageName: node linkType: hard +"@types/use-sync-external-store@npm:^1.0.0": + version: 1.5.0 + resolution: "@types/use-sync-external-store@npm:1.5.0" + checksum: 10/39e5be8dc2cca080b490f2f79fed4381ae7eebee3f981208e359856733eafb2479d229db07a552f6c99fe0b5c09b3e46a3e6a870e00a88b50f3e690e73d2649b + languageName: node + linkType: hard + "@types/vinyl@npm:^2.0.4": version: 2.0.6 resolution: "@types/vinyl@npm:2.0.6" From 5bc450b7f194f2ba6b112c549b41c12c71351f9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 30 Mar 2026 20:57:39 +0200 Subject: [PATCH 057/191] Rename entityPresentation parameter to entityPresentationApi for consistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Fredrik Adelöw --- plugins/catalog-react/report-alpha.api.md | 4 ++-- plugins/catalog-react/report.api.md | 8 ++++---- .../EntityDataTable/columnFactories.tsx | 16 +++++++-------- .../src/components/EntityTable/columns.tsx | 20 +++++++++---------- plugins/catalog/report.api.md | 2 +- .../src/components/CatalogTable/columns.tsx | 6 +++--- .../src/home/components/Tables/helpers.ts | 6 +++--- 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/plugins/catalog-react/report-alpha.api.md b/plugins/catalog-react/report-alpha.api.md index 17bd8de8af..c2818b78ee 100644 --- a/plugins/catalog-react/report-alpha.api.md +++ b/plugins/catalog-react/report-alpha.api.md @@ -500,7 +500,7 @@ export const entityDataTableColumns: Readonly<{ createEntityRefColumn(options: { defaultKind?: string; isRowHeader?: boolean; - entityPresentation?: EntityPresentationApi; + entityPresentationApi?: EntityPresentationApi; }): EntityColumnConfig; createEntityRelationColumn(options: { id: string; @@ -510,7 +510,7 @@ export const entityDataTableColumns: Readonly<{ filter?: { kind: string; }; - entityPresentation?: EntityPresentationApi; + entityPresentationApi?: EntityPresentationApi; }): EntityColumnConfig; createOwnerColumn(): EntityColumnConfig; createSystemColumn(): EntityColumnConfig; diff --git a/plugins/catalog-react/report.api.md b/plugins/catalog-react/report.api.md index 806d329fe0..251a0e853a 100644 --- a/plugins/catalog-react/report.api.md +++ b/plugins/catalog-react/report.api.md @@ -266,7 +266,7 @@ export type CatalogReactUserListPickerClassKey = export const columnFactories: Readonly<{ createEntityRefColumn(options: { defaultKind?: string; - entityPresentation?: EntityPresentationApi; + entityPresentationApi?: EntityPresentationApi; }): TableColumn; createEntityRelationColumn(options: { title: string | JSX.Element; @@ -275,7 +275,7 @@ export const columnFactories: Readonly<{ filter?: { kind: string; }; - entityPresentation?: EntityPresentationApi; + entityPresentationApi?: EntityPresentationApi; }): TableColumn; createOwnerColumn(): TableColumn; createDomainColumn(): TableColumn; @@ -689,7 +689,7 @@ export const EntityTable: { columns: Readonly<{ createEntityRefColumn(options: { defaultKind?: string; - entityPresentation?: EntityPresentationApi; + entityPresentationApi?: EntityPresentationApi; }): TableColumn; createEntityRelationColumn(options: { title: string | JSX.Element; @@ -698,7 +698,7 @@ export const EntityTable: { filter?: { kind: string; }; - entityPresentation?: EntityPresentationApi; + entityPresentationApi?: EntityPresentationApi; }): TableColumn; createOwnerColumn(): TableColumn; createDomainColumn(): TableColumn; diff --git a/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx b/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx index aed29dbd56..5c425907d3 100644 --- a/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx +++ b/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx @@ -36,11 +36,11 @@ export interface EntityColumnConfig extends ColumnConfig { function getEntityTitle( entityOrRef: Entity | { kind: string; namespace?: string; name: string }, context: { defaultKind?: string }, - entityPresentation?: EntityPresentationApi, + entityPresentationApi?: EntityPresentationApi, ): string { - if (entityPresentation) { - return entityPresentation.forEntity(entityOrRef as Entity, context).snapshot - .primaryTitle; + if (entityPresentationApi) { + return entityPresentationApi.forEntity(entityOrRef as Entity, context) + .snapshot.primaryTitle; } return defaultEntityPresentation(entityOrRef as Entity, context).primaryTitle; } @@ -50,7 +50,7 @@ export const columnFactories = Object.freeze({ createEntityRefColumn(options: { defaultKind?: string; isRowHeader?: boolean; - entityPresentation?: EntityPresentationApi; + entityPresentationApi?: EntityPresentationApi; }): EntityColumnConfig { const isRowHeader = options.isRowHeader ?? true; return { @@ -76,7 +76,7 @@ export const columnFactories = Object.freeze({ getEntityTitle( entity, { defaultKind: options.defaultKind }, - options.entityPresentation, + options.entityPresentationApi, ), }; }, @@ -87,7 +87,7 @@ export const columnFactories = Object.freeze({ relation: string; defaultKind?: string; filter?: { kind: string }; - entityPresentation?: EntityPresentationApi; + entityPresentationApi?: EntityPresentationApi; }): EntityColumnConfig { return { id: options.id, @@ -116,7 +116,7 @@ export const columnFactories = Object.freeze({ getEntityTitle( r, { defaultKind: options.defaultKind }, - options.entityPresentation, + options.entityPresentationApi, ), ) .join(', '), diff --git a/plugins/catalog-react/src/components/EntityTable/columns.tsx b/plugins/catalog-react/src/components/EntityTable/columns.tsx index 3768cf595e..01d1c7e1f3 100644 --- a/plugins/catalog-react/src/components/EntityTable/columns.tsx +++ b/plugins/catalog-react/src/components/EntityTable/columns.tsx @@ -29,11 +29,11 @@ import { EntityTableColumnTitle } from './TitleColumn'; function getEntityTitle( entityOrRef: Entity | CompoundEntityRef, context: { defaultKind?: string }, - entityPresentation?: EntityPresentationApi, + entityPresentationApi?: EntityPresentationApi, ): string { - if (entityPresentation) { - return entityPresentation.forEntity(entityOrRef as Entity, context).snapshot - .primaryTitle; + if (entityPresentationApi) { + return entityPresentationApi.forEntity(entityOrRef as Entity, context) + .snapshot.primaryTitle; } return defaultEntityPresentation(entityOrRef, context).primaryTitle; } @@ -42,11 +42,11 @@ function getEntityTitle( export const columnFactories = Object.freeze({ createEntityRefColumn(options: { defaultKind?: string; - entityPresentation?: EntityPresentationApi; + entityPresentationApi?: EntityPresentationApi; }): TableColumn { - const { defaultKind, entityPresentation } = options; + const { defaultKind, entityPresentationApi } = options; function formatContent(entity: T): string { - return getEntityTitle(entity, { defaultKind }, entityPresentation); + return getEntityTitle(entity, { defaultKind }, entityPresentationApi); } return { @@ -80,14 +80,14 @@ export const columnFactories = Object.freeze({ relation: string; defaultKind?: string; filter?: { kind: string }; - entityPresentation?: EntityPresentationApi; + entityPresentationApi?: EntityPresentationApi; }): TableColumn { const { title, relation, defaultKind, filter: entityFilter, - entityPresentation, + entityPresentationApi, } = options; function getRelations(entity: T): CompoundEntityRef[] { @@ -96,7 +96,7 @@ export const columnFactories = Object.freeze({ function formatContent(entity: T): string { return getRelations(entity) - .map(r => getEntityTitle(r, { defaultKind }, entityPresentation)) + .map(r => getEntityTitle(r, { defaultKind }, entityPresentationApi)) .join(', '); } diff --git a/plugins/catalog/report.api.md b/plugins/catalog/report.api.md index 816d469d13..d8c5c85402 100644 --- a/plugins/catalog/report.api.md +++ b/plugins/catalog/report.api.md @@ -153,7 +153,7 @@ export const CatalogTable: { columns: Readonly<{ createNameColumn(options?: { defaultKind?: string; - entityPresentation?: EntityPresentationApi; + entityPresentationApi?: EntityPresentationApi; }): TableColumn; createSystemColumn(): TableColumn; createOwnerColumn(): TableColumn; diff --git a/plugins/catalog/src/components/CatalogTable/columns.tsx b/plugins/catalog/src/components/CatalogTable/columns.tsx index 05a8b1979b..55b2d5c6a8 100644 --- a/plugins/catalog/src/components/CatalogTable/columns.tsx +++ b/plugins/catalog/src/components/CatalogTable/columns.tsx @@ -32,11 +32,11 @@ import { EntityTableColumnTitle } from '@backstage/plugin-catalog-react/alpha'; export const columnFactories = Object.freeze({ createNameColumn(options?: { defaultKind?: string; - entityPresentation?: EntityPresentationApi; + entityPresentationApi?: EntityPresentationApi; }): TableColumn { function formatContent(entity: Entity): string { - if (options?.entityPresentation) { - return options.entityPresentation.forEntity(entity, { + if (options?.entityPresentationApi) { + return options.entityPresentationApi.forEntity(entity, { defaultKind: options?.defaultKind, }).snapshot.primaryTitle; } diff --git a/plugins/techdocs/src/home/components/Tables/helpers.ts b/plugins/techdocs/src/home/components/Tables/helpers.ts index 4ec3123fe8..4f2424340e 100644 --- a/plugins/techdocs/src/home/components/Tables/helpers.ts +++ b/plugins/techdocs/src/home/components/Tables/helpers.ts @@ -37,7 +37,7 @@ export function entitiesToDocsMapper( entities: Entity[], getRouteToReaderPageFor: getRouteFunc, config: ConfigApi, - entityPresentation?: EntityPresentationApi, + entityPresentationApi?: EntityPresentationApi, ) { return entities.map(entity => { const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); @@ -55,8 +55,8 @@ export function entitiesToDocsMapper( ownedByRelations, ownedByRelationsTitle: ownedByRelations .map(r => { - if (entityPresentation) { - return entityPresentation.forEntity(stringifyEntityRef(r), { + if (entityPresentationApi) { + return entityPresentationApi.forEntity(stringifyEntityRef(r), { defaultKind: 'group', }).snapshot.primaryTitle; } From 0277801a960137a8e7e92903f2da751282d63b50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 30 Mar 2026 21:16:11 +0200 Subject: [PATCH 058/191] Remove duplicate presentation API exports from alpha, import from public API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Fredrik Adelöw --- plugins/catalog-react/report-alpha.api.md | 60 +------------------ plugins/catalog-react/src/alpha/index.ts | 8 --- .../EntityDataTable/columnFactories.tsx | 5 +- 3 files changed, 5 insertions(+), 68 deletions(-) diff --git a/plugins/catalog-react/report-alpha.api.md b/plugins/catalog-react/report-alpha.api.md index c2818b78ee..19f1b2ce3a 100644 --- a/plugins/catalog-react/report-alpha.api.md +++ b/plugins/catalog-react/report-alpha.api.md @@ -6,19 +6,17 @@ import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ColumnConfig } from '@backstage/ui'; import { ComponentType } from 'react'; -import { CompoundEntityRef } from '@backstage/catalog-model'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { Entity } from '@backstage/catalog-model'; +import { EntityPresentationApi } from '@backstage/plugin-catalog-react'; import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; import { FilterPredicate } from '@backstage/filter-predicates'; -import { IconComponent } from '@backstage/core-plugin-api'; import { IconLinkVerticalProps } from '@backstage/core-components'; import { JSX as JSX_2 } from 'react'; import { JSX as JSX_3 } from 'react/jsx-runtime'; import { JSXElementConstructor } from 'react'; -import { Observable } from '@backstage/types'; import { ReactElement } from 'react'; import { ReactNode } from 'react'; import { ResourcePermission } from '@backstage/plugin-permission-common'; @@ -186,15 +184,6 @@ export const defaultEntityContentGroups: Record< string >; -// @public -export function defaultEntityPresentation( - entityOrRef: Entity | CompoundEntityRef | string, - context?: { - defaultKind?: string; - defaultNamespace?: string; - }, -): EntityRefPresentationSnapshot; - // @alpha export const EntityCardBlueprint: ExtensionBlueprint<{ kind: 'entity-card'; @@ -534,18 +523,6 @@ export interface EntityDataTableProps { loading?: boolean; } -// @public -export const EntityDisplayName: (props: EntityDisplayNameProps) => JSX.Element; - -// @public -export type EntityDisplayNameProps = { - entityRef: Entity | CompoundEntityRef | string; - hideIcon?: boolean; - disableTooltip?: boolean; - defaultKind?: string; - defaultNamespace?: string; -}; - // @alpha (undocumented) export const EntityHeaderBlueprint: ExtensionBlueprint<{ kind: 'entity-header'; @@ -653,32 +630,6 @@ export const EntityIconLinkBlueprint: ExtensionBlueprint<{ }; }>; -// @public -export interface EntityPresentationApi { - forEntity( - entityOrRef: Entity | string, - context?: { - defaultKind?: string; - defaultNamespace?: string; - }, - ): EntityRefPresentation; -} - -// @public -export interface EntityRefPresentation { - promise: Promise; - snapshot: EntityRefPresentationSnapshot; - update$?: Observable; -} - -// @public -export interface EntityRefPresentationSnapshot { - entityRef: string; - Icon?: IconComponent | undefined | false; - primaryTitle: string; - secondaryTitle?: string; -} - // @public (undocumented) export function EntityRelationCard( props: EntityRelationCardProps, @@ -752,15 +703,6 @@ export function useEntityPermission( error?: Error; }; -// @public -export function useEntityPresentation( - entityOrRef: Entity | CompoundEntityRef | string, - context?: { - defaultKind?: string; - defaultNamespace?: string; - }, -): EntityRefPresentationSnapshot; - // @alpha (undocumented) export type UseProps = () => | { diff --git a/plugins/catalog-react/src/alpha/index.ts b/plugins/catalog-react/src/alpha/index.ts index 47418eaa2a..d473b0923f 100644 --- a/plugins/catalog-react/src/alpha/index.ts +++ b/plugins/catalog-react/src/alpha/index.ts @@ -26,13 +26,5 @@ export const catalogReactTranslationRef = _catalogReactTranslationRef; export { isOwnerOf } from '../utils/isOwnerOf'; export { useEntityPermission } from '../hooks/useEntityPermission'; export * from '../components/EntityTable/TitleColumn'; -export type { - EntityPresentationApi, - EntityRefPresentation, - EntityRefPresentationSnapshot, -} from '../apis'; -export { useEntityPresentation, defaultEntityPresentation } from '../apis'; -export { EntityDisplayName } from '../components/EntityDisplayName'; -export type { EntityDisplayNameProps } from '../components/EntityDisplayName'; export * from '../components/EntityDataTable'; export * from '../components/EntityRelationCard'; diff --git a/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx b/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx index 5c425907d3..b0ff221e9d 100644 --- a/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx +++ b/plugins/catalog-react/src/components/EntityDataTable/columnFactories.tsx @@ -21,7 +21,10 @@ import { } from '@backstage/catalog-model'; import { Cell, CellText, Column, ColumnConfig, TableItem } from '@backstage/ui'; import { EntityRefLink, EntityRefLinks } from '../EntityRefLink'; -import { defaultEntityPresentation, EntityPresentationApi } from '../../apis'; +import { + defaultEntityPresentation, + EntityPresentationApi, +} from '@backstage/plugin-catalog-react'; import { EntityTableColumnTitle } from '../EntityTable/TitleColumn'; import { getEntityRelations } from '../../utils'; From d1124998ca8545c9e5e95b662826a1ce4b886240 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 30 Mar 2026 21:35:40 +0200 Subject: [PATCH 059/191] Fix SingleInstanceGithubCredentialsProvider to return app credentials for bare host URLs Signed-off-by: Vincenzo Scamporlino --- .changeset/six-suits-write.md | 5 ++ ...eInstanceGithubCredentialsProvider.test.ts | 84 +++++++++++++++++++ ...SingleInstanceGithubCredentialsProvider.ts | 11 +++ 3 files changed, 100 insertions(+) create mode 100644 .changeset/six-suits-write.md diff --git a/.changeset/six-suits-write.md b/.changeset/six-suits-write.md new file mode 100644 index 0000000000..3789c6342f --- /dev/null +++ b/.changeset/six-suits-write.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': patch +--- + +Fixed `SingleInstanceGithubCredentialsProvider` to return app credentials when `getCredentials` is called with a bare host URL (e.g. `https://github.com`) instead of falling back to a personal access token. diff --git a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.test.ts b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.test.ts index fefccf22f6..ebd884e783 100644 --- a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.test.ts +++ b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.test.ts @@ -25,6 +25,12 @@ const octokit = { }, }; +const mockCreateAppAuth = jest.fn(); + +jest.mock('@octokit/auth-app', () => ({ + createAppAuth: (...args: any[]) => mockCreateAppAuth(...args), +})); + jest.mock('@octokit/rest', () => { class Octokit { constructor() { @@ -43,6 +49,12 @@ describe('SingleInstanceGithubCredentialsProvider tests', () => { beforeEach(() => { jest.resetAllMocks(); + mockCreateAppAuth.mockReturnValue(async (opts: { type: string }) => { + if (opts.type === 'app') { + return { token: 'mock-jwt-token' }; + } + throw new Error(`Unexpected auth type: ${opts.type}`); + }); github = SingleInstanceGithubCredentialsProvider.create({ host: 'github.com', apps: [ @@ -889,4 +901,76 @@ describe('SingleInstanceGithubCredentialsProvider tests', () => { expect(token).toEqual('public_access_from_app_2'); }); }); + + describe('bare host URL (no org/repo)', () => { + it('should return app JWT when URL has no org or repo', async () => { + const { token, headers, type } = await github.getCredentials({ + url: 'https://github.com', + }); + + expect(type).toEqual('app'); + expect(token).toEqual('mock-jwt-token'); + expect(headers).toEqual({ + Authorization: 'Bearer mock-jwt-token', + }); + }); + + it('should return app JWT with multiple apps configured', async () => { + const multiAppProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [ + { + appId: 1, + privateKey: 'privateKey', + webhookSecret: '123', + clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + }, + { + appId: 2, + privateKey: 'privateKey2', + webhookSecret: '456', + clientId: 'CLIENT_ID_2', + clientSecret: 'CLIENT_SECRET_2', + }, + ], + }); + + const { token, type } = await multiAppProvider.getCredentials({ + url: 'https://github.com', + }); + + expect(type).toEqual('app'); + expect(token).toBeDefined(); + }); + + it('should fall back to configured token when no apps are configured and URL has no org', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + apps: [], + token: 'fallback_token', + }); + + const { token, type } = await githubProvider.getCredentials({ + url: 'https://github.com', + }); + + expect(type).toEqual('token'); + expect(token).toEqual('fallback_token'); + }); + + it('should return undefined token when no apps and no token configured for bare host URL', async () => { + const githubProvider = SingleInstanceGithubCredentialsProvider.create({ + host: 'github.com', + }); + + const { token, headers, type } = await githubProvider.getCredentials({ + url: 'https://github.com', + }); + + expect(type).toEqual('token'); + expect(token).toBeUndefined(); + expect(headers).toBeUndefined(); + }); + }); }); diff --git a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts index 40d166b223..f48434bbf6 100644 --- a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts +++ b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts @@ -124,6 +124,17 @@ class GithubAppManager { owner: string, repo?: string, ): Promise<{ accessToken: string | undefined }> { + // No owner means a bare host URL (e.g. https://github.com) — return an + // app-level JWT rather than an installation token. + if (!owner) { + const auth = createAppAuth({ + appId: this.baseAuthConfig.appId, + privateKey: this.baseAuthConfig.privateKey, + }); + const { token } = await auth({ type: 'app' }); + return { accessToken: token }; + } + if (this.allowedInstallationOwners) { if ( !this.allowedInstallationOwners?.includes( From f55c195f039802376a753ee9adad6e84f953e1f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 30 Mar 2026 21:42:51 +0200 Subject: [PATCH 060/191] Use lodash sortBy for entity sorting to avoid repeated presentation lookups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Fredrik Adelöw --- .../src/components/CatalogTable/CatalogTable.tsx | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index a57a5f3d31..6c55ff21a3 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -42,7 +42,7 @@ import Typography from '@material-ui/core/Typography'; import { visuallyHidden } from '@mui/utils'; import Edit from '@material-ui/icons/Edit'; import OpenInNew from '@material-ui/icons/OpenInNew'; -import { capitalize } from 'lodash'; +import { capitalize, sortBy } from 'lodash'; import pluralize from 'pluralize'; import { ReactNode, useMemo } from 'react'; import { columnFactories } from './columns'; @@ -86,10 +86,8 @@ function getTitle( return defaultEntityPresentation(entityOrRef, context).primaryTitle; } -const refCompare = (a: Entity, b: Entity, api?: EntityPresentationApi) => { - return getTitle(a, { defaultKind: 'Component' }, api).localeCompare( - getTitle(b, { defaultKind: 'Component' }, api), - ); +const sortEntities = (entities: Entity[], api?: EntityPresentationApi) => { + return sortBy(entities, e => getTitle(e, { defaultKind: 'Component' }, api)); }; /** @@ -269,9 +267,9 @@ export const CatalogTable = (props: CatalogTableProps) => { ); } - const rows = entities - .sort((a, b) => refCompare(a, b, entityPresentationApi)) - .map(e => toEntityRow(e, entityPresentationApi)); + const rows = sortEntities(entities, entityPresentationApi).map(e => + toEntityRow(e, entityPresentationApi), + ); const pageSize = 20; const showPagination = rows.length > pageSize; From d96b7281dcacb00b1b3a1f6deb1f3105b854b09e Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 30 Mar 2026 21:59:27 +0200 Subject: [PATCH 061/191] Make owner parameter optional in getAppToken and getInstallationCredentials methods Signed-off-by: Vincenzo Scamporlino --- packages/integration/report.api.md | 2 +- .../src/github/SingleInstanceGithubCredentialsProvider.ts | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/integration/report.api.md b/packages/integration/report.api.md index df1c7e9112..af28eeebea 100644 --- a/packages/integration/report.api.md +++ b/packages/integration/report.api.md @@ -602,7 +602,7 @@ export class GithubAppCredentialsMux { RestEndpointMethodTypes['apps']['listInstallations']['response']['data'] >; // (undocumented) - getAppToken(owner: string, repo?: string): Promise; + getAppToken(owner?: string, repo?: string): Promise; } // @public diff --git a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts index f48434bbf6..3ecaa05e0c 100644 --- a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts +++ b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts @@ -121,7 +121,7 @@ class GithubAppManager { } async getInstallationCredentials( - owner: string, + owner?: string, repo?: string, ): Promise<{ accessToken: string | undefined }> { // No owner means a bare host URL (e.g. https://github.com) — return an @@ -267,7 +267,10 @@ export class GithubAppCredentialsMux { return installs.flat(); } - async getAppToken(owner: string, repo?: string): Promise { + async getAppToken( + owner?: string, + repo?: string, + ): Promise { if (this.apps.length === 0) { return undefined; } From 2e5c5f85b2b36f9062adc624e2a90a9445f1be73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 30 Mar 2026 23:02:37 +0200 Subject: [PATCH 062/191] Bump glob to v13 and rollup to v4.59+ to fix security vulnerabilities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses the high severity rollup path traversal vulnerability (GHSA-mw96-cpmx-2vgc) and the glob security advisory by upgrading all instances across the monorepo. Updates code that used the legacy callback-based glob API to use the modern promise/sync API. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Fredrik Adelöw --- .changeset/bump-glob-rollup-security.md | 11 + packages/cli-module-auth/package.json | 2 +- .../cli-module-auth/src/commands/login.ts | 4 +- packages/cli-module-build/package.json | 4 +- packages/cli-module-test-jest/config/jest.js | 2 +- packages/cli-module-test-jest/package.json | 2 +- packages/cli/package.json | 2 +- packages/repo-tools/package.json | 2 +- packages/ui/package.json | 2 +- plugins/catalog-backend/package.json | 3 +- .../src/processors/FileReaderProcessor.ts | 5 +- yarn.lock | 274 +++++++++--------- 12 files changed, 158 insertions(+), 155 deletions(-) create mode 100644 .changeset/bump-glob-rollup-security.md diff --git a/.changeset/bump-glob-rollup-security.md b/.changeset/bump-glob-rollup-security.md new file mode 100644 index 0000000000..1738169fd1 --- /dev/null +++ b/.changeset/bump-glob-rollup-security.md @@ -0,0 +1,11 @@ +--- +'@backstage/plugin-catalog-backend': patch +'@backstage/cli-module-auth': patch +'@backstage/cli-module-build': patch +'@backstage/cli-module-test-jest': patch +'@backstage/cli': patch +'@backstage/ui': patch +'@backstage/repo-tools': patch +--- + +Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc). diff --git a/packages/cli-module-auth/package.json b/packages/cli-module-auth/package.json index 17f502d03b..4976fe722a 100644 --- a/packages/cli-module-auth/package.json +++ b/packages/cli-module-auth/package.json @@ -37,7 +37,7 @@ "@backstage/errors": "workspace:^", "cleye": "^2.3.0", "fs-extra": "^11.2.0", - "glob": "^7.1.7", + "glob": "^13.0.0", "inquirer": "^8.2.0", "proper-lockfile": "^4.1.2", "yaml": "^2.0.0", diff --git a/packages/cli-module-auth/src/commands/login.ts b/packages/cli-module-auth/src/commands/login.ts index 6bb8751147..5320bae726 100644 --- a/packages/cli-module-auth/src/commands/login.ts +++ b/packages/cli-module-auth/src/commands/login.ts @@ -31,7 +31,7 @@ import { getSecretStore, getAuthInstanceService } from '@internal/cli'; import crypto from 'node:crypto'; import fs from 'fs-extra'; import path from 'node:path'; -import glob from 'glob'; +import { globSync } from 'glob'; import YAML from 'yaml'; import inquirer from 'inquirer'; @@ -178,7 +178,7 @@ async function pickBaseUrl() { 'packages/*/app-config.yaml', 'packages/*/app-config.*.yaml', ]; - const files = patterns.flatMap(p => glob.sync(p, { cwd, nodir: true })); + const files = patterns.flatMap(p => globSync(p, { cwd, nodir: true })); for (const file of files) { try { const content = await fs.readFile(path.resolve(cwd, file), 'utf8'); diff --git a/packages/cli-module-build/package.json b/packages/cli-module-build/package.json index f7c97d35f2..ce8a9f8a7e 100644 --- a/packages/cli-module-build/package.json +++ b/packages/cli-module-build/package.json @@ -70,7 +70,7 @@ "eslint-webpack-plugin": "^4.2.0", "fork-ts-checker-webpack-plugin": "^9.0.0", "fs-extra": "^11.2.0", - "glob": "^7.1.7", + "glob": "^13.0.0", "html-webpack-plugin": "^5.6.3", "lodash": "^4.17.21", "mini-css-extract-plugin": "^2.4.2", @@ -83,7 +83,7 @@ "raw-loader": "^4.0.2", "react-dev-utils": "^12.0.0-next.60", "react-refresh": "^0.18.0", - "rollup": "^4.27.3", + "rollup": "^4.59.0", "rollup-plugin-dts": "^6.1.0", "rollup-plugin-esbuild": "^6.1.1", "rollup-plugin-postcss": "^4.0.0", diff --git a/packages/cli-module-test-jest/config/jest.js b/packages/cli-module-test-jest/config/jest.js index 8a88483d42..52ccf989c4 100644 --- a/packages/cli-module-test-jest/config/jest.js +++ b/packages/cli-module-test-jest/config/jest.js @@ -17,7 +17,7 @@ const fs = require('fs-extra'); const path = require('node:path'); const crypto = require('node:crypto'); -const glob = require('node:util').promisify(require('glob')); +const { glob } = require('glob'); const { version } = require('../package.json'); const paths = require('@backstage/cli-common').findPaths(process.cwd()); const { diff --git a/packages/cli-module-test-jest/package.json b/packages/cli-module-test-jest/package.json index a1dccac101..0218bca911 100644 --- a/packages/cli-module-test-jest/package.json +++ b/packages/cli-module-test-jest/package.json @@ -40,7 +40,7 @@ "cleye": "^2.3.0", "cross-fetch": "^4.0.0", "fs-extra": "^11.2.0", - "glob": "^7.1.7", + "glob": "^13.0.0", "jest-css-modules": "^2.1.0", "sucrase": "^3.20.2", "yargs": "^16.2.0" diff --git a/packages/cli/package.json b/packages/cli/package.json index ab6a1f0ca2..9cabc2ee4d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -71,7 +71,7 @@ "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-unused-imports": "^4.1.4", "fs-extra": "^11.2.0", - "glob": "^7.1.7", + "glob": "^13.0.0", "jest-css-modules": "^2.1.0", "pirates": "^4.0.6", "postcss": "^8.1.0", diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index 27e6cdf6b7..cd1ba32c6b 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -69,7 +69,7 @@ "command-exists": "^1.2.9", "commander": "^14.0.3", "fs-extra": "^11.2.0", - "glob": "^8.0.3", + "glob": "^13.0.0", "globby": "^11.0.0", "is-glob": "^4.0.3", "js-yaml": "^4.1.0", diff --git a/packages/ui/package.json b/packages/ui/package.json index 6d99d851b7..0be76edf5d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -64,7 +64,7 @@ "@types/react-dom": "^18.0.0", "@types/use-sync-external-store": "^0.0.6", "eslint-plugin-storybook": "^10.3.3", - "glob": "^11.0.1", + "glob": "^13.0.0", "globals": "^17.0.0", "react": "^18.0.2", "react-dom": "^18.0.2", diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index d4a1298027..0bcf0fa9a2 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -85,7 +85,7 @@ "fast-json-stable-stringify": "^2.1.0", "fs-extra": "^11.2.0", "git-url-parse": "^15.0.0", - "glob": "^7.1.6", + "glob": "^13.0.0", "knex": "^3.0.0", "lodash": "^4.17.21", "luxon": "^3.0.0", @@ -107,7 +107,6 @@ "@types/core-js": "^2.5.4", "@types/express": "^4.17.6", "@types/git-url-parse": "^9.0.0", - "@types/glob": "^8.0.0", "@types/lodash": "^4.14.151", "@types/supertest": "^2.0.8", "better-sqlite3": "^12.0.0", diff --git a/plugins/catalog-backend/src/processors/FileReaderProcessor.ts b/plugins/catalog-backend/src/processors/FileReaderProcessor.ts index 4c692ead93..12648aa96d 100644 --- a/plugins/catalog-backend/src/processors/FileReaderProcessor.ts +++ b/plugins/catalog-backend/src/processors/FileReaderProcessor.ts @@ -15,9 +15,8 @@ */ import fs from 'fs-extra'; -import g from 'glob'; +import { glob } from 'glob'; import path from 'node:path'; -import { promisify } from 'node:util'; import { LocationSpec } from '@backstage/plugin-catalog-common'; import { CatalogProcessor, @@ -26,8 +25,6 @@ import { processingResult, } from '@backstage/plugin-catalog-node'; -const glob = promisify(g); - const LOCATION_TYPE = 'file'; /** @public */ diff --git a/yarn.lock b/yarn.lock index 6469dc403b..a882c51e18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2851,7 +2851,7 @@ __metadata: "@types/proper-lockfile": "npm:^4" cleye: "npm:^2.3.0" fs-extra: "npm:^11.2.0" - glob: "npm:^7.1.7" + glob: "npm:^13.0.0" inquirer: "npm:^8.2.0" keytar: "npm:^7.9.0" proper-lockfile: "npm:^4.1.2" @@ -2905,7 +2905,7 @@ __metadata: eslint-webpack-plugin: "npm:^4.2.0" fork-ts-checker-webpack-plugin: "npm:^9.0.0" fs-extra: "npm:^11.2.0" - glob: "npm:^7.1.7" + glob: "npm:^13.0.0" html-webpack-plugin: "npm:^5.6.3" lodash: "npm:^4.17.21" mini-css-extract-plugin: "npm:^2.4.2" @@ -2918,7 +2918,7 @@ __metadata: raw-loader: "npm:^4.0.2" react-dev-utils: "npm:^12.0.0-next.60" react-refresh: "npm:^0.18.0" - rollup: "npm:^4.27.3" + rollup: "npm:^4.59.0" rollup-plugin-dts: "npm:^6.1.0" rollup-plugin-esbuild: "npm:^6.1.1" rollup-plugin-postcss: "npm:^4.0.0" @@ -3108,7 +3108,7 @@ __metadata: cleye: "npm:^2.3.0" cross-fetch: "npm:^4.0.0" fs-extra: "npm:^11.2.0" - glob: "npm:^7.1.7" + glob: "npm:^13.0.0" jest-css-modules: "npm:^2.1.0" sucrase: "npm:^3.20.2" yargs: "npm:^16.2.0" @@ -3232,7 +3232,7 @@ __metadata: eslint-plugin-react-hooks: "npm:^5.0.0" eslint-plugin-unused-imports: "npm:^4.1.4" fs-extra: "npm:^11.2.0" - glob: "npm:^7.1.7" + glob: "npm:^13.0.0" jest: "npm:^30.2.0" jest-css-modules: "npm:^2.1.0" jsdom: "npm:^27.1.0" @@ -5139,7 +5139,6 @@ __metadata: "@types/core-js": "npm:^2.5.4" "@types/express": "npm:^4.17.6" "@types/git-url-parse": "npm:^9.0.0" - "@types/glob": "npm:^8.0.0" "@types/lodash": "npm:^4.14.151" "@types/supertest": "npm:^2.0.8" better-sqlite3: "npm:^12.0.0" @@ -5149,7 +5148,7 @@ __metadata: fast-json-stable-stringify: "npm:^2.1.0" fs-extra: "npm:^11.2.0" git-url-parse: "npm:^15.0.0" - glob: "npm:^7.1.6" + glob: "npm:^13.0.0" knex: "npm:^3.0.0" lodash: "npm:^4.17.21" luxon: "npm:^3.0.0" @@ -7823,7 +7822,7 @@ __metadata: command-exists: "npm:^1.2.9" commander: "npm:^14.0.3" fs-extra: "npm:^11.2.0" - glob: "npm:^8.0.3" + glob: "npm:^13.0.0" globby: "npm:^11.0.0" is-glob: "npm:^4.0.3" js-yaml: "npm:^4.1.0" @@ -7951,7 +7950,7 @@ __metadata: "@types/use-sync-external-store": "npm:^0.0.6" clsx: "npm:^2.1.1" eslint-plugin-storybook: "npm:^10.3.3" - glob: "npm:^11.0.1" + glob: "npm:^13.0.0" globals: "npm:^17.0.0" react: "npm:^18.0.2" react-aria: "npm:^3.47.0" @@ -17206,156 +17205,177 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.53.3" +"@rollup/rollup-android-arm-eabi@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.60.1" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-android-arm64@npm:4.53.3" +"@rollup/rollup-android-arm64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-android-arm64@npm:4.60.1" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-darwin-arm64@npm:4.53.3" +"@rollup/rollup-darwin-arm64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-darwin-arm64@npm:4.60.1" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-darwin-x64@npm:4.53.3" +"@rollup/rollup-darwin-x64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-darwin-x64@npm:4.60.1" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-freebsd-arm64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-freebsd-arm64@npm:4.53.3" +"@rollup/rollup-freebsd-arm64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.60.1" conditions: os=freebsd & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-freebsd-x64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-freebsd-x64@npm:4.53.3" +"@rollup/rollup-freebsd-x64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-freebsd-x64@npm:4.60.1" conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.53.3" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.60.1" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.53.3" +"@rollup/rollup-linux-arm-musleabihf@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.60.1" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.53.3" +"@rollup/rollup-linux-arm64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.60.1" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.53.3" +"@rollup/rollup-linux-arm64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.60.1" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-loong64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.53.3" +"@rollup/rollup-linux-loong64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.60.1" conditions: os=linux & cpu=loong64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-ppc64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.53.3" +"@rollup/rollup-linux-loong64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-loong64-musl@npm:4.60.1" + conditions: os=linux & cpu=loong64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.60.1" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.53.3" +"@rollup/rollup-linux-ppc64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.60.1" + conditions: os=linux & cpu=ppc64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-riscv64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.60.1" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-musl@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.53.3" +"@rollup/rollup-linux-riscv64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.60.1" conditions: os=linux & cpu=riscv64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.53.3" +"@rollup/rollup-linux-s390x-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.60.1" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.53.3" +"@rollup/rollup-linux-x64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.60.1" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.53.3" +"@rollup/rollup-linux-x64-musl@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.60.1" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-openharmony-arm64@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-openharmony-arm64@npm:4.53.3" +"@rollup/rollup-openbsd-x64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-openbsd-x64@npm:4.60.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.60.1" conditions: os=openharmony & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.53.3" +"@rollup/rollup-win32-arm64-msvc@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.60.1" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.53.3" +"@rollup/rollup-win32-ia32-msvc@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.60.1" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-gnu@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-win32-x64-gnu@npm:4.53.3" +"@rollup/rollup-win32-x64-gnu@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.60.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.53.3": - version: 4.53.3 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.53.3" +"@rollup/rollup-win32-x64-msvc@npm:4.60.1": + version: 4.60.1 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.60.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -21191,16 +21211,6 @@ __metadata: languageName: node linkType: hard -"@types/glob@npm:^8.0.0": - version: 8.1.0 - resolution: "@types/glob@npm:8.1.0" - dependencies: - "@types/minimatch": "npm:^5.1.2" - "@types/node": "npm:*" - checksum: 10/9101f3a9061e40137190f70626aa0e202369b5ec4012c3fabe6f5d229cce04772db9a94fa5a0eb39655e2e4ad105c38afbb4af56a56c0996a8c7d4fc72350e3d - languageName: node - linkType: hard - "@types/global-agent@npm:^2.1.3": version: 2.1.3 resolution: "@types/global-agent@npm:2.1.3" @@ -21600,13 +21610,6 @@ __metadata: languageName: node linkType: hard -"@types/minimatch@npm:^5.1.2": - version: 5.1.2 - resolution: "@types/minimatch@npm:5.1.2" - checksum: 10/94db5060d20df2b80d77b74dd384df3115f01889b5b6c40fa2dfa27cfc03a68fb0ff7c1f2a0366070263eb2e9d6bfd8c87111d4bc3ae93c3f291297c1bf56c85 - languageName: node - linkType: hard - "@types/minimist@npm:^1.2.5": version: 1.2.5 resolution: "@types/minimist@npm:1.2.5" @@ -31583,7 +31586,7 @@ __metadata: languageName: node linkType: hard -"foreground-child@npm:^3.1.0, foreground-child@npm:^3.3.1": +"foreground-child@npm:^3.1.0": version: 3.3.1 resolution: "foreground-child@npm:3.3.1" dependencies: @@ -32416,22 +32419,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:^11.0.1": - version: 11.1.0 - resolution: "glob@npm:11.1.0" - dependencies: - foreground-child: "npm:^3.3.1" - jackspeak: "npm:^4.1.1" - minimatch: "npm:^10.1.1" - minipass: "npm:^7.1.2" - package-json-from-dist: "npm:^1.0.0" - path-scurry: "npm:^2.0.0" - bin: - glob: dist/esm/bin.mjs - checksum: 10/da4501819633daff8822c007bb3f93d5c4d2cbc7b15a8e886660f4497dd251a1fb4f53a85fba1e760b31704eff7164aeb2c7a82db10f9f2c362d12c02fe52cf3 - languageName: node - linkType: hard - "glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6, glob@npm:^7.1.7, glob@npm:^7.2.3": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -32446,7 +32433,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^8.0.1, glob@npm:^8.0.3, glob@npm:^8.1.0": +"glob@npm:^8.0.1, glob@npm:^8.1.0": version: 8.1.0 resolution: "glob@npm:8.1.0" dependencies: @@ -35186,7 +35173,7 @@ __metadata: languageName: node linkType: hard -"jackspeak@npm:^4.1.1, jackspeak@npm:^4.2.3": +"jackspeak@npm:^4.2.3": version: 4.2.3 resolution: "jackspeak@npm:4.2.3" dependencies: @@ -38701,7 +38688,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^10.0.0, minimatch@npm:^10.1.1, minimatch@npm:^10.2.1, minimatch@npm:^10.2.2, minimatch@npm:^10.2.4": +"minimatch@npm:^10.0.0, minimatch@npm:^10.2.1, minimatch@npm:^10.2.2, minimatch@npm:^10.2.4": version: 10.2.4 resolution: "minimatch@npm:10.2.4" dependencies: @@ -41385,7 +41372,7 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^2.0.0, path-scurry@npm:^2.0.2": +"path-scurry@npm:^2.0.2": version: 2.0.2 resolution: "path-scurry@npm:2.0.2" dependencies: @@ -45017,32 +45004,35 @@ __metadata: languageName: node linkType: hard -"rollup@npm:^4.27.3, rollup@npm:^4.43.0": - version: 4.53.3 - resolution: "rollup@npm:4.53.3" +"rollup@npm:^4.43.0, rollup@npm:^4.59.0": + version: 4.60.1 + resolution: "rollup@npm:4.60.1" dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.53.3" - "@rollup/rollup-android-arm64": "npm:4.53.3" - "@rollup/rollup-darwin-arm64": "npm:4.53.3" - "@rollup/rollup-darwin-x64": "npm:4.53.3" - "@rollup/rollup-freebsd-arm64": "npm:4.53.3" - "@rollup/rollup-freebsd-x64": "npm:4.53.3" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.53.3" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.53.3" - "@rollup/rollup-linux-arm64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-arm64-musl": "npm:4.53.3" - "@rollup/rollup-linux-loong64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-ppc64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-riscv64-musl": "npm:4.53.3" - "@rollup/rollup-linux-s390x-gnu": "npm:4.53.3" - "@rollup/rollup-linux-x64-gnu": "npm:4.53.3" - "@rollup/rollup-linux-x64-musl": "npm:4.53.3" - "@rollup/rollup-openharmony-arm64": "npm:4.53.3" - "@rollup/rollup-win32-arm64-msvc": "npm:4.53.3" - "@rollup/rollup-win32-ia32-msvc": "npm:4.53.3" - "@rollup/rollup-win32-x64-gnu": "npm:4.53.3" - "@rollup/rollup-win32-x64-msvc": "npm:4.53.3" + "@rollup/rollup-android-arm-eabi": "npm:4.60.1" + "@rollup/rollup-android-arm64": "npm:4.60.1" + "@rollup/rollup-darwin-arm64": "npm:4.60.1" + "@rollup/rollup-darwin-x64": "npm:4.60.1" + "@rollup/rollup-freebsd-arm64": "npm:4.60.1" + "@rollup/rollup-freebsd-x64": "npm:4.60.1" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.60.1" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.60.1" + "@rollup/rollup-linux-arm64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-arm64-musl": "npm:4.60.1" + "@rollup/rollup-linux-loong64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-loong64-musl": "npm:4.60.1" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-ppc64-musl": "npm:4.60.1" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-riscv64-musl": "npm:4.60.1" + "@rollup/rollup-linux-s390x-gnu": "npm:4.60.1" + "@rollup/rollup-linux-x64-gnu": "npm:4.60.1" + "@rollup/rollup-linux-x64-musl": "npm:4.60.1" + "@rollup/rollup-openbsd-x64": "npm:4.60.1" + "@rollup/rollup-openharmony-arm64": "npm:4.60.1" + "@rollup/rollup-win32-arm64-msvc": "npm:4.60.1" + "@rollup/rollup-win32-ia32-msvc": "npm:4.60.1" + "@rollup/rollup-win32-x64-gnu": "npm:4.60.1" + "@rollup/rollup-win32-x64-msvc": "npm:4.60.1" "@types/estree": "npm:1.0.8" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -45068,8 +45058,12 @@ __metadata: optional: true "@rollup/rollup-linux-loong64-gnu": optional: true + "@rollup/rollup-linux-loong64-musl": + optional: true "@rollup/rollup-linux-ppc64-gnu": optional: true + "@rollup/rollup-linux-ppc64-musl": + optional: true "@rollup/rollup-linux-riscv64-gnu": optional: true "@rollup/rollup-linux-riscv64-musl": @@ -45080,6 +45074,8 @@ __metadata: optional: true "@rollup/rollup-linux-x64-musl": optional: true + "@rollup/rollup-openbsd-x64": + optional: true "@rollup/rollup-openharmony-arm64": optional: true "@rollup/rollup-win32-arm64-msvc": @@ -45094,7 +45090,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10/e2eff82405061fa907f15dfbf742b1f5fb4b214495c00989bcdbe21da5fcb3f6dec3deabacec491300a53c99da409586cfc77bdf29b411fccb9089b72cd3728d + checksum: 10/6866a35efc999990e191fc954a859ba802d13be63ca13b04746459455982f6b8784d92e5eea8db3ef8acf8baba8c43e8e6cb741f3233ba4c46adf148d3708a9c languageName: node linkType: hard From 8435ce9a81426ef5bd51d18ab03679549307ec07 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Tue, 31 Mar 2026 07:59:05 +0300 Subject: [PATCH 063/191] docs(ai): add skills documentation based on the BEP-0013 add the necessary docs for using and contributing AI skills Signed-off-by: Hellgren Heikki --- docs/ai/skills.md | 93 +++++++++++++++++++++++++++++++++++++++++++ microsite/sidebars.ts | 2 +- 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 docs/ai/skills.md diff --git a/docs/ai/skills.md b/docs/ai/skills.md new file mode 100644 index 0000000000..b983fe6421 --- /dev/null +++ b/docs/ai/skills.md @@ -0,0 +1,93 @@ +--- +id: skills +title: AI Skills +description: Reusable AI skills for common Backstage development tasks. +--- + +Backstage publishes a set of curated _AI skills_ — self-contained guidance files that teach an AI coding assistant how to perform common Backstage engineering tasks. Skills are published to a [well-known endpoint](https://backstage.io/.well-known/skills/) on `backstage.io` and can be installed into your repository with the [`skills.sh`](https://skills.sh/) tool. + +## Installing Skills + +You need [Node.js](https://nodejs.org/) to run `npx`. + +```bash +npx skills add https://backstage.io +``` + +This command reads the published index from `https://backstage.io/.well-known/skills/index.json` and allows you to select which of the available skills you want to install into your repository. + +### Where skills are installed + +`skills.sh` copies skill files into your repository under a directory it manages (typically `.github/skills/` or a similar location depending on your configuration). Refer to the [`skills.sh` documentation](https://skills.sh/) for details on target paths and how to customize them. + +After installation, you can modify the installed files to adapt them to your project's conventions. Subsequent updates from `npx skills add` will offer to merge upstream changes. + +### Using Skills with Your AI Assistant + +Once a skill is installed in your repository, attach or reference the relevant `SKILL.md` file when starting a task with your AI coding assistant. Most AI assistants in editors such as VS Code will automatically pick up instruction files that are committed to your repository. + +For example, when migrating MUI imports in a plugin, include the `mui-to-bui-migration` skill so the assistant follows the correct component mapping and import patterns. + +## Contributing New Skills + +Skills are authored in the Backstage monorepo at `docs/.well-known/skills/`. Each skill lives in its own subdirectory and must include a `SKILL.md` file as the primary entry point. + +### Skill directory layout + +```text +docs/.well-known/skills/ + index.json # Published index of all skills + / + SKILL.md # Primary skill entry point (required) + +``` + +### Writing a SKILL.md + +A `SKILL.md` file must include a YAML front matter block with the following fields: + +```markdown +--- +name: +description: +--- + +# Skill Title + +Introductory paragraph explaining when and why to use this skill. + +... +``` + +The `name` must match the directory name. The `description` is shown to users when they browse or install skills and should be one to two sentences describing the task the skill covers. + +Keep skills focused on a single, well-defined task. A skill that tries to cover too many scenarios is harder to use effectively. Prefer concrete, step-by-step guidance, working code examples, and explicit notes about common pitfalls. + +### Registering the skill in the index + +Add an entry to `docs/.well-known/skills/index.json`: + +```json +{ + "skills": [ + { + "name": "", + "description": "", + "files": ["SKILL.md"] + } + ] +} +``` + +If your skill includes additional supporting files, list each one in the `files` array. + +### Review process + +All changes to skills go through the standard Backstage pull request process. When authoring or reviewing a skill, consider: + +- **Accuracy** — Does the skill reflect current Backstage APIs and conventions? +- **Completeness** — Does it cover the most common cases a developer will encounter? +- **Safety** — Does it avoid patterns that could introduce security or correctness issues? +- **Scope** — Is the skill focused on a single task, or should it be split? + +Skills are part of the published Backstage documentation surface, so they follow the same contribution guidelines as the rest of the docs. See [CONTRIBUTING.md](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md) for the full contribution process. diff --git a/microsite/sidebars.ts b/microsite/sidebars.ts index 0c290ad6d8..09d25dfccf 100644 --- a/microsite/sidebars.ts +++ b/microsite/sidebars.ts @@ -127,7 +127,7 @@ export default { description: 'Features in Backstage you can leverage with your AI tools.', }, - ['ai/mcp-actions', 'ai/well-known-actions'], + ['ai/skills', 'ai/mcp-actions', 'ai/well-known-actions'], ), sidebarElementWithIndex( { From a3c65c8e2aec06cb2646587abf38a6258de0fee7 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Tue, 31 Mar 2026 13:27:17 +0200 Subject: [PATCH 064/191] add a note about the backstage version in the fe-migration skill Signed-off-by: Peter Macdonald --- .../skills/plugin-full-frontend-system-migration/SKILL.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/.well-known/skills/plugin-full-frontend-system-migration/SKILL.md b/docs/.well-known/skills/plugin-full-frontend-system-migration/SKILL.md index b8786211c3..55d6727732 100644 --- a/docs/.well-known/skills/plugin-full-frontend-system-migration/SKILL.md +++ b/docs/.well-known/skills/plugin-full-frontend-system-migration/SKILL.md @@ -9,6 +9,8 @@ This skill helps fully migrate an existing Backstage plugin from the old fronten This is the preferred approach for internal plugins that are only used in a single app, since there is no need to maintain backward compatibility. It can also be used for published plugins when you're ready to drop old system support entirely. +It is highly recommended to be on Backstage version 1.49.x or above. This can be verified by looking in the `backstage.json` file in the root of the repository. + ## Key Differences from Dual Support | Aspect | Dual Support | Full Migration | From 4a1b7f228db97c572523ff7e24dc8383db9b8fc6 Mon Sep 17 00:00:00 2001 From: Peter Macdonald Date: Tue, 31 Mar 2026 13:30:00 +0200 Subject: [PATCH 065/191] update wording Signed-off-by: Peter Macdonald --- .../skills/plugin-full-frontend-system-migration/SKILL.md | 2 +- .../skills/plugin-new-frontend-system-support/SKILL.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/.well-known/skills/plugin-full-frontend-system-migration/SKILL.md b/docs/.well-known/skills/plugin-full-frontend-system-migration/SKILL.md index 55d6727732..8f7ee3498b 100644 --- a/docs/.well-known/skills/plugin-full-frontend-system-migration/SKILL.md +++ b/docs/.well-known/skills/plugin-full-frontend-system-migration/SKILL.md @@ -9,7 +9,7 @@ This skill helps fully migrate an existing Backstage plugin from the old fronten This is the preferred approach for internal plugins that are only used in a single app, since there is no need to maintain backward compatibility. It can also be used for published plugins when you're ready to drop old system support entirely. -It is highly recommended to be on Backstage version 1.49.x or above. This can be verified by looking in the `backstage.json` file in the root of the repository. +It is highly recommended to be on Backstage version 1.49.x or above before starting this, although not mandatory, you may face issues with some of the instructions below. This can be verified by looking in the `backstage.json` file in the root of the repository. ## Key Differences from Dual Support diff --git a/docs/.well-known/skills/plugin-new-frontend-system-support/SKILL.md b/docs/.well-known/skills/plugin-new-frontend-system-support/SKILL.md index 2bb0bf4da3..2912215327 100644 --- a/docs/.well-known/skills/plugin-new-frontend-system-support/SKILL.md +++ b/docs/.well-known/skills/plugin-new-frontend-system-support/SKILL.md @@ -9,6 +9,8 @@ This skill helps add new frontend system (NFS) support to an existing Backstage This is the preferred approach for published plugins or plugins that are used by external parties, since it avoids forcing consumers to migrate their app before they are ready. +It is highly recommended to be on Backstage version 1.49.x or above before starting this, although not mandatory, you may face issues with some of the instructions below. This can be verified by looking in the `backstage.json` file in the root of the repository. + ## Key Concepts - **Dual entry point:** The plugin keeps its existing `src/plugin.ts` (old system) and adds a new `src/alpha.tsx` (new system) From feaf3d1adec0cc8b41c1057f7c6186b2f03f0e31 Mon Sep 17 00:00:00 2001 From: Johan Persson Date: Tue, 31 Mar 2026 10:06:30 +0200 Subject: [PATCH 066/191] fix(ui): fix HeaderNav hover indicator covering tab text Add `position: relative` and `z-index: 2` to nav items so they paint above the hover/active indicator, matching the Tabs pattern. This fixes themes with opaque `--bui-bg-neutral-2` values obscuring tab labels on hover. Also fix `--bui-font-family` (non-existent) to `--bui-font-regular`. Signed-off-by: Johan Persson --- .changeset/wicked-impalas-fry.md | 7 +++++++ packages/ui/src/components/Header/HeaderNav.module.css | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 .changeset/wicked-impalas-fry.md diff --git a/.changeset/wicked-impalas-fry.md b/.changeset/wicked-impalas-fry.md new file mode 100644 index 0000000000..35633b51e2 --- /dev/null +++ b/.changeset/wicked-impalas-fry.md @@ -0,0 +1,7 @@ +--- +'@backstage/ui': patch +--- + +Fixed HeaderNav hover indicator covering tab text when theme uses opaque background colors. Also fixed an incorrect CSS variable reference (`--bui-font-family` → `--bui-font-regular`). + +**Affected components:** Header diff --git a/packages/ui/src/components/Header/HeaderNav.module.css b/packages/ui/src/components/Header/HeaderNav.module.css index 992e989a46..d2446fd8c0 100644 --- a/packages/ui/src/components/Header/HeaderNav.module.css +++ b/packages/ui/src/components/Header/HeaderNav.module.css @@ -50,7 +50,7 @@ .bui-HeaderNavItem, .bui-HeaderNavGroup { - font-family: var(--bui-font-family); + font-family: var(--bui-font-regular); font-size: var(--bui-font-size-3); font-weight: var(--bui-font-weight-regular); color: var(--bui-fg-secondary); @@ -61,6 +61,8 @@ padding-inline: var(--bui-space-2); text-decoration: none; cursor: pointer; + position: relative; + z-index: 2; border: none; background: none; outline: none; From e38bcefb99933586c9d697afcffc911417bf4696 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 30 Mar 2026 23:22:03 +0200 Subject: [PATCH 067/191] Fix template name collision between new and legacy frontend plugin templates The legacy frontend plugin template had the same name (`frontend-plugin`) as the new frontend plugin template, causing a conflict error when both were shown (e.g. when the frontend system could not be auto-detected). Rename the legacy template to `frontend-plugin-legacy` so both can coexist. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Vincenzo Scamporlino --- .changeset/angry-clouds-tell.md | 2 +- .../loadPortableTemplateConfig.test.ts | 34 +++++++------------ .../preparation/loadPortableTemplateConfig.ts | 2 -- .../portable-template.yaml | 2 +- 4 files changed, 14 insertions(+), 26 deletions(-) diff --git a/.changeset/angry-clouds-tell.md b/.changeset/angry-clouds-tell.md index c2da96699f..a01306d84a 100644 --- a/.changeset/angry-clouds-tell.md +++ b/.changeset/angry-clouds-tell.md @@ -2,4 +2,4 @@ '@backstage/cli-module-new': patch --- -Rename the legacy `frontend-plugin` to `legacy-frontend-plugin` +Rename the legacy `frontend-plugin` to `frontend-plugin-legacy` diff --git a/packages/cli-module-new/src/lib/preparation/loadPortableTemplateConfig.test.ts b/packages/cli-module-new/src/lib/preparation/loadPortableTemplateConfig.test.ts index 454ece648a..5333b05739 100644 --- a/packages/cli-module-new/src/lib/preparation/loadPortableTemplateConfig.test.ts +++ b/packages/cli-module-new/src/lib/preparation/loadPortableTemplateConfig.test.ts @@ -374,14 +374,14 @@ describe('loadPortableTemplateConfig', () => { }, node_modules: Object.fromEntries( defaultTemplates.map(t => { - // Match the real behavior: both frontend-plugin and legacy-frontend-plugin - // have the same template name "frontend-plugin" const name = t.endsWith('/legacy-frontend-plugin') - ? 'frontend-plugin' + ? 'frontend-plugin-legacy' : basename(t); return [ t, - { [TEMPLATE_FILE_NAME]: `name: ${name}\nrole: web-library\n` }, + { + [TEMPLATE_FILE_NAME]: `name: ${name}\nrole: web-library\n`, + }, ]; }), ), @@ -398,14 +398,7 @@ describe('loadPortableTemplateConfig', () => { expect(templateNames).toContain('frontend-plugin-module'); expect(templateNames).toContain('backend-plugin'); // Legacy template should be filtered out - expect(templateNames).not.toContain('legacy-frontend-plugin'); - - // The frontend-plugin in the list should be from the new template, not legacy - const frontendPlugin = config.templatePointers.find( - t => t.name === 'frontend-plugin', - ); - expect(frontendPlugin?.target).toContain('/frontend-plugin/'); - expect(frontendPlugin?.target).not.toContain('/legacy-frontend-plugin/'); + expect(templateNames).not.toContain('frontend-plugin-legacy'); }); it('should filter out new frontend templates for legacy frontend system apps', async () => { @@ -424,11 +417,13 @@ describe('loadPortableTemplateConfig', () => { node_modules: Object.fromEntries( defaultTemplates.map(t => { const name = t.endsWith('/legacy-frontend-plugin') - ? 'frontend-plugin' + ? 'frontend-plugin-legacy' : basename(t); return [ t, - { [TEMPLATE_FILE_NAME]: `name: ${name}\nrole: web-library\n` }, + { + [TEMPLATE_FILE_NAME]: `name: ${name}\nrole: web-library\n`, + }, ]; }), ), @@ -441,17 +436,12 @@ describe('loadPortableTemplateConfig', () => { expect(config.isUsingDefaultTemplates).toBe(true); const templateNames = config.templatePointers.map(t => t.name); - // Legacy template should be present (shown as "frontend-plugin") - expect(templateNames).toContain('frontend-plugin'); + // Legacy template should be present + expect(templateNames).toContain('frontend-plugin-legacy'); expect(templateNames).toContain('backend-plugin'); // New frontend templates should be filtered out + expect(templateNames).not.toContain('frontend-plugin'); expect(templateNames).not.toContain('frontend-plugin-module'); - - // The frontend-plugin in the list should be from the legacy template - const frontendPlugin = config.templatePointers.find( - t => t.name === 'frontend-plugin', - ); - expect(frontendPlugin?.target).toContain('/legacy-frontend-plugin/'); }); it('should not filter templates when using explicit configuration', async () => { diff --git a/packages/cli-module-new/src/lib/preparation/loadPortableTemplateConfig.ts b/packages/cli-module-new/src/lib/preparation/loadPortableTemplateConfig.ts index fd5a92bb91..f0fb8ea5ce 100644 --- a/packages/cli-module-new/src/lib/preparation/loadPortableTemplateConfig.ts +++ b/packages/cli-module-new/src/lib/preparation/loadPortableTemplateConfig.ts @@ -177,8 +177,6 @@ export async function loadPortableTemplateConfig( ); // Auto-filter frontend templates based on detected frontend system. - // This must happen before the conflict check since both the new and legacy - // frontend plugin templates have the same name, but only one will be shown. if (isUsingDefaultTemplates) { const frontendSystem = await detectFrontendSystem(basePath); templatePointerEntries = filterTemplateEntriesForFrontendSystem( diff --git a/packages/cli-module-new/templates/legacy-frontend-plugin/portable-template.yaml b/packages/cli-module-new/templates/legacy-frontend-plugin/portable-template.yaml index ec4bd338c6..9350418967 100644 --- a/packages/cli-module-new/templates/legacy-frontend-plugin/portable-template.yaml +++ b/packages/cli-module-new/templates/legacy-frontend-plugin/portable-template.yaml @@ -1,4 +1,4 @@ -name: legacy-frontend-plugin +name: frontend-plugin-legacy role: frontend-plugin description: A new frontend plugin (legacy system) values: From 3964163dcef6be315bad8131887e72cbd7ac4aa9 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 30 Mar 2026 23:29:36 +0200 Subject: [PATCH 068/191] Fix export path in frontend plugin module template Signed-off-by: Vincenzo Scamporlino --- .../templates/frontend-plugin-module/src/index.ts.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli-module-new/templates/frontend-plugin-module/src/index.ts.hbs b/packages/cli-module-new/templates/frontend-plugin-module/src/index.ts.hbs index aec3cb2b06..ab301808ea 100644 --- a/packages/cli-module-new/templates/frontend-plugin-module/src/index.ts.hbs +++ b/packages/cli-module-new/templates/frontend-plugin-module/src/index.ts.hbs @@ -1 +1 @@ -export { {{ moduleVar }} as default } from './plugin'; +export { {{ moduleVar }} as default } from './module'; From b6c0ef5d5da814359f91d487d41978e0537256fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 31 Mar 2026 15:20:56 +0200 Subject: [PATCH 069/191] Add windowsPathsNoEscape to glob calls that may receive backslash paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Glob v13 treats backslashes as escape characters by default, unlike v7 which treated them as path separators on Windows. This broke Windows CI where path.join/resolve produce backslash paths. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Fredrik Adelöw --- .../src/commands/package/start/startPackage.ts | 5 ++++- packages/cli-module-test-jest/config/jest.js | 4 +++- packages/repo-tools/src/commands/package-docs/command.ts | 1 + .../catalog-backend/src/processors/FileReaderProcessor.ts | 4 +++- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/cli-module-build/src/commands/package/start/startPackage.ts b/packages/cli-module-build/src/commands/package/start/startPackage.ts index e0ab13facc..628034a6d8 100644 --- a/packages/cli-module-build/src/commands/package/start/startPackage.ts +++ b/packages/cli-module-build/src/commands/package/start/startPackage.ts @@ -25,7 +25,10 @@ export function resolveEntryPath( targetDir: string, ): string { const { dir: entryDir, name: entryName } = parse(entrypoint); - const [entryFile] = glob.sync(`${resolve(targetDir, entryDir, entryName)}.*`); + const [entryFile] = glob.sync( + `${resolve(targetDir, entryDir, entryName)}.*`, + { windowsPathsNoEscape: true }, + ); if (entryFile) { return join(entryDir, entryName); } diff --git a/packages/cli-module-test-jest/config/jest.js b/packages/cli-module-test-jest/config/jest.js index 52ccf989c4..7697fbb6d7 100644 --- a/packages/cli-module-test-jest/config/jest.js +++ b/packages/cli-module-test-jest/config/jest.js @@ -367,7 +367,9 @@ async function getRootConfig() { // workspace and load those in as separate jest projects instead. const projectPaths = await Promise.all( workspacePatterns.map(pattern => - glob(path.join(paths.targetRoot, pattern)), + glob(path.join(paths.targetRoot, pattern), { + windowsPathsNoEscape: true, + }), ), ).then(_ => _.flat()); diff --git a/packages/repo-tools/src/commands/package-docs/command.ts b/packages/repo-tools/src/commands/package-docs/command.ts index 3e635b34f4..777f0f5883 100644 --- a/packages/repo-tools/src/commands/package-docs/command.ts +++ b/packages/repo-tools/src/commands/package-docs/command.ts @@ -119,6 +119,7 @@ export default async function packageDocs(paths: string[] = [], opts: any) { const existingDocsJsonPaths = glob.sync( targetPaths.resolveRoot('dist-types/**/docs.json'), + { windowsPathsNoEscape: true }, ); if (existingDocsJsonPaths.length > 0) { console.warn( diff --git a/plugins/catalog-backend/src/processors/FileReaderProcessor.ts b/plugins/catalog-backend/src/processors/FileReaderProcessor.ts index 12648aa96d..5658b8e0d0 100644 --- a/plugins/catalog-backend/src/processors/FileReaderProcessor.ts +++ b/plugins/catalog-backend/src/processors/FileReaderProcessor.ts @@ -44,7 +44,9 @@ export class FileReaderProcessor implements CatalogProcessor { } try { - const fileMatches = await glob(location.target); + const fileMatches = await glob(location.target, { + windowsPathsNoEscape: true, + }); if (fileMatches.length > 0) { for (const fileMatch of fileMatches) { From 0cbba8a5289add5f8bcf0b9410b6cd6c3eb3cda0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 14:06:03 +0000 Subject: [PATCH 070/191] chore(deps): update aws-sdk-js-v3 monorepo Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 668 +++++++++++++++++++++++++++--------------------------- 1 file changed, 328 insertions(+), 340 deletions(-) diff --git a/yarn.lock b/yarn.lock index 01a68f75e1..c023cf939a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -492,221 +492,221 @@ __metadata: linkType: hard "@aws-sdk/client-codecommit@npm:^3.350.0": - version: 3.1018.0 - resolution: "@aws-sdk/client-codecommit@npm:3.1018.0" + version: 3.1020.0 + resolution: "@aws-sdk/client-codecommit@npm:3.1020.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:^3.973.25" - "@aws-sdk/credential-provider-node": "npm:^3.972.26" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-node": "npm:^3.972.28" "@aws-sdk/middleware-host-header": "npm:^3.972.8" "@aws-sdk/middleware-logger": "npm:^3.972.8" "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" - "@aws-sdk/middleware-user-agent": "npm:^3.972.26" + "@aws-sdk/middleware-user-agent": "npm:^3.972.27" "@aws-sdk/region-config-resolver": "npm:^3.972.10" "@aws-sdk/types": "npm:^3.973.6" "@aws-sdk/util-endpoints": "npm:^3.996.5" "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" - "@aws-sdk/util-user-agent-node": "npm:^3.973.12" + "@aws-sdk/util-user-agent-node": "npm:^3.973.13" "@smithy/config-resolver": "npm:^4.4.13" - "@smithy/core": "npm:^3.23.12" + "@smithy/core": "npm:^3.23.13" "@smithy/fetch-http-handler": "npm:^5.3.15" "@smithy/hash-node": "npm:^4.2.12" "@smithy/invalid-dependency": "npm:^4.2.12" "@smithy/middleware-content-length": "npm:^4.2.12" - "@smithy/middleware-endpoint": "npm:^4.4.27" - "@smithy/middleware-retry": "npm:^4.4.44" - "@smithy/middleware-serde": "npm:^4.2.15" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.45" + "@smithy/middleware-serde": "npm:^4.2.16" "@smithy/middleware-stack": "npm:^4.2.12" "@smithy/node-config-provider": "npm:^4.3.12" - "@smithy/node-http-handler": "npm:^4.5.0" + "@smithy/node-http-handler": "npm:^4.5.1" "@smithy/protocol-http": "npm:^5.3.12" - "@smithy/smithy-client": "npm:^4.12.7" + "@smithy/smithy-client": "npm:^4.12.8" "@smithy/types": "npm:^4.13.1" "@smithy/url-parser": "npm:^4.2.12" "@smithy/util-base64": "npm:^4.3.2" "@smithy/util-body-length-browser": "npm:^4.2.2" "@smithy/util-body-length-node": "npm:^4.2.3" - "@smithy/util-defaults-mode-browser": "npm:^4.3.43" - "@smithy/util-defaults-mode-node": "npm:^4.2.47" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" "@smithy/util-endpoints": "npm:^3.3.3" "@smithy/util-middleware": "npm:^4.2.12" "@smithy/util-retry": "npm:^4.2.12" "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/72dcdf5c4a6d1077575e8ff6b3fff54212a6f0ea248f1c326962af94dc24522643e2695e939f114e0e33341ee1aa8327df752d6c903dbe5ff89063205e96fd43 + checksum: 10/97aeb0bd0b2fa40e5bf5b1ebb4e4a04257f7c6d05c5480a1e07e9a1a9841d4a8799b5426b984181dc2aa77747cdb3523896fd02572dc985ef6f7e3d00a568ea4 languageName: node linkType: hard -"@aws-sdk/client-cognito-identity@npm:3.1018.0": - version: 3.1018.0 - resolution: "@aws-sdk/client-cognito-identity@npm:3.1018.0" +"@aws-sdk/client-cognito-identity@npm:3.1020.0": + version: 3.1020.0 + resolution: "@aws-sdk/client-cognito-identity@npm:3.1020.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:^3.973.25" - "@aws-sdk/credential-provider-node": "npm:^3.972.26" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-node": "npm:^3.972.28" "@aws-sdk/middleware-host-header": "npm:^3.972.8" "@aws-sdk/middleware-logger": "npm:^3.972.8" "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" - "@aws-sdk/middleware-user-agent": "npm:^3.972.26" + "@aws-sdk/middleware-user-agent": "npm:^3.972.27" "@aws-sdk/region-config-resolver": "npm:^3.972.10" "@aws-sdk/types": "npm:^3.973.6" "@aws-sdk/util-endpoints": "npm:^3.996.5" "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" - "@aws-sdk/util-user-agent-node": "npm:^3.973.12" + "@aws-sdk/util-user-agent-node": "npm:^3.973.13" "@smithy/config-resolver": "npm:^4.4.13" - "@smithy/core": "npm:^3.23.12" + "@smithy/core": "npm:^3.23.13" "@smithy/fetch-http-handler": "npm:^5.3.15" "@smithy/hash-node": "npm:^4.2.12" "@smithy/invalid-dependency": "npm:^4.2.12" "@smithy/middleware-content-length": "npm:^4.2.12" - "@smithy/middleware-endpoint": "npm:^4.4.27" - "@smithy/middleware-retry": "npm:^4.4.44" - "@smithy/middleware-serde": "npm:^4.2.15" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.45" + "@smithy/middleware-serde": "npm:^4.2.16" "@smithy/middleware-stack": "npm:^4.2.12" "@smithy/node-config-provider": "npm:^4.3.12" - "@smithy/node-http-handler": "npm:^4.5.0" + "@smithy/node-http-handler": "npm:^4.5.1" "@smithy/protocol-http": "npm:^5.3.12" - "@smithy/smithy-client": "npm:^4.12.7" + "@smithy/smithy-client": "npm:^4.12.8" "@smithy/types": "npm:^4.13.1" "@smithy/url-parser": "npm:^4.2.12" "@smithy/util-base64": "npm:^4.3.2" "@smithy/util-body-length-browser": "npm:^4.2.2" "@smithy/util-body-length-node": "npm:^4.2.3" - "@smithy/util-defaults-mode-browser": "npm:^4.3.43" - "@smithy/util-defaults-mode-node": "npm:^4.2.47" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" "@smithy/util-endpoints": "npm:^3.3.3" "@smithy/util-middleware": "npm:^4.2.12" "@smithy/util-retry": "npm:^4.2.12" "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/9aa99379a0512dfcb59679d67fed5fb6651c9e134b77bf1cebfe5dbc119355c2fe6f9e6cf3882b5645845ba9c2bdf4227c8c131085a57f60045d028674f83bb9 + checksum: 10/62e5feb1be4154524906e00f059520903c0f29daf8f6db036c30bff7756b563ea74e0f01e4393c6ebea9fad255da6c1b220c9c4ee9bd51fee2b8a3f48a42db56 languageName: node linkType: hard "@aws-sdk/client-eks@npm:^3.350.0": - version: 3.1018.0 - resolution: "@aws-sdk/client-eks@npm:3.1018.0" + version: 3.1020.0 + resolution: "@aws-sdk/client-eks@npm:3.1020.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:^3.973.25" - "@aws-sdk/credential-provider-node": "npm:^3.972.26" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-node": "npm:^3.972.28" "@aws-sdk/middleware-host-header": "npm:^3.972.8" "@aws-sdk/middleware-logger": "npm:^3.972.8" "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" - "@aws-sdk/middleware-user-agent": "npm:^3.972.26" + "@aws-sdk/middleware-user-agent": "npm:^3.972.27" "@aws-sdk/region-config-resolver": "npm:^3.972.10" "@aws-sdk/types": "npm:^3.973.6" "@aws-sdk/util-endpoints": "npm:^3.996.5" "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" - "@aws-sdk/util-user-agent-node": "npm:^3.973.12" + "@aws-sdk/util-user-agent-node": "npm:^3.973.13" "@smithy/config-resolver": "npm:^4.4.13" - "@smithy/core": "npm:^3.23.12" + "@smithy/core": "npm:^3.23.13" "@smithy/fetch-http-handler": "npm:^5.3.15" "@smithy/hash-node": "npm:^4.2.12" "@smithy/invalid-dependency": "npm:^4.2.12" "@smithy/middleware-content-length": "npm:^4.2.12" - "@smithy/middleware-endpoint": "npm:^4.4.27" - "@smithy/middleware-retry": "npm:^4.4.44" - "@smithy/middleware-serde": "npm:^4.2.15" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.45" + "@smithy/middleware-serde": "npm:^4.2.16" "@smithy/middleware-stack": "npm:^4.2.12" "@smithy/node-config-provider": "npm:^4.3.12" - "@smithy/node-http-handler": "npm:^4.5.0" + "@smithy/node-http-handler": "npm:^4.5.1" "@smithy/protocol-http": "npm:^5.3.12" - "@smithy/smithy-client": "npm:^4.12.7" + "@smithy/smithy-client": "npm:^4.12.8" "@smithy/types": "npm:^4.13.1" "@smithy/url-parser": "npm:^4.2.12" "@smithy/util-base64": "npm:^4.3.2" "@smithy/util-body-length-browser": "npm:^4.2.2" "@smithy/util-body-length-node": "npm:^4.2.3" - "@smithy/util-defaults-mode-browser": "npm:^4.3.43" - "@smithy/util-defaults-mode-node": "npm:^4.2.47" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" "@smithy/util-endpoints": "npm:^3.3.3" "@smithy/util-middleware": "npm:^4.2.12" "@smithy/util-retry": "npm:^4.2.12" "@smithy/util-utf8": "npm:^4.2.2" - "@smithy/util-waiter": "npm:^4.2.13" + "@smithy/util-waiter": "npm:^4.2.14" tslib: "npm:^2.6.2" - checksum: 10/280eb80cdc4f97fd63416ee5a9af89c27c8fcb6ae571a3e23edd3ad280c7ac7440d1b58f01a8e64176586587accda74173da5a241c221baa1b5dca70816041c4 + checksum: 10/846706c02b8d54f186b76f03755c1da0ca95c470e52397d660a537617e5b71d53de915185ba1d4a6ce5c3c7473dd836ae99b2e4612cfcf58e487fadc2c9f84ef languageName: node linkType: hard "@aws-sdk/client-organizations@npm:^3.350.0": - version: 3.1018.0 - resolution: "@aws-sdk/client-organizations@npm:3.1018.0" + version: 3.1020.0 + resolution: "@aws-sdk/client-organizations@npm:3.1020.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:^3.973.25" - "@aws-sdk/credential-provider-node": "npm:^3.972.26" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-node": "npm:^3.972.28" "@aws-sdk/middleware-host-header": "npm:^3.972.8" "@aws-sdk/middleware-logger": "npm:^3.972.8" "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" - "@aws-sdk/middleware-user-agent": "npm:^3.972.26" + "@aws-sdk/middleware-user-agent": "npm:^3.972.27" "@aws-sdk/region-config-resolver": "npm:^3.972.10" "@aws-sdk/types": "npm:^3.973.6" "@aws-sdk/util-endpoints": "npm:^3.996.5" "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" - "@aws-sdk/util-user-agent-node": "npm:^3.973.12" + "@aws-sdk/util-user-agent-node": "npm:^3.973.13" "@smithy/config-resolver": "npm:^4.4.13" - "@smithy/core": "npm:^3.23.12" + "@smithy/core": "npm:^3.23.13" "@smithy/fetch-http-handler": "npm:^5.3.15" "@smithy/hash-node": "npm:^4.2.12" "@smithy/invalid-dependency": "npm:^4.2.12" "@smithy/middleware-content-length": "npm:^4.2.12" - "@smithy/middleware-endpoint": "npm:^4.4.27" - "@smithy/middleware-retry": "npm:^4.4.44" - "@smithy/middleware-serde": "npm:^4.2.15" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.45" + "@smithy/middleware-serde": "npm:^4.2.16" "@smithy/middleware-stack": "npm:^4.2.12" "@smithy/node-config-provider": "npm:^4.3.12" - "@smithy/node-http-handler": "npm:^4.5.0" + "@smithy/node-http-handler": "npm:^4.5.1" "@smithy/protocol-http": "npm:^5.3.12" - "@smithy/smithy-client": "npm:^4.12.7" + "@smithy/smithy-client": "npm:^4.12.8" "@smithy/types": "npm:^4.13.1" "@smithy/url-parser": "npm:^4.2.12" "@smithy/util-base64": "npm:^4.3.2" "@smithy/util-body-length-browser": "npm:^4.2.2" "@smithy/util-body-length-node": "npm:^4.2.3" - "@smithy/util-defaults-mode-browser": "npm:^4.3.43" - "@smithy/util-defaults-mode-node": "npm:^4.2.47" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" "@smithy/util-endpoints": "npm:^3.3.3" "@smithy/util-middleware": "npm:^4.2.12" "@smithy/util-retry": "npm:^4.2.12" "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/713f3314c7d4145dda2e643bb1aa7c042a4e587c85eac80c12daa91cad42784ba6487623064c01ba0fff1b3f50f6515ca95da4e84f7cc3b76456a0fef0544693 + checksum: 10/932a6a3d84af6e3526d9180813b20c5ae7e1dfdf46a277d3c3368f75fdcc57e6d35e0dcd5fc150db8787343d617023662ea010252613a30ff971b96b66d31747 languageName: node linkType: hard "@aws-sdk/client-s3@npm:^3.350.0": - version: 3.1018.0 - resolution: "@aws-sdk/client-s3@npm:3.1018.0" + version: 3.1020.0 + resolution: "@aws-sdk/client-s3@npm:3.1020.0" dependencies: "@aws-crypto/sha1-browser": "npm:5.2.0" "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:^3.973.25" - "@aws-sdk/credential-provider-node": "npm:^3.972.26" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-node": "npm:^3.972.28" "@aws-sdk/middleware-bucket-endpoint": "npm:^3.972.8" "@aws-sdk/middleware-expect-continue": "npm:^3.972.8" - "@aws-sdk/middleware-flexible-checksums": "npm:^3.974.5" + "@aws-sdk/middleware-flexible-checksums": "npm:^3.974.6" "@aws-sdk/middleware-host-header": "npm:^3.972.8" "@aws-sdk/middleware-location-constraint": "npm:^3.972.8" "@aws-sdk/middleware-logger": "npm:^3.972.8" "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" - "@aws-sdk/middleware-sdk-s3": "npm:^3.972.26" + "@aws-sdk/middleware-sdk-s3": "npm:^3.972.27" "@aws-sdk/middleware-ssec": "npm:^3.972.8" - "@aws-sdk/middleware-user-agent": "npm:^3.972.26" + "@aws-sdk/middleware-user-agent": "npm:^3.972.27" "@aws-sdk/region-config-resolver": "npm:^3.972.10" - "@aws-sdk/signature-v4-multi-region": "npm:^3.996.14" + "@aws-sdk/signature-v4-multi-region": "npm:^3.996.15" "@aws-sdk/types": "npm:^3.973.6" "@aws-sdk/util-endpoints": "npm:^3.996.5" "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" - "@aws-sdk/util-user-agent-node": "npm:^3.973.12" + "@aws-sdk/util-user-agent-node": "npm:^3.973.13" "@smithy/config-resolver": "npm:^4.4.13" - "@smithy/core": "npm:^3.23.12" + "@smithy/core": "npm:^3.23.13" "@smithy/eventstream-serde-browser": "npm:^4.2.12" "@smithy/eventstream-serde-config-resolver": "npm:^4.3.12" "@smithy/eventstream-serde-node": "npm:^4.2.12" @@ -717,194 +717,194 @@ __metadata: "@smithy/invalid-dependency": "npm:^4.2.12" "@smithy/md5-js": "npm:^4.2.12" "@smithy/middleware-content-length": "npm:^4.2.12" - "@smithy/middleware-endpoint": "npm:^4.4.27" - "@smithy/middleware-retry": "npm:^4.4.44" - "@smithy/middleware-serde": "npm:^4.2.15" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.45" + "@smithy/middleware-serde": "npm:^4.2.16" "@smithy/middleware-stack": "npm:^4.2.12" "@smithy/node-config-provider": "npm:^4.3.12" - "@smithy/node-http-handler": "npm:^4.5.0" + "@smithy/node-http-handler": "npm:^4.5.1" "@smithy/protocol-http": "npm:^5.3.12" - "@smithy/smithy-client": "npm:^4.12.7" + "@smithy/smithy-client": "npm:^4.12.8" "@smithy/types": "npm:^4.13.1" "@smithy/url-parser": "npm:^4.2.12" "@smithy/util-base64": "npm:^4.3.2" "@smithy/util-body-length-browser": "npm:^4.2.2" "@smithy/util-body-length-node": "npm:^4.2.3" - "@smithy/util-defaults-mode-browser": "npm:^4.3.43" - "@smithy/util-defaults-mode-node": "npm:^4.2.47" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" "@smithy/util-endpoints": "npm:^3.3.3" "@smithy/util-middleware": "npm:^4.2.12" "@smithy/util-retry": "npm:^4.2.12" - "@smithy/util-stream": "npm:^4.5.20" + "@smithy/util-stream": "npm:^4.5.21" "@smithy/util-utf8": "npm:^4.2.2" - "@smithy/util-waiter": "npm:^4.2.13" + "@smithy/util-waiter": "npm:^4.2.14" tslib: "npm:^2.6.2" - checksum: 10/fdebc5cdad04d9aac29ebe462679f1643a34833042a00539bb22fdb60ea54923403cc50880721fc6d3dbc232d33f291db889bfea391a7bfe69d268e530472ce5 + checksum: 10/898815cd15bca7276090bc46ca065baba2d48796c7054bd61c9f5c7b76fc2f55fcfc3eb1b8784f82fec667eb26c7e3bc9af24bd47d04906ee1c7a6c1288f991c languageName: node linkType: hard "@aws-sdk/client-sesv2@npm:^3.911.0": - version: 3.1018.0 - resolution: "@aws-sdk/client-sesv2@npm:3.1018.0" + version: 3.1020.0 + resolution: "@aws-sdk/client-sesv2@npm:3.1020.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:^3.973.25" - "@aws-sdk/credential-provider-node": "npm:^3.972.26" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-node": "npm:^3.972.28" "@aws-sdk/middleware-host-header": "npm:^3.972.8" "@aws-sdk/middleware-logger": "npm:^3.972.8" "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" - "@aws-sdk/middleware-user-agent": "npm:^3.972.26" + "@aws-sdk/middleware-user-agent": "npm:^3.972.27" "@aws-sdk/region-config-resolver": "npm:^3.972.10" - "@aws-sdk/signature-v4-multi-region": "npm:^3.996.14" + "@aws-sdk/signature-v4-multi-region": "npm:^3.996.15" "@aws-sdk/types": "npm:^3.973.6" "@aws-sdk/util-endpoints": "npm:^3.996.5" "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" - "@aws-sdk/util-user-agent-node": "npm:^3.973.12" + "@aws-sdk/util-user-agent-node": "npm:^3.973.13" "@smithy/config-resolver": "npm:^4.4.13" - "@smithy/core": "npm:^3.23.12" + "@smithy/core": "npm:^3.23.13" "@smithy/fetch-http-handler": "npm:^5.3.15" "@smithy/hash-node": "npm:^4.2.12" "@smithy/invalid-dependency": "npm:^4.2.12" "@smithy/middleware-content-length": "npm:^4.2.12" - "@smithy/middleware-endpoint": "npm:^4.4.27" - "@smithy/middleware-retry": "npm:^4.4.44" - "@smithy/middleware-serde": "npm:^4.2.15" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.45" + "@smithy/middleware-serde": "npm:^4.2.16" "@smithy/middleware-stack": "npm:^4.2.12" "@smithy/node-config-provider": "npm:^4.3.12" - "@smithy/node-http-handler": "npm:^4.5.0" + "@smithy/node-http-handler": "npm:^4.5.1" "@smithy/protocol-http": "npm:^5.3.12" - "@smithy/smithy-client": "npm:^4.12.7" + "@smithy/smithy-client": "npm:^4.12.8" "@smithy/types": "npm:^4.13.1" "@smithy/url-parser": "npm:^4.2.12" "@smithy/util-base64": "npm:^4.3.2" "@smithy/util-body-length-browser": "npm:^4.2.2" "@smithy/util-body-length-node": "npm:^4.2.3" - "@smithy/util-defaults-mode-browser": "npm:^4.3.43" - "@smithy/util-defaults-mode-node": "npm:^4.2.47" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" "@smithy/util-endpoints": "npm:^3.3.3" "@smithy/util-middleware": "npm:^4.2.12" "@smithy/util-retry": "npm:^4.2.12" "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/f6aa8d84d0c6954a3e335534013fecdac9ff3c06aa277fcf52e0cb9658d0ce11076d3944fb54884433db8cbed3e99edb9f0be92fb0f4eda00890087bde185046 + checksum: 10/45a2b87531d51de5b1856eb9d69c4add3704ad12cd4789ccb836cf4ddf1bd6e4c40251f5726a0c5974a30802d2f20825f7a68a106b650cc2f70df1f3426535e8 languageName: node linkType: hard "@aws-sdk/client-sqs@npm:^3.350.0": - version: 3.1018.0 - resolution: "@aws-sdk/client-sqs@npm:3.1018.0" + version: 3.1020.0 + resolution: "@aws-sdk/client-sqs@npm:3.1020.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:^3.973.25" - "@aws-sdk/credential-provider-node": "npm:^3.972.26" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-node": "npm:^3.972.28" "@aws-sdk/middleware-host-header": "npm:^3.972.8" "@aws-sdk/middleware-logger": "npm:^3.972.8" "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" - "@aws-sdk/middleware-sdk-sqs": "npm:^3.972.17" - "@aws-sdk/middleware-user-agent": "npm:^3.972.26" + "@aws-sdk/middleware-sdk-sqs": "npm:^3.972.18" + "@aws-sdk/middleware-user-agent": "npm:^3.972.27" "@aws-sdk/region-config-resolver": "npm:^3.972.10" "@aws-sdk/types": "npm:^3.973.6" "@aws-sdk/util-endpoints": "npm:^3.996.5" "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" - "@aws-sdk/util-user-agent-node": "npm:^3.973.12" + "@aws-sdk/util-user-agent-node": "npm:^3.973.13" "@smithy/config-resolver": "npm:^4.4.13" - "@smithy/core": "npm:^3.23.12" + "@smithy/core": "npm:^3.23.13" "@smithy/fetch-http-handler": "npm:^5.3.15" "@smithy/hash-node": "npm:^4.2.12" "@smithy/invalid-dependency": "npm:^4.2.12" "@smithy/md5-js": "npm:^4.2.12" "@smithy/middleware-content-length": "npm:^4.2.12" - "@smithy/middleware-endpoint": "npm:^4.4.27" - "@smithy/middleware-retry": "npm:^4.4.44" - "@smithy/middleware-serde": "npm:^4.2.15" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.45" + "@smithy/middleware-serde": "npm:^4.2.16" "@smithy/middleware-stack": "npm:^4.2.12" "@smithy/node-config-provider": "npm:^4.3.12" - "@smithy/node-http-handler": "npm:^4.5.0" + "@smithy/node-http-handler": "npm:^4.5.1" "@smithy/protocol-http": "npm:^5.3.12" - "@smithy/smithy-client": "npm:^4.12.7" + "@smithy/smithy-client": "npm:^4.12.8" "@smithy/types": "npm:^4.13.1" "@smithy/url-parser": "npm:^4.2.12" "@smithy/util-base64": "npm:^4.3.2" "@smithy/util-body-length-browser": "npm:^4.2.2" "@smithy/util-body-length-node": "npm:^4.2.3" - "@smithy/util-defaults-mode-browser": "npm:^4.3.43" - "@smithy/util-defaults-mode-node": "npm:^4.2.47" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" "@smithy/util-endpoints": "npm:^3.3.3" "@smithy/util-middleware": "npm:^4.2.12" "@smithy/util-retry": "npm:^4.2.12" "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/2eb46ebfe69c49d9320c4e4c9ca2b43098278879f935eb375d917e24aaf554cae00f4d7ab4819116573ec134d9107f0eadd7e7e0af2cc8b02c836dfbc199236c + checksum: 10/1e47584b68796aa52a63d4eb1d7333f6d9cb032522d7a157f3b88b1491b3bba457a1061362c9400bfe7ad26bbbe14e145dd737863e29425edd2f4f63643aaf78 languageName: node linkType: hard "@aws-sdk/client-sts@npm:^3.350.0": - version: 3.1018.0 - resolution: "@aws-sdk/client-sts@npm:3.1018.0" + version: 3.1020.0 + resolution: "@aws-sdk/client-sts@npm:3.1020.0" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:^3.973.25" - "@aws-sdk/credential-provider-node": "npm:^3.972.26" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-node": "npm:^3.972.28" "@aws-sdk/middleware-host-header": "npm:^3.972.8" "@aws-sdk/middleware-logger": "npm:^3.972.8" "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" - "@aws-sdk/middleware-user-agent": "npm:^3.972.26" + "@aws-sdk/middleware-user-agent": "npm:^3.972.27" "@aws-sdk/region-config-resolver": "npm:^3.972.10" "@aws-sdk/types": "npm:^3.973.6" "@aws-sdk/util-endpoints": "npm:^3.996.5" "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" - "@aws-sdk/util-user-agent-node": "npm:^3.973.12" + "@aws-sdk/util-user-agent-node": "npm:^3.973.13" "@smithy/config-resolver": "npm:^4.4.13" - "@smithy/core": "npm:^3.23.12" + "@smithy/core": "npm:^3.23.13" "@smithy/fetch-http-handler": "npm:^5.3.15" "@smithy/hash-node": "npm:^4.2.12" "@smithy/invalid-dependency": "npm:^4.2.12" "@smithy/middleware-content-length": "npm:^4.2.12" - "@smithy/middleware-endpoint": "npm:^4.4.27" - "@smithy/middleware-retry": "npm:^4.4.44" - "@smithy/middleware-serde": "npm:^4.2.15" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.45" + "@smithy/middleware-serde": "npm:^4.2.16" "@smithy/middleware-stack": "npm:^4.2.12" "@smithy/node-config-provider": "npm:^4.3.12" - "@smithy/node-http-handler": "npm:^4.5.0" + "@smithy/node-http-handler": "npm:^4.5.1" "@smithy/protocol-http": "npm:^5.3.12" - "@smithy/smithy-client": "npm:^4.12.7" + "@smithy/smithy-client": "npm:^4.12.8" "@smithy/types": "npm:^4.13.1" "@smithy/url-parser": "npm:^4.2.12" "@smithy/util-base64": "npm:^4.3.2" "@smithy/util-body-length-browser": "npm:^4.2.2" "@smithy/util-body-length-node": "npm:^4.2.3" - "@smithy/util-defaults-mode-browser": "npm:^4.3.43" - "@smithy/util-defaults-mode-node": "npm:^4.2.47" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" "@smithy/util-endpoints": "npm:^3.3.3" "@smithy/util-middleware": "npm:^4.2.12" "@smithy/util-retry": "npm:^4.2.12" "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/7f9f013df69a5c2692e131e9e7527cc9872ba463f47356c4f60ea2860c9638be0fa3cdfb2503739d491958cd9f975ec861a84cf1d5651f95afc6ae2d4429639f + checksum: 10/6b6033e59d481c9805158dfb982a7a3229b9a08988ccea150d39a2363301aa0427a81273a052f6d6a3422cea6e29fa67fd89b5b0198453c4308c7ec04c63fc5f languageName: node linkType: hard -"@aws-sdk/core@npm:^3.973.25": - version: 3.973.25 - resolution: "@aws-sdk/core@npm:3.973.25" +"@aws-sdk/core@npm:^3.973.26": + version: 3.973.26 + resolution: "@aws-sdk/core@npm:3.973.26" dependencies: "@aws-sdk/types": "npm:^3.973.6" "@aws-sdk/xml-builder": "npm:^3.972.16" - "@smithy/core": "npm:^3.23.12" + "@smithy/core": "npm:^3.23.13" "@smithy/node-config-provider": "npm:^4.3.12" "@smithy/property-provider": "npm:^4.2.12" "@smithy/protocol-http": "npm:^5.3.12" "@smithy/signature-v4": "npm:^5.3.12" - "@smithy/smithy-client": "npm:^4.12.7" + "@smithy/smithy-client": "npm:^4.12.8" "@smithy/types": "npm:^4.13.1" "@smithy/util-base64": "npm:^4.3.2" "@smithy/util-middleware": "npm:^4.2.12" "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/cdc04326ebe09d4876ddc15eb7dac5842b3a987f01de5f950ff72e75db500ab2bfca17a5317f9e60f46924574f644ff64eacb5b1999fc657ab2060a5f26b1ea4 + checksum: 10/6760e19f912034cf2e28c6fe9872613a3a32f6f66c5bef7c104fa00d74f3408acf0aa52711eb3f4df2889c0d4ca055be35dd7fd2fdbc0ed057f773bff8cffeba languageName: node linkType: hard @@ -918,178 +918,178 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/credential-provider-cognito-identity@npm:^3.972.18": - version: 3.972.18 - resolution: "@aws-sdk/credential-provider-cognito-identity@npm:3.972.18" +"@aws-sdk/credential-provider-cognito-identity@npm:^3.972.20": + version: 3.972.20 + resolution: "@aws-sdk/credential-provider-cognito-identity@npm:3.972.20" dependencies: - "@aws-sdk/nested-clients": "npm:^3.996.15" + "@aws-sdk/nested-clients": "npm:^3.996.17" "@aws-sdk/types": "npm:^3.973.6" "@smithy/property-provider": "npm:^4.2.12" "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/3316003c2132dd69d6ea4985aa247f0bf4cf32e16d023306da4565c4018422edd10cc08026e339cc757d798b4bbaf9c20fbd4e0f131d01a5a24e76db69df7ad9 + checksum: 10/0ce90d2cb907e5b79ba326fa5743577af6ca377a865f2177469579e7afb7407111f7248e76183de77f5c72950c8e53474a6a1c598cdc4dfb3b5f6c9bdcfbc12a languageName: node linkType: hard -"@aws-sdk/credential-provider-env@npm:^3.972.23": - version: 3.972.23 - resolution: "@aws-sdk/credential-provider-env@npm:3.972.23" +"@aws-sdk/credential-provider-env@npm:^3.972.24": + version: 3.972.24 + resolution: "@aws-sdk/credential-provider-env@npm:3.972.24" dependencies: - "@aws-sdk/core": "npm:^3.973.25" + "@aws-sdk/core": "npm:^3.973.26" "@aws-sdk/types": "npm:^3.973.6" "@smithy/property-provider": "npm:^4.2.12" "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/3f610061255f339453ae2817731644967559bb1e67c4baa223cfa328d311effa167bd7c1da71a64285f17910ce0dd8d962390d8eb3a99ad6220f36541412a261 + checksum: 10/5ca69d965747507f8af0851eaf66fb2b51eceaecbb36b8ea0b20c0a87693aff33b7378f4a0f18f92e64b685d8bcd52123922bb45f2f5f51b4994cc64f8b210c5 languageName: node linkType: hard -"@aws-sdk/credential-provider-http@npm:^3.972.25": - version: 3.972.25 - resolution: "@aws-sdk/credential-provider-http@npm:3.972.25" +"@aws-sdk/credential-provider-http@npm:^3.972.26": + version: 3.972.26 + resolution: "@aws-sdk/credential-provider-http@npm:3.972.26" dependencies: - "@aws-sdk/core": "npm:^3.973.25" + "@aws-sdk/core": "npm:^3.973.26" "@aws-sdk/types": "npm:^3.973.6" "@smithy/fetch-http-handler": "npm:^5.3.15" - "@smithy/node-http-handler": "npm:^4.5.0" + "@smithy/node-http-handler": "npm:^4.5.1" "@smithy/property-provider": "npm:^4.2.12" "@smithy/protocol-http": "npm:^5.3.12" - "@smithy/smithy-client": "npm:^4.12.7" + "@smithy/smithy-client": "npm:^4.12.8" "@smithy/types": "npm:^4.13.1" - "@smithy/util-stream": "npm:^4.5.20" + "@smithy/util-stream": "npm:^4.5.21" tslib: "npm:^2.6.2" - checksum: 10/c3a6a52ae9193af27c8e2a17ec459c62a3437b3ab86604ce43731bd385ce8dd9290f755f3b70d8b67774e89e24e45fe2c1104004036e25ebd078e06dd9a7f1b1 + checksum: 10/d3b5904e168a43c53c32cfe16725c091bf3f41755afe910f50d2bede17f2682bed968ad1923a3dadd17e75ffdd53eda6ef790fb8700fed350066bdcdb160ad24 languageName: node linkType: hard -"@aws-sdk/credential-provider-ini@npm:^3.972.25": - version: 3.972.25 - resolution: "@aws-sdk/credential-provider-ini@npm:3.972.25" +"@aws-sdk/credential-provider-ini@npm:^3.972.27": + version: 3.972.27 + resolution: "@aws-sdk/credential-provider-ini@npm:3.972.27" dependencies: - "@aws-sdk/core": "npm:^3.973.25" - "@aws-sdk/credential-provider-env": "npm:^3.972.23" - "@aws-sdk/credential-provider-http": "npm:^3.972.25" - "@aws-sdk/credential-provider-login": "npm:^3.972.25" - "@aws-sdk/credential-provider-process": "npm:^3.972.23" - "@aws-sdk/credential-provider-sso": "npm:^3.972.25" - "@aws-sdk/credential-provider-web-identity": "npm:^3.972.25" - "@aws-sdk/nested-clients": "npm:^3.996.15" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-env": "npm:^3.972.24" + "@aws-sdk/credential-provider-http": "npm:^3.972.26" + "@aws-sdk/credential-provider-login": "npm:^3.972.27" + "@aws-sdk/credential-provider-process": "npm:^3.972.24" + "@aws-sdk/credential-provider-sso": "npm:^3.972.27" + "@aws-sdk/credential-provider-web-identity": "npm:^3.972.27" + "@aws-sdk/nested-clients": "npm:^3.996.17" "@aws-sdk/types": "npm:^3.973.6" "@smithy/credential-provider-imds": "npm:^4.2.12" "@smithy/property-provider": "npm:^4.2.12" "@smithy/shared-ini-file-loader": "npm:^4.4.7" "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/330993bece58c85df809cc8ee26ecb87556036c4a8ddb7bdf511a074d722bdc4a5e2066b4a2ee492f627db5ac4ead8081920e4c2a934dcdcbb5ae108e27c7306 + checksum: 10/65b10fa0428d49e44ea9ff8df4e4bdc43131e938ef1c94723ebfe4e9ff73cd8a39dd4301521662593b4c6c6d2d4b9cd090102df79b1d2efe8d2bc191ed90f401 languageName: node linkType: hard -"@aws-sdk/credential-provider-login@npm:^3.972.25": - version: 3.972.25 - resolution: "@aws-sdk/credential-provider-login@npm:3.972.25" +"@aws-sdk/credential-provider-login@npm:^3.972.27": + version: 3.972.27 + resolution: "@aws-sdk/credential-provider-login@npm:3.972.27" dependencies: - "@aws-sdk/core": "npm:^3.973.25" - "@aws-sdk/nested-clients": "npm:^3.996.15" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/nested-clients": "npm:^3.996.17" "@aws-sdk/types": "npm:^3.973.6" "@smithy/property-provider": "npm:^4.2.12" "@smithy/protocol-http": "npm:^5.3.12" "@smithy/shared-ini-file-loader": "npm:^4.4.7" "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/ede3b32c535784bacd1cd5edb9ab406a39ac659c7086f2e1f25dbb0e174f25409e64684dbb2e0f98a87872ce0ff13ef624bb5461d749721eb79889afefdaa358 + checksum: 10/765879fe6db638b2f64098dc5494e859d33cfa2888310332e1189a42e4abc2a981222d53de410bf436efe00b720755c85ea33d002e2277641937e787809ea205 languageName: node linkType: hard -"@aws-sdk/credential-provider-node@npm:^3.350.0, @aws-sdk/credential-provider-node@npm:^3.972.26": - version: 3.972.26 - resolution: "@aws-sdk/credential-provider-node@npm:3.972.26" +"@aws-sdk/credential-provider-node@npm:^3.350.0, @aws-sdk/credential-provider-node@npm:^3.972.28": + version: 3.972.28 + resolution: "@aws-sdk/credential-provider-node@npm:3.972.28" dependencies: - "@aws-sdk/credential-provider-env": "npm:^3.972.23" - "@aws-sdk/credential-provider-http": "npm:^3.972.25" - "@aws-sdk/credential-provider-ini": "npm:^3.972.25" - "@aws-sdk/credential-provider-process": "npm:^3.972.23" - "@aws-sdk/credential-provider-sso": "npm:^3.972.25" - "@aws-sdk/credential-provider-web-identity": "npm:^3.972.25" + "@aws-sdk/credential-provider-env": "npm:^3.972.24" + "@aws-sdk/credential-provider-http": "npm:^3.972.26" + "@aws-sdk/credential-provider-ini": "npm:^3.972.27" + "@aws-sdk/credential-provider-process": "npm:^3.972.24" + "@aws-sdk/credential-provider-sso": "npm:^3.972.27" + "@aws-sdk/credential-provider-web-identity": "npm:^3.972.27" "@aws-sdk/types": "npm:^3.973.6" "@smithy/credential-provider-imds": "npm:^4.2.12" "@smithy/property-provider": "npm:^4.2.12" "@smithy/shared-ini-file-loader": "npm:^4.4.7" "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/e6e0885c5b797fd04d1890afecee424925a37ef9fe1f849360fa51f84e5ecde6866a1242f0b330747b9ef29bd4d8a4177bb26a5eb2364ee6283562675c67002d + checksum: 10/e914b22d77bf47511cb2d32109bfc864b53778dd8951f8ea555927092662a512fe888e9fd53afbeccf23943f8a4494f60be7ea5a94d625c6e72ebe4ae9c2da36 languageName: node linkType: hard -"@aws-sdk/credential-provider-process@npm:^3.972.23": - version: 3.972.23 - resolution: "@aws-sdk/credential-provider-process@npm:3.972.23" +"@aws-sdk/credential-provider-process@npm:^3.972.24": + version: 3.972.24 + resolution: "@aws-sdk/credential-provider-process@npm:3.972.24" dependencies: - "@aws-sdk/core": "npm:^3.973.25" + "@aws-sdk/core": "npm:^3.973.26" "@aws-sdk/types": "npm:^3.973.6" "@smithy/property-provider": "npm:^4.2.12" "@smithy/shared-ini-file-loader": "npm:^4.4.7" "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/cb9ff490daf8942fbf7264ef6ae15e23f502f7223cae23a4e4123113b160f5784a41841a3341f80e08c968a12c38696827fc6a23e2bb573b5998747ee4c2126b + checksum: 10/aa2cc9ceefd0f717840fd460637710373624bd79ceb981cceff9065615a8538ceefbdaf5803372a590609e14657ad0e158f0d56b5f2c67abfc38df65c5c12432 languageName: node linkType: hard -"@aws-sdk/credential-provider-sso@npm:^3.972.25": - version: 3.972.25 - resolution: "@aws-sdk/credential-provider-sso@npm:3.972.25" +"@aws-sdk/credential-provider-sso@npm:^3.972.27": + version: 3.972.27 + resolution: "@aws-sdk/credential-provider-sso@npm:3.972.27" dependencies: - "@aws-sdk/core": "npm:^3.973.25" - "@aws-sdk/nested-clients": "npm:^3.996.15" - "@aws-sdk/token-providers": "npm:3.1018.0" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/nested-clients": "npm:^3.996.17" + "@aws-sdk/token-providers": "npm:3.1020.0" "@aws-sdk/types": "npm:^3.973.6" "@smithy/property-provider": "npm:^4.2.12" "@smithy/shared-ini-file-loader": "npm:^4.4.7" "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/a864c6f5bc1d4a985967b6e1d58f62620ae58bc208440dbbcb7c256d7e0d07871d475cc04664ea97e7ebce52e5a30c42bad381c49cda917df1639a739d0c8519 + checksum: 10/5007301d4830088ed31d69e06cb881a4608287174d08543e98df51416aa6dcd8e5d5d9716ba371f83b4d6a9fc753b3394cdb1c6d0826aa98b2901db2df7f0c44 languageName: node linkType: hard -"@aws-sdk/credential-provider-web-identity@npm:^3.972.25": - version: 3.972.25 - resolution: "@aws-sdk/credential-provider-web-identity@npm:3.972.25" +"@aws-sdk/credential-provider-web-identity@npm:^3.972.27": + version: 3.972.27 + resolution: "@aws-sdk/credential-provider-web-identity@npm:3.972.27" dependencies: - "@aws-sdk/core": "npm:^3.973.25" - "@aws-sdk/nested-clients": "npm:^3.996.15" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/nested-clients": "npm:^3.996.17" "@aws-sdk/types": "npm:^3.973.6" "@smithy/property-provider": "npm:^4.2.12" "@smithy/shared-ini-file-loader": "npm:^4.4.7" "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/3aef7107cca0ed933c1731f81efb94b72a11885012f925dff67439b14a328fd7316985b3e8f0a8801e0c3d6e385a15b158b0e13de74712b225b0f29463d966cc + checksum: 10/06661b21c47c7002d6a025245a34c20500f9976617dbb3956bb8436530065bdfba0c9ccd35fee1abb11b6df67162c0ab23866cacf2e8d2b2f57da91abfec1c51 languageName: node linkType: hard "@aws-sdk/credential-providers@npm:^3.350.0": - version: 3.1018.0 - resolution: "@aws-sdk/credential-providers@npm:3.1018.0" + version: 3.1020.0 + resolution: "@aws-sdk/credential-providers@npm:3.1020.0" dependencies: - "@aws-sdk/client-cognito-identity": "npm:3.1018.0" - "@aws-sdk/core": "npm:^3.973.25" - "@aws-sdk/credential-provider-cognito-identity": "npm:^3.972.18" - "@aws-sdk/credential-provider-env": "npm:^3.972.23" - "@aws-sdk/credential-provider-http": "npm:^3.972.25" - "@aws-sdk/credential-provider-ini": "npm:^3.972.25" - "@aws-sdk/credential-provider-login": "npm:^3.972.25" - "@aws-sdk/credential-provider-node": "npm:^3.972.26" - "@aws-sdk/credential-provider-process": "npm:^3.972.23" - "@aws-sdk/credential-provider-sso": "npm:^3.972.25" - "@aws-sdk/credential-provider-web-identity": "npm:^3.972.25" - "@aws-sdk/nested-clients": "npm:^3.996.15" + "@aws-sdk/client-cognito-identity": "npm:3.1020.0" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/credential-provider-cognito-identity": "npm:^3.972.20" + "@aws-sdk/credential-provider-env": "npm:^3.972.24" + "@aws-sdk/credential-provider-http": "npm:^3.972.26" + "@aws-sdk/credential-provider-ini": "npm:^3.972.27" + "@aws-sdk/credential-provider-login": "npm:^3.972.27" + "@aws-sdk/credential-provider-node": "npm:^3.972.28" + "@aws-sdk/credential-provider-process": "npm:^3.972.24" + "@aws-sdk/credential-provider-sso": "npm:^3.972.27" + "@aws-sdk/credential-provider-web-identity": "npm:^3.972.27" + "@aws-sdk/nested-clients": "npm:^3.996.17" "@aws-sdk/types": "npm:^3.973.6" "@smithy/config-resolver": "npm:^4.4.13" - "@smithy/core": "npm:^3.23.12" + "@smithy/core": "npm:^3.23.13" "@smithy/credential-provider-imds": "npm:^4.2.12" "@smithy/node-config-provider": "npm:^4.3.12" "@smithy/property-provider": "npm:^4.2.12" "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/7264c0898813556caa56a6c590c22bf3c3c0a0a7f2101c22ea277e882de2e7c89d2f605f29349601adf1b66a34b63d259a3c22824d18c708f651a74232a39837 + checksum: 10/405f4d6470df421709241489c30e86034a329443dd5d7ecd0a85439c2f9838da8e38b68fba58f9d2568c679d47d4ecd5d52ac3d864ab64a6ac4572e38dd22888 languageName: node linkType: hard @@ -1103,20 +1103,20 @@ __metadata: linkType: hard "@aws-sdk/lib-storage@npm:^3.350.0": - version: 3.1018.0 - resolution: "@aws-sdk/lib-storage@npm:3.1018.0" + version: 3.1020.0 + resolution: "@aws-sdk/lib-storage@npm:3.1020.0" dependencies: - "@smithy/middleware-endpoint": "npm:^4.4.27" + "@smithy/middleware-endpoint": "npm:^4.4.28" "@smithy/protocol-http": "npm:^5.3.12" - "@smithy/smithy-client": "npm:^4.12.7" + "@smithy/smithy-client": "npm:^4.12.8" "@smithy/types": "npm:^4.13.1" buffer: "npm:5.6.0" events: "npm:3.3.0" stream-browserify: "npm:3.0.0" tslib: "npm:^2.6.2" peerDependencies: - "@aws-sdk/client-s3": ^3.1018.0 - checksum: 10/3cf88211f339d3d9b2a1626274eb7b5dd038fd1b4116791d4e850586acf183293ce39eda2cbc665514e0decba3fd80759b1d1bb565f3147625b6199b788b7c92 + "@aws-sdk/client-s3": ^3.1020.0 + checksum: 10/79073880519b2fde7bb1307d2d62b98b0b5fe3db81c3d8b2463c280fab957f52cae679e8442187af368314577461ceb81a577c9843a0e50b7c4da7e3b4df382d languageName: node linkType: hard @@ -1160,14 +1160,14 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/middleware-flexible-checksums@npm:^3.974.5": - version: 3.974.5 - resolution: "@aws-sdk/middleware-flexible-checksums@npm:3.974.5" +"@aws-sdk/middleware-flexible-checksums@npm:^3.974.6": + version: 3.974.6 + resolution: "@aws-sdk/middleware-flexible-checksums@npm:3.974.6" dependencies: "@aws-crypto/crc32": "npm:5.2.0" "@aws-crypto/crc32c": "npm:5.2.0" "@aws-crypto/util": "npm:5.2.0" - "@aws-sdk/core": "npm:^3.973.25" + "@aws-sdk/core": "npm:^3.973.26" "@aws-sdk/crc64-nvme": "npm:^3.972.5" "@aws-sdk/types": "npm:^3.973.6" "@smithy/is-array-buffer": "npm:^4.2.2" @@ -1175,10 +1175,10 @@ __metadata: "@smithy/protocol-http": "npm:^5.3.12" "@smithy/types": "npm:^4.13.1" "@smithy/util-middleware": "npm:^4.2.12" - "@smithy/util-stream": "npm:^4.5.20" + "@smithy/util-stream": "npm:^4.5.21" "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/2776cd0b4586c477bd3ff51fc606fd3535e3d9264d8b175d92693f384714be2bdca921507452d7a7d8ea605fd41bd70c6205337dff48e627b0781105bc0967ed + checksum: 10/3f4d36d7793b39e52b394079548c7691273512dc8558cab0dac63a4fc3e75afad8390bdd7a35365fc2e82cddaa4afb269b57ca1e46b27c4c3f3a2f0cfed4674a languageName: node linkType: hard @@ -1229,39 +1229,39 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/middleware-sdk-s3@npm:^3.972.26": - version: 3.972.26 - resolution: "@aws-sdk/middleware-sdk-s3@npm:3.972.26" +"@aws-sdk/middleware-sdk-s3@npm:^3.972.27": + version: 3.972.27 + resolution: "@aws-sdk/middleware-sdk-s3@npm:3.972.27" dependencies: - "@aws-sdk/core": "npm:^3.973.25" + "@aws-sdk/core": "npm:^3.973.26" "@aws-sdk/types": "npm:^3.973.6" "@aws-sdk/util-arn-parser": "npm:^3.972.3" - "@smithy/core": "npm:^3.23.12" + "@smithy/core": "npm:^3.23.13" "@smithy/node-config-provider": "npm:^4.3.12" "@smithy/protocol-http": "npm:^5.3.12" "@smithy/signature-v4": "npm:^5.3.12" - "@smithy/smithy-client": "npm:^4.12.7" + "@smithy/smithy-client": "npm:^4.12.8" "@smithy/types": "npm:^4.13.1" "@smithy/util-config-provider": "npm:^4.2.2" "@smithy/util-middleware": "npm:^4.2.12" - "@smithy/util-stream": "npm:^4.5.20" + "@smithy/util-stream": "npm:^4.5.21" "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/00e1367dc7e2ff8b2cdbe00c134c86ac11b198a387aec634a565eb1e047c81adf19a81c21cc3f96df1f4e2d894200f4709efec7db6fd474c010007c5e0efe235 + checksum: 10/7163eb04379b2dbafe84adfabb3974b03dcd8af807341e03f49ed7e6d57c5307002492c833ba67697ca04731f0d3a399b2262435bbaf6f75f3f91b7e4d424a0d languageName: node linkType: hard -"@aws-sdk/middleware-sdk-sqs@npm:^3.972.17": - version: 3.972.17 - resolution: "@aws-sdk/middleware-sdk-sqs@npm:3.972.17" +"@aws-sdk/middleware-sdk-sqs@npm:^3.972.18": + version: 3.972.18 + resolution: "@aws-sdk/middleware-sdk-sqs@npm:3.972.18" dependencies: "@aws-sdk/types": "npm:^3.973.6" - "@smithy/smithy-client": "npm:^4.12.7" + "@smithy/smithy-client": "npm:^4.12.8" "@smithy/types": "npm:^4.13.1" "@smithy/util-hex-encoding": "npm:^4.2.2" "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/08b9a4168c448e3ed5cdc8d000af5762aaeeb2f8e6c90e44206eb833ab4afba06dedbd421a1ede401201bb842dba0bfb2b585a0d91b5fcf535c04ec6631867ab + checksum: 10/8e8dc79ed7c6213a157410f24ddc0349987be44b22f26c81a11d5c0eb37a86b18a50f030289b1656ba37ff797bb625321db6a1e050d8a8225f2385c3c06dabf5 languageName: node linkType: hard @@ -1286,65 +1286,65 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/middleware-user-agent@npm:^3.972.26": - version: 3.972.26 - resolution: "@aws-sdk/middleware-user-agent@npm:3.972.26" +"@aws-sdk/middleware-user-agent@npm:^3.972.27": + version: 3.972.27 + resolution: "@aws-sdk/middleware-user-agent@npm:3.972.27" dependencies: - "@aws-sdk/core": "npm:^3.973.25" + "@aws-sdk/core": "npm:^3.973.26" "@aws-sdk/types": "npm:^3.973.6" "@aws-sdk/util-endpoints": "npm:^3.996.5" - "@smithy/core": "npm:^3.23.12" + "@smithy/core": "npm:^3.23.13" "@smithy/protocol-http": "npm:^5.3.12" "@smithy/types": "npm:^4.13.1" "@smithy/util-retry": "npm:^4.2.12" tslib: "npm:^2.6.2" - checksum: 10/b182d605177583e4fa4897c3cfa9526c14ffea4c6c32c842a826ee6d42b67379c2c21dbf0a0d980ca12db3d2aa9c51c282cbe04b26ce659c1aadebb0651333c9 + checksum: 10/14c1f9579e0e72a8de27fc587208f4993dcb53c6980b181a26d1181c7a51212f51603014691448678b05495ecc01045971a95ad82f27f1f7e8dc32fb59b3dac7 languageName: node linkType: hard -"@aws-sdk/nested-clients@npm:^3.996.15": - version: 3.996.15 - resolution: "@aws-sdk/nested-clients@npm:3.996.15" +"@aws-sdk/nested-clients@npm:^3.996.17": + version: 3.996.17 + resolution: "@aws-sdk/nested-clients@npm:3.996.17" dependencies: "@aws-crypto/sha256-browser": "npm:5.2.0" "@aws-crypto/sha256-js": "npm:5.2.0" - "@aws-sdk/core": "npm:^3.973.25" + "@aws-sdk/core": "npm:^3.973.26" "@aws-sdk/middleware-host-header": "npm:^3.972.8" "@aws-sdk/middleware-logger": "npm:^3.972.8" "@aws-sdk/middleware-recursion-detection": "npm:^3.972.9" - "@aws-sdk/middleware-user-agent": "npm:^3.972.26" + "@aws-sdk/middleware-user-agent": "npm:^3.972.27" "@aws-sdk/region-config-resolver": "npm:^3.972.10" "@aws-sdk/types": "npm:^3.973.6" "@aws-sdk/util-endpoints": "npm:^3.996.5" "@aws-sdk/util-user-agent-browser": "npm:^3.972.8" - "@aws-sdk/util-user-agent-node": "npm:^3.973.12" + "@aws-sdk/util-user-agent-node": "npm:^3.973.13" "@smithy/config-resolver": "npm:^4.4.13" - "@smithy/core": "npm:^3.23.12" + "@smithy/core": "npm:^3.23.13" "@smithy/fetch-http-handler": "npm:^5.3.15" "@smithy/hash-node": "npm:^4.2.12" "@smithy/invalid-dependency": "npm:^4.2.12" "@smithy/middleware-content-length": "npm:^4.2.12" - "@smithy/middleware-endpoint": "npm:^4.4.27" - "@smithy/middleware-retry": "npm:^4.4.44" - "@smithy/middleware-serde": "npm:^4.2.15" + "@smithy/middleware-endpoint": "npm:^4.4.28" + "@smithy/middleware-retry": "npm:^4.4.45" + "@smithy/middleware-serde": "npm:^4.2.16" "@smithy/middleware-stack": "npm:^4.2.12" "@smithy/node-config-provider": "npm:^4.3.12" - "@smithy/node-http-handler": "npm:^4.5.0" + "@smithy/node-http-handler": "npm:^4.5.1" "@smithy/protocol-http": "npm:^5.3.12" - "@smithy/smithy-client": "npm:^4.12.7" + "@smithy/smithy-client": "npm:^4.12.8" "@smithy/types": "npm:^4.13.1" "@smithy/url-parser": "npm:^4.2.12" "@smithy/util-base64": "npm:^4.3.2" "@smithy/util-body-length-browser": "npm:^4.2.2" "@smithy/util-body-length-node": "npm:^4.2.3" - "@smithy/util-defaults-mode-browser": "npm:^4.3.43" - "@smithy/util-defaults-mode-node": "npm:^4.2.47" + "@smithy/util-defaults-mode-browser": "npm:^4.3.44" + "@smithy/util-defaults-mode-node": "npm:^4.2.48" "@smithy/util-endpoints": "npm:^3.3.3" "@smithy/util-middleware": "npm:^4.2.12" "@smithy/util-retry": "npm:^4.2.12" "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/14d790e99910b70b37da4683ff3f3667f918df0aa49c825a8fb6908901dcc26a8214498c0c786f3bc420f460a1129b8f39a03b78f1f4b3e166f89bce02e5c43f + checksum: 10/571e2c6606e489f2cfae4f114de6e72fea601f5ab9becedecf9a88a68e18025d90fc25a10d284a3970c405d3bdd5dfdfbdea384e4b7a881b96e950e8fcb84c9b languageName: node linkType: hard @@ -1405,32 +1405,32 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/signature-v4-multi-region@npm:^3.996.14": - version: 3.996.14 - resolution: "@aws-sdk/signature-v4-multi-region@npm:3.996.14" +"@aws-sdk/signature-v4-multi-region@npm:^3.996.15": + version: 3.996.15 + resolution: "@aws-sdk/signature-v4-multi-region@npm:3.996.15" dependencies: - "@aws-sdk/middleware-sdk-s3": "npm:^3.972.26" + "@aws-sdk/middleware-sdk-s3": "npm:^3.972.27" "@aws-sdk/types": "npm:^3.973.6" "@smithy/protocol-http": "npm:^5.3.12" "@smithy/signature-v4": "npm:^5.3.12" "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/a1f21cc3a37bd5941c5f25e2e70bdb6366f0e26d7e9fb56b70e446a9951295caa32e80f9827bc126fbc879b15861c02c72ddd8011c23d4ac9aeb98e3b2ef265a + checksum: 10/5449393486b058b0d6c041c2b44e0093869e5c26a9abda8fbdd5ba9d811b6d1ba518dce1e8068a7afb6e4931c08fb6598ac1d36a876dce66139d4ce20bcbf735 languageName: node linkType: hard -"@aws-sdk/token-providers@npm:3.1018.0": - version: 3.1018.0 - resolution: "@aws-sdk/token-providers@npm:3.1018.0" +"@aws-sdk/token-providers@npm:3.1020.0": + version: 3.1020.0 + resolution: "@aws-sdk/token-providers@npm:3.1020.0" dependencies: - "@aws-sdk/core": "npm:^3.973.25" - "@aws-sdk/nested-clients": "npm:^3.996.15" + "@aws-sdk/core": "npm:^3.973.26" + "@aws-sdk/nested-clients": "npm:^3.996.17" "@aws-sdk/types": "npm:^3.973.6" "@smithy/property-provider": "npm:^4.2.12" "@smithy/shared-ini-file-loader": "npm:^4.4.7" "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/a5b5e14c2a7cffb296c6ed808e6ce71bd7d09eab2fff6d5577cb3517ae31de3bfaea1d94aa3342f35b2e2e4d8145a17106b6e99879041abca5d561f595e5e964 + checksum: 10/e8758e5fda0013c1723eeaeb510de80952ab050ca975e6a5e4e231785a62b3373825315236cbf56af10d85cb2fda3a58f284145e60401c56c4eeee8303927d4b languageName: node linkType: hard @@ -1548,11 +1548,11 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/util-user-agent-node@npm:^3.973.12": - version: 3.973.12 - resolution: "@aws-sdk/util-user-agent-node@npm:3.973.12" +"@aws-sdk/util-user-agent-node@npm:^3.973.13": + version: 3.973.13 + resolution: "@aws-sdk/util-user-agent-node@npm:3.973.13" dependencies: - "@aws-sdk/middleware-user-agent": "npm:^3.972.26" + "@aws-sdk/middleware-user-agent": "npm:^3.972.27" "@aws-sdk/types": "npm:^3.973.6" "@smithy/node-config-provider": "npm:^4.3.12" "@smithy/types": "npm:^4.13.1" @@ -1563,7 +1563,7 @@ __metadata: peerDependenciesMeta: aws-crt: optional: true - checksum: 10/d5dde44808046c1aa8ec0b9f9b40a1438a68d36d0f27daf5a8abaf4c98c9df6b113653f853cc73d0f6163f07b8a086a500b3a03dd617144ca0bbcf4cc2adef8f + checksum: 10/d9265cc820993a09518abeb38e0dff03dacb3a5102683c10b1dceb22d387afae2e12ea14fc4f7008d891f7c288717d5bd1af9a4a5451eedac1b4b764b9ef3d56 languageName: node linkType: hard @@ -18146,16 +18146,6 @@ __metadata: languageName: node linkType: hard -"@smithy/abort-controller@npm:^4.2.12": - version: 4.2.12 - resolution: "@smithy/abort-controller@npm:4.2.12" - dependencies: - "@smithy/types": "npm:^4.13.1" - tslib: "npm:^2.6.2" - checksum: 10/6e7bd3c482c6737bb253a7b02f0f0f8cefb93497ab4374de810ab7afcdf7fa052f6706cdf02afa204d91bc5c85f404973be56396a50993acbc86e36e6d1bea05 - languageName: node - linkType: hard - "@smithy/chunked-blob-reader-native@npm:^4.2.3": version: 4.2.3 resolution: "@smithy/chunked-blob-reader-native@npm:4.2.3" @@ -18189,9 +18179,9 @@ __metadata: languageName: node linkType: hard -"@smithy/core@npm:^3.23.12": - version: 3.23.12 - resolution: "@smithy/core@npm:3.23.12" +"@smithy/core@npm:^3.23.13": + version: 3.23.13 + resolution: "@smithy/core@npm:3.23.13" dependencies: "@smithy/protocol-http": "npm:^5.3.12" "@smithy/types": "npm:^4.13.1" @@ -18199,11 +18189,11 @@ __metadata: "@smithy/util-base64": "npm:^4.3.2" "@smithy/util-body-length-browser": "npm:^4.2.2" "@smithy/util-middleware": "npm:^4.2.12" - "@smithy/util-stream": "npm:^4.5.20" + "@smithy/util-stream": "npm:^4.5.21" "@smithy/util-utf8": "npm:^4.2.2" "@smithy/uuid": "npm:^1.1.2" tslib: "npm:^2.6.2" - checksum: 10/5c658b3b3a482821de784218ba49a2b515c796481327bb7159a3e9953c7b8a575fcbab1a3484411eb9afd76789ac0b7c9f343c4b4b1ae4eb610c5ecc3b3f7a0f + checksum: 10/a901d75cbf172023dc0abcc8ceacd518c18867c8887752675059282fd6862e08379f75b4c6f41f0b7dbd13a8c1d231aafa1b3b1e28fe79d1593f9c622fa560b7 languageName: node linkType: hard @@ -18382,48 +18372,48 @@ __metadata: languageName: node linkType: hard -"@smithy/middleware-endpoint@npm:^4.4.27": - version: 4.4.27 - resolution: "@smithy/middleware-endpoint@npm:4.4.27" +"@smithy/middleware-endpoint@npm:^4.4.28": + version: 4.4.28 + resolution: "@smithy/middleware-endpoint@npm:4.4.28" dependencies: - "@smithy/core": "npm:^3.23.12" - "@smithy/middleware-serde": "npm:^4.2.15" + "@smithy/core": "npm:^3.23.13" + "@smithy/middleware-serde": "npm:^4.2.16" "@smithy/node-config-provider": "npm:^4.3.12" "@smithy/shared-ini-file-loader": "npm:^4.4.7" "@smithy/types": "npm:^4.13.1" "@smithy/url-parser": "npm:^4.2.12" "@smithy/util-middleware": "npm:^4.2.12" tslib: "npm:^2.6.2" - checksum: 10/bf9d87ac43363d1904de0294e34ad97741b068263fca0b269611659e207a03fa9910a1651617db29a278893566a33f0f686918f59b0a1cfd9972d78c979804c7 + checksum: 10/afbb873c955e4ca02a9c33f709f0dfc4e32f9b0a834ffb7860881167b611d80282955a220c3568a362efef47b273c7da533d0dafe4d3d0b5c3fa9258e4708e23 languageName: node linkType: hard -"@smithy/middleware-retry@npm:^4.4.44": - version: 4.4.44 - resolution: "@smithy/middleware-retry@npm:4.4.44" +"@smithy/middleware-retry@npm:^4.4.45": + version: 4.4.45 + resolution: "@smithy/middleware-retry@npm:4.4.45" dependencies: "@smithy/node-config-provider": "npm:^4.3.12" "@smithy/protocol-http": "npm:^5.3.12" "@smithy/service-error-classification": "npm:^4.2.12" - "@smithy/smithy-client": "npm:^4.12.7" + "@smithy/smithy-client": "npm:^4.12.8" "@smithy/types": "npm:^4.13.1" "@smithy/util-middleware": "npm:^4.2.12" "@smithy/util-retry": "npm:^4.2.12" "@smithy/uuid": "npm:^1.1.2" tslib: "npm:^2.6.2" - checksum: 10/f457128dbbb51fe3e25220cd230ce3adb1e33f87b7415eaf843d1eac1a2192c6f56c0583d20e6fc77d1b25fa61a1a06442745178d03ad11d5f80692fcaa206dd + checksum: 10/c5fb3806177efbf14066d5a173a3f5ec1b4d878ce669c81a4a34afe94aa69016151cd168175b8cbb5142455766a699991e44712621d39dfef621cd7a6f44c32e languageName: node linkType: hard -"@smithy/middleware-serde@npm:^4.2.15": - version: 4.2.15 - resolution: "@smithy/middleware-serde@npm:4.2.15" +"@smithy/middleware-serde@npm:^4.2.16": + version: 4.2.16 + resolution: "@smithy/middleware-serde@npm:4.2.16" dependencies: - "@smithy/core": "npm:^3.23.12" + "@smithy/core": "npm:^3.23.13" "@smithy/protocol-http": "npm:^5.3.12" "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/fa39127faeaadda5a6c599e28613c36f79dd0cc3b9c3969c9e51d009bc4df58fd5879187930137db096722bbf26c72ad459ef808a887bcef8f7c8907fce835ec + checksum: 10/57509a894067c111b1e71b5915d24d4ba5901616f016b308486d77bd18e71a1cec2eb5b73964486643f19d15a149d15f5db89b37f77a6bdcb65f11de789aed0f languageName: node linkType: hard @@ -18462,16 +18452,15 @@ __metadata: languageName: node linkType: hard -"@smithy/node-http-handler@npm:^4.5.0": - version: 4.5.0 - resolution: "@smithy/node-http-handler@npm:4.5.0" +"@smithy/node-http-handler@npm:^4.5.1": + version: 4.5.1 + resolution: "@smithy/node-http-handler@npm:4.5.1" dependencies: - "@smithy/abort-controller": "npm:^4.2.12" "@smithy/protocol-http": "npm:^5.3.12" "@smithy/querystring-builder": "npm:^4.2.12" "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/d7b55ab90389bcc1dfc0355abed5bc163ed4751e57f43b3398a760266ac4eaae384eab742a82fcdc12d4da0d6446f8c48533ef9d21fe90a58cfafddbbf2c8446 + checksum: 10/30a60b59759b8aee97588aecf121b0ab8b16f3040382a26247fe1f779d863f76e544cfc28d14f49a8705124ec58c73e37291602a4253055f3f2abf4f484c8e35 languageName: node linkType: hard @@ -18588,18 +18577,18 @@ __metadata: languageName: node linkType: hard -"@smithy/smithy-client@npm:^4.12.7": - version: 4.12.7 - resolution: "@smithy/smithy-client@npm:4.12.7" +"@smithy/smithy-client@npm:^4.12.8": + version: 4.12.8 + resolution: "@smithy/smithy-client@npm:4.12.8" dependencies: - "@smithy/core": "npm:^3.23.12" - "@smithy/middleware-endpoint": "npm:^4.4.27" + "@smithy/core": "npm:^3.23.13" + "@smithy/middleware-endpoint": "npm:^4.4.28" "@smithy/middleware-stack": "npm:^4.2.12" "@smithy/protocol-http": "npm:^5.3.12" "@smithy/types": "npm:^4.13.1" - "@smithy/util-stream": "npm:^4.5.20" + "@smithy/util-stream": "npm:^4.5.21" tslib: "npm:^2.6.2" - checksum: 10/fa5128a76a7825b755a0228b10a1c8e617c1fdf8a85a0d6155b249325684bb13b106fcae1061f8ac66233bec1ac5c1472791abda0e6ca24a8bfcdfe6067a2e86 + checksum: 10/c78efec622cb9596fdeccb18910a4dad63b9ededd85e49a216746cd787c1f683c7e8b41d508cde726fc75155b8607a199cafe7b90044f772e1a91dbcccd04105 languageName: node linkType: hard @@ -18709,30 +18698,30 @@ __metadata: languageName: node linkType: hard -"@smithy/util-defaults-mode-browser@npm:^4.3.43": - version: 4.3.43 - resolution: "@smithy/util-defaults-mode-browser@npm:4.3.43" +"@smithy/util-defaults-mode-browser@npm:^4.3.44": + version: 4.3.44 + resolution: "@smithy/util-defaults-mode-browser@npm:4.3.44" dependencies: "@smithy/property-provider": "npm:^4.2.12" - "@smithy/smithy-client": "npm:^4.12.7" + "@smithy/smithy-client": "npm:^4.12.8" "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/0543d5f4f8466849fcdc6aabe8fedd24184e34a39dc09588484c9cdf6234b47ea70cb318004d7abfe9ed46266fb5ae0deaf54e0b190097def20d2123abb1fc76 + checksum: 10/39ea98e4dd9a75390f07678982547e589386795cf939b3958c2da31b31e8f3f2009a6c65caf5831c5b40f51a461cb0e7d2aeb3199ba1e7e52768ce157e42a8b2 languageName: node linkType: hard -"@smithy/util-defaults-mode-node@npm:^4.2.47": - version: 4.2.47 - resolution: "@smithy/util-defaults-mode-node@npm:4.2.47" +"@smithy/util-defaults-mode-node@npm:^4.2.48": + version: 4.2.48 + resolution: "@smithy/util-defaults-mode-node@npm:4.2.48" dependencies: "@smithy/config-resolver": "npm:^4.4.13" "@smithy/credential-provider-imds": "npm:^4.2.12" "@smithy/node-config-provider": "npm:^4.3.12" "@smithy/property-provider": "npm:^4.2.12" - "@smithy/smithy-client": "npm:^4.12.7" + "@smithy/smithy-client": "npm:^4.12.8" "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/db1535ccb85c9c905373b03eed7504501b7a3f9fe425317d45117a63f38867b87eb0840d346d2a24cdb74971f79d781502aff9dbfac68456e2e06f95a69e45f3 + checksum: 10/aa11fb8e9e6748e1788153d27c5313db020799f79f0546730d852d961c148dbbcc1cdf59803944731b47b6fab0a747a1d287741f145be7e2c79425129506176f languageName: node linkType: hard @@ -18796,19 +18785,19 @@ __metadata: languageName: node linkType: hard -"@smithy/util-stream@npm:^4.5.20": - version: 4.5.20 - resolution: "@smithy/util-stream@npm:4.5.20" +"@smithy/util-stream@npm:^4.5.21": + version: 4.5.21 + resolution: "@smithy/util-stream@npm:4.5.21" dependencies: "@smithy/fetch-http-handler": "npm:^5.3.15" - "@smithy/node-http-handler": "npm:^4.5.0" + "@smithy/node-http-handler": "npm:^4.5.1" "@smithy/types": "npm:^4.13.1" "@smithy/util-base64": "npm:^4.3.2" "@smithy/util-buffer-from": "npm:^4.2.2" "@smithy/util-hex-encoding": "npm:^4.2.2" "@smithy/util-utf8": "npm:^4.2.2" tslib: "npm:^2.6.2" - checksum: 10/1e452cd1f90033b96a7a93fe6ec18fa93abf0e371dacd728b52016bf113256021e53629ea7b7bc7782a43a3e352314b08a3fe420ae93c5cb88a2a38a28e0038d + checksum: 10/f1d188450923c75153737393cecbc6885fdc3ed992cdbc4912ee9c902df2519007de1f65231e3e00bfcbbae862d450db5c85b4ea532025b8125725f9b1e4cd00 languageName: node linkType: hard @@ -18860,14 +18849,13 @@ __metadata: languageName: node linkType: hard -"@smithy/util-waiter@npm:^4.2.13": - version: 4.2.13 - resolution: "@smithy/util-waiter@npm:4.2.13" +"@smithy/util-waiter@npm:^4.2.14": + version: 4.2.14 + resolution: "@smithy/util-waiter@npm:4.2.14" dependencies: - "@smithy/abort-controller": "npm:^4.2.12" "@smithy/types": "npm:^4.13.1" tslib: "npm:^2.6.2" - checksum: 10/b20b3d439bde2c08be0bbaa0e72b16b49ffb43bed5e50da6c5c2f8516c4c62fc0a4a67751a3d85e6349c78fea3847e98b2acd8da03c80952c1157a16822ff7a7 + checksum: 10/b2ce66060db441105ecd5c4bf9a80752aa41e9d2b1c673b8c32e0c55963a0301d21e178caeb0ee6cd24ba69629b87bbbf36a6b8d036864d365c659ccfe0cacad languageName: node linkType: hard From fec31bdde5b0d552eefd89d4ac079bb08abae942 Mon Sep 17 00:00:00 2001 From: Jonathan Roebuck Date: Tue, 31 Mar 2026 13:31:23 +0100 Subject: [PATCH 071/191] feat(auth-node): add OAuthAuthenticatorLogoutResult type for provider logout redirects Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Jonathan Roebuck --- plugins/auth-node/src/oauth/index.ts | 1 + plugins/auth-node/src/oauth/types.ts | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/plugins/auth-node/src/oauth/index.ts b/plugins/auth-node/src/oauth/index.ts index 9cff6c5358..afc6faa27e 100644 --- a/plugins/auth-node/src/oauth/index.ts +++ b/plugins/auth-node/src/oauth/index.ts @@ -37,6 +37,7 @@ export { type OAuthAuthenticator, type OAuthAuthenticatorAuthenticateInput, type OAuthAuthenticatorLogoutInput, + type OAuthAuthenticatorLogoutResult, type OAuthAuthenticatorRefreshInput, type OAuthAuthenticatorResult, type OAuthAuthenticatorScopeOptions, diff --git a/plugins/auth-node/src/oauth/types.ts b/plugins/auth-node/src/oauth/types.ts index 50d81f99dc..968ffac39e 100644 --- a/plugins/auth-node/src/oauth/types.ts +++ b/plugins/auth-node/src/oauth/types.ts @@ -76,6 +76,16 @@ export interface OAuthAuthenticatorLogoutInput { req: Request; } +/** @public */ +export interface OAuthAuthenticatorLogoutResult { + /** + * If set, the frontend will redirect the browser to this URL after clearing + * the Backstage session. Use this to terminate provider-side sessions (e.g. + * Auth0's `/v2/logout` endpoint). + */ + logoutUrl?: string; +} + /** @public */ export interface OAuthAuthenticatorResult { fullProfile: TProfile; @@ -101,7 +111,10 @@ export interface OAuthAuthenticator { input: OAuthAuthenticatorRefreshInput, ctx: TContext, ): Promise>; - logout?(input: OAuthAuthenticatorLogoutInput, ctx: TContext): Promise; + logout?( + input: OAuthAuthenticatorLogoutInput, + ctx: TContext, + ): Promise; } /** @public */ From 0ef5a03fb376a76f9db99df215ebbee8fe0d745b Mon Sep 17 00:00:00 2001 From: Jonathan Roebuck Date: Tue, 31 Mar 2026 14:14:21 +0100 Subject: [PATCH 072/191] feat(auth-node): return logoutUrl in logout response when provided by authenticator Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Jonathan Roebuck --- .../oauth/createOAuthRouteHandlers.test.ts | 43 +++++++++++++++++++ .../src/oauth/createOAuthRouteHandlers.ts | 12 +++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/plugins/auth-node/src/oauth/createOAuthRouteHandlers.test.ts b/plugins/auth-node/src/oauth/createOAuthRouteHandlers.test.ts index f2cb27dfa9..c0fece0eaf 100644 --- a/plugins/auth-node/src/oauth/createOAuthRouteHandlers.test.ts +++ b/plugins/auth-node/src/oauth/createOAuthRouteHandlers.test.ts @@ -1264,6 +1264,49 @@ describe('createOAuthRouteHandlers', () => { }); }); + it('should return logoutUrl as JSON when authenticator provides one', async () => { + mockAuthenticator.logout.mockResolvedValueOnce({ + logoutUrl: 'https://example.auth0.com/v2/logout?federated', + }); + + const agent = request.agent( + wrapInApp(createOAuthRouteHandlers(baseConfig)), + ); + + agent.jar.setCookie( + 'my-provider-refresh-token=my-refresh-token', + '127.0.0.1', + '/my-provider', + ); + + const res = await agent + .post('/my-provider/logout') + .set('X-Requested-With', 'XMLHttpRequest'); + + expect(res.status).toBe(200); + expect(res.body).toEqual({ + logoutUrl: 'https://example.auth0.com/v2/logout?federated', + }); + + // Cookie should still be cleared even when logoutUrl is returned + expect(getRefreshTokenCookie(agent)).toBeUndefined(); + }); + + it('should return empty body when authenticator logout returns void', async () => { + mockAuthenticator.logout.mockResolvedValueOnce(undefined); + + const agent = request.agent( + wrapInApp(createOAuthRouteHandlers(baseConfig)), + ); + + const res = await agent + .post('/my-provider/logout') + .set('X-Requested-With', 'XMLHttpRequest'); + + expect(res.status).toBe(200); + expect(res.body).toEqual({}); + }); + it('should set error search param and redirect on caught error', async () => { const app = wrapInApp(createOAuthRouteHandlers(baseConfig)); const res = await request(app) diff --git a/plugins/auth-node/src/oauth/createOAuthRouteHandlers.ts b/plugins/auth-node/src/oauth/createOAuthRouteHandlers.ts index 0b250eceae..0d268eb052 100644 --- a/plugins/auth-node/src/oauth/createOAuthRouteHandlers.ts +++ b/plugins/auth-node/src/oauth/createOAuthRouteHandlers.ts @@ -280,9 +280,13 @@ export function createOAuthRouteHandlers( throw new AuthenticationError('Invalid X-Requested-With header'); } + let logoutResult: void | { logoutUrl?: string }; if (authenticator.logout) { const refreshToken = cookieManager.getRefreshToken(req); - await authenticator.logout({ req, refreshToken }, authenticatorCtx); + logoutResult = await authenticator.logout( + { req, refreshToken }, + authenticatorCtx, + ); } // remove refresh token cookie if it is set @@ -291,7 +295,11 @@ export function createOAuthRouteHandlers( // remove persisted scopes await scopeManager.clear(req); - res.status(200).end(); + if (logoutResult?.logoutUrl) { + res.status(200).json({ logoutUrl: logoutResult.logoutUrl }); + } else { + res.status(200).end(); + } }, async refresh( From 97850d0ef1b2660b611c7eeaa291cbfc310974c8 Mon Sep 17 00:00:00 2001 From: Jonathan Roebuck Date: Tue, 31 Mar 2026 14:17:23 +0100 Subject: [PATCH 073/191] feat(auth0): implement federated logout to clear Auth0 and IdP sessions Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Jonathan Roebuck --- .../src/authenticator.ts | 11 ++++- .../src/module.test.ts | 44 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/plugins/auth-backend-module-auth0-provider/src/authenticator.ts b/plugins/auth-backend-module-auth0-provider/src/authenticator.ts index 5ed5fc580c..6d867ecefe 100644 --- a/plugins/auth-backend-module-auth0-provider/src/authenticator.ts +++ b/plugins/auth-backend-module-auth0-provider/src/authenticator.ts @@ -85,7 +85,7 @@ export const auth0Authenticator = createOAuthAuthenticator({ }, ), ); - return { helper, audience, connection, connectionScope }; + return { helper, audience, connection, connectionScope, domain, clientID }; }, async start( @@ -115,4 +115,13 @@ export const auth0Authenticator = createOAuthAuthenticator({ async refresh(input, { helper }) { return helper.refresh(input); }, + + async logout(input, { domain, clientID }) { + const origin = input.req.get('origin') ?? ''; + return { + logoutUrl: `https://${domain}/v2/logout?federated&client_id=${encodeURIComponent( + clientID, + )}&returnTo=${encodeURIComponent(origin)}`, + }; + }, }); diff --git a/plugins/auth-backend-module-auth0-provider/src/module.test.ts b/plugins/auth-backend-module-auth0-provider/src/module.test.ts index 28ccf90fa4..4868c0d995 100644 --- a/plugins/auth-backend-module-auth0-provider/src/module.test.ts +++ b/plugins/auth-backend-module-auth0-provider/src/module.test.ts @@ -180,4 +180,48 @@ describe('authModuleAuth0Provider', () => { 'Organization mismatch. The organization provided in the request does not match the organization configured in the strategy.', ); }); + + it('should return Auth0 logout URL on logout', async () => { + const { server } = await startTestBackend({ + features: [ + authPlugin, + authModuleAuth0Provider, + mockServices.rootConfig.factory({ + data: { + app: { + baseUrl: 'http://localhost:3000', + }, + auth: { + providers: { + auth0: { + development: { + clientId: 'test-client-id', + clientSecret: 'clientSecret', + domain: 'test.eu.auth0.com', + }, + }, + }, + session: { + secret: 'secret', + }, + }, + }, + }), + ], + }); + + const res = await request(server) + .post('/api/auth/auth0/logout') + .query({ env: 'development' }) + .set('X-Requested-With', 'XMLHttpRequest') + .set('Origin', 'http://localhost:3000'); + + expect(res.status).toBe(200); + expect(res.body.logoutUrl).toContain('test.eu.auth0.com/v2/logout'); + expect(res.body.logoutUrl).toContain('federated'); + expect(res.body.logoutUrl).toContain('client_id=test-client-id'); + expect(res.body.logoutUrl).toContain( + `returnTo=${encodeURIComponent('http://localhost:3000')}`, + ); + }); }); From 906f104f6b29838ac71565fef8817f8f7b93f73e Mon Sep 17 00:00:00 2001 From: Jonathan Roebuck Date: Tue, 31 Mar 2026 14:33:05 +0100 Subject: [PATCH 074/191] feat(core-app-api): redirect to provider logoutUrl on sign-out when available Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Jonathan Roebuck --- .../DefaultAuthConnector.test.ts | 39 +++++++++++++++++++ .../lib/AuthConnector/DefaultAuthConnector.ts | 18 +++++++++ 2 files changed, 57 insertions(+) diff --git a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts index 192b1b7ac0..02bda40b64 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts @@ -262,4 +262,43 @@ describe('DefaultAuthConnector', () => { url: 'http://my-host/api/auth/my-provider/start?scope=-ab-&origin=http%3A%2F%2Flocalhost&flow=popup&env=production', }); }); + + it('should not resolve when provider returns a logoutUrl', async () => { + const logoutUrl = + 'https://test.auth0.com/v2/logout?federated&client_id=abc&returnTo=http%3A%2F%2Flocalhost'; + + server.use( + rest.post('*', (_req, res, ctx) => res(ctx.json({ logoutUrl }))), + ); + + const connector = new DefaultAuthConnector(defaultOptions); + + // When a logoutUrl is returned, removeSession redirects the browser and + // returns a never-resolving promise. Race against a short delay to verify + // that it does not resolve. + const result = await Promise.race([ + connector.removeSession().then(() => 'resolved'), + new Promise<'timeout'>(r => setTimeout(() => r('timeout'), 50)), + ]); + + expect(result).toBe('timeout'); + }); + + it('should complete normally when provider returns empty logout response', async () => { + server.use(rest.post('*', (_req, res, ctx) => res(ctx.status(200)))); + + const connector = new DefaultAuthConnector(defaultOptions); + await connector.removeSession(); + // No redirect, no error — the original behavior + }); + + it('should complete normally when response is not JSON', async () => { + server.use( + rest.post('*', (_req, res, ctx) => res(ctx.status(200), ctx.text('OK'))), + ); + + const connector = new DefaultAuthConnector(defaultOptions); + await connector.removeSession(); + // Should complete without error — non-JSON responses are ignored + }); }); diff --git a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts index 3671ddc6e6..32d86b9757 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts @@ -194,6 +194,24 @@ export class DefaultAuthConnector error.status = res.status; throw error; } + + // If the auth provider returned a logout URL (e.g. for Auth0 federated + // logout), redirect the browser to clear the provider's session cookies. + try { + const contentType = res.headers.get('content-type'); + if (contentType?.includes('application/json')) { + const body = await res.json(); + if (body.logoutUrl) { + window.location.href = body.logoutUrl; + return new Promise(() => {}); + } + } + } catch { + // Provider logout redirect is best-effort — the Backstage session is + // already cleared, so we degrade gracefully. + } + + return undefined; } private async showPopup(scopes: Set): Promise { From 9244b70c57817fdc7655508754249c72eb0dda8b Mon Sep 17 00:00:00 2001 From: Jonathan Roebuck Date: Tue, 31 Mar 2026 15:15:51 +0100 Subject: [PATCH 075/191] chore: add changesets, update API reports, fix type errors Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Jonathan Roebuck --- .changeset/auth-node-logout-result.md | 5 +++++ .changeset/auth0-federated-logout.md | 5 +++++ .changeset/core-app-api-logout-redirect.md | 5 +++++ plugins/auth-node/report.api.md | 10 +++++++++- .../src/oauth/createOAuthRouteHandlers.test.ts | 4 ++-- .../auth-node/src/oauth/createOAuthRouteHandlers.ts | 2 +- 6 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 .changeset/auth-node-logout-result.md create mode 100644 .changeset/auth0-federated-logout.md create mode 100644 .changeset/core-app-api-logout-redirect.md diff --git a/.changeset/auth-node-logout-result.md b/.changeset/auth-node-logout-result.md new file mode 100644 index 0000000000..a571fde853 --- /dev/null +++ b/.changeset/auth-node-logout-result.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-node': minor +--- + +Added `OAuthAuthenticatorLogoutResult` type. The `logout` method on `OAuthAuthenticator` can now optionally return `{ logoutUrl }` to trigger a browser redirect after sign-out. This allows providers like Auth0 to clear their session cookies by redirecting to their logout endpoint. diff --git a/.changeset/auth0-federated-logout.md b/.changeset/auth0-federated-logout.md new file mode 100644 index 0000000000..ad50b03ac9 --- /dev/null +++ b/.changeset/auth0-federated-logout.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-auth0-provider': minor +--- + +Added federated logout support. On sign-out, the Auth0 authenticator now returns a logout URL that redirects the browser to Auth0's `/v2/logout?federated` endpoint, clearing both the Auth0 session and any upstream IdP session. This ensures users must fully re-authenticate after signing out. diff --git a/.changeset/core-app-api-logout-redirect.md b/.changeset/core-app-api-logout-redirect.md new file mode 100644 index 0000000000..32aa405e94 --- /dev/null +++ b/.changeset/core-app-api-logout-redirect.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +The `DefaultAuthConnector` now checks for a `logoutUrl` in the logout response body. If the auth provider returns one (e.g. Auth0 federated logout), the browser is redirected to that URL to clear the provider's session cookies. This is backward compatible — providers that return an empty response are unaffected. diff --git a/plugins/auth-node/report.api.md b/plugins/auth-node/report.api.md index b360c2ae6a..c653897e21 100644 --- a/plugins/auth-node/report.api.md +++ b/plugins/auth-node/report.api.md @@ -293,7 +293,10 @@ export interface OAuthAuthenticator { // (undocumented) initialize(ctx: { callbackUrl: string; config: Config }): TContext; // (undocumented) - logout?(input: OAuthAuthenticatorLogoutInput, ctx: TContext): Promise; + logout?( + input: OAuthAuthenticatorLogoutInput, + ctx: TContext, + ): Promise; // (undocumented) refresh( input: OAuthAuthenticatorRefreshInput, @@ -329,6 +332,11 @@ export interface OAuthAuthenticatorLogoutInput { req: Request_2; } +// @public (undocumented) +export interface OAuthAuthenticatorLogoutResult { + logoutUrl?: string; +} + // @public (undocumented) export interface OAuthAuthenticatorRefreshInput { // (undocumented) diff --git a/plugins/auth-node/src/oauth/createOAuthRouteHandlers.test.ts b/plugins/auth-node/src/oauth/createOAuthRouteHandlers.test.ts index c0fece0eaf..62e7246003 100644 --- a/plugins/auth-node/src/oauth/createOAuthRouteHandlers.test.ts +++ b/plugins/auth-node/src/oauth/createOAuthRouteHandlers.test.ts @@ -1265,7 +1265,7 @@ describe('createOAuthRouteHandlers', () => { }); it('should return logoutUrl as JSON when authenticator provides one', async () => { - mockAuthenticator.logout.mockResolvedValueOnce({ + (mockAuthenticator.logout as jest.Mock).mockResolvedValueOnce({ logoutUrl: 'https://example.auth0.com/v2/logout?federated', }); @@ -1293,7 +1293,7 @@ describe('createOAuthRouteHandlers', () => { }); it('should return empty body when authenticator logout returns void', async () => { - mockAuthenticator.logout.mockResolvedValueOnce(undefined); + (mockAuthenticator.logout as jest.Mock).mockResolvedValueOnce(undefined); const agent = request.agent( wrapInApp(createOAuthRouteHandlers(baseConfig)), diff --git a/plugins/auth-node/src/oauth/createOAuthRouteHandlers.ts b/plugins/auth-node/src/oauth/createOAuthRouteHandlers.ts index 0d268eb052..d12060b20f 100644 --- a/plugins/auth-node/src/oauth/createOAuthRouteHandlers.ts +++ b/plugins/auth-node/src/oauth/createOAuthRouteHandlers.ts @@ -280,7 +280,7 @@ export function createOAuthRouteHandlers( throw new AuthenticationError('Invalid X-Requested-With header'); } - let logoutResult: void | { logoutUrl?: string }; + let logoutResult: void | { logoutUrl?: string } = undefined; if (authenticator.logout) { const refreshToken = cookieManager.getRefreshToken(req); logoutResult = await authenticator.logout( From 0449cf02fdee20bef9d44fcf71bcd4b2b31039e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 31 Mar 2026 17:20:34 +0200 Subject: [PATCH 076/191] Update packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../src/components/MarkdownContent/MarkdownContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx index 579eacf05e..61e519f33a 100644 --- a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx +++ b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx @@ -113,7 +113,7 @@ const components: Options['components'] = { a: ({ href, children, title, target, rel }) => href ? ( - + {children} ) : ( From a2cb332e2535b5cad6da99ba7434aa9d0dcbec9e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 31 Mar 2026 15:30:51 +0000 Subject: [PATCH 077/191] Version Packages (next) --- .changeset/create-app-1774970958.md | 5 + .changeset/pre.json | 50 +- docs/releases/v1.50.0-next.1-changelog.md | 1679 +++++++++++++++++ package.json | 2 +- packages/app-defaults/CHANGELOG.md | 10 + packages/app-defaults/package.json | 2 +- packages/app-example-plugin/CHANGELOG.md | 8 + packages/app-example-plugin/package.json | 2 +- packages/app-legacy/CHANGELOG.md | 35 + packages/app-legacy/package.json | 2 +- packages/app/CHANGELOG.md | 41 + packages/app/package.json | 2 +- packages/backend-app-api/CHANGELOG.md | 7 + packages/backend-app-api/package.json | 2 +- packages/backend-defaults/CHANGELOG.md | 14 + packages/backend-defaults/package.json | 2 +- .../CHANGELOG.md | 17 + .../package.json | 2 +- packages/backend-openapi-utils/CHANGELOG.md | 7 + packages/backend-openapi-utils/package.json | 2 +- packages/backend-plugin-api/CHANGELOG.md | 12 + packages/backend-plugin-api/package.json | 2 +- packages/backend-test-utils/CHANGELOG.md | 12 + packages/backend-test-utils/package.json | 2 +- packages/backend/CHANGELOG.md | 41 + packages/backend/package.json | 2 +- packages/cli-module-auth/CHANGELOG.md | 6 + packages/cli-module-auth/package.json | 2 +- packages/cli-module-build/CHANGELOG.md | 6 + packages/cli-module-build/package.json | 2 +- packages/cli-module-new/CHANGELOG.md | 6 + packages/cli-module-new/package.json | 2 +- packages/cli-module-test-jest/CHANGELOG.md | 7 + packages/cli-module-test-jest/package.json | 2 +- packages/cli/CHANGELOG.md | 11 + packages/cli/package.json | 2 +- packages/core-app-api/CHANGELOG.md | 12 + packages/core-app-api/package.json | 2 +- packages/core-compat-api/CHANGELOG.md | 12 + packages/core-compat-api/package.json | 2 +- packages/core-plugin-api/CHANGELOG.md | 7 + packages/core-plugin-api/package.json | 2 +- packages/create-app/CHANGELOG.md | 6 + packages/create-app/package.json | 2 +- packages/dev-utils/CHANGELOG.md | 12 + packages/dev-utils/package.json | 2 +- packages/eslint-plugin/CHANGELOG.md | 6 + packages/eslint-plugin/package.json | 2 +- packages/frontend-app-api/CHANGELOG.md | 11 + packages/frontend-app-api/package.json | 2 +- packages/frontend-defaults/CHANGELOG.md | 10 + packages/frontend-defaults/package.json | 2 +- packages/frontend-dev-utils/CHANGELOG.md | 10 + packages/frontend-dev-utils/package.json | 2 +- .../CHANGELOG.md | 7 + .../package.json | 2 +- packages/frontend-internal/CHANGELOG.md | 7 + packages/frontend-internal/package.json | 2 +- packages/frontend-plugin-api/CHANGELOG.md | 11 + packages/frontend-plugin-api/package.json | 2 +- packages/frontend-test-utils/CHANGELOG.md | 13 + packages/frontend-test-utils/package.json | 2 +- packages/repo-tools/CHANGELOG.md | 9 + packages/repo-tools/package.json | 2 +- packages/scaffolder-internal/CHANGELOG.md | 8 + packages/scaffolder-internal/package.json | 2 +- .../techdocs-cli-embedded-app/CHANGELOG.md | 17 + .../techdocs-cli-embedded-app/package.json | 2 +- packages/test-utils/CHANGELOG.md | 8 + packages/test-utils/package.json | 2 +- packages/ui/CHANGELOG.md | 30 + packages/ui/package.json | 2 +- plugins/api-docs/CHANGELOG.md | 12 + plugins/api-docs/package.json | 2 +- plugins/app-backend/CHANGELOG.md | 9 + plugins/app-backend/package.json | 2 +- plugins/app-node/CHANGELOG.md | 7 + plugins/app-node/package.json | 2 +- plugins/app-react/CHANGELOG.md | 8 + plugins/app-react/package.json | 2 +- plugins/app-visualizer/CHANGELOG.md | 10 + plugins/app-visualizer/package.json | 2 +- plugins/app/CHANGELOG.md | 25 + plugins/app/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 | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../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 | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- plugins/auth-backend/CHANGELOG.md | 9 + plugins/auth-backend/package.json | 2 +- plugins/auth-node/CHANGELOG.md | 32 + plugins/auth-node/package.json | 2 +- plugins/auth/CHANGELOG.md | 8 + plugins/auth/package.json | 2 +- .../catalog-backend-module-aws/CHANGELOG.md | 9 + .../catalog-backend-module-aws/package.json | 2 +- .../catalog-backend-module-azure/CHANGELOG.md | 10 + .../catalog-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../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 | 8 + .../package.json | 2 +- .../catalog-backend-module-gitea/CHANGELOG.md | 8 + .../catalog-backend-module-gitea/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../catalog-backend-module-ldap/CHANGELOG.md | 8 + .../catalog-backend-module-ldap/package.json | 2 +- .../catalog-backend-module-logs/CHANGELOG.md | 9 + .../catalog-backend-module-logs/package.json | 2 +- .../CHANGELOG.md | 8 + .../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 +- plugins/catalog-backend/CHANGELOG.md | 14 + plugins/catalog-backend/package.json | 2 +- plugins/catalog-graph/CHANGELOG.md | 12 + plugins/catalog-graph/package.json | 2 +- plugins/catalog-import/CHANGELOG.md | 11 + plugins/catalog-import/package.json | 2 +- plugins/catalog-node/CHANGELOG.md | 9 + plugins/catalog-node/package.json | 2 +- plugins/catalog-react/CHANGELOG.md | 14 + plugins/catalog-react/package.json | 2 +- .../catalog-unprocessed-entities/CHANGELOG.md | 11 + .../catalog-unprocessed-entities/package.json | 2 +- plugins/catalog/CHANGELOG.md | 14 + plugins/catalog/package.json | 2 +- plugins/devtools-backend/CHANGELOG.md | 8 + plugins/devtools-backend/package.json | 2 +- plugins/devtools-react/CHANGELOG.md | 8 + plugins/devtools-react/package.json | 2 +- plugins/devtools/CHANGELOG.md | 11 + plugins/devtools/package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../events-backend-module-azure/CHANGELOG.md | 8 + .../events-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 8 + .../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 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../events-backend-module-kafka/CHANGELOG.md | 8 + .../events-backend-module-kafka/package.json | 2 +- plugins/events-backend/CHANGELOG.md | 9 + plugins/events-backend/package.json | 2 +- plugins/events-node/CHANGELOG.md | 7 + plugins/events-node/package.json | 2 +- .../example-todo-list-backend/CHANGELOG.md | 7 + .../example-todo-list-backend/package.json | 2 +- plugins/gateway-backend/CHANGELOG.md | 7 + plugins/gateway-backend/package.json | 2 +- plugins/home-react/CHANGELOG.md | 10 + plugins/home-react/package.json | 2 +- plugins/home/CHANGELOG.md | 13 + plugins/home/package.json | 2 +- plugins/kubernetes-backend/CHANGELOG.md | 10 + plugins/kubernetes-backend/package.json | 2 +- plugins/kubernetes-node/CHANGELOG.md | 7 + plugins/kubernetes-node/package.json | 2 +- plugins/kubernetes/CHANGELOG.md | 12 + plugins/kubernetes/package.json | 2 +- plugins/mcp-actions-backend/CHANGELOG.md | 8 + plugins/mcp-actions-backend/package.json | 2 +- plugins/mui-to-bui/CHANGELOG.md | 9 + plugins/mui-to-bui/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/notifications-backend/CHANGELOG.md | 10 + plugins/notifications-backend/package.json | 2 +- plugins/notifications-node/CHANGELOG.md | 8 + plugins/notifications-node/package.json | 2 +- plugins/notifications/CHANGELOG.md | 11 + plugins/notifications/package.json | 2 +- plugins/org/CHANGELOG.md | 13 + plugins/org/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/permission-backend/CHANGELOG.md | 9 + plugins/permission-backend/package.json | 2 +- plugins/permission-node/CHANGELOG.md | 8 + plugins/permission-node/package.json | 2 +- plugins/proxy-backend/CHANGELOG.md | 8 + plugins/proxy-backend/package.json | 2 +- plugins/proxy-node/CHANGELOG.md | 7 + plugins/proxy-node/package.json | 2 +- .../CHANGELOG.md | 8 + .../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 | 8 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/scaffolder-backend/CHANGELOG.md | 17 + plugins/scaffolder-backend/package.json | 2 +- .../scaffolder-node-test-utils/CHANGELOG.md | 9 + .../scaffolder-node-test-utils/package.json | 2 +- plugins/scaffolder-node/CHANGELOG.md | 8 + plugins/scaffolder-node/package.json | 2 +- plugins/scaffolder-react/CHANGELOG.md | 11 + plugins/scaffolder-react/package.json | 2 +- plugins/scaffolder/CHANGELOG.md | 19 + plugins/scaffolder/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- plugins/search-backend-module-pg/CHANGELOG.md | 8 + plugins/search-backend-module-pg/package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/search-backend-node/CHANGELOG.md | 7 + plugins/search-backend-node/package.json | 2 +- plugins/search-backend/CHANGELOG.md | 10 + plugins/search-backend/package.json | 2 +- plugins/search-react/CHANGELOG.md | 9 + plugins/search-react/package.json | 2 +- plugins/search/CHANGELOG.md | 13 + plugins/search/package.json | 2 +- plugins/signals-backend/CHANGELOG.md | 9 + plugins/signals-backend/package.json | 2 +- plugins/signals-node/CHANGELOG.md | 9 + plugins/signals-node/package.json | 2 +- plugins/signals/CHANGELOG.md | 10 + plugins/signals/package.json | 2 +- .../techdocs-addons-test-utils/CHANGELOG.md | 14 + .../techdocs-addons-test-utils/package.json | 2 +- plugins/techdocs-backend/CHANGELOG.md | 9 + plugins/techdocs-backend/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/techdocs-node/CHANGELOG.md | 7 + plugins/techdocs-node/package.json | 2 +- plugins/techdocs-react/CHANGELOG.md | 9 + plugins/techdocs-react/package.json | 2 +- plugins/techdocs/CHANGELOG.md | 13 + plugins/techdocs/package.json | 2 +- plugins/user-settings-backend/CHANGELOG.md | 9 + plugins/user-settings-backend/package.json | 2 +- plugins/user-settings/CHANGELOG.md | 12 + plugins/user-settings/package.json | 2 +- 334 files changed, 3572 insertions(+), 167 deletions(-) create mode 100644 .changeset/create-app-1774970958.md create mode 100644 docs/releases/v1.50.0-next.1-changelog.md diff --git a/.changeset/create-app-1774970958.md b/.changeset/create-app-1774970958.md new file mode 100644 index 0000000000..b50d431d4b --- /dev/null +++ b/.changeset/create-app-1774970958.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Bumped create-app version. diff --git a/.changeset/pre.json b/.changeset/pre.json index 2efe81832c..c87c88cf1b 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -225,36 +225,84 @@ "@backstage/plugin-user-settings-common": "0.1.0" }, "changesets": [ + "add-actions-registry-examples", + "angry-clouds-tell", + "app-defaults-clarify-failures", + "app-routes-redirect-config", + "auditor-zod-v4-refactor", + "azure-scm-events-layer", + "bump-glob-rollup-security", "catalog-entity-page-no-header", + "catalog-exists-query-optimization", + "catalog-graph-nfs-page", + "catalog-import-nfs-page", + "catalog-search-table-sync", "chatty-cups-create", + "clarify-fetch-failures", + "cold-bikes-beam", + "cool-shoes-hide", + "create-app-1774970958", "dependabot-03b295a", + "dependabot-2a735a0", "dependabot-cc35625", "drop-react-17-packages-ui", "easy-pens-judge", + "fix-avatar-flex-shrink", + "fix-aws-s3-url-reader-custom-endpoints", + "fix-bui-relative-href-resolution", "fix-catalog-filter-flicker", "fix-catalog-refresh-state-deadlock", "fix-catalog-table-loading-flash", + "fix-entity-info-card-header-overflow", + "fix-org-profile-card-overflow", "fix-sidebar-settings-nav", + "fix-translation-ref-ts6", + "fix-zod-generic-auth-node", + "fix-zod-generic-frontend-plugin-api", + "frontend-app-api-permission-error", + "funny-items-wink", "fuzzy-mangos-design", + "gitlab-scm-events-layer", "happy-masks-allow", + "hide-compat-wrapper-headers", + "hip-parents-fall", "hot-yaks-crash", + "kubernetes-nfs-metadata", "lazy-jars-shake", "loopback-redirect-uri-port", "lucky-things-write", + "notifications-nfs-metadata", + "olive-peaches-fly", "olive-ravens-smell", + "orange-friends-march", + "org-nfs-metadata", + "parallelize-cli-reports", "plugin-header-remove-toolbar-wrapper", + "quiet-streets-laugh", "remove-cli-module-new-dep", + "remove-empty-examples-scaffolder-bridge", + "remove-jest-when-dep", "remove-plugin-header-action-define-params", "remove-type-fest-github", + "renovate-9d44e41", "renovate-ced70c0", "repo-tools-peer-deps-react18", "scaffolder-nfs-page-layout", + "show-pagination-label", "shy-doors-repair", + "signals-nfs-metadata", + "simplify-compat-route-ref", + "simplify-route-ref-types", + "smart-cycles-fall", "specialized-app-apis", "swift-lizards-bathe", + "ts6-cli-dom-asynciterable", + "ts6-eslint-plugin", + "ts6-plugin-scaffolder", "ui-menu-item-style-refactor", "ui-react-aria-deps", "unprocessed-entities-devtools", - "update-app-visualizer-header-action" + "update-app-visualizer-header-action", + "wicked-impalas-fry" ] } diff --git a/docs/releases/v1.50.0-next.1-changelog.md b/docs/releases/v1.50.0-next.1-changelog.md new file mode 100644 index 0000000000..269162faf7 --- /dev/null +++ b/docs/releases/v1.50.0-next.1-changelog.md @@ -0,0 +1,1679 @@ +# Release v1.50.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.50.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.50.0-next.1) + +## @backstage/backend-plugin-api@1.9.0-next.1 + +### Minor Changes + +- 4559806: Added support for typed `examples` on actions registered via the actions registry. Action authors can now provide examples with compile-time-checked `input` and `output` values that match their schema definitions. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + +## @backstage/core-app-api@1.20.0-next.1 + +### Minor Changes + +- 400aa23: Added `FetchMiddlewares.clarifyFailures()` which replaces the uninformative "TypeError: Failed to fetch" with a message that includes the request method and URL. + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/frontend-plugin-api@0.16.0-next.1 + +### Minor Changes + +- 49397c1: Simplified the type signature of `createRouteRef` by replacing the dual `TParams`/`TParamKeys` type parameters with a single `TParamKey` parameter. This is a breaking change for callers that explicitly provided type arguments, but most usage that relies on inference is unaffected. + +### Patch Changes + +- ddc5247: Fixed `FlattenedMessages` type to avoid excessive type instantiation depth in TypeScript 6 when using `createTranslationRef` with the `translations` option. +- fa55078: Refactored the internal `createSchemaFromZod` helper to use a schema-first generic pattern, replacing the `ZodSchema` constraint with `TSchema extends ZodType`. This avoids "excessively deep" type inference errors when multiple Zod copies are resolved. + +## @backstage/plugin-auth-node@0.7.0-next.1 + +### Minor Changes + +- fa55078: **BREAKING**: Refactored `SignInResolverFactoryOptions` to use a schema-first generic pattern, following Zod's [recommended approach](https://zod.dev/library-authors?id=how-to-accept-user-defined-schemas#how-to-accept-user-defined-schemas) for writing generic functions. The type parameters changed from `` to ``. + + This fixes "Type instantiation is excessively deep and possibly infinite" errors that occurred when the Zod version in a user's project did not align with the one in Backstage core. + + If you use `createSignInResolverFactory` without explicit type parameters (the typical usage), no changes are needed: + + ```ts + // This usage is unchanged + createSignInResolverFactory({ + optionsSchema: z.object({ domain: z.string() }).optional(), + create(options = {}) { + /* ... */ + }, + }); + ``` + + If you reference `SignInResolverFactoryOptions` with explicit type parameters, update as follows: + + ```diff + - SignInResolverFactoryOptions + + SignInResolverFactoryOptions + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## @backstage/plugin-scaffolder-backend@3.3.0-next.1 + +### Minor Changes + +- 309b712: Added a new `execute-template` actions registry action that executes a scaffolder template with provided input values and returns a task ID for tracking progress. + +### Patch Changes + +- 4559806: Removed unnecessary empty `examples` array from actions bridged via the actions registry. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-openapi-utils@0.6.8-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## @backstage/app-defaults@1.7.7-next.1 + +### Patch Changes + +- 400aa23: Added `FetchMiddlewares.clarifyFailures()` to the default fetch API middleware stack. +- Updated dependencies + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/backend-app-api@1.6.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## @backstage/backend-defaults@0.16.1-next.1 + +### Patch Changes + +- 4559806: Added support for typed `examples` on actions registered via the actions registry. Action authors can now provide examples with compile-time-checked `input` and `output` values that match their schema definitions. +- 5cd814f: Refactored auditor severity log level mappings to use `zod/v4` with schema-driven defaults and type inference. +- 6e2aaab: Fixed `AwsS3UrlReader` failing to read files from S3 buckets configured with custom endpoint hosts. When an integration was configured with a specific endpoint like `https://bucket-1.s3.eu-central-1.amazonaws.com`, the URL parser incorrectly fell through to the non-AWS code path, always defaulting the region to `us-east-1` instead of extracting it from the hostname. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/backend-app-api@1.6.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + +## @backstage/backend-dynamic-feature-service@0.8.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-defaults@0.16.1-next.1 + - @backstage/plugin-catalog-backend@3.5.1-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/backend-openapi-utils@0.6.8-next.1 + - @backstage/plugin-app-node@0.1.44-next.1 + - @backstage/plugin-events-backend@0.6.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + +## @backstage/backend-openapi-utils@0.6.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## @backstage/backend-test-utils@1.11.2-next.1 + +### Patch Changes + +- 4559806: Added support for typed `examples` on actions registered via the actions registry. Action authors can now provide examples with compile-time-checked `input` and `output` values that match their schema definitions. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-defaults@0.16.1-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/backend-app-api@1.6.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/cli@0.36.1-next.1 + +### Patch Changes + +- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc). +- a7a14b7: Added `DOM.AsyncIterable` to the default `lib` in the shared TypeScript configuration, enabling standard async iteration support for DOM APIs such as `FileSystemDirectoryHandle`. This aligns behavior with [TypeScript 6.0](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/#the-dom-lib-now-contains-domiterable-and-domasynciterable), where this lib is included in `DOM` by default. +- Updated dependencies + - @backstage/cli-module-build@0.1.1-next.1 + - @backstage/cli-module-test-jest@0.1.1-next.1 + - @backstage/eslint-plugin@0.2.3-next.0 + +## @backstage/cli-module-auth@0.1.1-next.1 + +### Patch Changes + +- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc). + +## @backstage/cli-module-build@0.1.1-next.1 + +### Patch Changes + +- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc). + +## @backstage/cli-module-new@0.1.1-next.1 + +### Patch Changes + +- 64a91d0: Rename the legacy `frontend-plugin` to `frontend-plugin-legacy` + +## @backstage/cli-module-test-jest@0.1.1-next.1 + +### Patch Changes + +- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc). +- 6cc4811: Minor error message update + +## @backstage/core-compat-api@0.5.10-next.1 + +### Patch Changes + +- 77ab7d5: Hide the default page header for pages created through the compatibility wrappers, since legacy plugins already render their own headers. +- 49397c1: Removed unnecessary type argument from internal `createRouteRef` call. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-app-react@0.2.2-next.1 + +## @backstage/core-plugin-api@1.12.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + +## @backstage/create-app@0.8.2-next.1 + +### Patch Changes + +- Bumped create-app version. + +## @backstage/dev-utils@1.1.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/app-defaults@1.7.7-next.1 + - @backstage/ui@0.14.0-next.1 + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/eslint-plugin@0.2.3-next.0 + +### Patch Changes + +- df43b0e: Fixed `no-mixed-plugin-imports` rule to return `null` from non-fixable suggestion handlers and added an explicit `SuggestionReportDescriptor[]` type annotation, matching the stricter type checking in TypeScript 6.0. + +## @backstage/frontend-app-api@0.16.2-next.1 + +### Patch Changes + +- 400aa23: Wrapped extension permission authorization in a try/catch to surface errors as `ForwardedError` with a clear message. +- Updated dependencies + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/frontend-defaults@0.5.1-next.1 + +## @backstage/frontend-defaults@0.5.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.4.3-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/frontend-app-api@0.16.2-next.1 + - @backstage/core-components@0.18.9-next.0 + +## @backstage/frontend-dev-utils@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.4.3-next.1 + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/frontend-defaults@0.5.1-next.1 + +## @backstage/frontend-dynamic-feature-loader@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + +## @backstage/frontend-test-utils@0.5.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.4.3-next.1 + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/frontend-app-api@0.16.2-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/test-utils@1.7.17-next.1 + - @backstage/plugin-app-react@0.2.2-next.1 + +## @backstage/repo-tools@0.17.1-next.1 + +### Patch Changes + +- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc). +- 8e9679b: Parallelized CLI report generation, reducing wall-clock time by ~4x. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## @backstage/test-utils@1.7.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/ui@0.14.0-next.1 + +### Patch Changes + +- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc). + +- 8d79835: Added RangeSlider component for selecting numeric ranges. + + **Affected components:** RangeSlider + +- 5081bcc: Fixed `Avatar` becoming elliptical in flex layouts by preventing it from shrinking. + + **Affected components:** Avatar + +- d840ba9: Fixed relative `href` resolution for BUI link components. Relative paths like `../other` are now correctly turned into absolute paths before reaching the React Aria layer, ensuring client-side navigation goes to the right place. + + **Affected components:** ButtonLink, Card, CellProfile, CellText, Link, ListRow, MenuItem, MenuListBoxItem, Row, SearchAutocompleteItem, Tab, Tag + +- 3bc23a5: Added support for disabling pagination in `useTable` complete mode by setting `paginationOptions: { type: 'none' }`. This skips data slicing and produces `pagination: { type: 'none' }` in `tableProps`, removing the need for consumers to manually override the pagination prop on `Table`. Also fixed complete mode not reacting to dynamic changes in `paginationOptions.pageSize`. + + **Affected components:** `useTable` + +- c368cf3: Updated dependency `@types/use-sync-external-store` to `^1.0.0`. + +- d0f055f: Added `showPaginationLabel` prop to `TablePagination` and `useTable` pagination options. When set to `false`, the pagination label (e.g., "1 - 20 of 150") is hidden while navigation controls remain visible. Defaults to `true`. + + **Affected components:** `TablePagination`, `useTable` + +- feaf3d1: Fixed HeaderNav hover indicator covering tab text when theme uses opaque background colors. Also fixed an incorrect CSS variable reference (`--bui-font-family` → `--bui-font-regular`). + + **Affected components:** Header + +## @backstage/plugin-api-docs@0.13.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-catalog@2.0.2-next.1 + +## @backstage/plugin-app@0.4.3-next.1 + +### Patch Changes + +- e5baa20: Added support for configuring URL redirects on the `app/routes` extension. Redirects can be configured through `app-config` as an array of `{from, to}` path pairs, which will cause navigation to the `from` path to be redirected to the `to` path. + + For example: + + ```yaml + app: + extensions: + - app/routes: + config: + redirects: + - from: /old-path + to: /new-path + ``` + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-app-react@0.2.2-next.1 + +## @backstage/plugin-app-backend@0.5.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-app-node@0.1.44-next.1 + +## @backstage/plugin-app-node@0.1.44-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## @backstage/plugin-app-react@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/plugin-app-visualizer@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/plugin-auth@0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + +## @backstage/plugin-auth-backend@0.28.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-auth0-provider@0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-auth-backend@0.28.0-next.1 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.5.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-google-provider@0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-guest-provider@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.4.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-auth-backend@0.28.0-next.1 + +## @backstage/plugin-auth-backend-module-okta-provider@0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-openshift-provider@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-catalog@2.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-search-react@1.11.1-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + +## @backstage/plugin-catalog-backend@3.5.1-next.1 + +### Patch Changes + +- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc). +- 6884814: Improved catalog entity filter query performance by switching from `IN (subquery)` to `EXISTS (correlated subquery)` patterns. This enables PostgreSQL semi-join optimizations and fixes `NOT IN` NULL-semantics pitfalls by using `NOT EXISTS` instead. +- 9da73bf: Reduced search table write churn during stitching by syncing only changed rows instead of doing a full delete and re-insert. On Postgres this uses a single writable CTE, on MySQL a temporary table merge with deadlock retry, and on SQLite the previous bulk replace. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-openapi-utils@0.6.8-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + +## @backstage/plugin-catalog-backend-module-aws@0.4.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-defaults@0.16.1-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + +## @backstage/plugin-catalog-backend-module-azure@0.3.16-next.1 + +### Patch Changes + +- 39d27ee: Add Azure DevOps SCM event translation layer for instant catalog reprocessing. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-openapi-utils@0.6.8-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-catalog-backend-module-gcp@0.3.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + +## @backstage/plugin-catalog-backend-module-gitea@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + +## @backstage/plugin-catalog-backend-module-github@0.13.1-next.1 + +### Patch Changes + +- b11e338: Fixed a bug where `GithubEntityProvider` with `validateLocationsExist: true` and `filters.branch` configured would always check for the catalog file on the repository's default branch (`HEAD`) instead of the configured branch. This caused repositories to be filtered out when the catalog file only existed on the non-default branch. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-backend-module-github@0.13.1-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.8.2-next.1 + +### Patch Changes + +- 54a8300: Add GitLab SCM event translation layer for instant catalog reprocessing. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-defaults@0.16.1-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.8.2-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-defaults@0.16.1-next.1 + - @backstage/plugin-catalog-backend@3.5.1-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-catalog-backend-module-ldap@0.12.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + +## @backstage/plugin-catalog-backend-module-logs@0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-backend@3.5.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.9.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.2.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.6.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + +## @backstage/plugin-catalog-graph@0.6.1-next.1 + +### Patch Changes + +- 0e147e8: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/plugin-catalog-import@0.13.12-next.1 + +### Patch Changes + +- fa0593e: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/plugin-catalog-node@2.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-test-utils@1.11.2-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + +## @backstage/plugin-catalog-react@2.1.2-next.1 + +### Patch Changes + +- eba2f61: Fixed `EntityInfoCard` header overflowing on narrow screens. +- 0416216: Fixed entity relation cards (e.g., "Has components") only showing one entity at a time by using `paginationOptions: { type: 'none' }` instead of deriving page size from data length. +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/frontend-test-utils@0.5.2-next.1 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/plugin-devtools@0.1.38-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/plugin-devtools-backend@0.5.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + +## @backstage/plugin-devtools-react@0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/plugin-events-backend@0.6.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-openapi-utils@0.6.8-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-events-backend-module-aws-sqs@0.4.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-events-backend-module-azure@0.2.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-server@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-events-backend-module-gerrit@0.2.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-events-backend-module-github@0.4.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-events-backend-module-gitlab@0.3.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-events-backend-module-google-pubsub@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-events-backend-module-kafka@0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-events-node@0.4.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## @backstage/plugin-gateway-backend@1.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## @backstage/plugin-home@0.9.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-home-react@0.1.37-next.1 + +## @backstage/plugin-home-react@0.1.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/plugin-kubernetes@0.12.18-next.1 + +### Patch Changes + +- d156cf4: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-kubernetes-react@0.5.18-next.0 + +## @backstage/plugin-kubernetes-backend@0.21.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-kubernetes-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + +## @backstage/plugin-kubernetes-node@0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## @backstage/plugin-mcp-actions-backend@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + +## @backstage/plugin-mui-to-bui@0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/plugin-notifications@0.5.16-next.1 + +### Patch Changes + +- d156cf4: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/plugin-notifications-backend@0.6.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-notifications-node@0.2.25-next.1 + - @backstage/plugin-signals-node@0.1.30-next.1 + +## @backstage/plugin-notifications-backend-module-email@0.3.20-next.1 + +### Patch Changes + +- 19ef9fb: build(deps): bump `nodemailer` from 7.0.13 to 8.0.4 +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-notifications-node@0.2.25-next.1 + +## @backstage/plugin-notifications-backend-module-slack@0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-notifications-node@0.2.25-next.1 + +## @backstage/plugin-notifications-node@0.2.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-signals-node@0.1.30-next.1 + +## @backstage/plugin-org@0.7.1-next.1 + +### Patch Changes + +- 87eb31c: Fixed `GroupProfileCard` and `UserProfileCard` content overflowing on narrow screens. +- d156cf4: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/plugin-permission-backend@0.7.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + +## @backstage/plugin-permission-node@0.10.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + +## @backstage/plugin-proxy-backend@0.6.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-proxy-node@0.1.14-next.1 + +## @backstage/plugin-proxy-node@0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## @backstage/plugin-scaffolder@1.36.2-next.1 + +### Patch Changes + +- 864a799: Fix the display of the description in `GitlabRepoPicker`: + + - Move `owner.description` helper text outside the `allowedOwners` conditional so it renders for both `Select` and `Autocomplete` modes. + - Update the `Autocomplete` label to use `fields.gitlabRepoPicker.owner.inputTitle` instead of `fields.gitlabRepoPicker.owner.title`. + +- a7a14b7: Removed custom `IterableDirectoryHandle` and `WritableFileHandle` types in favor of the standard DOM `FileSystemDirectoryHandle` and `FileSystemFileHandle` types, which are now available through the `DOM.AsyncIterable` lib added to the shared TypeScript configuration. + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-scaffolder-react@1.20.1-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-defaults@0.16.1-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.9.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.11.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-notifications-node@0.2.25-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.20-next.1 + +### Patch Changes + +- 2905c59: Removed unused `jest-when` dev dependency. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.3.10-next.1 + +## @backstage/plugin-scaffolder-node@0.13.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-test-utils@1.11.2-next.1 + +## @backstage/plugin-scaffolder-node-test-utils@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-test-utils@1.11.2-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + +## @backstage/plugin-scaffolder-react@1.20.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/frontend-test-utils@0.5.2-next.1 + +## @backstage/plugin-search@1.7.1-next.1 + +### Patch Changes + +- 34aebcc: Fixed the `SearchModal` leaving the page in a broken state by not restoring body overflow and aria-hidden attributes when closing. +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-search-react@1.11.1-next.1 + +## @backstage/plugin-search-backend@2.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-openapi-utils@0.6.8-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + +## @backstage/plugin-search-backend-module-catalog@0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + +## @backstage/plugin-search-backend-module-elasticsearch@1.8.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + +## @backstage/plugin-search-backend-module-explore@0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + +## @backstage/plugin-search-backend-module-pg@0.5.54-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + +## @backstage/plugin-search-backend-module-techdocs@0.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + - @backstage/plugin-techdocs-node@1.14.5-next.1 + +## @backstage/plugin-search-backend-node@1.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## @backstage/plugin-search-react@1.11.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/plugin-signals@0.0.30-next.1 + +### Patch Changes + +- d156cf4: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/plugin-signals-backend@0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + - @backstage/plugin-signals-node@0.1.30-next.1 + +## @backstage/plugin-signals-node@0.1.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + +## @backstage/plugin-techdocs@1.17.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-search-react@1.11.1-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + +## @backstage/plugin-techdocs-addons-test-utils@2.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/test-utils@1.7.17-next.1 + - @backstage/plugin-catalog@2.0.2-next.1 + - @backstage/plugin-search-react@1.11.1-next.1 + - @backstage/plugin-techdocs@1.17.3-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + +## @backstage/plugin-techdocs-backend@2.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-techdocs-node@1.14.5-next.1 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + +## @backstage/plugin-techdocs-node@1.14.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + +## @backstage/plugin-techdocs-react@1.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/plugin-user-settings@0.9.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + +## @backstage/plugin-user-settings-backend@0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-signals-node@0.1.30-next.1 + +## example-app@0.0.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/app-defaults@1.7.7-next.1 + - @backstage/plugin-app@0.4.3-next.1 + - @backstage/cli@0.36.1-next.1 + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-graph@0.6.1-next.1 + - @backstage/plugin-catalog-import@0.13.12-next.1 + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/plugin-org@0.7.1-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/frontend-app-api@0.16.2-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/plugin-kubernetes@0.12.18-next.1 + - @backstage/plugin-notifications@0.5.16-next.1 + - @backstage/plugin-scaffolder@1.36.2-next.1 + - @backstage/plugin-search@1.7.1-next.1 + - @backstage/plugin-signals@0.0.30-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-api-docs@0.13.6-next.1 + - @backstage/plugin-catalog@2.0.2-next.1 + - @backstage/plugin-home@0.9.4-next.1 + - @backstage/plugin-scaffolder-react@1.20.1-next.1 + - @backstage/plugin-search-react@1.11.1-next.1 + - @backstage/plugin-techdocs@1.17.3-next.1 + - @backstage/plugin-user-settings@0.9.2-next.1 + - @backstage/frontend-defaults@0.5.1-next.1 + - @backstage/plugin-app-react@0.2.2-next.1 + - @backstage/plugin-app-visualizer@0.2.2-next.1 + - @backstage/plugin-auth@0.1.7-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.1 + - @backstage/plugin-devtools@0.1.38-next.1 + - @backstage/plugin-home-react@0.1.37-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + +## app-example-plugin@0.0.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + +## example-app-legacy@0.2.120-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/app-defaults@1.7.7-next.1 + - @backstage/cli@0.36.1-next.1 + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-graph@0.6.1-next.1 + - @backstage/plugin-catalog-import@0.13.12-next.1 + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/plugin-org@0.7.1-next.1 + - @backstage/frontend-app-api@0.16.2-next.1 + - @backstage/plugin-kubernetes@0.12.18-next.1 + - @backstage/plugin-notifications@0.5.16-next.1 + - @backstage/plugin-scaffolder@1.36.2-next.1 + - @backstage/plugin-search@1.7.1-next.1 + - @backstage/plugin-signals@0.0.30-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-api-docs@0.13.6-next.1 + - @backstage/plugin-catalog@2.0.2-next.1 + - @backstage/plugin-home@0.9.4-next.1 + - @backstage/plugin-scaffolder-react@1.20.1-next.1 + - @backstage/plugin-search-react@1.11.1-next.1 + - @backstage/plugin-techdocs@1.17.3-next.1 + - @backstage/plugin-user-settings@0.9.2-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.1 + - @backstage/plugin-devtools@0.1.38-next.1 + - @backstage/plugin-home-react@0.1.37-next.1 + - @backstage/plugin-mui-to-bui@0.2.6-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + +## example-backend@0.0.49-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-defaults@0.16.1-next.1 + - @backstage/plugin-catalog-backend@3.5.1-next.1 + - @backstage/plugin-scaffolder-backend@3.3.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-app-backend@0.5.13-next.1 + - @backstage/plugin-auth-backend@0.28.0-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.5.2-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.18-next.1 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.6-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.13-next.1 + - @backstage/plugin-catalog-backend-module-logs@0.1.21-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.2.21-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.19-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.10-next.1 + - @backstage/plugin-devtools-backend@0.5.16-next.1 + - @backstage/plugin-events-backend@0.6.1-next.1 + - @backstage/plugin-events-backend-module-google-pubsub@0.2.2-next.1 + - @backstage/plugin-kubernetes-backend@0.21.3-next.1 + - @backstage/plugin-mcp-actions-backend@0.1.11-next.1 + - @backstage/plugin-notifications-backend@0.6.4-next.1 + - @backstage/plugin-permission-backend@0.7.11-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.18-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + - @backstage/plugin-proxy-backend@0.6.12-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.8-next.1 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.21-next.1 + - @backstage/plugin-search-backend@2.1.1-next.1 + - @backstage/plugin-search-backend-module-catalog@0.3.14-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.8.2-next.1 + - @backstage/plugin-search-backend-module-explore@0.3.13-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.13-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + - @backstage/plugin-signals-backend@0.3.14-next.1 + - @backstage/plugin-techdocs-backend@2.1.7-next.1 + +## @internal/frontend@0.0.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + +## @internal/scaffolder@0.0.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/plugin-scaffolder-react@1.20.1-next.1 + +## techdocs-cli-embedded-app@0.2.119-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.36.1-next.1 + - @backstage/ui@0.14.0-next.1 + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/test-utils@1.7.17-next.1 + - @backstage/plugin-catalog@2.0.2-next.1 + - @backstage/plugin-techdocs@1.17.3-next.1 + - @backstage/frontend-defaults@0.5.1-next.1 + - @backstage/plugin-app-react@0.2.2-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + +## @internal/plugin-todo-list-backend@1.0.49-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 diff --git a/package.json b/package.json index 93a6317951..82e8b96376 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.50.0-next.0", + "version": "1.50.0-next.1", "backstage": { "cli": { "new": { diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 7940adaf3d..1bfbb15648 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/app-defaults +## 1.7.7-next.1 + +### Patch Changes + +- 400aa23: Added `FetchMiddlewares.clarifyFailures()` to the default fetch API middleware stack. +- Updated dependencies + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 1.7.7-next.0 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index c5519ff8b7..74a712f952 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/app-defaults", - "version": "1.7.7-next.0", + "version": "1.7.7-next.1", "description": "Provides the default wiring of a Backstage App", "backstage": { "role": "web-library" diff --git a/packages/app-example-plugin/CHANGELOG.md b/packages/app-example-plugin/CHANGELOG.md index 91d782ed26..fc4424312f 100644 --- a/packages/app-example-plugin/CHANGELOG.md +++ b/packages/app-example-plugin/CHANGELOG.md @@ -1,5 +1,13 @@ # app-example-plugin +## 0.0.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + ## 0.0.34-next.0 ### Patch Changes diff --git a/packages/app-example-plugin/package.json b/packages/app-example-plugin/package.json index 95aaa962f7..bd031450e2 100644 --- a/packages/app-example-plugin/package.json +++ b/packages/app-example-plugin/package.json @@ -1,6 +1,6 @@ { "name": "app-example-plugin", - "version": "0.0.34-next.0", + "version": "0.0.34-next.1", "description": "Backstage internal example plugin", "backstage": { "role": "frontend-plugin", diff --git a/packages/app-legacy/CHANGELOG.md b/packages/app-legacy/CHANGELOG.md index 21596b4138..b769f0188f 100644 --- a/packages/app-legacy/CHANGELOG.md +++ b/packages/app-legacy/CHANGELOG.md @@ -1,5 +1,40 @@ # example-app-legacy +## 0.2.120-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/app-defaults@1.7.7-next.1 + - @backstage/cli@0.36.1-next.1 + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-graph@0.6.1-next.1 + - @backstage/plugin-catalog-import@0.13.12-next.1 + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/plugin-org@0.7.1-next.1 + - @backstage/frontend-app-api@0.16.2-next.1 + - @backstage/plugin-kubernetes@0.12.18-next.1 + - @backstage/plugin-notifications@0.5.16-next.1 + - @backstage/plugin-scaffolder@1.36.2-next.1 + - @backstage/plugin-search@1.7.1-next.1 + - @backstage/plugin-signals@0.0.30-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-api-docs@0.13.6-next.1 + - @backstage/plugin-catalog@2.0.2-next.1 + - @backstage/plugin-home@0.9.4-next.1 + - @backstage/plugin-scaffolder-react@1.20.1-next.1 + - @backstage/plugin-search-react@1.11.1-next.1 + - @backstage/plugin-techdocs@1.17.3-next.1 + - @backstage/plugin-user-settings@0.9.2-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.1 + - @backstage/plugin-devtools@0.1.38-next.1 + - @backstage/plugin-home-react@0.1.37-next.1 + - @backstage/plugin-mui-to-bui@0.2.6-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + ## 0.2.120-next.0 ### Patch Changes diff --git a/packages/app-legacy/package.json b/packages/app-legacy/package.json index 862c14d536..ca96c0a1ab 100644 --- a/packages/app-legacy/package.json +++ b/packages/app-legacy/package.json @@ -1,6 +1,6 @@ { "name": "example-app-legacy", - "version": "0.2.120-next.0", + "version": "0.2.120-next.1", "backstage": { "role": "frontend" }, diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 2ebabb9198..5897809ed5 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,46 @@ # example-app +## 0.0.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/app-defaults@1.7.7-next.1 + - @backstage/plugin-app@0.4.3-next.1 + - @backstage/cli@0.36.1-next.1 + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-graph@0.6.1-next.1 + - @backstage/plugin-catalog-import@0.13.12-next.1 + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/plugin-org@0.7.1-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/frontend-app-api@0.16.2-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/plugin-kubernetes@0.12.18-next.1 + - @backstage/plugin-notifications@0.5.16-next.1 + - @backstage/plugin-scaffolder@1.36.2-next.1 + - @backstage/plugin-search@1.7.1-next.1 + - @backstage/plugin-signals@0.0.30-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-api-docs@0.13.6-next.1 + - @backstage/plugin-catalog@2.0.2-next.1 + - @backstage/plugin-home@0.9.4-next.1 + - @backstage/plugin-scaffolder-react@1.20.1-next.1 + - @backstage/plugin-search-react@1.11.1-next.1 + - @backstage/plugin-techdocs@1.17.3-next.1 + - @backstage/plugin-user-settings@0.9.2-next.1 + - @backstage/frontend-defaults@0.5.1-next.1 + - @backstage/plugin-app-react@0.2.2-next.1 + - @backstage/plugin-app-visualizer@0.2.2-next.1 + - @backstage/plugin-auth@0.1.7-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.29-next.1 + - @backstage/plugin-devtools@0.1.38-next.1 + - @backstage/plugin-home-react@0.1.37-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.35-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + ## 0.0.34-next.0 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index e75dc53bcf..0016ecbdc0 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.0.34-next.0", + "version": "0.0.34-next.1", "backstage": { "role": "frontend" }, diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index 74a02da3d9..f46cfe28bb 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/backend-app-api +## 1.6.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + ## 1.6.1-next.0 ### Patch Changes diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 394d4b894d..2ebcc05941 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": "1.6.1-next.0", + "version": "1.6.1-next.1", "description": "Core API used by Backstage backend apps", "backstage": { "role": "node-library" diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index d78cbac68a..f635723677 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/backend-defaults +## 0.16.1-next.1 + +### Patch Changes + +- 4559806: Added support for typed `examples` on actions registered via the actions registry. Action authors can now provide examples with compile-time-checked `input` and `output` values that match their schema definitions. +- 5cd814f: Refactored auditor severity log level mappings to use `zod/v4` with schema-driven defaults and type inference. +- 6e2aaab: Fixed `AwsS3UrlReader` failing to read files from S3 buckets configured with custom endpoint hosts. When an integration was configured with a specific endpoint like `https://bucket-1.s3.eu-central-1.amazonaws.com`, the URL parser incorrectly fell through to the non-AWS code path, always defaulting the region to `us-east-1` instead of extracting it from the hostname. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/backend-app-api@1.6.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + ## 0.16.1-next.0 ### Patch Changes diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index c714f382c6..33a262aadd 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-defaults", - "version": "0.16.1-next.0", + "version": "0.16.1-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 4174bcb02a..3ea462e952 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/backend-dynamic-feature-service +## 0.8.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-defaults@0.16.1-next.1 + - @backstage/plugin-catalog-backend@3.5.1-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/backend-openapi-utils@0.6.8-next.1 + - @backstage/plugin-app-node@0.1.44-next.1 + - @backstage/plugin-events-backend@0.6.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + ## 0.8.1-next.0 ### Patch Changes diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 64fc088367..fb1e5dd803 100644 --- a/packages/backend-dynamic-feature-service/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-dynamic-feature-service", - "version": "0.8.1-next.0", + "version": "0.8.1-next.1", "description": "Backstage dynamic feature service", "backstage": { "role": "node-library" diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md index 6882d614a4..8b382a3f84 100644 --- a/packages/backend-openapi-utils/CHANGELOG.md +++ b/packages/backend-openapi-utils/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/backend-openapi-utils +## 0.6.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + ## 0.6.8-next.0 ### Patch Changes diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index 96f18c5352..135af02922 100644 --- a/packages/backend-openapi-utils/package.json +++ b/packages/backend-openapi-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-openapi-utils", - "version": "0.6.8-next.0", + "version": "0.6.8-next.1", "description": "OpenAPI typescript support.", "backstage": { "role": "node-library" diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index ec7df4e13d..bbcf28b17f 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/backend-plugin-api +## 1.9.0-next.1 + +### Minor Changes + +- 4559806: Added support for typed `examples` on actions registered via the actions registry. Action authors can now provide examples with compile-time-checked `input` and `output` values that match their schema definitions. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + ## 1.8.1-next.0 ### Patch Changes diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index f5f7e85db2..5f836592c0 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": "1.8.1-next.0", + "version": "1.9.0-next.1", "description": "Core API used by Backstage backend plugins", "backstage": { "role": "node-library" diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 27c2d93828..5f783d9029 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/backend-test-utils +## 1.11.2-next.1 + +### Patch Changes + +- 4559806: Added support for typed `examples` on actions registered via the actions registry. Action authors can now provide examples with compile-time-checked `input` and `output` values that match their schema definitions. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-defaults@0.16.1-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/backend-app-api@1.6.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 1.11.2-next.0 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 87e6f85ba5..4e5bcb2074 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": "1.11.2-next.0", + "version": "1.11.2-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 1f91aa0f9f..9e3353c8be 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,46 @@ # example-backend +## 0.0.49-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-defaults@0.16.1-next.1 + - @backstage/plugin-catalog-backend@3.5.1-next.1 + - @backstage/plugin-scaffolder-backend@3.3.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-app-backend@0.5.13-next.1 + - @backstage/plugin-auth-backend@0.28.0-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.5.2-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.18-next.1 + - @backstage/plugin-auth-backend-module-openshift-provider@0.1.6-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.13-next.1 + - @backstage/plugin-catalog-backend-module-logs@0.1.21-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.2.21-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.19-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.10-next.1 + - @backstage/plugin-devtools-backend@0.5.16-next.1 + - @backstage/plugin-events-backend@0.6.1-next.1 + - @backstage/plugin-events-backend-module-google-pubsub@0.2.2-next.1 + - @backstage/plugin-kubernetes-backend@0.21.3-next.1 + - @backstage/plugin-mcp-actions-backend@0.1.11-next.1 + - @backstage/plugin-notifications-backend@0.6.4-next.1 + - @backstage/plugin-permission-backend@0.7.11-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.18-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + - @backstage/plugin-proxy-backend@0.6.12-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.9.8-next.1 + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.21-next.1 + - @backstage/plugin-search-backend@2.1.1-next.1 + - @backstage/plugin-search-backend-module-catalog@0.3.14-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.8.2-next.1 + - @backstage/plugin-search-backend-module-explore@0.3.13-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.13-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + - @backstage/plugin-signals-backend@0.3.14-next.1 + - @backstage/plugin-techdocs-backend@2.1.7-next.1 + ## 0.0.49-next.0 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index b4ec4b43db..fd2c166773 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.49-next.0", + "version": "0.0.49-next.1", "backstage": { "role": "backend" }, diff --git a/packages/cli-module-auth/CHANGELOG.md b/packages/cli-module-auth/CHANGELOG.md index 22f0d3f8be..f2473fb4b6 100644 --- a/packages/cli-module-auth/CHANGELOG.md +++ b/packages/cli-module-auth/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/cli-module-auth +## 0.1.1-next.1 + +### Patch Changes + +- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc). + ## 0.1.1-next.0 ### Patch Changes diff --git a/packages/cli-module-auth/package.json b/packages/cli-module-auth/package.json index 4976fe722a..e1f0200d63 100644 --- a/packages/cli-module-auth/package.json +++ b/packages/cli-module-auth/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli-module-auth", - "version": "0.1.1-next.0", + "version": "0.1.1-next.1", "description": "CLI module for Backstage CLI", "backstage": { "role": "cli-module" diff --git a/packages/cli-module-build/CHANGELOG.md b/packages/cli-module-build/CHANGELOG.md index b9a647d89f..8f8ab43726 100644 --- a/packages/cli-module-build/CHANGELOG.md +++ b/packages/cli-module-build/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/cli-module-build +## 0.1.1-next.1 + +### Patch Changes + +- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc). + ## 0.1.1-next.0 ### Patch Changes diff --git a/packages/cli-module-build/package.json b/packages/cli-module-build/package.json index ce8a9f8a7e..605df9a542 100644 --- a/packages/cli-module-build/package.json +++ b/packages/cli-module-build/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli-module-build", - "version": "0.1.1-next.0", + "version": "0.1.1-next.1", "description": "CLI module for Backstage CLI", "backstage": { "role": "cli-module" diff --git a/packages/cli-module-new/CHANGELOG.md b/packages/cli-module-new/CHANGELOG.md index 281aa9dc85..79a44dfd4f 100644 --- a/packages/cli-module-new/CHANGELOG.md +++ b/packages/cli-module-new/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/cli-module-new +## 0.1.1-next.1 + +### Patch Changes + +- 64a91d0: Rename the legacy `frontend-plugin` to `frontend-plugin-legacy` + ## 0.1.1-next.0 ### Patch Changes diff --git a/packages/cli-module-new/package.json b/packages/cli-module-new/package.json index 5baa539831..981ecf82e3 100644 --- a/packages/cli-module-new/package.json +++ b/packages/cli-module-new/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli-module-new", - "version": "0.1.1-next.0", + "version": "0.1.1-next.1", "description": "CLI module for Backstage CLI", "backstage": { "role": "cli-module" diff --git a/packages/cli-module-test-jest/CHANGELOG.md b/packages/cli-module-test-jest/CHANGELOG.md index 7f2d82f46b..c93300a575 100644 --- a/packages/cli-module-test-jest/CHANGELOG.md +++ b/packages/cli-module-test-jest/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/cli-module-test-jest +## 0.1.1-next.1 + +### Patch Changes + +- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc). +- 6cc4811: Minor error message update + ## 0.1.1-next.0 ### Patch Changes diff --git a/packages/cli-module-test-jest/package.json b/packages/cli-module-test-jest/package.json index 0218bca911..1308fd668a 100644 --- a/packages/cli-module-test-jest/package.json +++ b/packages/cli-module-test-jest/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli-module-test-jest", - "version": "0.1.1-next.0", + "version": "0.1.1-next.1", "description": "CLI module for Backstage CLI", "backstage": { "role": "cli-module" diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 8a5d13478b..7827b5c28a 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/cli +## 0.36.1-next.1 + +### Patch Changes + +- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc). +- a7a14b7: Added `DOM.AsyncIterable` to the default `lib` in the shared TypeScript configuration, enabling standard async iteration support for DOM APIs such as `FileSystemDirectoryHandle`. This aligns behavior with [TypeScript 6.0](https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/#the-dom-lib-now-contains-domiterable-and-domasynciterable), where this lib is included in `DOM` by default. +- Updated dependencies + - @backstage/cli-module-build@0.1.1-next.1 + - @backstage/cli-module-test-jest@0.1.1-next.1 + - @backstage/eslint-plugin@0.2.3-next.0 + ## 0.36.1-next.0 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 9cabc2ee4d..85a3a2e178 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli", - "version": "0.36.1-next.0", + "version": "0.36.1-next.1", "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 8f38cb8073..242afa6610 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/core-app-api +## 1.20.0-next.1 + +### Minor Changes + +- 400aa23: Added `FetchMiddlewares.clarifyFailures()` which replaces the uninformative "TypeError: Failed to fetch" with a message that includes the request method and URL. + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 1.19.7-next.0 ### Patch Changes diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index aacc995a8c..e7d976cc04 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-app-api", - "version": "1.19.7-next.0", + "version": "1.20.0-next.1", "description": "Core app API used by Backstage apps", "backstage": { "role": "web-library" diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md index 1272fe294c..ab25626d89 100644 --- a/packages/core-compat-api/CHANGELOG.md +++ b/packages/core-compat-api/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/core-compat-api +## 0.5.10-next.1 + +### Patch Changes + +- 77ab7d5: Hide the default page header for pages created through the compatibility wrappers, since legacy plugins already render their own headers. +- 49397c1: Removed unnecessary type argument from internal `createRouteRef` call. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-app-react@0.2.2-next.1 + ## 0.5.10-next.0 ### Patch Changes diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index 5564728926..7b87eda201 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.5.10-next.0", + "version": "0.5.10-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index 699d5e858d..d7b1a3baae 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/core-plugin-api +## 1.12.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + ## 1.12.5-next.0 ### Patch Changes diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index bb5c8b5ca7..7334f909ee 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-plugin-api", - "version": "1.12.5-next.0", + "version": "1.12.5-next.1", "description": "Core API used by Backstage plugins", "backstage": { "role": "web-library" diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index ce5c6a38b1..20f54304b0 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/create-app +## 0.8.2-next.1 + +### Patch Changes + +- Bumped create-app version. + ## 0.8.2-next.0 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 35ede3b977..9035f74a51 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/create-app", - "version": "0.8.2-next.0", + "version": "0.8.2-next.1", "description": "A CLI that helps you create your own Backstage app", "backstage": { "role": "cli" diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 4979085e60..da1c076c63 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/dev-utils +## 1.1.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/app-defaults@1.7.7-next.1 + - @backstage/ui@0.14.0-next.1 + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 1.1.22-next.0 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index ce1a96f5b0..ef12268f73 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/dev-utils", - "version": "1.1.22-next.0", + "version": "1.1.22-next.1", "description": "Utilities for developing Backstage plugins.", "backstage": { "role": "web-library" diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 2c7d56784b..63d2d47da7 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/eslint-plugin +## 0.2.3-next.0 + +### Patch Changes + +- df43b0e: Fixed `no-mixed-plugin-imports` rule to return `null` from non-fixable suggestion handlers and added an explicit `SuggestionReportDescriptor[]` type annotation, matching the stricter type checking in TypeScript 6.0. + ## 0.2.2 ### Patch Changes diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 9eba112b6d..c864496c45 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/eslint-plugin", - "version": "0.2.2", + "version": "0.2.3-next.0", "description": "Backstage ESLint plugin", "publishConfig": { "access": "public" diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md index c06e01826f..d2d92ec4c1 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/frontend-app-api +## 0.16.2-next.1 + +### Patch Changes + +- 400aa23: Wrapped extension permission authorization in a try/catch to surface errors as `ForwardedError` with a clear message. +- Updated dependencies + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/frontend-defaults@0.5.1-next.1 + ## 0.16.2-next.0 ### Patch Changes diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index daaa33988a..c3637dfb51 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.16.2-next.0", + "version": "0.16.2-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-defaults/CHANGELOG.md b/packages/frontend-defaults/CHANGELOG.md index 8905dd5c73..f306ef6642 100644 --- a/packages/frontend-defaults/CHANGELOG.md +++ b/packages/frontend-defaults/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/frontend-defaults +## 0.5.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.4.3-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/frontend-app-api@0.16.2-next.1 + - @backstage/core-components@0.18.9-next.0 + ## 0.5.1-next.0 ### Patch Changes diff --git a/packages/frontend-defaults/package.json b/packages/frontend-defaults/package.json index 3b0f3b790b..44f360901d 100644 --- a/packages/frontend-defaults/package.json +++ b/packages/frontend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-defaults", - "version": "0.5.1-next.0", + "version": "0.5.1-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-dev-utils/CHANGELOG.md b/packages/frontend-dev-utils/CHANGELOG.md index ec8fb47a97..e1ce4aae50 100644 --- a/packages/frontend-dev-utils/CHANGELOG.md +++ b/packages/frontend-dev-utils/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/frontend-dev-utils +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.4.3-next.1 + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/frontend-defaults@0.5.1-next.1 + ## 0.1.1-next.0 ### Patch Changes diff --git a/packages/frontend-dev-utils/package.json b/packages/frontend-dev-utils/package.json index 755adb2712..6643d46131 100644 --- a/packages/frontend-dev-utils/package.json +++ b/packages/frontend-dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-dev-utils", - "version": "0.1.1-next.0", + "version": "0.1.1-next.1", "description": "Utilities for developing Backstage frontend plugins using the new frontend system.", "backstage": { "role": "web-library" diff --git a/packages/frontend-dynamic-feature-loader/CHANGELOG.md b/packages/frontend-dynamic-feature-loader/CHANGELOG.md index 4cf074010f..cba740e898 100644 --- a/packages/frontend-dynamic-feature-loader/CHANGELOG.md +++ b/packages/frontend-dynamic-feature-loader/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/frontend-dynamic-feature-loader +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + ## 0.1.11-next.0 ### Patch Changes diff --git a/packages/frontend-dynamic-feature-loader/package.json b/packages/frontend-dynamic-feature-loader/package.json index 5b71bb7f6f..2d0aec6596 100644 --- a/packages/frontend-dynamic-feature-loader/package.json +++ b/packages/frontend-dynamic-feature-loader/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-dynamic-feature-loader", - "version": "0.1.11-next.0", + "version": "0.1.11-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-internal/CHANGELOG.md b/packages/frontend-internal/CHANGELOG.md index 7061d14b6a..d695c8ae09 100644 --- a/packages/frontend-internal/CHANGELOG.md +++ b/packages/frontend-internal/CHANGELOG.md @@ -1,5 +1,12 @@ # @internal/frontend +## 0.0.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + ## 0.0.19-next.0 ### Patch Changes diff --git a/packages/frontend-internal/package.json b/packages/frontend-internal/package.json index 4f979fb284..418e20e127 100644 --- a/packages/frontend-internal/package.json +++ b/packages/frontend-internal/package.json @@ -1,6 +1,6 @@ { "name": "@internal/frontend", - "version": "0.0.19-next.0", + "version": "0.0.19-next.1", "backstage": { "role": "web-library", "inline": true diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md index 47cc94703c..cb4df8180c 100644 --- a/packages/frontend-plugin-api/CHANGELOG.md +++ b/packages/frontend-plugin-api/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/frontend-plugin-api +## 0.16.0-next.1 + +### Minor Changes + +- 49397c1: Simplified the type signature of `createRouteRef` by replacing the dual `TParams`/`TParamKeys` type parameters with a single `TParamKey` parameter. This is a breaking change for callers that explicitly provided type arguments, but most usage that relies on inference is unaffected. + +### Patch Changes + +- ddc5247: Fixed `FlattenedMessages` type to avoid excessive type instantiation depth in TypeScript 6 when using `createTranslationRef` with the `translations` option. +- fa55078: Refactored the internal `createSchemaFromZod` helper to use a schema-first generic pattern, replacing the `ZodSchema` constraint with `TSchema extends ZodType`. This avoids "excessively deep" type inference errors when multiple Zod copies are resolved. + ## 0.15.2-next.0 ### Patch Changes diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 2c95fe00fb..daee6ddbbd 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.15.2-next.0", + "version": "0.16.0-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index 42d7b95123..a497077774 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/frontend-test-utils +## 0.5.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-app@0.4.3-next.1 + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/frontend-app-api@0.16.2-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/test-utils@1.7.17-next.1 + - @backstage/plugin-app-react@0.2.2-next.1 + ## 0.5.2-next.0 ### Patch Changes diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index 16dcabe3dc..7193b88127 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.5.2-next.0", + "version": "0.5.2-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index 88a3e3e9a8..657ce53760 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/repo-tools +## 0.17.1-next.1 + +### Patch Changes + +- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc). +- 8e9679b: Parallelized CLI report generation, reducing wall-clock time by ~4x. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + ## 0.17.1-next.0 ### Patch Changes diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index cd1ba32c6b..7eeeb2ed58 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/repo-tools", - "version": "0.17.1-next.0", + "version": "0.17.1-next.1", "description": "CLI for Backstage repo tooling ", "backstage": { "role": "cli" diff --git a/packages/scaffolder-internal/CHANGELOG.md b/packages/scaffolder-internal/CHANGELOG.md index cec1a07e0e..1c609c1919 100644 --- a/packages/scaffolder-internal/CHANGELOG.md +++ b/packages/scaffolder-internal/CHANGELOG.md @@ -1,5 +1,13 @@ # @internal/scaffolder +## 0.0.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/plugin-scaffolder-react@1.20.1-next.1 + ## 0.0.20-next.0 ### Patch Changes diff --git a/packages/scaffolder-internal/package.json b/packages/scaffolder-internal/package.json index 398acb0084..4c68cfa523 100644 --- a/packages/scaffolder-internal/package.json +++ b/packages/scaffolder-internal/package.json @@ -1,6 +1,6 @@ { "name": "@internal/scaffolder", - "version": "0.0.20-next.0", + "version": "0.0.20-next.1", "backstage": { "role": "web-library", "inline": true diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 7458cae039..e46e01d691 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.119-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.36.1-next.1 + - @backstage/ui@0.14.0-next.1 + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/test-utils@1.7.17-next.1 + - @backstage/plugin-catalog@2.0.2-next.1 + - @backstage/plugin-techdocs@1.17.3-next.1 + - @backstage/frontend-defaults@0.5.1-next.1 + - @backstage/plugin-app-react@0.2.2-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + ## 0.2.119-next.0 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 3520bab1c1..305d0a99a5 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.119-next.0", + "version": "0.2.119-next.1", "backstage": { "role": "frontend" }, diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 2d701fee0b..e38c0acd8b 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/test-utils +## 1.7.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 1.7.17-next.0 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 98bd12004e..106f3376d2 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/test-utils", - "version": "1.7.17-next.0", + "version": "1.7.17-next.1", "description": "Utilities to test Backstage plugins and apps.", "backstage": { "role": "web-library" diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index 88554b06d4..50296e9add 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/ui +## 0.14.0-next.1 + +### Patch Changes + +- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc). +- 8d79835: Added RangeSlider component for selecting numeric ranges. + + **Affected components:** RangeSlider + +- 5081bcc: Fixed `Avatar` becoming elliptical in flex layouts by preventing it from shrinking. + + **Affected components:** Avatar + +- d840ba9: Fixed relative `href` resolution for BUI link components. Relative paths like `../other` are now correctly turned into absolute paths before reaching the React Aria layer, ensuring client-side navigation goes to the right place. + + **Affected components:** ButtonLink, Card, CellProfile, CellText, Link, ListRow, MenuItem, MenuListBoxItem, Row, SearchAutocompleteItem, Tab, Tag + +- 3bc23a5: Added support for disabling pagination in `useTable` complete mode by setting `paginationOptions: { type: 'none' }`. This skips data slicing and produces `pagination: { type: 'none' }` in `tableProps`, removing the need for consumers to manually override the pagination prop on `Table`. Also fixed complete mode not reacting to dynamic changes in `paginationOptions.pageSize`. + + **Affected components:** `useTable` + +- c368cf3: Updated dependency `@types/use-sync-external-store` to `^1.0.0`. +- d0f055f: Added `showPaginationLabel` prop to `TablePagination` and `useTable` pagination options. When set to `false`, the pagination label (e.g., "1 - 20 of 150") is hidden while navigation controls remain visible. Defaults to `true`. + + **Affected components:** `TablePagination`, `useTable` + +- feaf3d1: Fixed HeaderNav hover indicator covering tab text when theme uses opaque background colors. Also fixed an incorrect CSS variable reference (`--bui-font-family` → `--bui-font-regular`). + + **Affected components:** Header + ## 0.14.0-next.0 ### Minor Changes diff --git a/packages/ui/package.json b/packages/ui/package.json index d1d9f411c4..fc8b7e9aba 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/ui", - "version": "0.14.0-next.0", + "version": "0.14.0-next.1", "backstage": { "role": "web-library" }, diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 11a389b28d..7d6978f30e 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-api-docs +## 0.13.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-catalog@2.0.2-next.1 + ## 0.13.6-next.0 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 0250f0ff95..e5895b8d3c 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.13.6-next.0", + "version": "0.13.6-next.1", "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 ad2fa6ee58..a7606ca75b 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-app-backend +## 0.5.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-app-node@0.1.44-next.1 + ## 0.5.13-next.0 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index c3dc3efd87..8cf27ff652 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-backend", - "version": "0.5.13-next.0", + "version": "0.5.13-next.1", "description": "A Backstage backend plugin that serves the Backstage frontend app", "backstage": { "role": "backend-plugin", diff --git a/plugins/app-node/CHANGELOG.md b/plugins/app-node/CHANGELOG.md index 9a5a8b4430..5888498449 100644 --- a/plugins/app-node/CHANGELOG.md +++ b/plugins/app-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-app-node +## 0.1.44-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + ## 0.1.44-next.0 ### Patch Changes diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json index af7360d3ef..b6f6cbec33 100644 --- a/plugins/app-node/package.json +++ b/plugins/app-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-node", - "version": "0.1.44-next.0", + "version": "0.1.44-next.1", "description": "Node.js library for the app plugin", "backstage": { "role": "node-library", diff --git a/plugins/app-react/CHANGELOG.md b/plugins/app-react/CHANGELOG.md index 14a530daeb..f3eced0445 100644 --- a/plugins/app-react/CHANGELOG.md +++ b/plugins/app-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-app-react +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 0.2.2-next.0 ### Patch Changes diff --git a/plugins/app-react/package.json b/plugins/app-react/package.json index 23d103e031..bb66bdc8f2 100644 --- a/plugins/app-react/package.json +++ b/plugins/app-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-react", - "version": "0.2.2-next.0", + "version": "0.2.2-next.1", "description": "Web library for the app plugin", "backstage": { "role": "web-library", diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md index 55e2c595ce..3d26a35ddc 100644 --- a/plugins/app-visualizer/CHANGELOG.md +++ b/plugins/app-visualizer/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-app-visualizer +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 0.2.2-next.0 ### Patch Changes diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json index c8ae602f30..fbdaaae248 100644 --- a/plugins/app-visualizer/package.json +++ b/plugins/app-visualizer/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-visualizer", - "version": "0.2.2-next.0", + "version": "0.2.2-next.1", "description": "Visualizes the Backstage app structure", "backstage": { "role": "frontend-plugin", diff --git a/plugins/app/CHANGELOG.md b/plugins/app/CHANGELOG.md index 1dfeb02e96..05b3a7d5c2 100644 --- a/plugins/app/CHANGELOG.md +++ b/plugins/app/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-app +## 0.4.3-next.1 + +### Patch Changes + +- e5baa20: Added support for configuring URL redirects on the `app/routes` extension. Redirects can be configured through `app-config` as an array of `{from, to}` path pairs, which will cause navigation to the `from` path to be redirected to the `to` path. + + For example: + + ```yaml + app: + extensions: + - app/routes: + config: + redirects: + - from: /old-path + to: /new-path + ``` + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-app-react@0.2.2-next.1 + ## 0.4.3-next.0 ### Patch Changes diff --git a/plugins/app/package.json b/plugins/app/package.json index 300145a02e..743f633883 100644 --- a/plugins/app/package.json +++ b/plugins/app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app", - "version": "0.4.3-next.0", + "version": "0.4.3-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "app", diff --git a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md index 8e1004c544..9a28999af1 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.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.4.14-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-atlassian-provider/package.json b/plugins/auth-backend-module-atlassian-provider/package.json index 2352281f3a..a91ae5f34f 100644 --- a/plugins/auth-backend-module-atlassian-provider/package.json +++ b/plugins/auth-backend-module-atlassian-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-atlassian-provider", - "version": "0.4.14-next.0", + "version": "0.4.14-next.1", "description": "The atlassian-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-auth0-provider/CHANGELOG.md b/plugins/auth-backend-module-auth0-provider/CHANGELOG.md index 5a2d8ee4ef..843cace2de 100644 --- a/plugins/auth-backend-module-auth0-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-auth0-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-auth0-provider +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.3.2-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-auth0-provider/package.json b/plugins/auth-backend-module-auth0-provider/package.json index cd05a5f595..6c8843060a 100644 --- a/plugins/auth-backend-module-auth0-provider/package.json +++ b/plugins/auth-backend-module-auth0-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-auth0-provider", - "version": "0.3.2-next.0", + "version": "0.3.2-next.1", "description": "The auth0-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md index 530bdcd67d..7e6172fb85 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.4.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-auth-backend@0.28.0-next.1 + ## 0.4.15-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 2b8628fac5..d08444ab8b 100644 --- a/plugins/auth-backend-module-aws-alb-provider/package.json +++ b/plugins/auth-backend-module-aws-alb-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-aws-alb-provider", - "version": "0.4.15-next.0", + "version": "0.4.15-next.1", "description": "The aws-alb provider module for the Backstage auth backend.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md index 94c3423141..61e3e21c40 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-azure-easyauth-provider +## 0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.2.19-next.0 ### 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 58b4aa119d..251c88089f 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/package.json +++ b/plugins/auth-backend-module-azure-easyauth-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-azure-easyauth-provider", - "version": "0.2.19-next.0", + "version": "0.2.19-next.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 e562b53ed0..115a57cc8d 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.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.3.14-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-bitbucket-provider/package.json b/plugins/auth-backend-module-bitbucket-provider/package.json index 17a4acea78..6c7789a9ee 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.3.14-next.0", + "version": "0.3.14-next.1", "description": "The bitbucket-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md b/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md index 2f2f59489d..b975dc5e05 100644 --- a/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-bitbucket-server-provider +## 0.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.2.14-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-bitbucket-server-provider/package.json b/plugins/auth-backend-module-bitbucket-server-provider/package.json index b6b92a6a42..e511c61713 100644 --- a/plugins/auth-backend-module-bitbucket-server-provider/package.json +++ b/plugins/auth-backend-module-bitbucket-server-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-bitbucket-server-provider", - "version": "0.2.14-next.0", + "version": "0.2.14-next.1", "description": "The bitbucket-server-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 1d25025499..70f1495239 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.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.4.14-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 54bf93e16c..d9a2cff9de 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.4.14-next.0", + "version": "0.4.14-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-gcp-iap-provider/CHANGELOG.md b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md index 2db3e76b80..00a59bd11e 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.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.4.14-next.0 ### 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 ad331cc383..82168334ee 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/package.json +++ b/plugins/auth-backend-module-gcp-iap-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-gcp-iap-provider", - "version": "0.4.14-next.0", + "version": "0.4.14-next.1", "description": "A GCP IAP auth provider module for the Backstage auth backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-github-provider/CHANGELOG.md b/plugins/auth-backend-module-github-provider/CHANGELOG.md index 8155ad8aff..0a55198fb7 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.5.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.5.2-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json index f44b9795b9..c03fdaad07 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.5.2-next.0", + "version": "0.5.2-next.1", "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 c064edb0c8..0e53fc6a4b 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.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.4.2-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-gitlab-provider/package.json b/plugins/auth-backend-module-gitlab-provider/package.json index 77d962859c..d3d9f80086 100644 --- a/plugins/auth-backend-module-gitlab-provider/package.json +++ b/plugins/auth-backend-module-gitlab-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-gitlab-provider", - "version": "0.4.2-next.0", + "version": "0.4.2-next.1", "description": "The gitlab-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-google-provider/CHANGELOG.md b/plugins/auth-backend-module-google-provider/CHANGELOG.md index 6d3980bec9..2bb4c047fc 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.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.3.14-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-google-provider/package.json b/plugins/auth-backend-module-google-provider/package.json index 004e5977d2..78c57fb155 100644 --- a/plugins/auth-backend-module-google-provider/package.json +++ b/plugins/auth-backend-module-google-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-google-provider", - "version": "0.3.14-next.0", + "version": "0.3.14-next.1", "description": "A Google auth provider module for the Backstage auth backend", "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 85c97ea29a..34c6d7f924 100644 --- a/plugins/auth-backend-module-guest-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-guest-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-guest-provider +## 0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.2.18-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 51320b130b..31bb0d996f 100644 --- a/plugins/auth-backend-module-guest-provider/package.json +++ b/plugins/auth-backend-module-guest-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-guest-provider", - "version": "0.2.18-next.0", + "version": "0.2.18-next.1", "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 625a97c5b1..974ab2c255 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.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.3.14-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-microsoft-provider/package.json b/plugins/auth-backend-module-microsoft-provider/package.json index c5bd533051..27519180b2 100644 --- a/plugins/auth-backend-module-microsoft-provider/package.json +++ b/plugins/auth-backend-module-microsoft-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-microsoft-provider", - "version": "0.3.14-next.0", + "version": "0.3.14-next.1", "description": "The microsoft-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md index 06bb41dd9e..43f628a9a5 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.4.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.4.14-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-provider/package.json b/plugins/auth-backend-module-oauth2-provider/package.json index a30a8655ee..b607a0a767 100644 --- a/plugins/auth-backend-module-oauth2-provider/package.json +++ b/plugins/auth-backend-module-oauth2-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-provider", - "version": "0.4.14-next.0", + "version": "0.4.14-next.1", "description": "The oauth2-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md index 2e9a851a29..0a1c5f3f7e 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.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.2.19-next.0 ### 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 f771a2607e..198691eea8 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/package.json +++ b/plugins/auth-backend-module-oauth2-proxy-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-proxy-provider", - "version": "0.2.19-next.0", + "version": "0.2.19-next.1", "description": "The oauth2-proxy-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 07bc1d70b1..1f0b01d0aa 100644 --- a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-oidc-provider +## 0.4.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-auth-backend@0.28.0-next.1 + ## 0.4.15-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 8224e28ec9..4a32e192a6 100644 --- a/plugins/auth-backend-module-oidc-provider/package.json +++ b/plugins/auth-backend-module-oidc-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-oidc-provider", - "version": "0.4.15-next.0", + "version": "0.4.15-next.1", "description": "The oidc-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-okta-provider/CHANGELOG.md b/plugins/auth-backend-module-okta-provider/CHANGELOG.md index 6016b6ecd2..6c9fc5a63b 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.2.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.2.14-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-okta-provider/package.json b/plugins/auth-backend-module-okta-provider/package.json index 202631dc0f..ff6a45a928 100644 --- a/plugins/auth-backend-module-okta-provider/package.json +++ b/plugins/auth-backend-module-okta-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-okta-provider", - "version": "0.2.14-next.0", + "version": "0.2.14-next.1", "description": "The okta-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md b/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md index 582e10f05c..33e17ac956 100644 --- a/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-onelogin-provider +## 0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.3.14-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-onelogin-provider/package.json b/plugins/auth-backend-module-onelogin-provider/package.json index b55f9395a3..1ca3f3f7b2 100644 --- a/plugins/auth-backend-module-onelogin-provider/package.json +++ b/plugins/auth-backend-module-onelogin-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-onelogin-provider", - "version": "0.3.14-next.0", + "version": "0.3.14-next.1", "description": "The onelogin-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-openshift-provider/CHANGELOG.md b/plugins/auth-backend-module-openshift-provider/CHANGELOG.md index 4b15d50a5f..6a2890af35 100644 --- a/plugins/auth-backend-module-openshift-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-openshift-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-openshift-provider +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.1.6-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-openshift-provider/package.json b/plugins/auth-backend-module-openshift-provider/package.json index 9fba12b41e..cbdccf7277 100644 --- a/plugins/auth-backend-module-openshift-provider/package.json +++ b/plugins/auth-backend-module-openshift-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-openshift-provider", - "version": "0.1.6-next.0", + "version": "0.1.6-next.1", "description": "The OpenShift backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md index 9356e6bb06..7fa9ab519c 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.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.3.13-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-pinniped-provider/package.json b/plugins/auth-backend-module-pinniped-provider/package.json index 3d7d1051bd..112da23e94 100644 --- a/plugins/auth-backend-module-pinniped-provider/package.json +++ b/plugins/auth-backend-module-pinniped-provider/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend-module-pinniped-provider", - "version": "0.3.13-next.0", + "version": "0.3.13-next.1", "description": "The pinniped-provider backend module for the auth plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md index ac4c2ab5c1..b16b741afd 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-vmware-cloud-provider +## 0.5.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.5.13-next.0 ### 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 42a4beb0ea..64605c1410 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.5.13-next.0", + "version": "0.5.13-next.1", "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 4f6f17709b..dbe8e1083b 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend +## 0.28.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + ## 0.28.0-next.0 ### Minor Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 59275da454..e26e41fc9e 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.28.0-next.0", + "version": "0.28.0-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 a27435e568..66b6a43ea5 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-auth-node +## 0.7.0-next.1 + +### Minor Changes + +- fa55078: **BREAKING**: Refactored `SignInResolverFactoryOptions` to use a schema-first generic pattern, following Zod's [recommended approach](https://zod.dev/library-authors?id=how-to-accept-user-defined-schemas#how-to-accept-user-defined-schemas) for writing generic functions. The type parameters changed from `` to ``. + + This fixes "Type instantiation is excessively deep and possibly infinite" errors that occurred when the Zod version in a user's project did not align with the one in Backstage core. + + If you use `createSignInResolverFactory` without explicit type parameters (the typical usage), no changes are needed: + + ```ts + // This usage is unchanged + createSignInResolverFactory({ + optionsSchema: z.object({ domain: z.string() }).optional(), + create(options = {}) { + /* ... */ + }, + }); + ``` + + If you reference `SignInResolverFactoryOptions` with explicit type parameters, update as follows: + + ```diff + - SignInResolverFactoryOptions + + SignInResolverFactoryOptions + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + ## 0.6.15-next.0 ### Patch Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 3dea21feb8..565263f620 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.6.15-next.0", + "version": "0.7.0-next.1", "backstage": { "role": "node-library", "pluginId": "auth", diff --git a/plugins/auth/CHANGELOG.md b/plugins/auth/CHANGELOG.md index feef5a513f..8012831a89 100644 --- a/plugins/auth/CHANGELOG.md +++ b/plugins/auth/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth +## 0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + ## 0.1.7-next.0 ### Patch Changes diff --git a/plugins/auth/package.json b/plugins/auth/package.json index a9f69061fe..437366faaf 100644 --- a/plugins/auth/package.json +++ b/plugins/auth/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth", - "version": "0.1.7-next.0", + "version": "0.1.7-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "auth", diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 5dd16c852f..fe8e20a740 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.4.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-defaults@0.16.1-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + ## 0.4.22-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 0590dbc936..12271acc72 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", - "version": "0.4.22-next.0", + "version": "0.4.22-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 fe17043b5c..883c8c4b9e 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.3.16-next.1 + +### Patch Changes + +- 39d27ee: Add Azure DevOps SCM event translation layer for instant catalog reprocessing. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.3.16-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 754bf1c9a5..fc39d301d4 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.3.16-next.0", + "version": "0.3.16-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-backstage-openapi/CHANGELOG.md b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md index 59b8b2a997..974029ceb8 100644 --- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-backstage-openapi +## 0.5.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-openapi-utils@0.6.8-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + ## 0.5.13-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index 0221c2a9e9..e55d06f0ea 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.5.13-next.0", + "version": "0.5.13-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index f5d65c7582..c079810e63 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.5.10-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 eebe2b4d8e..bda38d6ffe 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", - "version": "0.5.10-next.0", + "version": "0.5.10-next.1", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index e5881c2331..cc26699f61 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.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.5.10-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 72f96f4805..0134f065af 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.5.10-next.0", + "version": "0.5.10-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-gcp/CHANGELOG.md b/plugins/catalog-backend-module-gcp/CHANGELOG.md index 254f473e6b..0e00f7cfc2 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.3.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + ## 0.3.18-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index c19c570a47..5880e808e6 100644 --- a/plugins/catalog-backend-module-gcp/package.json +++ b/plugins/catalog-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gcp", - "version": "0.3.18-next.0", + "version": "0.3.18-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 bb71efebca..14fffa414b 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + ## 0.3.13-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 1b38077bf2..f1f322a5f2 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.3.13-next.0", + "version": "0.3.13-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-gitea/CHANGELOG.md b/plugins/catalog-backend-module-gitea/CHANGELOG.md index 41a45001b3..f889cb3d9e 100644 --- a/plugins/catalog-backend-module-gitea/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-backend-module-gitea +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + ## 0.1.11-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitea/package.json b/plugins/catalog-backend-module-gitea/package.json index 2ef0f73a52..75066aa93e 100644 --- a/plugins/catalog-backend-module-gitea/package.json +++ b/plugins/catalog-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitea", - "version": "0.1.11-next.0", + "version": "0.1.11-next.1", "description": "The gitea backend module for the catalog plugin.", "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 02eb855c61..cd3108dfa2 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.3.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-backend-module-github@0.13.1-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.3.21-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 c29528320f..d3ed302a8e 100644 --- a/plugins/catalog-backend-module-github-org/package.json +++ b/plugins/catalog-backend-module-github-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github-org", - "version": "0.3.21-next.0", + "version": "0.3.21-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 3066f365fc..6f2349446b 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-github +## 0.13.1-next.1 + +### Patch Changes + +- b11e338: Fixed a bug where `GithubEntityProvider` with `validateLocationsExist: true` and `filters.branch` configured would always check for the catalog file on the repository's default branch (`HEAD`) instead of the configured branch. This caused repositories to be filtered out when the catalog file only existed on the non-default branch. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.13.1-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index a9c4e0ec70..e42357d60c 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.13.1-next.0", + "version": "0.13.1-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 c884f9cd47..e79b10e0d7 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.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.8.2-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.2.20-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 499e16a071..a8db0545fd 100644 --- a/plugins/catalog-backend-module-gitlab-org/package.json +++ b/plugins/catalog-backend-module-gitlab-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab-org", - "version": "0.2.20-next.0", + "version": "0.2.20-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 3117046c69..e19fe931d2 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.8.2-next.1 + +### Patch Changes + +- 54a8300: Add GitLab SCM event translation layer for instant catalog reprocessing. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-defaults@0.16.1-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.8.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 0a240200ec..2a3fef0f9d 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.8.2-next.0", + "version": "0.8.2-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 b07f07417c..06f4ee057d 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.7.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-defaults@0.16.1-next.1 + - @backstage/plugin-catalog-backend@3.5.1-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.7.11-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 e285d844f6..e76e76f16b 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.7.11-next.0", + "version": "0.7.11-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 a4e8fa3d48..3fff999f8b 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.12.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + ## 0.12.4-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index 8da76a4d1d..d2e236f4e2 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.12.4-next.0", + "version": "0.12.4-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-logs/CHANGELOG.md b/plugins/catalog-backend-module-logs/CHANGELOG.md index 4c7e39ea0b..c8316ab4d7 100644 --- a/plugins/catalog-backend-module-logs/CHANGELOG.md +++ b/plugins/catalog-backend-module-logs/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-logs +## 0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-backend@3.5.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.1.21-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index 6f20425632..4a6f71e4d9 100644 --- a/plugins/catalog-backend-module-logs/package.json +++ b/plugins/catalog-backend-module-logs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-logs", - "version": "0.1.21-next.0", + "version": "0.1.21-next.1", "description": "A module that subscribes to catalog related events and logs them.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 11910fbf4c..b8306dccf1 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.9.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + ## 0.9.2-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index eead4a48cf..7571c9937a 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.9.2-next.0", + "version": "0.9.2-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 a14850e615..21f450bf9b 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.2.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + ## 0.2.21-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 5063d2fbe2..f1bb5698fe 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", - "version": "0.2.21-next.0", + "version": "0.2.21-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 8b778fb84b..7c40effecd 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.2.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + ## 0.2.21-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index fe32abda54..80a110890c 100644 --- a/plugins/catalog-backend-module-puppetdb/package.json +++ b/plugins/catalog-backend-module-puppetdb/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-puppetdb", - "version": "0.2.21-next.0", + "version": "0.2.21-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 b6a95bb0d5..b3ff48ea78 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + ## 0.2.19-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 ce200d068d..32f3fb7dee 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/package.json +++ b/plugins/catalog-backend-module-scaffolder-entity-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-scaffolder-entity-model", - "version": "0.2.19-next.0", + "version": "0.2.19-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 6c6c68de9b..7ff5e26e1e 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.6.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + ## 0.6.10-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index beaa81280c..523af7f521 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.6.10-next.0", + "version": "0.6.10-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 a96984479f..875754d396 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend +## 3.5.1-next.1 + +### Patch Changes + +- 2e5c5f8: Bumped `glob` dependency from v7/v8/v11 to v13 to address security vulnerabilities in older versions. Bumped `rollup` from v4.27 to v4.59+ to fix a high severity path traversal vulnerability (GHSA-mw96-cpmx-2vgc). +- 6884814: Improved catalog entity filter query performance by switching from `IN (subquery)` to `EXISTS (correlated subquery)` patterns. This enables PostgreSQL semi-join optimizations and fixes `NOT IN` NULL-semantics pitfalls by using `NOT EXISTS` instead. +- 9da73bf: Reduced search table write churn during stitching by syncing only changed rows instead of doing a full delete and re-insert. On Postgres this uses a single writable CTE, on MySQL a temporary table merge with deadlock retry, and on SQLite the previous bulk replace. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-openapi-utils@0.6.8-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + ## 3.5.1-next.0 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 0bcf0fa9a2..52f1325440 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "3.5.1-next.0", + "version": "3.5.1-next.1", "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 a5b4d70b09..8235015db1 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-graph +## 0.6.1-next.1 + +### Patch Changes + +- 0e147e8: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 0.6.1-next.0 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index c7e1074f8e..62a8fb02d8 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.6.1-next.0", + "version": "0.6.1-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-graph", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index de326fe779..dbe29ad6e7 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-import +## 0.13.12-next.1 + +### Patch Changes + +- fa0593e: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 0.13.12-next.0 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 028c7017e7..ab1971c4e9 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.13.12-next.0", + "version": "0.13.12-next.1", "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 2850facb83..da7190a185 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-node +## 2.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-test-utils@1.11.2-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + ## 2.1.1-next.0 ### Patch Changes diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 5b88394358..b73f0112ce 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-node", - "version": "2.1.1-next.0", + "version": "2.1.1-next.1", "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 4c2eb216e8..e382925b3a 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-react +## 2.1.2-next.1 + +### Patch Changes + +- eba2f61: Fixed `EntityInfoCard` header overflowing on narrow screens. +- 0416216: Fixed entity relation cards (e.g., "Has components") only showing one entity at a time by using `paginationOptions: { type: 'none' }` instead of deriving page size from data length. +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/frontend-test-utils@0.5.2-next.1 + ## 2.1.1-next.0 ### Patch Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index a324e4524f..04b05330ff 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-react", - "version": "2.1.1-next.0", + "version": "2.1.2-next.1", "description": "A frontend library that helps other Backstage plugins interact with the catalog", "backstage": { "role": "web-library", diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md index ffd48e5dc9..516f834343 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.2.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 0.2.29-next.0 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index 28b1996490..e9d27f2899 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.29-next.0", + "version": "0.2.29-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-unprocessed-entities", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 59a844b0c9..8be6712d76 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog +## 2.0.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-search-react@1.11.1-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + ## 2.0.2-next.0 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 04cdd1e3e0..702ec6171f 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "2.0.2-next.0", + "version": "2.0.2-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 562691f185..321c4331ec 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-devtools-backend +## 0.5.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + ## 0.5.16-next.0 ### Patch Changes diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index 0df1eae9c3..ebd73fd1d4 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-backend", - "version": "0.5.16-next.0", + "version": "0.5.16-next.1", "backstage": { "role": "backend-plugin", "pluginId": "devtools", diff --git a/plugins/devtools-react/CHANGELOG.md b/plugins/devtools-react/CHANGELOG.md index dc0a586708..0586b39e6d 100644 --- a/plugins/devtools-react/CHANGELOG.md +++ b/plugins/devtools-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-devtools-react +## 0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 0.2.1-next.0 ### Patch Changes diff --git a/plugins/devtools-react/package.json b/plugins/devtools-react/package.json index da60209d5b..00fa279c39 100644 --- a/plugins/devtools-react/package.json +++ b/plugins/devtools-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-react", - "version": "0.2.1-next.0", + "version": "0.2.1-next.1", "description": "Web library for the devtools plugin", "backstage": { "role": "web-library", diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md index 66050e0f50..da21387a81 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-devtools +## 0.1.38-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 0.1.38-next.0 ### Patch Changes diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index b2498b3efc..c3d11540db 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.38-next.0", + "version": "0.1.38-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "devtools", diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md index a988c3f366..48864ace2c 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.4.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.4.21-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index d88aee8f63..8bf87c843e 100644 --- a/plugins/events-backend-module-aws-sqs/package.json +++ b/plugins/events-backend-module-aws-sqs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-aws-sqs", - "version": "0.4.21-next.0", + "version": "0.4.21-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-azure/CHANGELOG.md b/plugins/events-backend-module-azure/CHANGELOG.md index 814a1f819e..168e1e1572 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.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.2.30-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index 6569b8b46c..241a478c2a 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.30-next.0", + "version": "0.2.30-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md index b19ad67b97..73f5eda9e3 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.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.2.30-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index 124c8a867d..22015d230a 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.30-next.0", + "version": "0.2.30-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-bitbucket-server/CHANGELOG.md b/plugins/events-backend-module-bitbucket-server/CHANGELOG.md index 9506e7aace..30953d4aaf 100644 --- a/plugins/events-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-bitbucket-server +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.1.11-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-server/package.json b/plugins/events-backend-module-bitbucket-server/package.json index dc05d81d87..a574cb489e 100644 --- a/plugins/events-backend-module-bitbucket-server/package.json +++ b/plugins/events-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-bitbucket-server", - "version": "0.1.11-next.0", + "version": "0.1.11-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-gerrit/CHANGELOG.md b/plugins/events-backend-module-gerrit/CHANGELOG.md index ae559b9f99..237a7eda11 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.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.2.30-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index 2f06b8b45b..a54ee7ae67 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.30-next.0", + "version": "0.2.30-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md index 1dbd10be33..7f007a5fee 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.4.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.4.11-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index 118fac42b0..8e961a9e92 100644 --- a/plugins/events-backend-module-github/package.json +++ b/plugins/events-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-github", - "version": "0.4.11-next.0", + "version": "0.4.11-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-gitlab/CHANGELOG.md b/plugins/events-backend-module-gitlab/CHANGELOG.md index 448b65a36d..6eab96b062 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.3.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.3.11-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json index 1c7b3df544..c498fbb214 100644 --- a/plugins/events-backend-module-gitlab/package.json +++ b/plugins/events-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-gitlab", - "version": "0.3.11-next.0", + "version": "0.3.11-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/events-backend-module-google-pubsub/CHANGELOG.md b/plugins/events-backend-module-google-pubsub/CHANGELOG.md index a04b917f41..bfa6bcbd7b 100644 --- a/plugins/events-backend-module-google-pubsub/CHANGELOG.md +++ b/plugins/events-backend-module-google-pubsub/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-google-pubsub +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.2.2-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-google-pubsub/package.json b/plugins/events-backend-module-google-pubsub/package.json index 3cfe461f3f..23bb7b1e9f 100644 --- a/plugins/events-backend-module-google-pubsub/package.json +++ b/plugins/events-backend-module-google-pubsub/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-google-pubsub", - "version": "0.2.2-next.0", + "version": "0.2.2-next.1", "description": "The google-pubsub backend module for the events plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/events-backend-module-kafka/CHANGELOG.md b/plugins/events-backend-module-kafka/CHANGELOG.md index 83d600f260..42780a79ca 100644 --- a/plugins/events-backend-module-kafka/CHANGELOG.md +++ b/plugins/events-backend-module-kafka/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-kafka +## 0.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.3.3-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-kafka/package.json b/plugins/events-backend-module-kafka/package.json index 64f4b69005..9320501913 100644 --- a/plugins/events-backend-module-kafka/package.json +++ b/plugins/events-backend-module-kafka/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-kafka", - "version": "0.3.3-next.0", + "version": "0.3.3-next.1", "description": "The kafka backend module for the events plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/events-backend/CHANGELOG.md b/plugins/events-backend/CHANGELOG.md index 1e291c469a..7087102ebc 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-events-backend +## 0.6.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-openapi-utils@0.6.8-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.6.1-next.0 ### Patch Changes diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index 5916ad2a0f..6cd6f031de 100644 --- a/plugins/events-backend/package.json +++ b/plugins/events-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend", - "version": "0.6.1-next.0", + "version": "0.6.1-next.1", "backstage": { "role": "backend-plugin", "pluginId": "events", diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md index 6d99a323ad..e701ad477a 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-events-node +## 0.4.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + ## 0.4.21-next.0 ### Patch Changes diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index 85bb57e7d6..a162c8ac1d 100644 --- a/plugins/events-node/package.json +++ b/plugins/events-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-node", - "version": "0.4.21-next.0", + "version": "0.4.21-next.1", "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 4d56066962..f8349a7e2c 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,12 @@ # @internal/plugin-todo-list-backend +## 1.0.49-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + ## 1.0.49-next.0 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index aa55017918..14afe85259 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.49-next.0", + "version": "1.0.49-next.1", "backstage": { "role": "backend-plugin", "pluginId": "todo-list", diff --git a/plugins/gateway-backend/CHANGELOG.md b/plugins/gateway-backend/CHANGELOG.md index 445ab4e684..40e4528f41 100644 --- a/plugins/gateway-backend/CHANGELOG.md +++ b/plugins/gateway-backend/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-gateway-backend +## 1.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + ## 1.1.4-next.0 ### Patch Changes diff --git a/plugins/gateway-backend/package.json b/plugins/gateway-backend/package.json index ad46c927b3..d9254f617d 100644 --- a/plugins/gateway-backend/package.json +++ b/plugins/gateway-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gateway-backend", - "version": "1.1.4-next.0", + "version": "1.1.4-next.1", "backstage": { "role": "backend-plugin", "pluginId": "gateway", diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index 7aece89893..3bdcce3a9f 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-home-react +## 0.1.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 0.1.37-next.0 ### Patch Changes diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index 463b78dfa9..8c1627ac16 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.37-next.0", + "version": "0.1.37-next.1", "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 cdfbe7be4d..9bfed7e424 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-home +## 0.9.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-compat-api@0.5.10-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-home-react@0.1.37-next.1 + ## 0.9.4-next.0 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index f1fe2701be..fbb8c731eb 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.9.4-next.0", + "version": "0.9.4-next.1", "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 24fd647dbe..b2ec3b531b 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-kubernetes-backend +## 0.21.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-kubernetes-node@0.4.3-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + ## 0.21.3-next.0 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 21c4847f90..f04b521dab 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-backend", - "version": "0.21.3-next.0", + "version": "0.21.3-next.1", "description": "A Backstage backend plugin that integrates towards Kubernetes", "backstage": { "role": "backend-plugin", diff --git a/plugins/kubernetes-node/CHANGELOG.md b/plugins/kubernetes-node/CHANGELOG.md index d888b6a4b1..c7ce350045 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-kubernetes-node +## 0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + ## 0.4.3-next.0 ### Patch Changes diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index 8b8176f9ef..289e5b64d6 100644 --- a/plugins/kubernetes-node/package.json +++ b/plugins/kubernetes-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-node", - "version": "0.4.3-next.0", + "version": "0.4.3-next.1", "description": "Node.js library for the kubernetes plugin", "backstage": { "role": "node-library", diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index bc1a6693cc..74a3b63993 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-kubernetes +## 0.12.18-next.1 + +### Patch Changes + +- d156cf4: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-kubernetes-react@0.5.18-next.0 + ## 0.12.18-next.0 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index f55a5e9b57..c3a6602b6b 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.12.18-next.0", + "version": "0.12.18-next.1", "description": "A Backstage plugin that integrates towards Kubernetes", "backstage": { "role": "frontend-plugin", diff --git a/plugins/mcp-actions-backend/CHANGELOG.md b/plugins/mcp-actions-backend/CHANGELOG.md index d7bebd5cdf..e099f7e38f 100644 --- a/plugins/mcp-actions-backend/CHANGELOG.md +++ b/plugins/mcp-actions-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-mcp-actions-backend +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + ## 0.1.11-next.0 ### Patch Changes diff --git a/plugins/mcp-actions-backend/package.json b/plugins/mcp-actions-backend/package.json index 8c200cec92..4d07561dea 100644 --- a/plugins/mcp-actions-backend/package.json +++ b/plugins/mcp-actions-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-mcp-actions-backend", - "version": "0.1.11-next.0", + "version": "0.1.11-next.1", "backstage": { "role": "backend-plugin", "pluginId": "mcp-actions", diff --git a/plugins/mui-to-bui/CHANGELOG.md b/plugins/mui-to-bui/CHANGELOG.md index 5bab61bf25..a08cecff1b 100644 --- a/plugins/mui-to-bui/CHANGELOG.md +++ b/plugins/mui-to-bui/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-mui-to-bui +## 0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 0.2.6-next.0 ### Patch Changes diff --git a/plugins/mui-to-bui/package.json b/plugins/mui-to-bui/package.json index 7efbc38bd4..7cb9f9799e 100644 --- a/plugins/mui-to-bui/package.json +++ b/plugins/mui-to-bui/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-mui-to-bui", - "version": "0.2.6-next.0", + "version": "0.2.6-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "mui-to-bui", diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md index 475fd8b202..81d7394750 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-notifications-backend-module-email +## 0.3.20-next.1 + +### Patch Changes + +- 19ef9fb: build(deps): bump `nodemailer` from 7.0.13 to 8.0.4 +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-notifications-node@0.2.25-next.1 + ## 0.3.20-next.0 ### Patch Changes diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 2db8027e36..8cef4c6d6d 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-email", - "version": "0.3.20-next.0", + "version": "0.3.20-next.1", "description": "The email backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/notifications-backend-module-slack/CHANGELOG.md b/plugins/notifications-backend-module-slack/CHANGELOG.md index 38758d3b44..7c3be5d6a0 100644 --- a/plugins/notifications-backend-module-slack/CHANGELOG.md +++ b/plugins/notifications-backend-module-slack/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-notifications-backend-module-slack +## 0.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-notifications-node@0.2.25-next.1 + ## 0.4.1-next.0 ### Patch Changes diff --git a/plugins/notifications-backend-module-slack/package.json b/plugins/notifications-backend-module-slack/package.json index 01113137b4..4ace3174a2 100644 --- a/plugins/notifications-backend-module-slack/package.json +++ b/plugins/notifications-backend-module-slack/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-slack", - "version": "0.4.1-next.0", + "version": "0.4.1-next.1", "description": "The slack 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 f6479c59a3..cc5b38c120 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-notifications-backend +## 0.6.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-notifications-node@0.2.25-next.1 + - @backstage/plugin-signals-node@0.1.30-next.1 + ## 0.6.4-next.0 ### Patch Changes diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 4a973ce5a5..fd145ac2c0 100644 --- a/plugins/notifications-backend/package.json +++ b/plugins/notifications-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend", - "version": "0.6.4-next.0", + "version": "0.6.4-next.1", "backstage": { "role": "backend-plugin", "pluginId": "notifications", diff --git a/plugins/notifications-node/CHANGELOG.md b/plugins/notifications-node/CHANGELOG.md index df06ef4e93..587f448a14 100644 --- a/plugins/notifications-node/CHANGELOG.md +++ b/plugins/notifications-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-notifications-node +## 0.2.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-signals-node@0.1.30-next.1 + ## 0.2.25-next.0 ### Patch Changes diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index d4adfeda72..dfc44cf17c 100644 --- a/plugins/notifications-node/package.json +++ b/plugins/notifications-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-node", - "version": "0.2.25-next.0", + "version": "0.2.25-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 b001dbff7d..9d5a2d6293 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-notifications +## 0.5.16-next.1 + +### Patch Changes + +- d156cf4: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 0.5.16-next.0 ### Patch Changes diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index 58ac16a7bf..4773cb09e9 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.5.16-next.0", + "version": "0.5.16-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "notifications", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 9ad552e629..029302cfd8 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-org +## 0.7.1-next.1 + +### Patch Changes + +- 87eb31c: Fixed `GroupProfileCard` and `UserProfileCard` content overflowing on narrow screens. +- d156cf4: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 0.7.1-next.0 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index 307b675b35..0b81a6b022 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.7.1-next.0", + "version": "0.7.1-next.1", "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 2be8184086..625b9c14df 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.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + ## 0.2.18-next.0 ### Patch Changes diff --git a/plugins/permission-backend-module-policy-allow-all/package.json b/plugins/permission-backend-module-policy-allow-all/package.json index 66729c85b0..7064bd6033 100644 --- a/plugins/permission-backend-module-policy-allow-all/package.json +++ b/plugins/permission-backend-module-policy-allow-all/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend-module-allow-all-policy", - "version": "0.2.18-next.0", + "version": "0.2.18-next.1", "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 d851e4a0b5..4a7b8ec1f2 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-permission-backend +## 0.7.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + ## 0.7.11-next.0 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 157207fea5..bdb5924996 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.7.11-next.0", + "version": "0.7.11-next.1", "backstage": { "role": "backend-plugin", "pluginId": "permission", diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index c960a52388..30e0d008b5 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-permission-node +## 0.10.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + ## 0.10.12-next.0 ### Patch Changes diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index e768bd4a73..dd38c38628 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-node", - "version": "0.10.12-next.0", + "version": "0.10.12-next.1", "description": "Common permission and authorization utilities for backend plugins", "backstage": { "role": "node-library", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index 813605e358..8aeba98539 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-proxy-backend +## 0.6.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-proxy-node@0.1.14-next.1 + ## 0.6.12-next.0 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 9325f30ad4..020f48c0de 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-backend", - "version": "0.6.12-next.0", + "version": "0.6.12-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/proxy-node/CHANGELOG.md b/plugins/proxy-node/CHANGELOG.md index 8e42a28fac..1bc58447e6 100644 --- a/plugins/proxy-node/CHANGELOG.md +++ b/plugins/proxy-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-proxy-node +## 0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + ## 0.1.14-next.0 ### Patch Changes diff --git a/plugins/proxy-node/package.json b/plugins/proxy-node/package.json index 0860967386..d015c0a9f8 100644 --- a/plugins/proxy-node/package.json +++ b/plugins/proxy-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-node", - "version": "0.1.14-next.0", + "version": "0.1.14-next.1", "description": "The plugin-proxy-node module for @backstage/plugin-proxy-backend", "backstage": { "role": "node-library", diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md index a9ec268cac..53b5f66769 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.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + ## 0.2.20-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index 38f6e44255..d7aa064e17 100644 --- a/plugins/scaffolder-backend-module-azure/package.json +++ b/plugins/scaffolder-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-azure", - "version": "0.2.20-next.0", + "version": "0.2.20-next.1", "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 3078da22a4..8ec9b44040 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.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + ## 0.3.5-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index 3147764237..3647e3ec73 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.3.5-next.0", + "version": "0.3.5-next.1", "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 01ea979219..00b0cee233 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.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + ## 0.2.20-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index 111bd1143f..8c69651128 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-server", - "version": "0.2.20-next.0", + "version": "0.2.20-next.1", "description": "The Bitbucket Server 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 79bd39e0c6..78985628d5 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.3.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + ## 0.3.20-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index 0909f88612..d92babdad3 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown", - "version": "0.3.20-next.0", + "version": "0.3.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 35acb8ae7f..c7d27f93cb 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.3.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-defaults@0.16.1-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + ## 0.3.22-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 410561e85e..a22fc82712 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", - "version": "0.3.22-next.0", + "version": "0.3.22-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-gcp/CHANGELOG.md b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md index 1c75a5bd28..605669bcea 100644 --- a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-backend-module-gcp +## 0.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + ## 0.2.20-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gcp/package.json b/plugins/scaffolder-backend-module-gcp/package.json index 6b5809ec5f..b14852324f 100644 --- a/plugins/scaffolder-backend-module-gcp/package.json +++ b/plugins/scaffolder-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gcp", - "version": "0.2.20-next.0", + "version": "0.2.20-next.1", "description": "The GCP Bucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md index 3a79d4f38e..d1d2764ff3 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.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + ## 0.2.20-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index e65234e07c..4e80112826 100644 --- a/plugins/scaffolder-backend-module-gerrit/package.json +++ b/plugins/scaffolder-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gerrit", - "version": "0.2.20-next.0", + "version": "0.2.20-next.1", "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 f175645b33..658f43f100 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.2.20-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + ## 0.2.20-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index 66f3a9bd5c..83c91bf6b2 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitea", - "version": "0.2.20-next.0", + "version": "0.2.20-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 ccee2f6206..7894de4b03 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.9.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + ## 0.9.8-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index ffea7ed1fd..94fc4fe066 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-github", - "version": "0.9.8-next.0", + "version": "0.9.8-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 418fb35648..e579d073cc 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.11.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + ## 0.11.5-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index ba484c2a28..e7c31b0e3a 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.11.5-next.0", + "version": "0.11.5-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md index db7d7338ba..bef0260d6f 100644 --- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-notifications +## 0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-notifications-node@0.2.25-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + ## 0.1.21-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json index a737e5aa20..061a2a4089 100644 --- a/plugins/scaffolder-backend-module-notifications/package.json +++ b/plugins/scaffolder-backend-module-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-notifications", - "version": "0.1.21-next.0", + "version": "0.1.21-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 f4ad242699..d32b9e4856 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.5.20-next.1 + +### Patch Changes + +- 2905c59: Removed unused `jest-when` dev dependency. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + ## 0.5.20-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 19653745f5..a7b992c6ae 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", - "version": "0.5.20-next.0", + "version": "0.5.20-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 365bd88f13..ea6bd26432 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.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + ## 0.3.3-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index ad2b3f214f..d38b495de0 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.3.3-next.0", + "version": "0.3.3-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index aa723c4a7a..7cafbcd8d6 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.4.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.3.10-next.1 + ## 0.4.21-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 5eac8e5ad6..d4d05fe7c0 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.4.21-next.0", + "version": "0.4.21-next.1", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 80fdae742b..25d3f0f8f2 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-scaffolder-backend +## 3.3.0-next.1 + +### Minor Changes + +- 309b712: Added a new `execute-template` actions registry action that executes a scaffolder template with provided input values and returns a task ID for tracking progress. + +### Patch Changes + +- 4559806: Removed unnecessary empty `examples` array from actions bridged via the actions registry. +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-openapi-utils@0.6.8-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + ## 3.2.1-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index ec20d1c1af..ec7bdba97c 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "3.2.1-next.0", + "version": "3.3.0-next.1", "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 fba0cda8ae..2f3bbb71cd 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.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-test-utils@1.11.2-next.1 + - @backstage/plugin-scaffolder-node@0.13.1-next.1 + ## 0.3.10-next.0 ### Patch Changes diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json index 4deb4cdec5..5091389bf2 100644 --- a/plugins/scaffolder-node-test-utils/package.json +++ b/plugins/scaffolder-node-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node-test-utils", - "version": "0.3.10-next.0", + "version": "0.3.10-next.1", "backstage": { "role": "node-library", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index d03d547357..cb48e0a4cd 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-node +## 0.13.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-test-utils@1.11.2-next.1 + ## 0.13.1-next.0 ### Patch Changes diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index a4ee7c01f0..1cdf194285 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node", - "version": "0.13.1-next.0", + "version": "0.13.1-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 0851b8ebaa..a14c3db6ff 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-react +## 1.20.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/frontend-test-utils@0.5.2-next.1 + ## 1.20.1-next.0 ### Patch Changes diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 5e0c58384c..4c930122c6 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-react", - "version": "1.20.1-next.0", + "version": "1.20.1-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 3b34d0b39d..a30fb1b049 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-scaffolder +## 1.36.2-next.1 + +### Patch Changes + +- 864a799: Fix the display of the description in `GitlabRepoPicker`: + + - Move `owner.description` helper text outside the `allowedOwners` conditional so it renders for both `Select` and `Autocomplete` modes. + - Update the `Autocomplete` label to use `fields.gitlabRepoPicker.owner.inputTitle` instead of `fields.gitlabRepoPicker.owner.title`. + +- a7a14b7: Removed custom `IterableDirectoryHandle` and `WritableFileHandle` types in favor of the standard DOM `FileSystemDirectoryHandle` and `FileSystemFileHandle` types, which are now available through the `DOM.AsyncIterable` lib added to the shared TypeScript configuration. +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-scaffolder-react@1.20.1-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + ## 1.36.2-next.0 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 9e98d3e67c..20f5d1f55c 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.36.2-next.0", + "version": "1.36.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 cbcababce4..7296e02106 100644 --- a/plugins/search-backend-module-catalog/CHANGELOG.md +++ b/plugins/search-backend-module-catalog/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-search-backend-module-catalog +## 0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + ## 0.3.14-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-catalog/package.json b/plugins/search-backend-module-catalog/package.json index 969d92dddb..1e2ab797ba 100644 --- a/plugins/search-backend-module-catalog/package.json +++ b/plugins/search-backend-module-catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-catalog", - "version": "0.3.14-next.0", + "version": "0.3.14-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 01e3ff75ad..78d75b514e 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 1.8.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + ## 1.8.2-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 4d9bd1cffa..38911ab2c2 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.8.2-next.0", + "version": "1.8.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 0c7e7e5da5..f780033b02 100644 --- a/plugins/search-backend-module-explore/CHANGELOG.md +++ b/plugins/search-backend-module-explore/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-search-backend-module-explore +## 0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + ## 0.3.13-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-explore/package.json b/plugins/search-backend-module-explore/package.json index acf2712fec..00f453cbc5 100644 --- a/plugins/search-backend-module-explore/package.json +++ b/plugins/search-backend-module-explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-explore", - "version": "0.3.13-next.0", + "version": "0.3.13-next.1", "description": "A module for the search backend that exports explore modules", "backstage": { "moved": "@backstage-community/plugin-search-backend-module-explore", diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index 542a9d3972..c68e62d79b 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-search-backend-module-pg +## 0.5.54-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + ## 0.5.54-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index df6a599c15..342afea629 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.54-next.0", + "version": "0.5.54-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 1fa52da36f..bd4cbc2cce 100644 --- a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md +++ b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-search-backend-module-stack-overflow-collator +## 0.3.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + ## 0.3.19-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-stack-overflow-collator/package.json b/plugins/search-backend-module-stack-overflow-collator/package.json index 91aa803dd2..b34b748ef0 100644 --- a/plugins/search-backend-module-stack-overflow-collator/package.json +++ b/plugins/search-backend-module-stack-overflow-collator/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-stack-overflow-collator", - "version": "0.3.19-next.0", + "version": "0.3.19-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 aaccba4751..da01df76fc 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.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + - @backstage/plugin-techdocs-node@1.14.5-next.1 + ## 0.4.13-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index 75c2f72816..7d7c84905e 100644 --- a/plugins/search-backend-module-techdocs/package.json +++ b/plugins/search-backend-module-techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-techdocs", - "version": "0.4.13-next.0", + "version": "0.4.13-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 fe9ff79b94..8d75e06a12 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-search-backend-node +## 1.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + ## 1.4.3-next.0 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index af59ea0b36..f3d363f3ab 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.4.3-next.0", + "version": "1.4.3-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 580eab0e65..45bdc4d0bc 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend +## 2.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/backend-openapi-utils@0.6.8-next.1 + - @backstage/plugin-permission-node@0.10.12-next.1 + - @backstage/plugin-search-backend-node@1.4.3-next.1 + ## 2.1.1-next.0 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 7f83191e14..5d1d5a0827 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend", - "version": "2.1.1-next.0", + "version": "2.1.1-next.1", "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 1f0c85bc06..ece5f2a647 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-search-react +## 1.11.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 1.11.1-next.0 ### Patch Changes diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index 7a9151ee9b..9ae2e68eb7 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-react", - "version": "1.11.1-next.0", + "version": "1.11.1-next.1", "backstage": { "role": "web-library", "pluginId": "search", diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index a70a4eb59d..449a4d2871 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-search +## 1.7.1-next.1 + +### Patch Changes + +- 34aebcc: Fixed the `SearchModal` leaving the page in a broken state by not restoring body overflow and aria-hidden attributes when closing. +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-search-react@1.11.1-next.1 + ## 1.7.1-next.0 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 2061bb421c..d1acc680a0 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "1.7.1-next.0", + "version": "1.7.1-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 b352b52e0a..b52ca9b5e7 100644 --- a/plugins/signals-backend/CHANGELOG.md +++ b/plugins/signals-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-signals-backend +## 0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + - @backstage/plugin-signals-node@0.1.30-next.1 + ## 0.3.14-next.0 ### Patch Changes diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index baf6784dbd..e335cd0771 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-backend", - "version": "0.3.14-next.0", + "version": "0.3.14-next.1", "backstage": { "role": "backend-plugin", "pluginId": "signals", diff --git a/plugins/signals-node/CHANGELOG.md b/plugins/signals-node/CHANGELOG.md index 2ab6ecbde7..05b9c85912 100644 --- a/plugins/signals-node/CHANGELOG.md +++ b/plugins/signals-node/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-signals-node +## 0.1.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-events-node@0.4.21-next.1 + ## 0.1.30-next.0 ### Patch Changes diff --git a/plugins/signals-node/package.json b/plugins/signals-node/package.json index a615070807..62a172a0ef 100644 --- a/plugins/signals-node/package.json +++ b/plugins/signals-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-node", - "version": "0.1.30-next.0", + "version": "0.1.30-next.1", "description": "Node.js library for the signals plugin", "backstage": { "role": "node-library", diff --git a/plugins/signals/CHANGELOG.md b/plugins/signals/CHANGELOG.md index a776654620..5f5c9cd35b 100644 --- a/plugins/signals/CHANGELOG.md +++ b/plugins/signals/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-signals +## 0.0.30-next.1 + +### Patch Changes + +- d156cf4: Added `title` and `icon` to the new frontend system plugin definition. +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 0.0.30-next.0 ### Patch Changes diff --git a/plugins/signals/package.json b/plugins/signals/package.json index 4e06dd4105..07566d5e35 100644 --- a/plugins/signals/package.json +++ b/plugins/signals/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals", - "version": "0.0.30-next.0", + "version": "0.0.30-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "signals", diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index 2cd36e305f..b74862cb27 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-techdocs-addons-test-utils +## 2.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/test-utils@1.7.17-next.1 + - @backstage/plugin-catalog@2.0.2-next.1 + - @backstage/plugin-search-react@1.11.1-next.1 + - @backstage/plugin-techdocs@1.17.3-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + ## 2.0.4-next.0 ### Patch Changes diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index 522e3a1c9c..b15412181f 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": "2.0.4-next.0", + "version": "2.0.4-next.1", "backstage": { "role": "web-library", "pluginId": "techdocs-addons", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index e0023e5aff..58625e0d2f 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-techdocs-backend +## 2.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-catalog-node@2.1.1-next.1 + - @backstage/plugin-techdocs-node@1.14.5-next.1 + ## 2.1.7-next.0 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 30933da80b..100e64acc3 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "2.1.7-next.0", + "version": "2.1.7-next.1", "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 7871917847..90bc132983 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.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + ## 1.1.35-next.0 ### Patch Changes diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index 32ff9c0cfa..ba6ecbd445 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.35-next.0", + "version": "1.1.35-next.1", "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 306e5e2531..256ba90041 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-techdocs-node +## 1.14.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + ## 1.14.5-next.0 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index ffe473bab8..5f67fc65ee 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-node", - "version": "1.14.5-next.0", + "version": "1.14.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/techdocs-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index 3f3077dca8..e1557862ef 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-techdocs-react +## 1.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 1.3.10-next.0 ### Patch Changes diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index 41dcddecb4..a141b508cb 100644 --- a/plugins/techdocs-react/package.json +++ b/plugins/techdocs-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-react", - "version": "1.3.10-next.0", + "version": "1.3.10-next.1", "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 97e108d6d1..d9ae9e75b9 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-techdocs +## 1.17.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + - @backstage/plugin-search-react@1.11.1-next.1 + - @backstage/plugin-techdocs-react@1.3.10-next.1 + ## 1.17.3-next.0 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 6dc5adcb8a..cbd9d4bd26 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "1.17.3-next.0", + "version": "1.17.3-next.1", "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 f116a511a0..b413386d86 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.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@1.9.0-next.1 + - @backstage/plugin-auth-node@0.7.0-next.1 + - @backstage/plugin-signals-node@0.1.30-next.1 + ## 0.4.2-next.0 ### Patch Changes diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index 57109fd8e4..f7bf4267d7 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.4.2-next.0", + "version": "0.4.2-next.1", "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 3d3d0daf72..6823afc6cd 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-user-settings +## 0.9.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/ui@0.14.0-next.1 + - @backstage/core-app-api@1.20.0-next.1 + - @backstage/plugin-catalog-react@2.1.2-next.1 + - @backstage/frontend-plugin-api@0.16.0-next.1 + - @backstage/core-components@0.18.9-next.0 + - @backstage/core-plugin-api@1.12.5-next.1 + ## 0.9.2-next.0 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 44652c480e..9e11c889fd 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.9.2-next.0", + "version": "0.9.2-next.1", "description": "A Backstage plugin that provides a settings page", "backstage": { "role": "frontend-plugin", From f46363130ca697e5e8ff2ed7f66a89af411fa52a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 31 Mar 2026 17:54:39 +0200 Subject: [PATCH 078/191] Update packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../src/components/MarkdownContent/MarkdownContent.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx index 61e519f33a..4098a6e52d 100644 --- a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx +++ b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx @@ -113,7 +113,12 @@ const components: Options['components'] = { a: ({ href, children, title, target, rel }) => href ? ( - + {children} ) : ( From bb7768ba06d259e3e9929749c8f5f388b3c19402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 31 Mar 2026 18:43:40 +0200 Subject: [PATCH 079/191] docs: fix broken links and anchors across documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix ~30 broken links and anchors across the documentation site, including incorrect relative paths, mismatched anchor names, zero-width characters in URLs, and references to renamed or removed headings. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Fredrik Adelöw --- docs/deployment/docker.md | 2 +- docs/faq/product.md | 2 +- docs/features/kubernetes/proxy.md | 2 +- docs/features/search/README.md | 2 +- docs/features/search/getting-started.md | 2 +- docs/features/software-catalog/faq.md | 2 +- docs/features/software-catalog/index.md | 2 +- docs/features/software-templates/index.md | 2 +- docs/features/software-templates/writing-templates.md | 2 +- docs/features/techdocs/FAQ.md | 4 ++-- docs/features/techdocs/architecture.md | 3 +-- docs/features/techdocs/configuring-ci-cd.md | 2 +- docs/frontend-system/architecture/10-app.md | 2 +- docs/frontend-system/architecture/20-extensions.md | 2 +- docs/frontend-system/architecture/25-extension-overrides.md | 2 +- docs/frontend-system/building-plugins/01-index.md | 2 +- docs/getting-started/config/authentication--old.md | 2 +- docs/getting-started/config/authentication.md | 2 +- docs/getting-started/config/database.md | 2 +- docs/getting-started/homepage--old.md | 2 +- docs/getting-started/homepage.md | 2 +- docs/getting-started/keeping-backstage-updated.md | 2 +- docs/getting-started/viewing-catalog.md | 2 +- docs/golden-path/create-app/keeping-backstage-updated.md | 2 +- docs/integrations/azure/org.md | 4 ++-- docs/permissions/concepts.md | 2 +- docs/publishing.md | 4 ++-- docs/references/glossary.md | 4 ++-- docs/releases/v1.3.0.md | 2 +- docs/tutorials/using-backstage-proxy-within-plugin.md | 4 ++-- microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.mdx | 2 +- 31 files changed, 36 insertions(+), 37 deletions(-) diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index 852248d543..05e5d63076 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -113,7 +113,7 @@ CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app For more details on how the `backend:bundle` command and the `skeleton.tar.gz` file works, see the -[`backend:bundle` command docs](../tooling/cli/03-commands.md#backendbundle). +[`backend:bundle` command docs](../tooling/cli/03-commands.md#package-bundle). The `Dockerfile` is located at `packages/backend/Dockerfile`, but needs to be executed with the root of the repo as the build context, in order to get access diff --git a/docs/faq/product.md b/docs/faq/product.md index f09b5696b8..fed4c2eb65 100644 --- a/docs/faq/product.md +++ b/docs/faq/product.md @@ -16,7 +16,7 @@ brand. No, but it can be! Backstage is designed to be a developer portal for all your infrastructure tooling, services, and documentation. So, it's not a monitoring platform — but that doesn't mean you can't integrate a monitoring tool into -Backstage by writing [a plugin](#what-is-a-plugin-in-backstage). +Backstage by writing [a plugin](technical.md#what-is-a-plugin-in-backstage). ### How is Backstage licensed? diff --git a/docs/features/kubernetes/proxy.md b/docs/features/kubernetes/proxy.md index 85ad86f83b..35dcd1f8c3 100644 --- a/docs/features/kubernetes/proxy.md +++ b/docs/features/kubernetes/proxy.md @@ -99,7 +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](../../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) has a known bug: - [#15901](https://github.com/backstage/backstage/issues/15901) - it cannot diff --git a/docs/features/search/README.md b/docs/features/search/README.md index eb5d12080c..742df07e25 100644 --- a/docs/features/search/README.md +++ b/docs/features/search/README.md @@ -34,7 +34,7 @@ The following sections show the plugins and search engines currently supported b | Plugin | Support Status | | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | | Software Catalog | ✅ | -| [TechDocs](./how-to-guides.md#how-to-index-techdocs-documents) | ✅ | +| [TechDocs](./how-to-guides.md#how-to-customize-fields-in-the-software-catalog-or-techdocs-index) | ✅ | | [Stack Overflow](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-stack-overflow-collator/README.md#index-stack-overflow-questions-to-search) | ✅ | ### Search engines diff --git a/docs/features/search/getting-started.md b/docs/features/search/getting-started.md index e5029ed35f..155aaa2e58 100644 --- a/docs/features/search/getting-started.md +++ b/docs/features/search/getting-started.md @@ -342,4 +342,4 @@ indexBuilder.addCollator({ }); ``` -> For more advanced customization of the Search backend, also see how to guides such as [How to index TechDocs documents](./how-to-guides.md#how-to-index-techdocs-documents) and [How to limit what can be searched in the Software Catalog](./how-to-guides.md#how-to-limit-what-can-be-searched-in-the-software-catalog) +> For more advanced customization of the Search backend, also see how to guides such as [How to customize fields in the Software Catalog or TechDocs index](./how-to-guides.md#how-to-customize-fields-in-the-software-catalog-or-techdocs-index) diff --git a/docs/features/software-catalog/faq.md b/docs/features/software-catalog/faq.md index 24b490ee13..6a8f9b99d4 100644 --- a/docs/features/software-catalog/faq.md +++ b/docs/features/software-catalog/faq.md @@ -25,7 +25,7 @@ On the technical side, this is unwanted complexity. You need to implement and ma On the user experience side, a Backstage experience without complete organizational data is a serious hindrance to getting the full power out of the tool. Your users won't be able to click on owners and seeing who they are and what teams they belong to. They won't be able to find out what the communications paths are when they need to reach you or your managers when something goes wrong or they have a feature request. They can't get an overview of what teams own and how they relate to each other. It will be a much more barren experience. Organizational data is highly valuable to have centrally available, complete and correct. -## Can I call the catalog itself from inside a processor / provider? +## Can I call the catalog itself from inside a processor / provider? {#can-i-call-the-catalog-itself-from-inside-a-processor--provider} While it's possible to get hold of a catalog client via the `catalogServiceRef` from `@backstage/plugin-catalog-node`, it's almost never the right thing to do, and we strongly discourage from doing so. diff --git a/docs/features/software-catalog/index.md b/docs/features/software-catalog/index.md index a221ed9157..0c812bd8dd 100644 --- a/docs/features/software-catalog/index.md +++ b/docs/features/software-catalog/index.md @@ -32,7 +32,7 @@ More specifically, the Software Catalog enables two main use-cases: ## Getting Started The Software Catalog is available to browse at `/catalog`. If you've followed -[Getting Started with Backstage](../../getting-started), you should be able to +[Getting Started with Backstage](../../getting-started/index.md), you should be able to browse the catalog at `http://localhost:3000`. ![screenshot of software catalog](../../assets/software-catalog/software-catalog-home.png) diff --git a/docs/features/software-templates/index.md b/docs/features/software-templates/index.md index 1b36fcb2dd..37419b8bcf 100644 --- a/docs/features/software-templates/index.md +++ b/docs/features/software-templates/index.md @@ -20,7 +20,7 @@ See the [Writing Custom Actions guide](./writing-custom-actions.md#naming-conven ## Prerequisites -These docs assume you have already gone over the [Backstage Getting Started](../../getting-started) section and you are able to run Backstage locally or it has been deployed somewhere. +These docs assume you have already gone over the [Backstage Getting Started](../../getting-started/index.md) section and you are able to run Backstage locally or it has been deployed somewhere. ## Getting Started diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index 64c1acc67d..23b376caf5 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -576,7 +576,7 @@ token from the user, which you can do on a per-provider basis, in case your template can be published to multiple providers. Note, that you will need to configure an [authentication provider](../../auth/index.md#configuring-authentication-providers), alongside the -[`ScmAuthApi`](../../auth/index.md#scaffolder-configuration-software-templates) for your source code management (SCM) service to make this feature work. +[`ScmAuthApi`](../../auth/index.md#custom-scmauthapi-implementation) for your source code management (SCM) service to make this feature work. ### The Repository Branch Picker diff --git a/docs/features/techdocs/FAQ.md b/docs/features/techdocs/FAQ.md index 9c78e3e123..6def714b1b 100644 --- a/docs/features/techdocs/FAQ.md +++ b/docs/features/techdocs/FAQ.md @@ -11,7 +11,7 @@ This page answers frequently asked questions about [TechDocs](README.md). - [What static site generator is TechDocs using?](#what-static-site-generator-is-techdocs-using) - [What is the mkdocs-techdocs-core plugin?](#what-is-the-mkdocs-techdocs-core-plugin) -- [Does TechDocs support file formats other than Markdown (e.g. RST, AsciiDoc)?](#does-techdocs-support-file-formats-other-than-markdown-eg-rst-asciidoc-) +- [Does TechDocs support file formats other than Markdown (e.g. RST, AsciiDoc)?](#does-techdocs-support-file-formats-other-than-markdown-eg-rst-asciidoc) - [What should be the value of `backstage.io/techdocs-ref` when using external build and storage?](#what-should-be-the-value-of-backstageiotechdocs-ref-when-using-external-build-and-storage) - [Is it possible for users to suggest changes or provide feedback on a TechDocs page?](#is-it-possible-for-users-to-suggest-changes-or-provide-feedback-on-a-techdocs-page) @@ -30,7 +30,7 @@ plugins (e.g. [MkDocs Monorepo Plugin](https://github.com/spotify/mkdocs-monorepo-plugin)) as well as a selection of Python Markdown extensions that TechDocs supports. -#### Does TechDocs support file formats other than Markdown (e.g. RST, AsciiDoc) ? +#### Does TechDocs support file formats other than Markdown (e.g. RST, AsciiDoc)? Not right now. We are currently using MkDocs to generate the documentation from source, so the files have to be in Markdown format. However, in the future we diff --git a/docs/features/techdocs/architecture.md b/docs/features/techdocs/architecture.md index 8c9c4401cc..ff1c9d30eb 100644 --- a/docs/features/techdocs/architecture.md +++ b/docs/features/techdocs/architecture.md @@ -17,8 +17,7 @@ out-of-the box experience. 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). +[HOW TO migrate guide](how-to-guides.md#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach). ::: diff --git a/docs/features/techdocs/configuring-ci-cd.md b/docs/features/techdocs/configuring-ci-cd.md index d818c129a4..a06eb5e475 100644 --- a/docs/features/techdocs/configuring-ci-cd.md +++ b/docs/features/techdocs/configuring-ci-cd.md @@ -104,7 +104,7 @@ the TechDocs plugin in your Backstage app. Here is an example workflow using GitHub Actions CI and AWS S3 storage. You can use any CI and any other -[TechDocs supported cloud storage providers](README.md#platforms-supported). +[TechDocs supported cloud storage providers](README.md#supported). Add a `.github/workflows/techdocs.yml` file in your [Software Template(s)](../software-templates/index.md) like this - diff --git a/docs/frontend-system/architecture/10-app.md b/docs/frontend-system/architecture/10-app.md index 3e7c760a42..d2af7b1c72 100644 --- a/docs/frontend-system/architecture/10-app.md +++ b/docs/frontend-system/architecture/10-app.md @@ -96,7 +96,7 @@ Utility APIs that are first materialized during bootstrap are frozen for the lif ## Plugin Info Resolution -When a plugin is installed in an app it may provide sources of information about the plugin that can be useful to end users and admins. This includes things like what version of a plugin is running, what team owns the plugin, and who to contact for support. You can read more about how the plugins provide this information in the [plugins `info` option section](./15-plugins.md#info). +When a plugin is installed in an app it may provide sources of information about the plugin that can be useful to end users and admins. This includes things like what version of a plugin is running, what team owns the plugin, and who to contact for support. You can read more about how the plugins provide this information in the [plugins `info` option section](./15-plugins.md#info-option). By default the app will pick a few common fields from `package.json` files, and assume that the opaque manifests are `catalog-info.yaml` files that some information can be gathered from too. This information will then be available via the `info()` method on plugin instances, returning a structure of the `FrontendPluginInfo` type. diff --git a/docs/frontend-system/architecture/20-extensions.md b/docs/frontend-system/architecture/20-extensions.md index 3df33b9cad..14c31764ca 100644 --- a/docs/frontend-system/architecture/20-extensions.md +++ b/docs/frontend-system/architecture/20-extensions.md @@ -17,7 +17,7 @@ Each extensions has a number of different properties that define how it behaves The ID of an extension is used to uniquely identity it, and it should ideally be unique across the entire Backstage ecosystem. For each frontend app instance there can only be a single extension for any given ID. Installing multiple extensions with the same ID will either result in an error or one of the extensions will override the others. The ID is also used to reference the extensions from other extensions, in configuration, and in other places such as developer tools and analytics. -When creating an extension you do not provide the ID directly. Instead, you indirectly or directly provide the kind, namespace, and name parts that make up the ID. The kind is always provided by the [extension blueprint](./23-extension-blueprints.md), the only exception is if you use [`createExtension`](#creating-an-extensions) directly. Any extension that is provided by a plugin will by default have its namespace set to the plugin ID, so you generally only need to provide an explicit namespace if you want to override an existing extension. The name is also optional, and primarily used to distinguish between multiple extensions of the same kind and namespace. If a plugin doesn't need to distinguish between different extensions of the same kind, the name can be omitted. +When creating an extension you do not provide the ID directly. Instead, you indirectly or directly provide the kind, namespace, and name parts that make up the ID. The kind is always provided by the [extension blueprint](./23-extension-blueprints.md), the only exception is if you use [`createExtension`](#creating-an-extension) directly. Any extension that is provided by a plugin will by default have its namespace set to the plugin ID, so you generally only need to provide an explicit namespace if you want to override an existing extension. The name is also optional, and primarily used to distinguish between multiple extensions of the same kind and namespace. If a plugin doesn't need to distinguish between different extensions of the same kind, the name can be omitted. The extension ID will be constructed using the pattern `[:][][/][]`, where the separating `/` is only present if both a namespace and name are defined. diff --git a/docs/frontend-system/architecture/25-extension-overrides.md b/docs/frontend-system/architecture/25-extension-overrides.md index 7afbd2bacb..22d553b084 100644 --- a/docs/frontend-system/architecture/25-extension-overrides.md +++ b/docs/frontend-system/architecture/25-extension-overrides.md @@ -9,7 +9,7 @@ description: Frontend extension overrides An important customization point in the frontend system is the ability to override existing extensions. It can be used for anything from slight tweaks to the extension logic, to completely replacing an extension with a custom implementation. While extensions are encouraged to make themselves configurable, there are many situations where you need to override an extension to achieve the desired behavior. The ability to override extensions should be kept in mind when building plugins, and can be a powerful tool to allow for deeper customizations without the need to re-implement large parts of the plugin. -In general, most features should have a good level of customization built into them, so that users do not have to leverage extension overrides to achieve common goals. A well written feature often has [configuration](../../conf/) settings, or uses extension inputs for extensibility where applicable. An example of this is the search plugin, which allows you to provide result renderers as inputs rather than replacing the result page wholesale just to tweak how results are shown. Adopters should take advantage of those when possible in order to reduce the need and size of extension overrides. +In general, most features should have a good level of customization built into them, so that users do not have to leverage extension overrides to achieve common goals. A well written feature often has [configuration](../../conf/index.md) settings, or uses extension inputs for extensibility where applicable. An example of this is the search plugin, which allows you to provide result renderers as inputs rather than replacing the result page wholesale just to tweak how results are shown. Adopters should take advantage of those when possible in order to reduce the need and size of extension overrides. Extension overrides can also replace or remove existing `if` predicates. This applies both to direct extension overrides through `.override(...)` and to plugin-level overrides through `plugin.withOverrides(...)`. Frontend modules can use the same extension override mechanism to adjust or clear the condition for an overridden extension. diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index d89eae87b0..9d5507c012 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -11,7 +11,7 @@ frontend _features_, and what you install to build up a Backstage frontend [app] ## Creating a new plugin -This guide assumes that you already have a Backstage project set up. Even if you only want to develop a single plugin for publishing, we still recommend that you do so in a standard Backstage monorepo project, as you often end up needing multiple packages. For instructions on how to set up a new project, see our [getting started](../../getting-started/index.md#prerequisites) documentation. +This guide assumes that you already have a Backstage project set up. Even if you only want to develop a single plugin for publishing, we still recommend that you do so in a standard Backstage monorepo project, as you often end up needing multiple packages. For instructions on how to set up a new project, see our [getting started](../../getting-started/index.md) documentation. To create a frontend plugin, run `yarn new`, select `plugin`, and fill out the rest of the prompts. This will create a new package at `plugins/`, which will be the main entrypoint for your plugin. diff --git a/docs/getting-started/config/authentication--old.md b/docs/getting-started/config/authentication--old.md index cc02eb2b7f..f719d87181 100644 --- a/docs/getting-started/config/authentication--old.md +++ b/docs/getting-started/config/authentication--old.md @@ -210,4 +210,4 @@ If you've updated the configuration for your integration, it's likely that the b Some helpful links, for if you want to learn more about: - [Other available integrations](../../integrations/index.md) -- [Using GitHub Apps instead of a Personal Access Token](../../integrations/github/github-apps.md#docsNav) +- [Using GitHub Apps instead of a Personal Access Token](../../integrations/github/github-apps.md) diff --git a/docs/getting-started/config/authentication.md b/docs/getting-started/config/authentication.md index 9df27692b8..3fc5fbe69e 100644 --- a/docs/getting-started/config/authentication.md +++ b/docs/getting-started/config/authentication.md @@ -236,4 +236,4 @@ If you've updated the configuration for your integration, it's likely that the b Some helpful links, for if you want to learn more about: - [Other available integrations](../../integrations/index.md) -- [Using GitHub Apps instead of a Personal Access Token](../../integrations/github/github-apps.md#docsNav) +- [Using GitHub Apps instead of a Personal Access Token](../../integrations/github/github-apps.md) diff --git a/docs/getting-started/config/database.md b/docs/getting-started/config/database.md index 7f85e173c9..cfad91fc62 100644 --- a/docs/getting-started/config/database.md +++ b/docs/getting-started/config/database.md @@ -190,7 +190,7 @@ backend: # highlight-remove-end ``` -[Start the Backstage app](../index.md#2-run-the-backstage-app): +[Start the Backstage app](../index.md#creating-and-running-a-backstage-application): ```shell yarn start diff --git a/docs/getting-started/homepage--old.md b/docs/getting-started/homepage--old.md index 98021d76a2..34a3afdbbf 100644 --- a/docs/getting-started/homepage--old.md +++ b/docs/getting-started/homepage--old.md @@ -39,7 +39,7 @@ At the end of this tutorial, you can expect: Before we begin, make sure - You have created your own standalone Backstage app using - [`@backstage/create-app`](./index.md#1-create-your-backstage-app) and not + [`@backstage/create-app`](./index.md#creating-and-running-a-backstage-application) and not using a fork of the [backstage](https://github.com/backstage/backstage) repository. - You do not have an existing homepage, and by default you are redirected to diff --git a/docs/getting-started/homepage.md b/docs/getting-started/homepage.md index 22761eb6ed..810b891d65 100644 --- a/docs/getting-started/homepage.md +++ b/docs/getting-started/homepage.md @@ -26,7 +26,7 @@ At the end of this tutorial, you can expect: Before we begin, make sure -- You have created your own standalone Backstage app using [`@backstage/create-app`](./index.md#1-create-your-backstage-app) and not using a fork of the [backstage](https://github.com/backstage/backstage) repository. +- You have created your own standalone Backstage app using [`@backstage/create-app`](./index.md#creating-and-running-a-backstage-application) and not using a fork of the [backstage](https://github.com/backstage/backstage) repository. - You do not have an existing homepage, and by default you are redirected to Software Catalog when you open Backstage. Now, let's get started by installing the home plugin and creating a simple homepage for your Backstage app. diff --git a/docs/getting-started/keeping-backstage-updated.md b/docs/getting-started/keeping-backstage-updated.md index a55f872f53..243760b69a 100644 --- a/docs/getting-started/keeping-backstage-updated.md +++ b/docs/getting-started/keeping-backstage-updated.md @@ -155,7 +155,7 @@ On Node.js 22.21.0+, the Backstage CLI respects the standard `HTTP_PROXY`, `HTTP On older Node.js versions, the CLI falls back to [global-agent](https://www.npmjs.com/package/global-agent) and `undici` for proxy support, which require their own environment variables (prefixed with `GLOBAL_AGENT_`). This allows you to route the CLI’s network traffic through a proxy server, which can be useful in environments with restricted internet access. -Additionally, yarn needs a proxy too (sometimes), when in environments with restricted internet access. It uses different settings than the other modules. If you decide to use the backstage yarn plugin [mentioned above](#plugin), you will need to set additional proxy values. +Additionally, yarn needs a proxy too (sometimes), when in environments with restricted internet access. It uses different settings than the other modules. If you decide to use the backstage yarn plugin [mentioned above](#managing-package-versions-with-the-backstage-yarn-plugin), you will need to set additional proxy values. If you will always need proxy settings in all environments and situations, you can add `httpProxy` and `httpsProxy` values to [the yarnrc.yml file](https://yarnpkg.com/configuration/yarnrc). If some environments need it (say a developer workstation) but other environments do not (perhaps a CI build server running on AWS), then you may not want to update the yarnrc.yml file but just set environment variables `YARN_HTTP_PROXY` and `YARN_HTTPS_PROXY` in the environments/situations where you need to proxy. **If you plan to use the backstage yarn plugin, you will need these extra yarn proxy settings to both install the plugin and run the `versions:bump` command**. If you do not plan to use the backstage yarn plugin, it seems like the proxy settings alone are sufficient. diff --git a/docs/getting-started/viewing-catalog.md b/docs/getting-started/viewing-catalog.md index acfa470cc2..6ca9075629 100644 --- a/docs/getting-started/viewing-catalog.md +++ b/docs/getting-started/viewing-catalog.md @@ -27,7 +27,7 @@ Initially, the Catalog displays registered entities matching the following filte - `Kind` - Component - `Type` - all - `Owner` - Owned -- `Lifecycle` - list of [lifecycle](../features/software-catalog/descriptor-format.md#speclifecycle-required-1) values of entities in the Catalog +- `Lifecycle` - list of [lifecycle](../features/software-catalog/descriptor-format.md#speclifecycle-required) values of entities in the Catalog - `Processing Status` - normal - `Namespace` - The ID of a [namespace](../features/software-catalog/descriptor-format.md#namespace-optional) to which the entity belongs diff --git a/docs/golden-path/create-app/keeping-backstage-updated.md b/docs/golden-path/create-app/keeping-backstage-updated.md index b0561e5304..540b200f1b 100644 --- a/docs/golden-path/create-app/keeping-backstage-updated.md +++ b/docs/golden-path/create-app/keeping-backstage-updated.md @@ -146,7 +146,7 @@ On Node.js 22.21.0+, the Backstage CLI respects the standard `HTTP_PROXY`, `HTTP On older Node.js versions, the CLI falls back to [global-agent](https://www.npmjs.com/package/global-agent) and `undici` for proxy support, which require their own environment variables (prefixed with `GLOBAL_AGENT_`). This allows you to route the CLI’s network traffic through a proxy server, which can be useful in environments with restricted internet access. -Additionally, `yarn` needs a proxy too (sometimes), when in environments with restricted internet access. It uses different settings than the other modules. If you decide to use the backstage yarn plugin [mentioned above](#plugin), you will need to set additional proxy values. +Additionally, `yarn` needs a proxy too (sometimes), when in environments with restricted internet access. It uses different settings than the other modules. If you decide to use the backstage yarn plugin [mentioned above](#managing-package-versions-with-the-backstage-yarn-plugin), you will need to set additional proxy values. If you will always need proxy settings in all environments and situations, you can add `httpProxy` and `httpsProxy` values to [the yarnrc.yml file](https://yarnpkg.com/configuration/yarnrc). If some environments need it (say a developer workstation) but other environments do not (perhaps a CI build server running on AWS), then you may not want to update the yarnrc.yml file but just set environment variables `YARN_HTTP_PROXY` and `YARN_HTTPS_PROXY` in the environments/situations where you need to proxy. **If you plan to use the backstage yarn plugin, you will need these extra yarn proxy settings to both install the plugin and run the `versions:bump` command**. If you do not plan to use the backstage yarn plugin, it seems like the proxy settings alone are sufficient. diff --git a/docs/integrations/azure/org.md b/docs/integrations/azure/org.md index 7bd93f38b3..3c82f6e2be 100644 --- a/docs/integrations/azure/org.md +++ b/docs/integrations/azure/org.md @@ -120,7 +120,7 @@ microsoftGraphOrg: In addition to these groups, one additional group will be created for your organization. All imported groups will be a child of this group. -By default the provider will get groups using the msgraph `/group` endpoint, but it is possible to use different endpoints by setting the `path` configuration. All the endpoint containing `/microsoft.graph.group` will return the right type of group object. [See usage](#Using-path-parameter) for more details. +By default the provider will get groups using the msgraph `/group` endpoint, but it is possible to use different endpoints by setting the `path` configuration. All the endpoint containing `/microsoft.graph.group` will return the right type of group object. [See usage](#using-path-parameter) for more details. ### Users @@ -145,7 +145,7 @@ microsoftGraphOrg: search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")' ``` -By default the provider will get user using the msgraph `/user` endpoint, but it is possible to use different endpoints by setting the `path` configuration. All the endpoint containing `/microsoft.graph.user` will return the right type of user object. [See usage](#Using-path-parameter) for more details. +By default the provider will get user using the msgraph `/user` endpoint, but it is possible to use different endpoints by setting the `path` configuration. All the endpoint containing `/microsoft.graph.user` will return the right type of user object. [See usage](#using-path-parameter) for more details. ### Using `path` parameter diff --git a/docs/permissions/concepts.md b/docs/permissions/concepts.md index 7c8b9e9f69..e43c944eff 100644 --- a/docs/permissions/concepts.md +++ b/docs/permissions/concepts.md @@ -22,6 +22,6 @@ In many cases, a permission represents a user's interaction with another object. ### Conditional decisions -[Rules](../references/glossary.md#rule-permission-plugin) need additional data before they can be used in a decision. Once a [rule](../references/glossary.md#rule-permission-plugin) is bound to relevant information it forms a [condition](../references/glossary.md#condition-permission-plugin). Conditional decisions tell the [permission framework](#permission) to delegate evaluation to the [plugin](#plugin) that owns the corresponding [resource](#resource-permission-plugin). Permission requests that result in a conditional decision are allowed if all of the provided conditions evaluate to be true. +[Rules](../references/glossary.md#rule-permission-plugin) need additional data before they can be used in a decision. Once a [rule](../references/glossary.md#rule-permission-plugin) is bound to relevant information it forms a [condition](../references/glossary.md#condition-permission-plugin). Conditional decisions tell the [permission framework](#permission) to delegate evaluation to the [plugin](../references/glossary.md#plugin) that owns the corresponding [resource](../references/glossary.md#resource-permission-plugin). Permission requests that result in a conditional decision are allowed if all of the provided conditions evaluate to be true. A good example would be the catalog plugin's "has annotation" rule which needs to know what annotation to look for on a given entity. The permission framework would respond to a request by the catalog plugin in this case with a condition decision. The catalog plugin would then need to correctly filter for entities matching the "has annotations" condition. This conditional behavior avoids coupling between policies and resource schemas, and allows plugins to evaluate complex rules in an efficient way. For example, a plugin may convert a conditional decision to a database query instead of loading and filtering objects in memory. diff --git a/docs/publishing.md b/docs/publishing.md index 15b613f800..9c2ecb0157 100644 --- a/docs/publishing.md +++ b/docs/publishing.md @@ -47,12 +47,12 @@ Additional steps for the main line release - Check for mentions of "major" & "breaking" and if they are expected in the current release - Verify the version we are shipping is correct - Create Release Notes - - There exists a [release notes template](./.release-notes-template.md) for creating the release notes. It can already be created after the last main line release to keep track of major changes during the month + - There exists a release notes template (`.release-notes-template.md`) for creating the release notes. It can already be created after the last main line release to keep track of major changes during the month - The content is picked by relevancy showcasing the work of the community during the month of the release - Mention newly added packages or features - Mention any security fixes - Create Release Notes PR - - Add the release note file as [`/docs/releases/vx.y.0.md`](./releases) + - Add the release note file as [`/docs/releases/vx.y.0.md`](https://backstage.io/docs/releases/) - Finally copy the content, without the metadata header, into the description of the [`Version Packages` Pull Request](https://github.com/backstage/backstage/pulls?q=is%3Aopen+is%3Apr+in%3Atitle+%22Version+Packages) Once the release has been published edit the newly created release in the [GitHub repository](https://github.com/backstage/backstage/releases) and replace the text content with the release notes. diff --git a/docs/references/glossary.md b/docs/references/glossary.md index ad9e2bbee5..31b7115efa 100644 --- a/docs/references/glossary.md +++ b/docs/references/glossary.md @@ -186,7 +186,7 @@ This standard is a key component of [OpenID Connect](#openid-connect-aka-oidc). Classification of an [entity](#entity) in the Backstage Software Catalog, for example _service_, _database_, or _team_. An element of the [kind|namespace|name triplet](#kind-namespace-name-triplet) that is an important concept for uniqueness. -## Kind|namespace|name triplet +## Kind|namespace|name triplet {#kind-namespace-name-triplet} The primary reference for [Software Catalog](#software-catalog) entities. It is human-readable and should be unique across your Backstage instance. @@ -379,7 +379,7 @@ Existing search technology that [Backstage Search](#search) can take advantage o The Software Catalog is a core feature of Backstage. See [Backstage Software Catalog](https://backstage.io/docs/next/features/software-catalog/) for an overview, the life of an entity in the catalog, how to configure the catalog, its architecture and high-level design, how to configure and customize it, and its API. The overview describes how the catalog works, how to add components to it, how to find software in it, and more. -## Software Templates (aka Scaffolder) +## Software Templates (aka Scaffolder) {#software-templates} 1. A "skeleton" software project created and managed in the Backstage Software Templates tool. diff --git a/docs/releases/v1.3.0.md b/docs/releases/v1.3.0.md index 57004158cf..b87851ca83 100644 --- a/docs/releases/v1.3.0.md +++ b/docs/releases/v1.3.0.md @@ -28,7 +28,7 @@ Several new [entity providers](https://backstage.io/docs/features/software-catal - `AzureDevOpsEntityProvider` as replacement for `AzureDevOpsDiscoveryProcessor`. PR [#11604](https://github.com/backstage/backstage/pull/11604) contributed by [@goenning](https://github.com/goenning) - `GitlabDiscoveryEntityProvider` as replacement for `GitLabDiscoveryProcessor`. PR [#11886](https://github.com/backstage/backstage/pull/11886) contributed by [@ivangonzalezacuna](https://github.com/ivangonzalezacuna) -- `BitbucketCloudEntityProvider` as a replacement for `BitbucketDiscoveryProcessor` (for Bitbucket Cloud only). PR [#11345](https://github.com/backstage/backstage/pull/11345) contributed by [@pjungermann](​​https://github.com/pjungermann) +- `BitbucketCloudEntityProvider` as a replacement for `BitbucketDiscoveryProcessor` (for Bitbucket Cloud only). PR [#11345](https://github.com/backstage/backstage/pull/11345) contributed by [@pjungermann](https://github.com/pjungermann) ### New plugin: Vault diff --git a/docs/tutorials/using-backstage-proxy-within-plugin.md b/docs/tutorials/using-backstage-proxy-within-plugin.md index 01c7e566b6..fef4b5b8e0 100644 --- a/docs/tutorials/using-backstage-proxy-within-plugin.md +++ b/docs/tutorials/using-backstage-proxy-within-plugin.md @@ -25,7 +25,7 @@ If your plugin requires access to an API, backstage offers - [Option 2: Defining the API client interface](#defining-the-api-client-interface) - [Creating the API client](#creating-the-api-client) - [Bundling your ApiRef with your plugin](#bundling-your-apiref-with-your-plugin) - - [Using the API in your components](#using-your-plugin-in-your-components) + - [Using the API in your components](#using-the-api-in-your-components) ## Setting up the backstage proxy @@ -46,7 +46,7 @@ proxy: You can find more details about the proxy config options in the [proxying section](../plugins/proxying.md). -# Calling an API using the backstage proxy +## Calling an API using the backstage proxy If you followed the previous steps, you should now be able to access your API by calling `${backend-url}/api/proxy/`. The reason why diff --git a/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.mdx b/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.mdx index d01bd496d1..735191a3e2 100644 --- a/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.mdx +++ b/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.mdx @@ -50,7 +50,7 @@ yarn start And you are good to go! 👍 -Read the full documentation on how to [create an app](/docs/getting-started/create-an-app) on GitHub. +Read the full documentation on how to [create an app](/docs/getting-started/) on GitHub. ## What do I get? (Let's get technical...) From 2072182904e7465e5a8f799422cf1dbb165961d0 Mon Sep 17 00:00:00 2001 From: Paul Schultz Date: Tue, 31 Mar 2026 15:32:54 -0500 Subject: [PATCH 080/191] docs: replace JSX codemod tutorial with link to codemod.com Signed-off-by: Paul Schultz --- .../docs/tutorials/jsx-migration-codemod.md | 468 ------------------ docs/tutorials/jsx-transform-migration.md | 2 +- 2 files changed, 1 insertion(+), 469 deletions(-) delete mode 100644 contrib/docs/tutorials/jsx-migration-codemod.md diff --git a/contrib/docs/tutorials/jsx-migration-codemod.md b/contrib/docs/tutorials/jsx-migration-codemod.md deleted file mode 100644 index 14ccd44080..0000000000 --- a/contrib/docs/tutorials/jsx-migration-codemod.md +++ /dev/null @@ -1,468 +0,0 @@ -# Migrating to the New JSX Transform using a Codemod - -## Using the Codemod - -While a codemod for the New JSX Transform was originally introduced in the [Introducing the New JSX Transform](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) article, it is no longer functional. A working solution, inspired by the original, is detailed below: - -1. **Create the transform file** - - Create a file named `transform.js` in the root directory of your Backstage project. - - ```js - /** - * (c) Facebook, Inc. and its affiliates. Confidential and proprietary. - * - * @format - */ - - module.exports = function (file, api, options) { - const j = api.jscodeshift; - const printOptions = options.printOptions || {}; - const root = j(file.source); - const destructureNamespaceImports = options.destructureNamespaceImports; - - // - function getFirstNode() { - return root.find(j.Program).get('body', 0).node; - } - - // Save the comments attached to the first node - const firstNode = getFirstNode(); - const { comments } = firstNode; - - function isVariableDeclared(variable) { - return ( - root - .find(j.Identifier, { - name: variable, - }) - .filter( - path => - path.parent.value.type !== 'MemberExpression' && - path.parent.value.type !== 'QualifiedTypeIdentifier' && - // Added this - path.parent.value.type !== 'TSQualifiedName' && - path.parent.value.type !== 'JSXMemberExpression', - ) - .size() > 0 - ); - } - - // Get all paths that import from React - const reactImportPaths = root - .find(j.ImportDeclaration, { - type: 'ImportDeclaration', - }) - .filter(path => { - return ( - (path.value.source.type === 'Literal' || - path.value.source.type === 'StringLiteral') && - (path.value.source.value === 'React' || - path.value.source.value === 'react') - ); - }); - - // get all namespace/default React imports - const reactPaths = reactImportPaths.filter(path => { - return ( - path.value.specifiers.length > 0 && - path.value.importKind === 'value' && - path.value.specifiers.some( - specifier => specifier.local.name === 'React', - ) - ); - }); - - if (reactPaths.size() > 1) { - throw Error( - 'There should only be one React import. Please remove the duplicate import and try again.', - ); - } - - if (reactPaths.size() === 0) { - return null; - } - - const reactPath = reactPaths.paths()[0]; - // Reuse the node so that we can preserve quoting style. - const reactLiteral = reactPath.value.source; - - const isDefaultImport = reactPath.value.specifiers.some( - specifier => - specifier.type === 'ImportDefaultSpecifier' && - specifier.local.name === 'React', - ); - - // Check to see if we should keep the React import - const isReactImportUsed = - root - .find(j.Identifier, { - name: 'React', - }) - .filter(path => { - return path.parent.parent.value.type !== 'ImportDeclaration'; - }) - .size() > 0; - - // local: imported - const reactIdentifiers = {}; - const reactTypeIdentifiers = {}; - let canDestructureReactVariable = false; - if ( - isReactImportUsed && - (isDefaultImport || destructureNamespaceImports) - ) { - // Checks to see if the react variable is used itself (rather than used to access its properties) - canDestructureReactVariable = - root - .find(j.Identifier, { - name: 'React', - }) - .filter(path => { - return path.parent.parent.value.type !== 'ImportDeclaration'; - }) - .filter( - path => - !( - path.parent.value.type === 'MemberExpression' && - path.parent.value.object.name === 'React' - ) && - !( - path.parent.value.type === 'QualifiedTypeIdentifier' && - path.parent.value.qualification.name === 'React' - ) && - !( - // Added this - ( - path.parent.value.type === 'TSQualifiedName' && - path.parent.value.left.name === 'React' - ) - ) && - !( - path.parent.value.type === 'JSXMemberExpression' && - path.parent.value.object.name === 'React' - ), - ) - .size() === 0; - - if (canDestructureReactVariable) { - // Add React identifiers to separate object so we can destructure the imports - // later if we can. If a type variable that we are trying to import has already - // been declared, do not try to destructure imports - // (ex. Element is declared and we are using React.Element) - root - .find(j.QualifiedTypeIdentifier, { - qualification: { - type: 'Identifier', - name: 'React', - }, - }) - .forEach(path => { - const id = path.value.id.name; - if (path.parent.parent.value.type === 'TypeofTypeAnnotation') { - // This is a typeof import so it isn't actually a type - reactIdentifiers[id] = id; - - if (reactTypeIdentifiers[id]) { - canDestructureReactVariable = false; - } - } else { - reactTypeIdentifiers[id] = id; - - if (reactIdentifiers[id]) { - canDestructureReactVariable = false; - } - } - - if (isVariableDeclared(id)) { - canDestructureReactVariable = false; - } - }); - - // Added this - root - .find(j.TSQualifiedName, { - left: { - type: 'Identifier', - name: 'React', - }, - }) - .forEach(path => { - const id = path.value.right.name; - reactIdentifiers[id] = id; - // We don't tend to use type imports - // Comment line above out and uncomment this to use type imports - // Also ignoring typeof imports? - // reactTypeIdentifiers[id] = id - - // if (reactIdentifiers[id]) { - // canDestructureReactVariable = false - // } - - if (isVariableDeclared(id)) { - canDestructureReactVariable = false; - } - }); - - // Add React identifiers to separate object so we can destructure the imports - // later if we can. If a variable that we are trying to import has already - // been declared, do not try to destructure imports - // (ex. createElement is declared and we are using React.createElement) - root - .find(j.MemberExpression, { - object: { - type: 'Identifier', - name: 'React', - }, - }) - .forEach(path => { - const property = path.value.property.name; - reactIdentifiers[property] = property; - - if ( - isVariableDeclared(property) || - reactTypeIdentifiers[property] - ) { - canDestructureReactVariable = false; - } - }); - - // Add React identifiers to separate object so we can destructure the imports - // later if we can. If a JSX variable that we are trying to import has already - // been declared, do not try to destructure imports - // (ex. Fragment is declared and we are using React.Fragment) - root - .find(j.JSXMemberExpression, { - object: { - type: 'JSXIdentifier', - name: 'React', - }, - }) - .forEach(path => { - const property = path.value.property.name; - reactIdentifiers[property] = property; - - if ( - isVariableDeclared(property) || - reactTypeIdentifiers[property] - ) { - canDestructureReactVariable = false; - } - }); - } - } - - if (canDestructureReactVariable) { - // replace react identifiers - root - .find(j.QualifiedTypeIdentifier, { - qualification: { - type: 'Identifier', - name: 'React', - }, - }) - .forEach(path => { - const id = path.value.id.name; - - j(path).replaceWith(j.identifier(id)); - }); - - // Added this - root - .find(j.TSQualifiedName, { - left: { - type: 'Identifier', - name: 'React', - }, - }) - .forEach(path => { - const id = path.value.right.name; - - j(path).replaceWith(j.identifier(id)); - }); - - root - .find(j.MemberExpression, { - object: { - type: 'Identifier', - name: 'React', - }, - }) - .forEach(path => { - const property = path.value.property.name; - - j(path).replaceWith(j.identifier(property)); - }); - - root - .find(j.JSXMemberExpression, { - object: { - type: 'JSXIdentifier', - name: 'React', - }, - }) - .forEach(path => { - const property = path.value.property.name; - - j(path).replaceWith(j.jsxIdentifier(property)); - }); - - // Add existing React imports to map - reactImportPaths.forEach(path => { - const specifiers = path.value.specifiers; - for (let i = 0; i < specifiers.length; i++) { - const specifier = specifiers[i]; - // get all type and regular imports that are imported - // from React - if (specifier.type === 'ImportSpecifier') { - if ( - path.value.importKind === 'type' || - specifier.importKind === 'type' - ) { - reactTypeIdentifiers[specifier.local.name] = - specifier.imported.name; - } else { - reactIdentifiers[specifier.local.name] = specifier.imported.name; - } - } - } - }); - - const regularImports = []; - Object.keys(reactIdentifiers).forEach(local => { - const imported = reactIdentifiers[local]; - regularImports.push( - j.importSpecifier(j.identifier(imported), j.identifier(local)), - ); - }); - - const typeImports = []; - Object.keys(reactTypeIdentifiers).forEach(local => { - const imported = reactTypeIdentifiers[local]; - typeImports.push( - j.importSpecifier(j.identifier(imported), j.identifier(local)), - ); - }); - - if (regularImports.length > 0) { - j(reactPath).insertAfter( - j.importDeclaration(regularImports, reactLiteral), - ); - } - if (typeImports.length > 0) { - j(reactPath).insertAfter( - j.importDeclaration(typeImports, reactLiteral, 'type'), - ); - } - - // remove all old react imports - reactImportPaths.forEach(path => { - // This is for import type React from 'react' which shouldn't - // be removed - if ( - path.value.specifiers.some( - specifier => - specifier.type === 'ImportDefaultSpecifier' && - specifier.local.name === 'React' && - (specifier.importKind === 'type' || - path.value.importKind === 'type'), - ) - ) { - j(path).insertAfter( - j.importDeclaration( - [j.importDefaultSpecifier(j.identifier('React'))], - reactLiteral, - 'type', - ), - ); - } - j(path).remove(); - }); - } else { - // Remove the import because it's not being used - // If we should keep the React import, just convert - // default imports to named imports - let isImportRemoved = false; - const specifiers = reactPath.value.specifiers; - for (let i = 0; i < specifiers.length; i++) { - const specifier = specifiers[i]; - if (specifier.type === 'ImportNamespaceSpecifier') { - if (!isReactImportUsed) { - isImportRemoved = true; - j(reactPath).remove(); - } - } else if (specifier.type === 'ImportDefaultSpecifier') { - if (isReactImportUsed) { - j(reactPath).insertAfter( - j.importDeclaration( - [j.importNamespaceSpecifier(j.identifier('React'))], - reactLiteral, - ), - ); - } - - if (specifiers.length > 1) { - const typeImports = []; - const regularImports = []; - for (let x = 0; x < specifiers.length; x++) { - if (specifiers[x].type !== 'ImportDefaultSpecifier') { - if (specifiers[x].importKind === 'type') { - typeImports.push(specifiers[x]); - } else { - regularImports.push(specifiers[x]); - } - } - } - if (regularImports.length > 0) { - j(reactPath).insertAfter( - j.importDeclaration(regularImports, reactLiteral), - ); - } - if (typeImports.length > 0) { - j(reactPath).insertAfter( - j.importDeclaration(typeImports, reactLiteral, 'type'), - ); - } - } - - isImportRemoved = true; - j(reactPath).remove(); - } - } - - if (!isImportRemoved) { - return null; - } - } - - // If the first node has been modified or deleted, reattach the comments - const firstNode2 = getFirstNode(); - if (firstNode2 !== firstNode) { - firstNode2.comments = comments; - } - - return root.toSource(printOptions); - }; - ``` - -2. **Execute the transformation** - - To apply the necessary changes, execute the following command twice from your root Backstage directory. First, run it for your packages, and then again for your plugins and any additional directories. Remember to adjust the paths to the transform script and parser source accordingly. - - ```console - npx jscodeshift --verbose=2 --ignore-pattern="**/node_modules/**" --parser ts --extensions=tsx,ts,jsx,js --transform ./path/to/the/transform.js --destructureNamespaceImports=true --parser=tsx ./path/to/src/ - ``` - -3. **Verify and clean up imports** - - Review the codebase for any remaining instances of `import * as React from 'react'` or `import React from 'react'`. Replace these with named imports where possible, such as: - - ```tsx - import { useState, useEffect } from 'react'; - ``` - - If retaining the default React import is absolutely necessary, use the following syntax instead: - - ```tsx - import { default as React } from 'react'; - ``` diff --git a/docs/tutorials/jsx-transform-migration.md b/docs/tutorials/jsx-transform-migration.md index d82ba5e8aa..5c818dd565 100644 --- a/docs/tutorials/jsx-transform-migration.md +++ b/docs/tutorials/jsx-transform-migration.md @@ -35,7 +35,7 @@ If you must preserve the default React import for compatibility reasons, you can import { default as React } from 'react'; ``` -To streamline this process, consider using an automated codemod. Instructions are available in this [migration guide](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/jsx-migration-codemod.md). +To streamline this process, consider using an [automated codemod](https://app.codemod.com/registry/jsx-new-transform). ### Updating Configuration Files From a405d69bdde8ebfb670ceda5505ceada2164a9ad Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 00:07:43 +0000 Subject: [PATCH 081/191] chore(deps): update dependency @modelcontextprotocol/sdk to v1.28.0 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 267a5bdf1b..ac0b821af8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11439,8 +11439,8 @@ __metadata: linkType: hard "@modelcontextprotocol/sdk@npm:^1.25.2": - version: 1.27.1 - resolution: "@modelcontextprotocol/sdk@npm:1.27.1" + version: 1.29.0 + resolution: "@modelcontextprotocol/sdk@npm:1.29.0" dependencies: "@hono/node-server": "npm:^1.19.9" ajv: "npm:^8.17.1" @@ -11467,7 +11467,7 @@ __metadata: optional: true zod: optional: false - checksum: 10/3cb0d61cfb916e555c85b4a527e772f88fcf9c6abacbe5eb5e965aac7c898190c416341ab3b3cba8c2d5f5ce4d513279fba3ad7784a0903d7ccd335decc55395 + checksum: 10/ff551b97e06b661f95fec8fd34e112c446e69894a84a9979cdac369fb5de27f0a1a5c1f4e2a1f270cc60f93e54c28a8059a94ca51c3d528d2670ade874b244f9 languageName: node linkType: hard From 93466ca419efebe7d3f6bd1c30029bc8577f040e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 01:10:56 +0000 Subject: [PATCH 082/191] chore(deps): update dependency axios to v1.14.0 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index ac0b821af8..13b51ed309 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25199,13 +25199,13 @@ __metadata: linkType: hard "axios@npm:^1.0.0, axios@npm:^1.12.0, axios@npm:^1.12.2, axios@npm:^1.13.0, axios@npm:^1.13.5, axios@npm:^1.13.6, axios@npm:^1.7.4": - version: 1.13.6 - resolution: "axios@npm:1.13.6" + version: 1.14.0 + resolution: "axios@npm:1.14.0" dependencies: follow-redirects: "npm:^1.15.11" form-data: "npm:^4.0.5" - proxy-from-env: "npm:^1.1.0" - checksum: 10/a7ed83c2af3ef21d64609df0f85e76893a915a864c5934df69241001d0578082d6521a0c730bf37518ee458821b5695957cb10db9fc705f2a8996c8686ea7a89 + proxy-from-env: "npm:^2.1.0" + checksum: 10/c3444e9e3da1714916e4ddd7cda05bb41a5d5d80e3e27b099a116439684c63f2280c88503d1acd65841698b63af0b542b4d5780454e28fd0aed2d783ef90943e languageName: node linkType: hard @@ -42778,6 +42778,13 @@ __metadata: languageName: node linkType: hard +"proxy-from-env@npm:^2.1.0": + version: 2.1.0 + resolution: "proxy-from-env@npm:2.1.0" + checksum: 10/fbbaf4dab2a6231dc9e394903a5f66f20475e36b734335790b46feb9da07c37d6b32e2c02e3e2ea4d4b23774c53d8562e5b7cc73282cb43f4a597b7eacaee2ee + languageName: node + linkType: hard + "pstree.remy@npm:^1.1.8": version: 1.1.8 resolution: "pstree.remy@npm:1.1.8" From 19a2a038aae72c5ba615f8f72a2a7883f6c81d7d Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Tue, 31 Mar 2026 14:16:10 +0300 Subject: [PATCH 083/191] feat(notifications): migrate to backstage ui migrated notifications plugin to use backstage ui instead material ui. Signed-off-by: Hellgren Heikki --- .changeset/major-banks-prove.md | 5 + plugins/notifications/dev/index.tsx | 15 +- plugins/notifications/package.json | 3 - plugins/notifications/report-alpha.api.md | 3 +- plugins/notifications/report.api.md | 3 +- .../NotificationsFilters.tsx | 227 +++++-------- .../NotificationsPage/NotificationsPage.tsx | 75 ++--- .../NotificationsSideBarItem.module.css | 26 ++ .../NotificationsSideBarItem.tsx | 139 ++++---- .../NotificationsTable/BulkActions.tsx | 105 +++--- .../NotificationDescription.tsx | 23 +- .../NotificationsTable/NotificationIcon.tsx | 7 +- .../NotificationsTable.module.css | 36 ++ .../NotificationsTable/NotificationsTable.tsx | 311 +++++++++--------- .../NotificationsTable/SelectAll.module.css | 28 ++ .../NotificationsTable/SelectAll.tsx | 47 +-- .../SeverityIcon.module.css | 33 ++ .../NotificationsTable/SeverityIcon.tsx | 65 ++-- .../NoBorderTableCell.module.css | 23 ++ .../NoBorderTableCell.tsx | 20 +- .../OriginRow.tsx | 55 ++-- .../UserNotificationSettingsCard/TopicRow.tsx | 38 +-- .../UserNotificationSettingsPanel.module.css | 29 ++ .../UserNotificationSettingsPanel.tsx | 63 ++-- plugins/notifications/src/translation.ts | 3 +- yarn.lock | 14 - 26 files changed, 738 insertions(+), 658 deletions(-) create mode 100644 .changeset/major-banks-prove.md create mode 100644 plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.module.css create mode 100644 plugins/notifications/src/components/NotificationsTable/NotificationsTable.module.css create mode 100644 plugins/notifications/src/components/NotificationsTable/SelectAll.module.css create mode 100644 plugins/notifications/src/components/NotificationsTable/SeverityIcon.module.css create mode 100644 plugins/notifications/src/components/UserNotificationSettingsCard/NoBorderTableCell.module.css create mode 100644 plugins/notifications/src/components/UserNotificationSettingsCard/UserNotificationSettingsPanel.module.css diff --git a/.changeset/major-banks-prove.md b/.changeset/major-banks-prove.md new file mode 100644 index 0000000000..51de7df727 --- /dev/null +++ b/.changeset/major-banks-prove.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Migrated notifications plugin to use backstage UI diff --git a/plugins/notifications/dev/index.tsx b/plugins/notifications/dev/index.tsx index 81354e2a73..df2258372d 100644 --- a/plugins/notifications/dev/index.tsx +++ b/plugins/notifications/dev/index.tsx @@ -21,7 +21,18 @@ import { } from '../src'; import { signalsPlugin } from '@backstage/plugin-signals'; import { SidebarItem } from '@backstage/core-components'; -import AddAlert from '@material-ui/icons/AddAlert'; +import { IconComponent } from '@backstage/core-plugin-api'; +import { RiBellLine } from '@remixicon/react'; + +const AddAlertIcon: IconComponent = props => { + let size = 24; + if (props.fontSize === 'large') { + size = 32; + } else if (props.fontSize === 'small') { + size = 16; + } + return ; +}; createDevApp() .registerPlugin(notificationsPlugin) @@ -38,7 +49,7 @@ createDevApp() .addSidebarItem() .addSidebarItem( { fetch('http://localhost:7007/api/notifications-debug/', { diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index 58ac16a7bf..6fb8931422 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -59,11 +59,8 @@ "@backstage/plugin-signals-react": "workspace:^", "@backstage/theme": "workspace:^", "@backstage/ui": "workspace:^", - "@material-ui/core": "^4.9.13", - "@material-ui/icons": "^4.9.1", "@remixicon/react": "^4.6.0", "lodash": "^4.17.21", - "material-ui-confirm": "^3.0.12", "notistack": "^3.0.1", "react-relative-time": "^0.0.9", "react-use": "^17.2.4" diff --git a/plugins/notifications/report-alpha.api.md b/plugins/notifications/report-alpha.api.md index cfc88c3d7a..73a210ac22 100644 --- a/plugins/notifications/report-alpha.api.md +++ b/plugins/notifications/report-alpha.api.md @@ -137,8 +137,9 @@ export const notificationsTranslationRef: TranslationRef< readonly 'table.bulkActions.returnSelectedAmongUnread': 'Return selected among unread'; readonly 'table.bulkActions.saveSelectedForLater': 'Save selected for later'; readonly 'table.bulkActions.undoSaveForSelected': 'Undo save for selected'; + readonly 'table.confirmDialog.cancel': 'Cancel'; readonly 'table.confirmDialog.title': 'Are you sure?'; - readonly 'table.confirmDialog.markAllReadDescription': 'Mark all notifications as read.'; + readonly 'table.confirmDialog.markAllReadDescription': 'Mark all notifications as read.'; readonly 'table.confirmDialog.markAllReadConfirmation': 'Mark All'; readonly 'filters.view.all': 'All'; readonly 'filters.view.label': 'View'; diff --git a/plugins/notifications/report.api.md b/plugins/notifications/report.api.md index 21dc817d91..ff39a03a8a 100644 --- a/plugins/notifications/report.api.md +++ b/plugins/notifications/report.api.md @@ -205,8 +205,9 @@ export const notificationsTranslationRef: TranslationRef< readonly 'table.bulkActions.returnSelectedAmongUnread': 'Return selected among unread'; readonly 'table.bulkActions.saveSelectedForLater': 'Save selected for later'; readonly 'table.bulkActions.undoSaveForSelected': 'Undo save for selected'; + readonly 'table.confirmDialog.cancel': 'Cancel'; readonly 'table.confirmDialog.title': 'Are you sure?'; - readonly 'table.confirmDialog.markAllReadDescription': 'Mark all notifications as read.'; + readonly 'table.confirmDialog.markAllReadDescription': 'Mark all notifications as read.'; readonly 'table.confirmDialog.markAllReadConfirmation': 'Mark All'; readonly 'filters.view.all': 'All'; readonly 'filters.view.label': 'View'; diff --git a/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx b/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx index 66aacdb72e..6f68763c1b 100644 --- a/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx +++ b/plugins/notifications/src/components/NotificationsFilters/NotificationsFilters.tsx @@ -13,14 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ChangeEvent } from 'react'; -import FormControl from '@material-ui/core/FormControl'; -import Divider from '@material-ui/core/Divider'; -import Grid from '@material-ui/core/Grid'; -import InputLabel from '@material-ui/core/InputLabel'; -import MenuItem from '@material-ui/core/MenuItem'; -import Select from '@material-ui/core/Select'; -import Typography from '@material-ui/core/Typography'; +import { Select, Text, Flex } from '@backstage/ui'; +import type { Key } from 'react-aria-components'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { notificationsTranslationRef } from '../../translation'; import { GetNotificationsOptions } from '../../api'; @@ -128,22 +122,20 @@ export const NotificationsFilters = ({ const { t } = useTranslationRef(notificationsTranslationRef); const sortByText = getSortByText(sorting); - const handleOnCreatedAfterChanged = ( - event: ChangeEvent<{ name?: string; value: unknown }>, - ) => { - onCreatedAfterChanged(event.target.value as string); + const handleOnCreatedAfterChanged = (key: Key | Key[] | null) => { + if (key !== null && !Array.isArray(key)) + onCreatedAfterChanged(key as string); }; - const handleOnViewChanged = ( - event: ChangeEvent<{ name?: string; value: unknown }>, - ) => { - if (event.target.value === 'unread') { + const handleOnViewChanged = (key: Key | Key[] | null) => { + const value = Array.isArray(key) ? key[0] : key; + if (value === 'unread') { onUnreadOnlyChanged(true); onSavedChanged(undefined); - } else if (event.target.value === 'read') { + } else if (value === 'read') { onUnreadOnlyChanged(false); onSavedChanged(undefined); - } else if (event.target.value === 'saved') { + } else if (value === 'saved') { onUnreadOnlyChanged(undefined); onSavedChanged(true); } else { @@ -153,10 +145,8 @@ export const NotificationsFilters = ({ } }; - const handleOnSortByChanged = ( - event: ChangeEvent<{ name?: string; value: unknown }>, - ) => { - const idx = ((event.target.value as string) || + const handleOnSortByChanged = (key: Key | Key[] | null) => { + const idx = (((Array.isArray(key) ? key[0] : key) as string) || 'newest') as keyof typeof SortByOptions; const option = SortByOptions[idx]; onSortingChanged({ ...option.sortBy }); @@ -171,145 +161,86 @@ export const NotificationsFilters = ({ viewValue = 'read'; } - const handleOnSeverityChanged = ( - event: ChangeEvent<{ name?: string; value: unknown }>, - ) => { + const handleOnSeverityChanged = (key: Key | Key[] | null) => { const value: NotificationSeverity = - (event.target.value as NotificationSeverity) || 'normal'; + ((Array.isArray(key) ? key[0] : key) as NotificationSeverity) || 'normal'; onSeverityChanged(value); }; - const handleOnTopicChanged = ( - event: ChangeEvent<{ name?: string; value: unknown }>, - ) => { - const value = event.target.value as string; + const handleOnTopicChanged = (key: Key | Key[] | null) => { + const value = (Array.isArray(key) ? key[0] : key) as string; onTopicChanged(value === ALL ? undefined : value); }; - const sortedAllTopics = (allTopics || []).sort((a, b) => a.localeCompare(b)); + const sortedAllTopics = [...(allTopics ?? [])].sort((a, b) => + a.localeCompare(b), + ); return ( - <> - - - {t('filters.title')} - - + +
+ {t('filters.title')} +
- - - - {t('filters.view.label')} - - - - + ({ + value: key, + label: t( + CreatedAfterOptions[key as keyof typeof CreatedAfterOptions] + .labelKey, + ), + }))} + /> - - -
+ ({ + value: key, + label: t(`filters.severity.${key}`), + }))} + /> - - - - - - - - {t('filters.severity.label')} - - - - - - - - - - {t('filters.topic.label')} - - - - - - - +