@docusaurus/core 0.0.0-5785 → 0.0.0-5792
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client/BrokenLinksContext.d.ts +19 -0
- package/lib/client/{LinksCollector.js → BrokenLinksContext.js} +14 -6
- package/lib/client/exports/Link.js +3 -3
- package/lib/client/exports/useBrokenLinks.d.ts +9 -0
- package/lib/client/exports/useBrokenLinks.js +10 -0
- package/lib/client/serverEntry.js +9 -5
- package/lib/commands/build.js +6 -7
- package/lib/server/brokenLinks.d.ts +9 -5
- package/lib/server/brokenLinks.js +113 -80
- package/lib/server/configValidation.d.ts +1 -1
- package/lib/server/configValidation.js +4 -0
- package/package.json +10 -10
- package/lib/client/LinksCollector.d.ts +0 -20
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
/// <reference types="@docusaurus/module-type-aliases" />
|
|
8
|
+
import { type ReactNode } from 'react';
|
|
9
|
+
import type { BrokenLinks } from '@docusaurus/useBrokenLinks';
|
|
10
|
+
export type StatefulBrokenLinks = BrokenLinks & {
|
|
11
|
+
getCollectedLinks: () => string[];
|
|
12
|
+
getCollectedAnchors: () => string[];
|
|
13
|
+
};
|
|
14
|
+
export declare const createStatefulBrokenLinks: () => StatefulBrokenLinks;
|
|
15
|
+
export declare const useBrokenLinksContext: () => BrokenLinks;
|
|
16
|
+
export declare function BrokenLinksProvider({ children, brokenLinks, }: {
|
|
17
|
+
children: ReactNode;
|
|
18
|
+
brokenLinks: BrokenLinks;
|
|
19
|
+
}): JSX.Element;
|
|
@@ -5,22 +5,30 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
import React, { useContext } from 'react';
|
|
8
|
-
export const
|
|
9
|
-
// Set to dedup, as it's not useful to collect multiple times the same
|
|
8
|
+
export const createStatefulBrokenLinks = () => {
|
|
9
|
+
// Set to dedup, as it's not useful to collect multiple times the same value
|
|
10
|
+
const allAnchors = new Set();
|
|
10
11
|
const allLinks = new Set();
|
|
11
12
|
return {
|
|
13
|
+
collectAnchor: (anchor) => {
|
|
14
|
+
allAnchors.add(anchor);
|
|
15
|
+
},
|
|
12
16
|
collectLink: (link) => {
|
|
13
17
|
allLinks.add(link);
|
|
14
18
|
},
|
|
19
|
+
getCollectedAnchors: () => [...allAnchors],
|
|
15
20
|
getCollectedLinks: () => [...allLinks],
|
|
16
21
|
};
|
|
17
22
|
};
|
|
18
23
|
const Context = React.createContext({
|
|
24
|
+
collectAnchor: () => {
|
|
25
|
+
// No-op for client
|
|
26
|
+
},
|
|
19
27
|
collectLink: () => {
|
|
20
|
-
// No-op for client
|
|
28
|
+
// No-op for client
|
|
21
29
|
},
|
|
22
30
|
});
|
|
23
|
-
export const
|
|
24
|
-
export function
|
|
25
|
-
return <Context.Provider value={
|
|
31
|
+
export const useBrokenLinksContext = () => useContext(Context);
|
|
32
|
+
export function BrokenLinksProvider({ children, brokenLinks, }) {
|
|
33
|
+
return <Context.Provider value={brokenLinks}>{children}</Context.Provider>;
|
|
26
34
|
}
|
|
@@ -10,7 +10,7 @@ import { applyTrailingSlash } from '@docusaurus/utils-common';
|
|
|
10
10
|
import useDocusaurusContext from './useDocusaurusContext';
|
|
11
11
|
import isInternalUrl from './isInternalUrl';
|
|
12
12
|
import ExecutionEnvironment from './ExecutionEnvironment';
|
|
13
|
-
import
|
|
13
|
+
import useBrokenLinks from './useBrokenLinks';
|
|
14
14
|
import { useBaseUrlUtils } from './useBaseUrl';
|
|
15
15
|
// TODO all this wouldn't be necessary if we used ReactRouter basename feature
|
|
16
16
|
// We don't automatically add base urls to all links,
|
|
@@ -21,7 +21,7 @@ const shouldAddBaseUrlAutomatically = (to) => to.startsWith('/');
|
|
|
21
21
|
function Link({ isNavLink, to, href, activeClassName, isActive, 'data-noBrokenLinkCheck': noBrokenLinkCheck, autoAddBaseUrl = true, ...props }, forwardedRef) {
|
|
22
22
|
const { siteConfig: { trailingSlash, baseUrl }, } = useDocusaurusContext();
|
|
23
23
|
const { withBaseUrl } = useBaseUrlUtils();
|
|
24
|
-
const
|
|
24
|
+
const brokenLinks = useBrokenLinks();
|
|
25
25
|
const innerRef = useRef(null);
|
|
26
26
|
useImperativeHandle(forwardedRef, () => innerRef.current);
|
|
27
27
|
// IMPORTANT: using to or href should not change anything
|
|
@@ -100,7 +100,7 @@ function Link({ isNavLink, to, href, activeClassName, isActive, 'data-noBrokenLi
|
|
|
100
100
|
const isAnchorLink = targetLink?.startsWith('#') ?? false;
|
|
101
101
|
const isRegularHtmlLink = !targetLink || !isInternal || isAnchorLink;
|
|
102
102
|
if (!isRegularHtmlLink && !noBrokenLinkCheck) {
|
|
103
|
-
|
|
103
|
+
brokenLinks.collectLink(targetLink);
|
|
104
104
|
}
|
|
105
105
|
return isRegularHtmlLink ? (
|
|
106
106
|
// eslint-disable-next-line jsx-a11y/anchor-has-content, @docusaurus/no-html-links
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
/// <reference types="@docusaurus/module-type-aliases" />
|
|
8
|
+
import type { BrokenLinks } from '@docusaurus/useBrokenLinks';
|
|
9
|
+
export default function useBrokenLinks(): BrokenLinks;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import { useBrokenLinksContext } from '../BrokenLinksContext';
|
|
8
|
+
export default function useBrokenLinks() {
|
|
9
|
+
return useBrokenLinksContext();
|
|
10
|
+
}
|
|
@@ -18,7 +18,7 @@ import { minify } from 'html-minifier-terser';
|
|
|
18
18
|
import { renderStaticApp } from './serverRenderer';
|
|
19
19
|
import preload from './preload';
|
|
20
20
|
import App from './App';
|
|
21
|
-
import {
|
|
21
|
+
import { createStatefulBrokenLinks, BrokenLinksProvider, } from './BrokenLinksContext';
|
|
22
22
|
const getCompiledSSRTemplate = _.memoize((template) => eta.compile(template.trim(), {
|
|
23
23
|
rmWhitespace: true,
|
|
24
24
|
}));
|
|
@@ -61,20 +61,24 @@ async function doRender(locals) {
|
|
|
61
61
|
const modules = new Set();
|
|
62
62
|
const routerContext = {};
|
|
63
63
|
const helmetContext = {};
|
|
64
|
-
const
|
|
64
|
+
const statefulBrokenLinks = createStatefulBrokenLinks();
|
|
65
65
|
const app = (
|
|
66
66
|
// @ts-expect-error: we are migrating away from react-loadable anyways
|
|
67
67
|
<Loadable.Capture report={(moduleName) => modules.add(moduleName)}>
|
|
68
68
|
<HelmetProvider context={helmetContext}>
|
|
69
69
|
<StaticRouter location={location} context={routerContext}>
|
|
70
|
-
<
|
|
70
|
+
<BrokenLinksProvider brokenLinks={statefulBrokenLinks}>
|
|
71
71
|
<App />
|
|
72
|
-
</
|
|
72
|
+
</BrokenLinksProvider>
|
|
73
73
|
</StaticRouter>
|
|
74
74
|
</HelmetProvider>
|
|
75
75
|
</Loadable.Capture>);
|
|
76
76
|
const appHtml = await renderStaticApp(app);
|
|
77
|
-
onLinksCollected(
|
|
77
|
+
onLinksCollected({
|
|
78
|
+
staticPagePath: location,
|
|
79
|
+
anchors: statefulBrokenLinks.getCollectedAnchors(),
|
|
80
|
+
links: statefulBrokenLinks.getCollectedLinks(),
|
|
81
|
+
});
|
|
78
82
|
const { helmet } = helmetContext;
|
|
79
83
|
const htmlAttributes = helmet.htmlAttributes.toString();
|
|
80
84
|
const bodyAttributes = helmet.bodyAttributes.toString();
|
package/lib/commands/build.js
CHANGED
|
@@ -100,7 +100,7 @@ async function buildLocale({ siteDir, locale, cliOptions, forceTerminate, isLast
|
|
|
100
100
|
localizePath: cliOptions.locale ? false : undefined,
|
|
101
101
|
});
|
|
102
102
|
// Apply user webpack config.
|
|
103
|
-
const { outDir, generatedFilesDir, plugins, siteConfig: {
|
|
103
|
+
const { outDir, generatedFilesDir, plugins, siteConfig: { onBrokenLinks, onBrokenAnchors, staticDirectories: staticDirectoriesOption, }, routes, } = props;
|
|
104
104
|
const clientManifestPath = path_1.default.join(generatedFilesDir, 'client-manifest.json');
|
|
105
105
|
let clientConfig = (0, webpack_merge_1.default)(await (0, client_1.default)(props, cliOptions.minify, true), {
|
|
106
106
|
plugins: [
|
|
@@ -115,12 +115,12 @@ async function buildLocale({ siteDir, locale, cliOptions, forceTerminate, isLast
|
|
|
115
115
|
}),
|
|
116
116
|
].filter((x) => Boolean(x)),
|
|
117
117
|
});
|
|
118
|
-
const
|
|
118
|
+
const collectedLinks = {};
|
|
119
119
|
const headTags = {};
|
|
120
120
|
let serverConfig = await (0, server_2.default)({
|
|
121
121
|
props,
|
|
122
|
-
onLinksCollected: (staticPagePath, links) => {
|
|
123
|
-
|
|
122
|
+
onLinksCollected: ({ staticPagePath, links, anchors }) => {
|
|
123
|
+
collectedLinks[staticPagePath] = { links, anchors };
|
|
124
124
|
},
|
|
125
125
|
onHeadTagsCollected: (staticPagePath, tags) => {
|
|
126
126
|
headTags[staticPagePath] = tags;
|
|
@@ -190,11 +190,10 @@ async function buildLocale({ siteDir, locale, cliOptions, forceTerminate, isLast
|
|
|
190
190
|
});
|
|
191
191
|
}));
|
|
192
192
|
await (0, brokenLinks_1.handleBrokenLinks)({
|
|
193
|
-
|
|
193
|
+
collectedLinks,
|
|
194
194
|
routes,
|
|
195
195
|
onBrokenLinks,
|
|
196
|
-
|
|
197
|
-
baseUrl,
|
|
196
|
+
onBrokenAnchors,
|
|
198
197
|
});
|
|
199
198
|
logger_1.default.success `Generated static files in path=${path_1.default.relative(process.cwd(), outDir)}.`;
|
|
200
199
|
if (isLastLocale) {
|
|
@@ -5,12 +5,16 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
import type { RouteConfig, ReportingSeverity } from '@docusaurus/types';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
type CollectedLinks = {
|
|
9
|
+
[pathname: string]: {
|
|
10
|
+
links: string[];
|
|
11
|
+
anchors: string[];
|
|
11
12
|
};
|
|
13
|
+
};
|
|
14
|
+
export declare function handleBrokenLinks({ collectedLinks, onBrokenLinks, onBrokenAnchors, routes, }: {
|
|
15
|
+
collectedLinks: CollectedLinks;
|
|
12
16
|
onBrokenLinks: ReportingSeverity;
|
|
17
|
+
onBrokenAnchors: ReportingSeverity;
|
|
13
18
|
routes: RouteConfig[];
|
|
14
|
-
baseUrl: string;
|
|
15
|
-
outDir: string;
|
|
16
19
|
}): Promise<void>;
|
|
20
|
+
export {};
|
|
@@ -8,28 +8,15 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.handleBrokenLinks = void 0;
|
|
10
10
|
const tslib_1 = require("tslib");
|
|
11
|
-
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
12
|
-
const path_1 = tslib_1.__importDefault(require("path"));
|
|
13
11
|
const lodash_1 = tslib_1.__importDefault(require("lodash"));
|
|
14
12
|
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
|
|
15
|
-
const combine_promises_1 = tslib_1.__importDefault(require("combine-promises"));
|
|
16
13
|
const react_router_config_1 = require("react-router-config");
|
|
17
14
|
const utils_1 = require("@docusaurus/utils");
|
|
18
15
|
const utils_2 = require("./utils");
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
function getPageBrokenLinks({ pagePath, pageLinks, routes, }) {
|
|
24
|
-
// ReactRouter is able to support links like ./../somePath but `matchRoutes`
|
|
25
|
-
// does not do this resolution internally. We must resolve the links before
|
|
26
|
-
// using `matchRoutes`. `resolvePathname` is used internally by React Router
|
|
27
|
-
function resolveLink(link) {
|
|
28
|
-
const resolvedLink = (0, utils_1.resolvePathname)(onlyPathname(link), pagePath);
|
|
29
|
-
return { link, resolvedLink };
|
|
30
|
-
}
|
|
31
|
-
function isBrokenLink(link) {
|
|
32
|
-
const matchedRoutes = [link, decodeURI(link)]
|
|
16
|
+
function getBrokenLinksForPage({ collectedLinks, pagePath, pageLinks, routes, }) {
|
|
17
|
+
// console.log('routes:', routes);
|
|
18
|
+
function isPathBrokenLink(linkPath) {
|
|
19
|
+
const matchedRoutes = [linkPath.pathname, decodeURI(linkPath.pathname)]
|
|
33
20
|
// @ts-expect-error: React router types RouteConfig with an actual React
|
|
34
21
|
// component, but we load route components with string paths.
|
|
35
22
|
// We don't actually access component here, so it's fine.
|
|
@@ -37,7 +24,45 @@ function getPageBrokenLinks({ pagePath, pageLinks, routes, }) {
|
|
|
37
24
|
.flat();
|
|
38
25
|
return matchedRoutes.length === 0;
|
|
39
26
|
}
|
|
40
|
-
|
|
27
|
+
function isAnchorBrokenLink(linkPath) {
|
|
28
|
+
const { pathname, hash } = linkPath;
|
|
29
|
+
// Link has no hash: it can't be a broken anchor link
|
|
30
|
+
if (hash === undefined) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const targetPage = collectedLinks[pathname] || collectedLinks[decodeURI(pathname)];
|
|
34
|
+
// link with anchor to a page that does not exist (or did not collect any
|
|
35
|
+
// link/anchor) is considered as a broken anchor
|
|
36
|
+
if (!targetPage) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
// it's a broken anchor if the target page exists
|
|
40
|
+
// but the anchor does not exist on that page
|
|
41
|
+
return !targetPage.anchors.includes(hash);
|
|
42
|
+
}
|
|
43
|
+
const brokenLinks = pageLinks.flatMap((link) => {
|
|
44
|
+
const linkPath = (0, utils_1.parseURLPath)(link, pagePath);
|
|
45
|
+
if (isPathBrokenLink(linkPath)) {
|
|
46
|
+
return [
|
|
47
|
+
{
|
|
48
|
+
link,
|
|
49
|
+
resolvedLink: (0, utils_1.serializeURLPath)(linkPath),
|
|
50
|
+
anchor: false,
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
if (isAnchorBrokenLink(linkPath)) {
|
|
55
|
+
return [
|
|
56
|
+
{
|
|
57
|
+
link,
|
|
58
|
+
resolvedLink: (0, utils_1.serializeURLPath)(linkPath),
|
|
59
|
+
anchor: true,
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
return [];
|
|
64
|
+
});
|
|
65
|
+
return brokenLinks;
|
|
41
66
|
}
|
|
42
67
|
/**
|
|
43
68
|
* The route defs can be recursive, and have a parent match-all route. We don't
|
|
@@ -49,25 +74,48 @@ function filterIntermediateRoutes(routesInput) {
|
|
|
49
74
|
const routesWithout404 = routesInput.filter((route) => route.path !== '*');
|
|
50
75
|
return (0, utils_2.getAllFinalRoutes)(routesWithout404);
|
|
51
76
|
}
|
|
52
|
-
function
|
|
77
|
+
function getBrokenLinks({ collectedLinks, routes, }) {
|
|
53
78
|
const filteredRoutes = filterIntermediateRoutes(routes);
|
|
54
|
-
|
|
55
|
-
|
|
79
|
+
return lodash_1.default.mapValues(collectedLinks, (pageCollectedData, pagePath) => getBrokenLinksForPage({
|
|
80
|
+
collectedLinks,
|
|
81
|
+
pageLinks: pageCollectedData.links,
|
|
82
|
+
pageAnchors: pageCollectedData.anchors,
|
|
83
|
+
pagePath,
|
|
84
|
+
routes: filteredRoutes,
|
|
85
|
+
}));
|
|
56
86
|
}
|
|
57
|
-
function
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
function pageBrokenLinksMessage(pagePath, brokenLinks) {
|
|
66
|
-
return `
|
|
67
|
-
- On source page path = ${pagePath}:
|
|
87
|
+
function brokenLinkMessage(brokenLink) {
|
|
88
|
+
const showResolvedLink = brokenLink.link !== brokenLink.resolvedLink;
|
|
89
|
+
return `${brokenLink.link}${showResolvedLink ? ` (resolved as: ${brokenLink.resolvedLink})` : ''}`;
|
|
90
|
+
}
|
|
91
|
+
function createBrokenLinksMessage(pagePath, brokenLinks) {
|
|
92
|
+
const type = brokenLinks[0]?.anchor === true ? 'anchor' : 'link';
|
|
93
|
+
const anchorMessage = brokenLinks.length > 0
|
|
94
|
+
? `- Broken ${type} on source page path = ${pagePath}:
|
|
68
95
|
-> linking to ${brokenLinks
|
|
69
96
|
.map(brokenLinkMessage)
|
|
70
|
-
.join('\n -> linking to ')}
|
|
97
|
+
.join('\n -> linking to ')}`
|
|
98
|
+
: '';
|
|
99
|
+
return `${anchorMessage}`;
|
|
100
|
+
}
|
|
101
|
+
function createBrokenAnchorsMessage(brokenAnchors) {
|
|
102
|
+
if (Object.keys(brokenAnchors).length === 0) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
return `Docusaurus found broken anchors!
|
|
106
|
+
|
|
107
|
+
Please check the pages of your site in the list below, and make sure you don't reference any anchor that does not exist.
|
|
108
|
+
Note: it's possible to ignore broken anchors with the 'onBrokenAnchors' Docusaurus configuration, and let the build pass.
|
|
109
|
+
|
|
110
|
+
Exhaustive list of all broken anchors found:
|
|
111
|
+
${Object.entries(brokenAnchors)
|
|
112
|
+
.map(([pagePath, brokenLinks]) => createBrokenLinksMessage(pagePath, brokenLinks))
|
|
113
|
+
.join('\n')}
|
|
114
|
+
`;
|
|
115
|
+
}
|
|
116
|
+
function createBrokenPathsMessage(brokenPathsMap) {
|
|
117
|
+
if (Object.keys(brokenPathsMap).length === 0) {
|
|
118
|
+
return undefined;
|
|
71
119
|
}
|
|
72
120
|
/**
|
|
73
121
|
* If there's a broken link appearing very often, it is probably a broken link
|
|
@@ -75,7 +123,7 @@ function getBrokenLinksErrorMessage(allBrokenLinks) {
|
|
|
75
123
|
* this out. See https://github.com/facebook/docusaurus/issues/3567#issuecomment-706973805
|
|
76
124
|
*/
|
|
77
125
|
function getLayoutBrokenLinksHelpMessage() {
|
|
78
|
-
const flatList = Object.entries(
|
|
126
|
+
const flatList = Object.entries(brokenPathsMap).flatMap(([pagePage, brokenLinks]) => brokenLinks.map((brokenLink) => ({ pagePage, brokenLink })));
|
|
79
127
|
const countedBrokenLinks = lodash_1.default.countBy(flatList, (item) => item.brokenLink.link);
|
|
80
128
|
const FrequencyThreshold = 5; // Is this a good value?
|
|
81
129
|
const frequentLinks = Object.entries(countedBrokenLinks)
|
|
@@ -97,60 +145,45 @@ Please check the pages of your site in the list below, and make sure you don't r
|
|
|
97
145
|
Note: it's possible to ignore broken links with the 'onBrokenLinks' Docusaurus configuration, and let the build pass.${getLayoutBrokenLinksHelpMessage()}
|
|
98
146
|
|
|
99
147
|
Exhaustive list of all broken links found:
|
|
100
|
-
${Object.entries(
|
|
101
|
-
.map(([pagePath,
|
|
148
|
+
${Object.entries(brokenPathsMap)
|
|
149
|
+
.map(([pagePath, brokenPaths]) => createBrokenLinksMessage(pagePath, brokenPaths))
|
|
102
150
|
.join('\n')}
|
|
103
151
|
`;
|
|
104
152
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
// If a file actually exist on the file system, we know the link is valid
|
|
114
|
-
// even if docusaurus does not know about this file, so we don't report it
|
|
115
|
-
async function filterExistingFileLinks({ baseUrl, outDir, allCollectedLinks, }) {
|
|
116
|
-
async function linkFileExists(link) {
|
|
117
|
-
// /baseUrl/javadoc/ -> /outDir/javadoc
|
|
118
|
-
const baseFilePath = onlyPathname((0, utils_1.removeSuffix)(`${outDir}/${(0, utils_1.removePrefix)(link, baseUrl)}`, '/'));
|
|
119
|
-
// -> /outDir/javadoc
|
|
120
|
-
// -> /outDir/javadoc.html
|
|
121
|
-
// -> /outDir/javadoc/index.html
|
|
122
|
-
const filePathsToTry = [baseFilePath];
|
|
123
|
-
if (!path_1.default.extname(baseFilePath)) {
|
|
124
|
-
filePathsToTry.push(`${baseFilePath}.html`, path_1.default.join(baseFilePath, 'index.html'));
|
|
153
|
+
function splitBrokenLinks(brokenLinks) {
|
|
154
|
+
const brokenPaths = {};
|
|
155
|
+
const brokenAnchors = {};
|
|
156
|
+
Object.entries(brokenLinks).forEach(([pathname, pageBrokenLinks]) => {
|
|
157
|
+
const [anchorBrokenLinks, pathBrokenLinks] = lodash_1.default.partition(pageBrokenLinks, (link) => link.anchor);
|
|
158
|
+
if (pathBrokenLinks.length > 0) {
|
|
159
|
+
brokenPaths[pathname] = pathBrokenLinks;
|
|
125
160
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
161
|
+
if (anchorBrokenLinks.length > 0) {
|
|
162
|
+
brokenAnchors[pathname] = anchorBrokenLinks;
|
|
130
163
|
}
|
|
131
|
-
|
|
164
|
+
});
|
|
165
|
+
return { brokenPaths, brokenAnchors };
|
|
166
|
+
}
|
|
167
|
+
function reportBrokenLinks({ brokenLinks, onBrokenLinks, onBrokenAnchors, }) {
|
|
168
|
+
// We need to split the broken links reporting in 2 for better granularity
|
|
169
|
+
// This is because we need to report broken path/anchors independently
|
|
170
|
+
// For v3.x retro-compatibility, we can't throw by default for broken anchors
|
|
171
|
+
// TODO Docusaurus v4: make onBrokenAnchors throw by default?
|
|
172
|
+
const { brokenPaths, brokenAnchors } = splitBrokenLinks(brokenLinks);
|
|
173
|
+
const pathErrorMessage = createBrokenPathsMessage(brokenPaths);
|
|
174
|
+
if (pathErrorMessage) {
|
|
175
|
+
logger_1.default.report(onBrokenLinks)(pathErrorMessage);
|
|
176
|
+
}
|
|
177
|
+
const anchorErrorMessage = createBrokenAnchorsMessage(brokenAnchors);
|
|
178
|
+
if (anchorErrorMessage) {
|
|
179
|
+
logger_1.default.report(onBrokenAnchors)(anchorErrorMessage);
|
|
132
180
|
}
|
|
133
|
-
return (0, combine_promises_1.default)(lodash_1.default.mapValues(allCollectedLinks, async (links) => (await Promise.all(links.map(async (link) => ((await linkFileExists(link)) ? '' : link)))).filter(Boolean)));
|
|
134
181
|
}
|
|
135
|
-
async function handleBrokenLinks({
|
|
136
|
-
if (onBrokenLinks === 'ignore') {
|
|
182
|
+
async function handleBrokenLinks({ collectedLinks, onBrokenLinks, onBrokenAnchors, routes, }) {
|
|
183
|
+
if (onBrokenLinks === 'ignore' && onBrokenAnchors === 'ignore') {
|
|
137
184
|
return;
|
|
138
185
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// existing static file...
|
|
142
|
-
const allCollectedLinksFiltered = await filterExistingFileLinks({
|
|
143
|
-
allCollectedLinks,
|
|
144
|
-
baseUrl,
|
|
145
|
-
outDir,
|
|
146
|
-
});
|
|
147
|
-
const allBrokenLinks = getAllBrokenLinks({
|
|
148
|
-
allCollectedLinks: allCollectedLinksFiltered,
|
|
149
|
-
routes,
|
|
150
|
-
});
|
|
151
|
-
const errorMessage = getBrokenLinksErrorMessage(allBrokenLinks);
|
|
152
|
-
if (errorMessage) {
|
|
153
|
-
logger_1.default.report(onBrokenLinks)(errorMessage);
|
|
154
|
-
}
|
|
186
|
+
const brokenLinks = getBrokenLinks({ routes, collectedLinks });
|
|
187
|
+
reportBrokenLinks({ brokenLinks, onBrokenLinks, onBrokenAnchors });
|
|
155
188
|
}
|
|
156
189
|
exports.handleBrokenLinks = handleBrokenLinks;
|
|
@@ -8,6 +8,6 @@ import { Joi } from '@docusaurus/utils-validation';
|
|
|
8
8
|
import type { DocusaurusConfig, I18nConfig, MarkdownConfig } from '@docusaurus/types';
|
|
9
9
|
export declare const DEFAULT_I18N_CONFIG: I18nConfig;
|
|
10
10
|
export declare const DEFAULT_MARKDOWN_CONFIG: MarkdownConfig;
|
|
11
|
-
export declare const DEFAULT_CONFIG: Pick<DocusaurusConfig, 'i18n' | 'onBrokenLinks' | 'onBrokenMarkdownLinks' | 'onDuplicateRoutes' | 'plugins' | 'themes' | 'presets' | 'headTags' | 'stylesheets' | 'scripts' | 'clientModules' | 'customFields' | 'themeConfig' | 'titleDelimiter' | 'noIndex' | 'tagline' | 'baseUrlIssueBanner' | 'staticDirectories' | 'markdown'>;
|
|
11
|
+
export declare const DEFAULT_CONFIG: Pick<DocusaurusConfig, 'i18n' | 'onBrokenLinks' | 'onBrokenAnchors' | 'onBrokenMarkdownLinks' | 'onDuplicateRoutes' | 'plugins' | 'themes' | 'presets' | 'headTags' | 'stylesheets' | 'scripts' | 'clientModules' | 'customFields' | 'themeConfig' | 'titleDelimiter' | 'noIndex' | 'tagline' | 'baseUrlIssueBanner' | 'staticDirectories' | 'markdown'>;
|
|
12
12
|
export declare const ConfigSchema: Joi.ObjectSchema<DocusaurusConfig>;
|
|
13
13
|
export declare function validateConfig(config: unknown, siteConfigPath: string): DocusaurusConfig;
|
|
@@ -31,6 +31,7 @@ exports.DEFAULT_MARKDOWN_CONFIG = {
|
|
|
31
31
|
exports.DEFAULT_CONFIG = {
|
|
32
32
|
i18n: exports.DEFAULT_I18N_CONFIG,
|
|
33
33
|
onBrokenLinks: 'throw',
|
|
34
|
+
onBrokenAnchors: 'warn',
|
|
34
35
|
onBrokenMarkdownLinks: 'warn',
|
|
35
36
|
onDuplicateRoutes: 'warn',
|
|
36
37
|
plugins: [],
|
|
@@ -150,6 +151,9 @@ exports.ConfigSchema = utils_validation_1.Joi.object({
|
|
|
150
151
|
onBrokenLinks: utils_validation_1.Joi.string()
|
|
151
152
|
.equal('ignore', 'log', 'warn', 'throw')
|
|
152
153
|
.default(exports.DEFAULT_CONFIG.onBrokenLinks),
|
|
154
|
+
onBrokenAnchors: utils_validation_1.Joi.string()
|
|
155
|
+
.equal('ignore', 'log', 'warn', 'throw')
|
|
156
|
+
.default(exports.DEFAULT_CONFIG.onBrokenAnchors),
|
|
153
157
|
onBrokenMarkdownLinks: utils_validation_1.Joi.string()
|
|
154
158
|
.equal('ignore', 'log', 'warn', 'throw')
|
|
155
159
|
.default(exports.DEFAULT_CONFIG.onBrokenMarkdownLinks),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/core",
|
|
3
3
|
"description": "Easy to Maintain Open Source Documentation Websites",
|
|
4
|
-
"version": "0.0.0-
|
|
4
|
+
"version": "0.0.0-5792",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -43,13 +43,13 @@
|
|
|
43
43
|
"@babel/runtime": "^7.22.6",
|
|
44
44
|
"@babel/runtime-corejs3": "^7.22.6",
|
|
45
45
|
"@babel/traverse": "^7.22.8",
|
|
46
|
-
"@docusaurus/cssnano-preset": "0.0.0-
|
|
47
|
-
"@docusaurus/logger": "0.0.0-
|
|
48
|
-
"@docusaurus/mdx-loader": "0.0.0-
|
|
46
|
+
"@docusaurus/cssnano-preset": "0.0.0-5792",
|
|
47
|
+
"@docusaurus/logger": "0.0.0-5792",
|
|
48
|
+
"@docusaurus/mdx-loader": "0.0.0-5792",
|
|
49
49
|
"@docusaurus/react-loadable": "5.5.2",
|
|
50
|
-
"@docusaurus/utils": "0.0.0-
|
|
51
|
-
"@docusaurus/utils-common": "0.0.0-
|
|
52
|
-
"@docusaurus/utils-validation": "0.0.0-
|
|
50
|
+
"@docusaurus/utils": "0.0.0-5792",
|
|
51
|
+
"@docusaurus/utils-common": "0.0.0-5792",
|
|
52
|
+
"@docusaurus/utils-validation": "0.0.0-5792",
|
|
53
53
|
"@slorber/static-site-generator-webpack-plugin": "^4.0.7",
|
|
54
54
|
"@svgr/webpack": "^6.5.1",
|
|
55
55
|
"autoprefixer": "^10.4.14",
|
|
@@ -104,8 +104,8 @@
|
|
|
104
104
|
"webpackbar": "^5.0.2"
|
|
105
105
|
},
|
|
106
106
|
"devDependencies": {
|
|
107
|
-
"@docusaurus/module-type-aliases": "0.0.0-
|
|
108
|
-
"@docusaurus/types": "0.0.0-
|
|
107
|
+
"@docusaurus/module-type-aliases": "0.0.0-5792",
|
|
108
|
+
"@docusaurus/types": "0.0.0-5792",
|
|
109
109
|
"@types/detect-port": "^1.3.3",
|
|
110
110
|
"@types/react-dom": "^18.2.7",
|
|
111
111
|
"@types/react-router-config": "^5.0.7",
|
|
@@ -124,5 +124,5 @@
|
|
|
124
124
|
"engines": {
|
|
125
125
|
"node": ">=18.0"
|
|
126
126
|
},
|
|
127
|
-
"gitHead": "
|
|
127
|
+
"gitHead": "df52b60192f42bb72e56e6919afcc1a45fb202da"
|
|
128
128
|
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the MIT license found in the
|
|
5
|
-
* LICENSE file in the root directory of this source tree.
|
|
6
|
-
*/
|
|
7
|
-
import { type ReactNode } from 'react';
|
|
8
|
-
type LinksCollector = {
|
|
9
|
-
collectLink: (link: string) => void;
|
|
10
|
-
};
|
|
11
|
-
type StatefulLinksCollector = LinksCollector & {
|
|
12
|
-
getCollectedLinks: () => string[];
|
|
13
|
-
};
|
|
14
|
-
export declare const createStatefulLinksCollector: () => StatefulLinksCollector;
|
|
15
|
-
export declare const useLinksCollector: () => LinksCollector;
|
|
16
|
-
export declare function LinksCollectorProvider({ children, linksCollector, }: {
|
|
17
|
-
children: ReactNode;
|
|
18
|
-
linksCollector: LinksCollector;
|
|
19
|
-
}): JSX.Element;
|
|
20
|
-
export {};
|