This commit is contained in:
Bruno Martins 2025-04-15 15:50:59 +08:00 committed by GitHub
commit 1f5e42c89b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 97 additions and 12 deletions

View file

@ -0,0 +1,9 @@
export type BrokenLink = {
link: string;
resolvedLink: string;
anchor: boolean;
};
export type BrokenLinksMap = {
[pathname: string]: BrokenLink[]
};

View file

@ -13,6 +13,8 @@ import type {PluginConfig, PresetConfig, HtmlTagObject} from './plugin';
import type {ProcessorOptions} from '@mdx-js/mdx';
import type {BrokenLinksMap} from './bronkenLinks';
export type RemarkRehypeOptions = ProcessorOptions['remarkRehypeOptions'];
export type ReportingSeverity = 'ignore' | 'log' | 'warn' | 'throw';
@ -251,6 +253,14 @@ export type DocusaurusConfig = {
* @default "warn"
*/
onBrokenAnchors: ReportingSeverity;
/**
* The behavior of Docusaurus when it detects any broken link or anchor and generates a report.
* This functions runs before generating a report.
*
* @see https://docusaurus.io/docs/api/docusaurus-config#onReportBrokenLinks
* @default "warn"
*/
onReportBrokenLinks: (brokenLinksMap: BrokenLinksMap) => void;
/**
* The behavior of Docusaurus when it detects any broken markdown link.
*

View file

@ -90,3 +90,9 @@ export {
} from './routing';
export {UseDataOptions} from './utils';
export {
BrokenLinksMap,
BrokenLink
} from './bronkenLinks';

View file

@ -159,7 +159,7 @@ async function executePluginsPostBuild({
async function executeBrokenLinksCheck({
props: {
routes,
siteConfig: {onBrokenLinks, onBrokenAnchors},
siteConfig: {onBrokenLinks, onBrokenAnchors, onReportBrokenLinks},
},
collectedData,
}: {
@ -172,9 +172,10 @@ async function executeBrokenLinksCheck({
}));
await handleBrokenLinks({
collectedLinks,
routes,
onBrokenLinks,
onBrokenAnchors,
routes,
onReportBrokenLinks
});
}

View file

@ -8,7 +8,7 @@
import {jest} from '@jest/globals';
import reactRouterConfig from 'react-router-config';
import {handleBrokenLinks} from '../brokenLinks';
import type {RouteConfig} from '@docusaurus/types';
import type {BrokenLinksMap, RouteConfig} from '@docusaurus/types';
type Params = Parameters<typeof handleBrokenLinks>[0];
@ -26,6 +26,7 @@ async function testBrokenLinks(params: {
onBrokenLinks?: Params['onBrokenLinks'];
onBrokenAnchors?: Params['onBrokenAnchors'];
routes?: SimpleRoute[];
onReportBrokenLinks?: Params['onReportBrokenLinks'];
}) {
await handleBrokenLinks({
collectedLinks: {},
@ -34,6 +35,7 @@ async function testBrokenLinks(params: {
...params,
// Unsafe but convenient for tests
routes: (params.routes ?? []) as RouteConfig[],
onReportBrokenLinks: params.onReportBrokenLinks,
});
}
@ -728,6 +730,31 @@ describe('handleBrokenLinks', () => {
warnMock.mockRestore();
});
it('can warn for broken links and remove them before building the report', async () => {
const warnMock = jest.spyOn(console, 'warn');
await testBrokenLinks({
onBrokenLinks: 'warn',
routes: [{path: '/page1'}],
collectedLinks: {
'/page1': {
links: ['/page2'],
anchors: [],
},
},
onReportBrokenLinks: (brokenLinksMap: BrokenLinksMap) => {
for (const pathname in brokenLinksMap) {
if (pathname.startsWith('/page1')) {
delete brokenLinksMap[pathname];
}
}
},
});
expect(warnMock).toHaveBeenCalledTimes(0);
warnMock.mockRestore();
});
it('can warn for broken anchors', async () => {
const warnMock = jest.spyOn(console, 'warn');

View file

@ -15,7 +15,12 @@ import {
type URLPath,
} from '@docusaurus/utils';
import {addTrailingSlash, removeTrailingSlash} from '@docusaurus/utils-common';
import type {RouteConfig, ReportingSeverity} from '@docusaurus/types';
import type {
RouteConfig,
ReportingSeverity,
BrokenLink,
BrokenLinksMap,
} from '@docusaurus/types';
function matchRoutes(routeConfig: RouteConfig[], pathname: string) {
// @ts-expect-error: React router types RouteConfig with an actual React
@ -24,14 +29,6 @@ function matchRoutes(routeConfig: RouteConfig[], pathname: string) {
return reactRouterMatchRoutes(routeConfig, pathname);
}
type BrokenLink = {
link: string;
resolvedLink: string;
anchor: boolean;
};
type BrokenLinksMap = {[pathname: string]: BrokenLink[]};
// The linking data that has been collected on Docusaurus pages during SSG
// {rendered page pathname => links and anchors collected on that page}
type CollectedLinks = {
@ -404,11 +401,13 @@ export async function handleBrokenLinks({
onBrokenLinks,
onBrokenAnchors,
routes,
onReportBrokenLinks,
}: {
collectedLinks: CollectedLinks;
onBrokenLinks: ReportingSeverity;
onBrokenAnchors: ReportingSeverity;
routes: RouteConfig[];
onReportBrokenLinks?: (brokenLinksMap: BrokenLinksMap) => void;
}): Promise<void> {
if (onBrokenLinks === 'ignore' && onBrokenAnchors === 'ignore') {
return;
@ -417,5 +416,10 @@ export async function handleBrokenLinks({
routes,
collectedLinks: normalizeCollectedLinks(collectedLinks),
});
if (onReportBrokenLinks) {
onReportBrokenLinks(brokenLinks);
}
reportBrokenLinks({brokenLinks, onBrokenLinks, onBrokenAnchors});
}

View file

@ -343,6 +343,7 @@ export const ConfigSchema = Joi.object<DocusaurusConfig>({
onBrokenAnchors: Joi.string()
.equal('ignore', 'log', 'warn', 'throw')
.default(DEFAULT_CONFIG.onBrokenAnchors),
onReportBrokenLinks: Joi.function(),
onBrokenMarkdownLinks: Joi.string()
.equal('ignore', 'log', 'warn', 'throw')
.default(DEFAULT_CONFIG.onBrokenMarkdownLinks),

View file

@ -271,6 +271,33 @@ The behavior of Docusaurus when it detects any broken anchor declared with the `
By default, it prints a warning, to let you know about your broken anchors.
### `onReportBrokenLinks` {#onReportBrokenLinks}
- Type: `function`
When Docusaurus detects a broken link or anchor, it generates a report detailing the issues.
This function runs before the report is generated, allowing you to modify its contents before final output.
#### USAGE
In this example we will delete broken links that start with the path "/api" from the final report.
```javascript
import { Config, BrokenLinksMap } from '@docusaurus/types';
const config: Config = {
onReportBrokenLinks: (brokenLinksMap: BrokenLinksMap) => {
for (const pathname in brokenLinksMap) {
if (pathname.startsWith('/api')) {
delete brokenLinksMap[pathname];
}
}
}
}
```
### `onBrokenMarkdownLinks` {#onBrokenMarkdownLinks}
- Type: `'ignore' | 'log' | 'warn' | 'throw'`