Merge remote-tracking branch 'origin/master' into techdocs-nfs

Signed-off-by: Gabriel Dugny <gabriel.dugny@believe.com>

# Conflicts:
#	packages/techdocs-cli-embedded-app/package.json
#	plugins/techdocs/src/alpha/index.tsx
#	yarn.lock
This commit is contained in:
Gabriel Dugny
2026-02-10 23:47:25 +01:00
2387 changed files with 108769 additions and 40604 deletions
+74
View File
@@ -1,5 +1,79 @@
# @backstage/plugin-techdocs
## 1.16.3-next.2
### Patch Changes
- 7feb83b: Adjusted to use the new `@backstage/filter-predicates` types for predicate expressions.
- 491a06c: Add the ability to show icons for the tabs on the entity page (new frontend)
- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components.
- Updated dependencies
- @backstage/integration@1.20.0-next.2
- @backstage/plugin-catalog-react@2.0.0-next.2
- @backstage/catalog-client@1.12.2-next.0
- @backstage/frontend-plugin-api@0.14.0-next.2
- @backstage/integration-react@1.2.15-next.2
- @backstage/core-components@0.18.7-next.2
- @backstage/core-plugin-api@1.12.3-next.1
- @backstage/plugin-techdocs-react@1.3.8-next.1
- @backstage/plugin-search-react@1.10.3-next.2
- @backstage/plugin-auth-react@0.1.24-next.1
- @backstage/theme@0.7.2-next.1
## 1.16.3-next.1
### Patch Changes
- Updated dependencies
- @backstage/plugin-catalog-react@1.22.0-next.1
- @backstage/theme@0.7.2-next.0
- @backstage/integration@1.20.0-next.1
- @backstage/frontend-plugin-api@0.14.0-next.1
- @backstage/plugin-search-react@1.10.3-next.1
- @backstage/core-components@0.18.7-next.1
- @backstage/plugin-techdocs-react@1.3.8-next.0
- @backstage/integration-react@1.2.15-next.1
## 1.16.3-next.0
### Patch Changes
- 9e29545: Improve sidebars (nav/TOC) layout and scrolling
- 22dce2b: TechDocs addons in the new frontend system now use a Utility API pattern instead of multiple attachment points. The `AddonBlueprint` now uses this new approach, and while addons created with older versions still work, they will produce a deprecation warning and will stop working in a future release.
- Updated dependencies
- @backstage/plugin-catalog-react@1.21.6-next.0
- @backstage/core-components@0.18.6-next.0
- @backstage/plugin-search-common@1.2.22-next.0
- @backstage/integration@1.19.3-next.0
- @backstage/frontend-plugin-api@0.14.0-next.0
- @backstage/core-plugin-api@1.12.2-next.0
- @backstage/plugin-techdocs-react@1.3.8-next.0
- @backstage/plugin-search-react@1.10.3-next.0
- @backstage/catalog-client@1.12.1
- @backstage/catalog-model@1.7.6
- @backstage/config@1.3.6
- @backstage/errors@1.2.7
- @backstage/integration-react@1.2.15-next.0
- @backstage/theme@0.7.1
- @backstage/plugin-auth-react@0.1.24-next.0
- @backstage/plugin-techdocs-common@0.1.1
## 1.16.2
### Patch Changes
- 0afb8a6: Corrected color of some elements such as Grid cards and Tables.
- 94ff7ab: Code block "Copy to clipboard" button was not positioned correctly for docs built with `mkdocs-material>=9.7`
- Updated dependencies
- @backstage/frontend-plugin-api@0.13.3
- @backstage/core-components@0.18.5
- @backstage/integration@1.19.2
- @backstage/plugin-search-react@1.10.2
- @backstage/integration-react@1.2.14
- @backstage/plugin-auth-react@0.1.23
- @backstage/plugin-catalog-react@1.21.5
- @backstage/plugin-techdocs-react@1.3.7
## 1.16.2-next.1
### Patch Changes
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-techdocs",
"version": "1.16.2-next.1",
"version": "1.16.3-next.2",
"description": "The Backstage plugin that renders technical documentation for your components",
"backstage": {
"role": "frontend-plugin",
@@ -101,13 +101,13 @@
"@types/react": "^18.0.0",
"react": "^18.0.2",
"react-dom": "^18.0.2",
"react-router-dom": "^6.3.0"
"react-router-dom": "^6.30.2"
},
"peerDependencies": {
"@types/react": "^17.0.0 || ^18.0.0",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0",
"react-router-dom": "^6.3.0"
"react-router-dom": "^6.30.2"
},
"peerDependenciesMeta": {
"@types/react": {
+51 -7
View File
@@ -9,15 +9,17 @@ import { ApiFactory } from '@backstage/frontend-plugin-api';
import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api';
import { defaultEntityContentGroups } from '@backstage/plugin-catalog-react/alpha';
import { Entity } from '@backstage/catalog-model';
import { EntityPredicate } from '@backstage/plugin-catalog-react/alpha';
import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api';
import { ExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ExtensionInput } from '@backstage/frontend-plugin-api';
import { FilterPredicate } from '@backstage/filter-predicates';
import { IconComponent } from '@backstage/frontend-plugin-api';
import { IconLinkVerticalProps } from '@backstage/core-components';
import { JSX as JSX_2 } from 'react';
import { JSXElementConstructor } from 'react';
import { OverridableExtensionDefinition } from '@backstage/frontend-plugin-api';
import { OverridableFrontendPlugin } from '@backstage/frontend-plugin-api';
import { ReactElement } from 'react';
import { RouteRef } from '@backstage/core-plugin-api';
import { RouteRef as RouteRef_2 } from '@backstage/frontend-plugin-api';
import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha';
@@ -53,6 +55,34 @@ const _default: OverridableFrontendPlugin<
params: ApiFactory<TApi, TImpl, TDeps>,
) => ExtensionBlueprintParams<AnyApiFactory>;
}>;
'api:techdocs/addons': OverridableExtensionDefinition<{
config: {};
configInput: {};
output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>;
inputs: {
addons: ExtensionInput<
ConfigurableExtensionDataRef<
TechDocsAddonOptions,
'techdocs.addon',
{}
>,
{
singleton: false;
optional: false;
internal: false;
}
>;
};
kind: 'api';
name: 'addons';
params: <
TApi,
TImpl extends TApi,
TDeps extends { [name in string]: unknown },
>(
params: ApiFactory<TApi, TImpl, TDeps>,
) => ExtensionBlueprintParams<AnyApiFactory>;
}>;
'api:techdocs/storage': OverridableExtensionDefinition<{
kind: 'api';
name: 'storage';
@@ -84,6 +114,7 @@ const _default: OverridableFrontendPlugin<
{
singleton: boolean;
optional: boolean;
internal?: boolean;
}
>;
};
@@ -95,14 +126,16 @@ const _default: OverridableFrontendPlugin<
config: {
path: string | undefined;
title: string | undefined;
filter: EntityPredicate | undefined;
filter: FilterPredicate | undefined;
group: string | false | undefined;
icon: string | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
filter?: FilterPredicate | undefined;
title?: string | undefined;
path?: string | undefined;
group?: string | false | undefined;
icon?: string | undefined;
};
output:
| ExtensionDataRef<string, 'core.routing.path', {}>
@@ -135,6 +168,13 @@ const _default: OverridableFrontendPlugin<
{
optional: true;
}
>
| ExtensionDataRef<
string | ReactElement<any, string | JSXElementConstructor<any>>,
'catalog.entity-content-icon',
{
optional: true;
}
>;
inputs: {
addons: ExtensionInput<
@@ -146,6 +186,7 @@ const _default: OverridableFrontendPlugin<
{
singleton: false;
optional: false;
internal: false;
}
>;
emptyState: ExtensionInput<
@@ -159,6 +200,7 @@ const _default: OverridableFrontendPlugin<
{
singleton: true;
optional: true;
internal: false;
}
>;
};
@@ -171,9 +213,10 @@ const _default: OverridableFrontendPlugin<
title: string;
defaultGroup?: [Error: `Use the 'group' param instead`];
group?: keyof defaultEntityContentGroups | (string & {});
icon?: string | ReactElement;
loader: () => Promise<JSX.Element>;
routeRef?: RouteRef_2;
filter?: string | EntityPredicate | ((entity: Entity) => boolean);
filter?: string | FilterPredicate | ((entity: Entity) => boolean);
};
}>;
'entity-icon-link:techdocs/read-docs': OverridableExtensionDefinition<{
@@ -182,10 +225,10 @@ const _default: OverridableFrontendPlugin<
config: {
label: string | undefined;
title: string | undefined;
filter: EntityPredicate | undefined;
filter: FilterPredicate | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
filter?: FilterPredicate | undefined;
label?: string | undefined;
title?: string | undefined;
};
@@ -212,7 +255,7 @@ const _default: OverridableFrontendPlugin<
inputs: {};
params: {
useProps: () => Omit<IconLinkVerticalProps, 'color'>;
filter?: EntityPredicate | ((entity: Entity) => boolean);
filter?: FilterPredicate | ((entity: Entity) => boolean);
};
}>;
'nav-item:techdocs': OverridableExtensionDefinition<{
@@ -294,6 +337,7 @@ const _default: OverridableFrontendPlugin<
{
singleton: false;
optional: false;
internal: false;
}
>;
};
+52
View File
@@ -0,0 +1,52 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
ApiBlueprint,
createApiRef,
createExtensionInput,
} from '@backstage/frontend-plugin-api';
import { AddonBlueprint } from '@backstage/plugin-techdocs-react/alpha';
import { TechDocsAddonOptions } from '@backstage/plugin-techdocs-react';
interface TechDocsAddonsApi {
getAddons(): TechDocsAddonOptions[];
}
export const techdocsAddonsApiRef = createApiRef<TechDocsAddonsApi>({
id: 'plugin.techdocs.addons',
});
export const TechDocsAddonsApiExtension = ApiBlueprint.makeWithOverrides({
name: 'addons',
inputs: {
addons: createExtensionInput([AddonBlueprint.dataRefs.addon]),
},
factory(originalFactory, { inputs }) {
const addons = inputs.addons.map(output =>
output.get(AddonBlueprint.dataRefs.addon),
);
return originalFactory(defineParams =>
defineParams({
api: techdocsAddonsApiRef,
deps: {},
factory: () => ({
getAddons: () => addons,
}),
}),
);
},
});
+57 -21
View File
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { Suspense } from 'react';
import LibraryBooks from '@material-ui/icons/LibraryBooks';
import {
createFrontendPlugin,
@@ -34,7 +35,11 @@ import {
EntityIconLinkBlueprint,
} from '@backstage/plugin-catalog-react/alpha';
import { SearchResultListItemBlueprint } from '@backstage/plugin-search-react/alpha';
import { AddonBlueprint } from '@backstage/plugin-techdocs-react/alpha';
import {
AddonBlueprint,
attachTechDocsAddonComponentData,
} from '@backstage/plugin-techdocs-react/alpha';
import { TechDocsAddonsApiExtension, techdocsAddonsApiRef } from './addonsApi';
import { TechDocsClient, TechDocsStorageClient } from '../client';
import {
rootCatalogDocsRouteRef,
@@ -42,7 +47,6 @@ import {
rootRouteRef,
} from '../routes';
import { TechDocsReaderLayout } from '../reader';
import { attachTechDocsAddonComponentData } from '@backstage/plugin-techdocs-react/alpha';
import {
TechDocsAddons,
techdocsApiRef,
@@ -159,6 +163,7 @@ const techDocsReaderPage = PageBlueprint.makeWithOverrides({
},
},
factory(originalFactory, { inputs, config }) {
const addonsApi = apis.get(techdocsAddonsApiRef);
const addons = inputs.addons.map(output => {
const options = output.get(AddonBlueprint.dataRefs.addon);
const Addon = options.component;
@@ -169,8 +174,25 @@ const techDocsReaderPage = PageBlueprint.makeWithOverrides({
return originalFactory({
path: '/docs/:namespace/:kind/:name',
routeRef: rootDocsRouteRef,
loader: async () =>
await import('../Router').then(({ TechDocsReaderRouter }) => (
loader: async () => {
// Merge addons from the API with old-style direct attachments
const apiAddons = addonsApi?.getAddons() ?? [];
const directAddons = inputs.addons.map(output =>
output.get(AddonBlueprint.dataRefs.addon),
);
const addonOptions = [...apiAddons, ...directAddons];
const addons = addonOptions.map(options => {
const Addon = options.component;
attachTechDocsAddonComponentData(Addon, options);
return (
<Suspense key={options.name} fallback={null}>
<Addon />
</Suspense>
);
});
return import('../Router').then(({ TechDocsReaderRouter }) => (
<TechDocsReaderRouter>
<TechDocsReaderLayout
withSearch={config.withSearch}
@@ -178,7 +200,8 @@ const techDocsReaderPage = PageBlueprint.makeWithOverrides({
/>
<TechDocsAddons>{addons}</TechDocsAddons>
</TechDocsReaderRouter>
)),
));
},
});
},
});
@@ -200,29 +223,41 @@ const techDocsEntityContent = EntityContentBlueprint.makeWithOverrides({
),
},
factory(originalFactory, context) {
const addonsApi = context.apis.get(techdocsAddonsApiRef);
return originalFactory(
{
path: 'docs',
title: 'TechDocs',
routeRef: rootCatalogDocsRouteRef,
loader: () =>
import('../Router').then(({ EmbeddedDocsRouter }) => {
const addons = context.inputs.addons.map(output => {
const options = output.get(AddonBlueprint.dataRefs.addon);
const Addon = options.component;
attachTechDocsAddonComponentData(Addon, options);
return <Addon key={options.name} />;
});
loader: () => {
// Merge addons from the API with old-style direct attachments
const apiAddons = addonsApi?.getAddons() ?? [];
const directAddons = context.inputs.addons.map(output =>
output.get(AddonBlueprint.dataRefs.addon),
);
const addonOptions = [...apiAddons, ...directAddons];
const addons = addonOptions.map(options => {
const Addon = options.component;
attachTechDocsAddonComponentData(Addon, options);
return (
<EmbeddedDocsRouter
emptyState={context.inputs.emptyState?.get(
coreExtensionData.reactElement,
)}
>
<TechDocsAddons>{addons}</TechDocsAddons>
</EmbeddedDocsRouter>
<Suspense key={options.name} fallback={null}>
<Addon />
</Suspense>
);
}),
});
return import('../Router').then(({ EmbeddedDocsRouter }) => (
<EmbeddedDocsRouter
emptyState={context.inputs.emptyState?.get(
coreExtensionData.reactElement,
)}
>
<TechDocsAddons>{addons}</TechDocsAddons>
</EmbeddedDocsRouter>
));
},
},
context,
);
@@ -253,6 +288,7 @@ export default createFrontendPlugin({
extensions: [
techDocsClientApi,
techDocsStorageApi,
TechDocsAddonsApiExtension,
techDocsNavItem,
techDocsPage,
techDocsReaderPage,
@@ -67,7 +67,12 @@ const CopyToClipboardButton = ({ text }: CopyToClipboardButtonProps) => {
leaveDelay={1000}
>
<IconButton
style={{ position: 'absolute' }}
style={{
position: 'absolute',
// top & right was removed from upstream .md-clipboard in mkdocs-material 9.7.0
top: '0.5rem',
right: '0.5rem',
}}
className="md-clipboard md-icon"
onClick={handleClick}
aria-label="Copy to clipboard"
@@ -81,6 +86,8 @@ const CopyToClipboardButton = ({ text }: CopyToClipboardButtonProps) => {
/**
* Recreates copy-to-clipboard functionality attached to <code> snippets that
* is native to mkdocs-material theme.
*
* Unlike native mkdocs-material theme, this is always enabled and does not respect the mkdocs's config `theme.features` `content.code.copy` setting.
*/
export const copyToClipboard = (theme: Theme): Transformer => {
return dom => {
@@ -81,15 +81,12 @@ export default ({ theme, sidebar }: RuleOptions) => `
bottom: 75px;
position: fixed;
width: 16rem;
overflow-y: auto;
overflow-x: hidden;
scrollbar-color: rgb(193, 193, 193) #eee;
scrollbar-width: thin;
}
.md-sidebar .md-sidebar__scrollwrap {
width: calc(16rem);
overflow-y: hidden;
height: 100%
}
@supports selector(::-webkit-scrollbar) {
[dir=ltr] .md-sidebar__inner {
padding-right: calc(100% - 15.1rem);
@@ -98,28 +95,6 @@ export default ({ theme, sidebar }: RuleOptions) => `
.md-sidebar--secondary {
right: ${theme.spacing(3)}px;
}
.md-sidebar::-webkit-scrollbar {
width: 5px;
}
.md-sidebar::-webkit-scrollbar-button {
width: 5px;
height: 5px;
}
.md-sidebar::-webkit-scrollbar-track {
background: #eee;
border: 1 px solid rgb(250, 250, 250);
box-shadow: 0px 0px 3px #dfdfdf inset;
border-radius: 3px;
}
.md-sidebar::-webkit-scrollbar-thumb {
width: 5px;
background: rgb(193, 193, 193);
border: transparent;
border-radius: 3px;
}
.md-sidebar::-webkit-scrollbar-thumb:hover {
background: rgb(125, 125, 125);
}
.md-content {
max-width: calc(100% - 16rem * 2);
@@ -155,6 +130,8 @@ export default ({ theme, sidebar }: RuleOptions) => `
@media screen and (min-width: 76.25em) {
.md-sidebar {
height: auto;
/* Less padding before the Previous / Next buttons */
padding-bottom: 0 !important;
}
}
@@ -110,7 +110,7 @@ ${headings.reduce<string>((style, heading) => {
.md-typeset table:not([class]) {
font-size: var(--md-typeset-font-size);
border: 1px solid var(--md-default-fg-color);
border: 1px solid var(--md-typeset-table-color);
border-bottom: none;
border-collapse: collapse;
border-radius: ${theme.shape.borderRadius}px;
@@ -119,7 +119,7 @@ ${headings.reduce<string>((style, heading) => {
font-weight: bold;
}
.md-typeset table:not([class]) td, .md-typeset table:not([class]) th {
border-bottom: 1px solid var(--md-default-fg-color);
border-bottom: 1px solid var(--md-typeset-table-color);
}
.md-typeset pre > code::-webkit-scrollbar-thumb {
@@ -28,11 +28,8 @@ export default ({ theme }: RuleOptions) => `
/* FONT */
--md-default-fg-color: ${theme.palette.text.primary};
--md-default-fg-color--light: ${theme.palette.text.secondary};
--md-default-fg-color--lighter: ${lighten(theme.palette.text.secondary, 0.7)};
--md-default-fg-color--lightest: ${lighten(
theme.palette.text.secondary,
0.3,
)};
--md-default-fg-color--lighter: ${alpha(theme.palette.text.secondary, 0.3)};
--md-default-fg-color--lightest: ${alpha(theme.palette.text.secondary, 0.15)};
/* BACKGROUND */
--md-default-bg-color:${theme.palette.background.default};
@@ -146,7 +143,7 @@ export default ({ theme }: RuleOptions) => `
--md-typeset-font-size: 1rem;
--md-typeset-color: var(--md-default-fg-color);
--md-typeset-a-color: ${theme.palette.link};
--md-typeset-table-color: ${theme.palette.text.primary};
--md-typeset-table-color: ${alpha(theme.palette.text.primary, 0.15)};
--md-typeset-table-color--light: ${alpha(theme.palette.text.primary, 0.05)};
--md-typeset-del-color: ${
theme.palette.type === 'dark'