Merge branch 'main' into ozaki/execa

This commit is contained in:
sebastien 2025-02-27 17:32:23 +01:00
commit 5fa32e3950
1179 changed files with 91436 additions and 15880 deletions

View file

@ -1,6 +1,6 @@
{
"name": "@docusaurus/plugin-content-docs",
"version": "3.4.0",
"version": "3.7.0",
"description": "Docs plugin for Docusaurus.",
"main": "lib/index.js",
"sideEffects": false,
@ -35,20 +35,21 @@
},
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.4.0",
"@docusaurus/logger": "3.4.0",
"@docusaurus/mdx-loader": "3.4.0",
"@docusaurus/module-type-aliases": "3.4.0",
"@docusaurus/theme-common": "3.4.0",
"@docusaurus/types": "3.4.0",
"@docusaurus/utils": "3.4.0",
"@docusaurus/utils-common": "3.4.0",
"@docusaurus/utils-validation": "3.4.0",
"@docusaurus/core": "3.7.0",
"@docusaurus/logger": "3.7.0",
"@docusaurus/mdx-loader": "3.7.0",
"@docusaurus/module-type-aliases": "3.7.0",
"@docusaurus/theme-common": "3.7.0",
"@docusaurus/types": "3.7.0",
"@docusaurus/utils": "3.7.0",
"@docusaurus/utils-common": "3.7.0",
"@docusaurus/utils-validation": "3.7.0",
"@types/react-router-config": "^5.0.7",
"combine-promises": "^1.1.0",
"fs-extra": "^11.1.1",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
"schema-dts": "^1.1.2",
"tslib": "^2.6.0",
"utility-types": "^3.10.0",
"webpack": "^5.88.1"
@ -60,8 +61,8 @@
"picomatch": "^2.3.1"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"engines": {
"node": ">=18.0"

View file

@ -13,16 +13,20 @@ import {isMatch} from 'picomatch';
import commander from 'commander';
import webpack from 'webpack';
import {loadContext} from '@docusaurus/core/src/server/site';
import {applyConfigureWebpack} from '@docusaurus/core/src/webpack/configure';
import {
applyConfigureWebpack,
createConfigureWebpackUtils,
} from '@docusaurus/core/src/webpack/configure';
import {sortRoutes} from '@docusaurus/core/src/server/plugins/routeConfig';
import {posixPath} from '@docusaurus/utils';
import {normalizePluginOptions} from '@docusaurus/utils-validation';
import {fromPartial} from '@total-typescript/shoehorn';
import pluginContentDocs from '../index';
import {toSidebarsProp} from '../props';
import {DefaultSidebarItemsGenerator} from '../sidebars/generator';
import {DisabledSidebars} from '../sidebars';
import * as cliDocs from '../cli';
import cliDocs from '../cli';
import {validateOptions} from '../options';
import type {RouteConfig, Validate, Plugin} from '@docusaurus/types';
@ -273,19 +277,26 @@ describe('simple website', () => {
const content = await plugin.loadContent?.();
const config = applyConfigureWebpack(
plugin.configureWebpack as NonNullable<Plugin['configureWebpack']>,
{
const config = applyConfigureWebpack({
configureWebpack: plugin.configureWebpack as NonNullable<
Plugin['configureWebpack']
>,
config: {
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
},
false,
undefined,
isServer: false,
configureWebpackUtils: await createConfigureWebpackUtils({
siteConfig: {
webpack: {jsLoader: 'babel'},
future: {experimental_faster: fromPartial({})},
},
}),
content,
);
});
const errors = webpack.validate(config);
expect(errors).toBeUndefined();
});

View file

@ -14,6 +14,7 @@ import {
getVersionDocsDirPath,
getVersionSidebarsPath,
getDocsDirPathLocalized,
getPluginDirPathLocalized,
readVersionsFile,
} from './versions/files';
import {validateVersionName} from './versions/validation';
@ -53,7 +54,7 @@ async function createVersionedSidebarFile({
}
// Tests depend on non-default export for mocking.
export async function cliDocsVersionCommand(
async function cliDocsVersionCommand(
version: unknown,
{id: pluginId, path: docsPath, sidebarPath}: PluginOptions,
{siteDir, i18n}: LoadContext,
@ -123,6 +124,23 @@ export async function cliDocsVersionCommand(
versionName: version,
});
await fs.copy(docsDir, newVersionDir);
// Copy version JSON translation file for this locale
// i18n/<l>/docusaurus-plugin-content-docs/current.json => version-v1.json
// See https://docusaurus.io/docs/next/api/plugins/@docusaurus/plugin-content-docs#translation-files-location
if (locale !== i18n.defaultLocale) {
const dir = getPluginDirPathLocalized({
localizationDir,
pluginId,
});
const sourceFile = path.join(dir, 'current.json');
const dest = path.join(dir, `version-${version}.json`);
if (await fs.pathExists(sourceFile)) {
await fs.copy(sourceFile, dest);
} else {
logger.warn`${pluginIdLogPrefix}: i18n translation file does not exist in path=${sourceFile}. Skipping.`;
}
}
}),
);
@ -142,3 +160,17 @@ export async function cliDocsVersionCommand(
logger.success`name=${pluginIdLogPrefix}: version name=${version} created!`;
}
// TODO try to remove this workaround
// Why use a default export instead of named exports here?
// This is only to make Jest mocking happy
// After upgrading Jest/SWC we got this weird mocking error in extendCli tests
// "spyOn: Cannot redefine property cliDocsVersionCommand"
// I tried various workarounds, and it's the only one that worked :/
// See also:
// - https://pyk.sh/fixing-typeerror-cannot-redefine-property-x-error-in-jest-tests#heading-solution-2-using-barrel-imports
// - https://github.com/aelbore/esbuild-jest/issues/26
// - https://stackoverflow.com/questions/67872622/jest-spyon-not-working-on-index-file-cannot-redefine-property/69951703#69951703
export default {
cliDocsVersionCommand,
};

View file

@ -15,6 +15,7 @@ import {
useDocById,
findSidebarCategory,
useCurrentSidebarCategory,
useCurrentSidebarSiblings,
useSidebarBreadcrumbs,
isVisibleSidebarItem,
} from '../docsUtils';
@ -440,6 +441,7 @@ describe('isVisibleSidebarItem', () => {
it('works with category', () => {
const subCategoryAllUnlisted = testCategory({
href: '/sub-category-path',
linkUnlisted: true,
items: [
{
type: 'link',
@ -455,6 +457,7 @@ describe('isVisibleSidebarItem', () => {
},
testCategory({
href: '/sub-sub-category-path',
linkUnlisted: true,
items: [
{
type: 'link',
@ -500,6 +503,22 @@ describe('isVisibleSidebarItem', () => {
expect(
isVisibleSidebarItem(categorySomeUnlisted, categorySomeUnlisted.href!),
).toBe(true);
const categoryOnlyIndexListed = testCategory({
href: '/category-only-index-listed',
items: [
{
type: 'link',
href: '/sub-link-path',
label: 'Label',
unlisted: true,
},
subCategoryAllUnlisted,
],
});
expect(
isVisibleSidebarItem(categoryOnlyIndexListed, '/nonexistentPath'),
).toBe(true);
});
});
@ -762,3 +781,128 @@ describe('useCurrentSidebarCategory', () => {
);
});
});
describe('useCurrentSidebarSiblings', () => {
const createUseCurrentSidebarSiblingsMock =
(sidebar?: PropSidebar) => (location: string) =>
renderHook(() => useCurrentSidebarSiblings(), {
wrapper: ({children}) => (
<DocsSidebarProvider name="sidebarName" items={sidebar}>
<StaticRouter location={location}>{children}</StaticRouter>
</DocsSidebarProvider>
),
}).result.current;
it('works for sidebar category', () => {
const category: PropSidebarItemCategory = testCategory({
href: '/cat',
items: [testLink(), testLink()],
});
const sidebar: PropSidebar = [
testLink(),
testLink(),
category,
testCategory(),
];
const mockUseCurrentSidebarCategory =
createUseCurrentSidebarSiblingsMock(sidebar);
expect(mockUseCurrentSidebarCategory('/cat')).toEqual(category.items);
});
it('works for sidebar root', () => {
const category: PropSidebarItemCategory = testCategory({
href: '/cat',
items: [testLink(), testLink()],
});
const sidebar: PropSidebar = [
testLink({href: '/rootLink'}),
testLink(),
category,
testCategory(),
];
const mockUseCurrentSidebarCategory =
createUseCurrentSidebarSiblingsMock(sidebar);
expect(mockUseCurrentSidebarCategory('/rootLink')).toEqual(sidebar);
});
it('works for nested sidebar category', () => {
const category2: PropSidebarItemCategory = testCategory({
href: '/cat2',
items: [testLink(), testCategory()],
});
const category1: PropSidebarItemCategory = testCategory({
href: '/cat1',
items: [testLink(), testLink(), category2, testCategory()],
});
const sidebar: PropSidebar = [
testLink(),
testLink(),
category1,
testCategory(),
];
const mockUseCurrentSidebarCategory =
createUseCurrentSidebarSiblingsMock(sidebar);
expect(mockUseCurrentSidebarCategory('/cat2')).toEqual(category2.items);
});
it('works for category link item', () => {
const link = testLink({href: '/my/link/path'});
const category: PropSidebarItemCategory = testCategory({
href: '/cat1',
items: [testLink(), testLink(), link, testCategory()],
});
const sidebar: PropSidebar = [
testLink(),
testLink(),
category,
testCategory(),
];
const mockUseCurrentSidebarCategory =
createUseCurrentSidebarSiblingsMock(sidebar);
expect(mockUseCurrentSidebarCategory('/my/link/path')).toEqual(
category.items,
);
});
it('works for nested category link item', () => {
const link = testLink({href: '/my/link/path'});
const category2: PropSidebarItemCategory = testCategory({
href: '/cat2',
items: [testLink(), testLink(), link, testCategory()],
});
const category1: PropSidebarItemCategory = testCategory({
href: '/cat1',
items: [testLink(), testLink(), category2, testCategory()],
});
const sidebar: PropSidebar = [
testLink(),
testLink(),
category1,
testCategory(),
];
const mockUseCurrentSidebarCategory =
createUseCurrentSidebarSiblingsMock(sidebar);
expect(mockUseCurrentSidebarCategory('/my/link/path')).toEqual(
category2.items,
);
});
it('throws when sidebar is missing', () => {
const mockUseCurrentSidebarCategory = createUseCurrentSidebarSiblingsMock();
expect(() =>
mockUseCurrentSidebarCategory('/cat'),
).toThrowErrorMatchingInlineSnapshot(
`"Unexpected: cant find current sidebar in context"`,
);
});
});

View file

@ -50,7 +50,7 @@ export function DocProvider({
}: {
children: ReactNode;
content: PropDocContent;
}): JSX.Element {
}): ReactNode {
const contextValue = useContextValue(content);
return <Context.Provider value={contextValue}>{children}</Context.Provider>;
}

View file

@ -36,7 +36,7 @@ export function DocSidebarItemsExpandedStateProvider({
children,
}: {
children: ReactNode;
}): JSX.Element {
}): ReactNode {
const [expandedItem, setExpandedItem] = useState<number | null>(null);
const contextValue = useMemo(
() => ({expandedItem, setExpandedItem}),

View file

@ -163,7 +163,7 @@ function DocsPreferredVersionContextProviderUnsafe({
children,
}: {
children: ReactNode;
}): JSX.Element {
}): ReactNode {
const value = useContextValue();
return <Context.Provider value={value}>{children}</Context.Provider>;
}
@ -176,7 +176,7 @@ export function DocsPreferredVersionContextProvider({
children,
}: {
children: ReactNode;
}): JSX.Element {
}): ReactNode {
return (
<DocsPreferredVersionContextProviderUnsafe>
{children}

View file

@ -30,7 +30,7 @@ export function DocsSidebarProvider({
children: ReactNode;
name: string | undefined;
items: PropSidebar | undefined;
}): JSX.Element {
}): ReactNode {
const stableValue: ContextValue | null = useMemo(
() => (name && items ? {name, items} : null),
[name, items],

View file

@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {useMemo} from 'react';
import {type ReactNode, useMemo} from 'react';
import {matchPath, useLocation} from '@docusaurus/router';
import renderRoutes from '@docusaurus/renderRoutes';
import {
@ -132,6 +132,25 @@ export function useCurrentSidebarCategory(): PropSidebarItemCategory {
return deepestCategory;
}
/**
* Gets the category associated with the current location. Should only be used
* on category index pages.
*/
export function useCurrentSidebarSiblings(): PropSidebarItem[] {
const {pathname} = useLocation();
const sidebar = useDocsSidebar();
if (!sidebar) {
throw new Error('Unexpected: cant find current sidebar in context');
}
const categoryBreadcrumbs = getSidebarBreadcrumbs({
sidebarItems: sidebar.items,
pathname,
onlyCategories: true,
});
const deepestCategory = categoryBreadcrumbs.slice(-1)[0];
return deepestCategory?.items ?? sidebar.items;
}
const isActive = (testedPath: string | undefined, activePath: string) =>
typeof testedPath !== 'undefined' && isSamePath(testedPath, activePath);
const containsActiveSidebarItem = (
@ -168,6 +187,7 @@ export function isVisibleSidebarItem(
case 'category':
return (
isActiveSidebarItem(item, activePath) ||
(typeof item.href !== 'undefined' && !item.linkUnlisted) ||
item.items.some((subItem) => isVisibleSidebarItem(subItem, activePath))
);
case 'link':
@ -363,7 +383,7 @@ Available doc ids are:
*/
export function useDocRootMetadata({route}: DocRootProps): null | {
/** The element that should be rendered at the current location. */
docElement: JSX.Element;
docElement: ReactNode;
/**
* The name of the sidebar associated with the current doc. `sidebarName` and
* `sidebarItems` correspond to the value of {@link useDocsSidebar}.

View file

@ -20,7 +20,7 @@ export function DocsVersionProvider({
}: {
children: ReactNode;
version: PropVersionMetadata | null;
}): JSX.Element {
}): ReactNode {
return <Context.Provider value={version}>{children}</Context.Provider>;
}

View file

@ -33,6 +33,7 @@ export {
useLayoutDocsSidebar,
useDocRootMetadata,
useCurrentSidebarCategory,
useCurrentSidebarSiblings,
filterDocCardListItems,
} from './docsUtils';
@ -59,6 +60,8 @@ export {
getDocsVersionSearchTag,
} from './docsSearch';
export {useBreadcrumbsStructuredData} from './structuredDataUtils';
export type ActivePlugin = {
pluginId: string;
pluginData: GlobalPluginData;

View file

@ -0,0 +1,32 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import type {PropSidebarBreadcrumbsItem} from '@docusaurus/plugin-content-docs';
import type {WithContext, BreadcrumbList} from 'schema-dts';
export function useBreadcrumbsStructuredData({
breadcrumbs,
}: {
breadcrumbs: PropSidebarBreadcrumbsItem[];
}): WithContext<BreadcrumbList> {
const {siteConfig} = useDocusaurusContext();
return {
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: breadcrumbs
// We filter breadcrumb items without links, they are not allowed
// See also https://github.com/facebook/docusaurus/issues/9319#issuecomment-2643560845
.filter((breadcrumb) => breadcrumb.href)
.map((breadcrumb, index) => ({
'@type': 'ListItem',
position: index + 1,
name: breadcrumb.label,
item: `${siteConfig.url}${breadcrumb.href}`,
})),
};
}

View file

@ -0,0 +1,34 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type {DocMetadata, LoadedContent} from '@docusaurus/plugin-content-docs';
function indexDocsBySource(content: LoadedContent): Map<string, DocMetadata> {
const allDocs = content.loadedVersions.flatMap((v) => v.docs);
return new Map(allDocs.map((doc) => [doc.source, doc]));
}
// TODO this is bad, we should have a better way to do this (new lifecycle?)
// The source to doc/permalink is a mutable map passed to the mdx loader
// See https://github.com/facebook/docusaurus/pull/10457
// See https://github.com/facebook/docusaurus/pull/10185
export function createContentHelpers() {
const sourceToDoc = new Map<string, DocMetadata>();
const sourceToPermalink = new Map<string, string>();
// Mutable map update :/
function updateContent(content: LoadedContent): void {
sourceToDoc.clear();
sourceToPermalink.clear();
indexDocsBySource(content).forEach((value, key) => {
sourceToDoc.set(key, value);
sourceToPermalink.set(key, value.permalink);
});
}
return {updateContent, sourceToDoc, sourceToPermalink};
}

View file

@ -6,6 +6,7 @@
*/
import path from 'path';
import fs from 'fs-extra';
import _ from 'lodash';
import logger from '@docusaurus/logger';
import {
@ -19,13 +20,13 @@ import {
createSlugger,
resolveMarkdownLinkPathname,
DEFAULT_PLUGIN_ID,
type SourceToPermalink,
type TagsFile,
} from '@docusaurus/utils';
import {
getTagsFile,
getTagsFilePathsToWatch,
} from '@docusaurus/utils-validation';
import {createMDXLoaderRule} from '@docusaurus/mdx-loader';
import {loadSidebars, resolveSidebarPathOption} from './sidebars';
import {CategoryMetadataFilenamePattern} from './sidebars/generator';
import {
@ -40,7 +41,7 @@ import {
readVersionsMetadata,
toFullVersion,
} from './versions';
import {cliDocsVersionCommand} from './cli';
import cliDocs from './cli';
import {VERSIONS_JSON_FILE} from './constants';
import {toGlobalDataVersion} from './globalData';
import {
@ -49,8 +50,8 @@ import {
} from './translations';
import {createAllRoutes} from './routes';
import {createSidebarsUtils} from './sidebars/utils';
import type {Options as MDXLoaderOptions} from '@docusaurus/mdx-loader';
import {createContentHelpers} from './contentHelpers';
import type {
PluginOptions,
DocMetadataBase,
@ -61,29 +62,37 @@ import type {
} from '@docusaurus/plugin-content-docs';
import type {LoadContext, Plugin} from '@docusaurus/types';
import type {DocFile, FullVersion} from './types';
import type {RuleSetUseItem} from 'webpack';
import type {RuleSetRule} from 'webpack';
// TODO this is bad, we should have a better way to do this (new lifecycle?)
// The source to permalink is currently a mutable map passed to the mdx loader
// for link resolution
// see https://github.com/facebook/docusaurus/pull/10185
function createSourceToPermalinkHelper() {
const sourceToPermalink: SourceToPermalink = new Map();
function computeSourceToPermalink(content: LoadedContent): SourceToPermalink {
const allDocs = content.loadedVersions.flatMap((v) => v.docs);
return new Map(allDocs.map(({source, permalink}) => [source, permalink]));
// MDX loader is not 100% deterministic, leading to cache invalidation issue
// This permits to invalidate the MDX loader cache entries when content changes
// Problem documented here: https://github.com/facebook/docusaurus/pull/10934
// TODO this is not a perfect solution, find better?
async function createMdxLoaderDependencyFile({
dataDir,
options,
versionsMetadata,
}: {
dataDir: string;
options: PluginOptions;
versionsMetadata: VersionMetadata[];
}): Promise<string | undefined> {
// TODO this has been temporarily made opt-in until Rspack cache bug is fixed
// See https://github.com/facebook/docusaurus/pull/10931
// See https://github.com/facebook/docusaurus/pull/10934#issuecomment-2672253145
if (!process.env.DOCUSAURUS_ENABLE_MDX_DEPENDENCY_FILE) {
return undefined;
}
// Mutable map update :/
function update(content: LoadedContent): void {
sourceToPermalink.clear();
computeSourceToPermalink(content).forEach((value, key) => {
sourceToPermalink.set(key, value);
});
}
return {get: () => sourceToPermalink, update};
const filePath = path.join(dataDir, '__mdx-loader-dependency.json');
// the cache is invalidated whenever this file content changes
const fileContent = {
options,
versionsMetadata,
};
await fs.ensureDir(dataDir);
await fs.writeFile(filePath, JSON.stringify(fileContent));
return filePath;
}
export default async function pluginContentDocs(
@ -112,7 +121,82 @@ export default async function pluginContentDocs(
// TODO env should be injected into all plugins
const env = process.env.NODE_ENV as DocEnv;
const sourceToPermalinkHelper = createSourceToPermalinkHelper();
const contentHelpers = createContentHelpers();
async function createDocsMDXLoaderRule(): Promise<RuleSetRule> {
const {
rehypePlugins,
remarkPlugins,
recmaPlugins,
beforeDefaultRehypePlugins,
beforeDefaultRemarkPlugins,
} = options;
const contentDirs = versionsMetadata
.flatMap(getContentPathList)
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
.map(addTrailingPathSeparator);
return createMDXLoaderRule({
include: contentDirs,
options: {
dependencies: [
await createMdxLoaderDependencyFile({
dataDir,
options,
versionsMetadata,
}),
].filter((d): d is string => typeof d === 'string'),
useCrossCompilerCache:
siteConfig.future.experimental_faster.mdxCrossCompilerCache,
admonitions: options.admonitions,
remarkPlugins,
rehypePlugins,
recmaPlugins,
beforeDefaultRehypePlugins,
beforeDefaultRemarkPlugins,
staticDirs: siteConfig.staticDirectories.map((dir) =>
path.resolve(siteDir, dir),
),
siteDir,
isMDXPartial: createAbsoluteFilePathMatcher(
options.exclude,
contentDirs,
),
metadataPath: (mdxPath: string) => {
// Note that metadataPath must be the same/in-sync as
// the path from createData for each MDX.
const aliasedPath = aliasedSitePath(mdxPath, siteDir);
return path.join(dataDir, `${docuHash(aliasedPath)}.json`);
},
// createAssets converts relative paths to require() calls
createAssets: ({frontMatter}: {frontMatter: DocFrontMatter}) => ({
image: frontMatter.image,
}),
markdownConfig: siteConfig.markdown,
resolveMarkdownLink: ({linkPathname, sourceFilePath}) => {
const version = getVersionFromSourceFilePath(
sourceFilePath,
versionsMetadata,
);
const permalink = resolveMarkdownLinkPathname(linkPathname, {
sourceFilePath,
sourceToPermalink: contentHelpers.sourceToPermalink,
siteDir,
contentPaths: version,
});
if (permalink === null) {
logger.report(
siteConfig.onBrokenMarkdownLinks,
)`Docs markdown link couldn't be resolved: (url=${linkPathname}) in source file path=${sourceFilePath} for version number=${version.versionName}`;
}
return permalink;
},
},
});
}
const docsMDXLoaderRule = await createDocsMDXLoaderRule();
return {
name: 'docusaurus-plugin-content-docs',
@ -134,7 +218,7 @@ export default async function pluginContentDocs(
.arguments('<version>')
.description(commandDescription)
.action((version: unknown) =>
cliDocsVersionCommand(version, options, context),
cliDocs.cliDocsVersionCommand(version, options, context),
);
},
@ -270,7 +354,7 @@ export default async function pluginContentDocs(
},
async contentLoaded({content, actions}) {
sourceToPermalinkHelper.update(content);
contentHelpers.updateContent(content);
const versions: FullVersion[] = content.loadedVersions.map(toFullVersion);
@ -289,74 +373,7 @@ export default async function pluginContentDocs(
});
},
configureWebpack(_config, isServer, utils, content) {
const {
rehypePlugins,
remarkPlugins,
recmaPlugins,
beforeDefaultRehypePlugins,
beforeDefaultRemarkPlugins,
} = options;
const contentDirs = versionsMetadata
.flatMap(getContentPathList)
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
.map(addTrailingPathSeparator);
function createMDXLoader(): RuleSetUseItem {
const loaderOptions: MDXLoaderOptions = {
admonitions: options.admonitions,
remarkPlugins,
rehypePlugins,
recmaPlugins,
beforeDefaultRehypePlugins,
beforeDefaultRemarkPlugins,
staticDirs: siteConfig.staticDirectories.map((dir) =>
path.resolve(siteDir, dir),
),
siteDir,
isMDXPartial: createAbsoluteFilePathMatcher(
options.exclude,
contentDirs,
),
metadataPath: (mdxPath: string) => {
// Note that metadataPath must be the same/in-sync as
// the path from createData for each MDX.
const aliasedPath = aliasedSitePath(mdxPath, siteDir);
return path.join(dataDir, `${docuHash(aliasedPath)}.json`);
},
// Assets allow to convert some relative images paths to
// require(...) calls
createAssets: ({frontMatter}: {frontMatter: DocFrontMatter}) => ({
image: frontMatter.image,
}),
markdownConfig: siteConfig.markdown,
resolveMarkdownLink: ({linkPathname, sourceFilePath}) => {
const version = getVersionFromSourceFilePath(
sourceFilePath,
content.loadedVersions,
);
const permalink = resolveMarkdownLinkPathname(linkPathname, {
sourceFilePath,
sourceToPermalink: sourceToPermalinkHelper.get(),
siteDir,
contentPaths: version,
});
if (permalink === null) {
logger.report(
siteConfig.onBrokenMarkdownLinks,
)`Docs markdown link couldn't be resolved: (url=${linkPathname}) in source file path=${sourceFilePath} for version number=${version.versionName}`;
}
return permalink;
},
};
return {
loader: require.resolve('@docusaurus/mdx-loader'),
options: loaderOptions,
};
}
configureWebpack() {
return {
ignoreWarnings: [
// Suppress warnings about non-existing of versions file.
@ -370,13 +387,7 @@ export default async function pluginContentDocs(
},
},
module: {
rules: [
{
test: /\.mdx?$/i,
include: contentDirs,
use: [createMDXLoader()],
},
],
rules: [docsMDXLoaderRule],
},
};
},

View file

@ -20,7 +20,11 @@ declare module '@docusaurus/plugin-content-docs' {
TagMetadata,
TagsPluginOptions,
} from '@docusaurus/utils';
import type {Plugin, LoadContext} from '@docusaurus/types';
import type {
Plugin,
LoadContext,
OptionValidationContext,
} from '@docusaurus/types';
import type {Overwrite, Required} from 'utility-types';
export type Assets = {
@ -559,13 +563,18 @@ declare module '@docusaurus/plugin-content-docs' {
context: LoadContext,
options: PluginOptions,
): Promise<Plugin<LoadedContent>>;
export function validateOptions(
args: OptionValidationContext<Options | undefined, PluginOptions>,
): PluginOptions;
}
declare module '@theme/DocItem' {
import type {ReactNode} from 'react';
import type {PropDocContent} from '@docusaurus/plugin-content-docs';
export type DocumentRoute = {
readonly component: () => JSX.Element;
readonly component: () => ReactNode;
readonly exact: boolean;
readonly path: string;
readonly sidebar?: string;
@ -576,10 +585,11 @@ declare module '@theme/DocItem' {
readonly content: PropDocContent;
}
export default function DocItem(props: Props): JSX.Element;
export default function DocItem(props: Props): ReactNode;
}
declare module '@theme/DocCategoryGeneratedIndexPage' {
import type {ReactNode} from 'react';
import type {PropCategoryGeneratedIndex} from '@docusaurus/plugin-content-docs';
export interface Props {
@ -588,39 +598,45 @@ declare module '@theme/DocCategoryGeneratedIndexPage' {
export default function DocCategoryGeneratedIndexPage(
props: Props,
): JSX.Element;
): ReactNode;
}
declare module '@theme/DocTagsListPage' {
import type {ReactNode} from 'react';
import type {PropTagsListPage} from '@docusaurus/plugin-content-docs';
export interface Props extends PropTagsListPage {}
export default function DocTagsListPage(props: Props): JSX.Element;
export default function DocTagsListPage(props: Props): ReactNode;
}
declare module '@theme/DocTagDocListPage' {
import type {ReactNode} from 'react';
import type {PropTagDocList} from '@docusaurus/plugin-content-docs';
export interface Props {
readonly tag: PropTagDocList;
}
export default function DocTagDocListPage(props: Props): JSX.Element;
export default function DocTagDocListPage(props: Props): ReactNode;
}
declare module '@theme/DocBreadcrumbs' {
export default function DocBreadcrumbs(): JSX.Element;
import type {ReactNode} from 'react';
export default function DocBreadcrumbs(): ReactNode;
}
declare module '@theme/DocsRoot' {
import type {ReactNode} from 'react';
import type {RouteConfigComponentProps} from 'react-router-config';
import type {Required} from 'utility-types';
export interface Props extends Required<RouteConfigComponentProps, 'route'> {}
export default function DocsRoot(props: Props): JSX.Element;
export default function DocsRoot(props: Props): ReactNode;
}
declare module '@theme/DocVersionRoot' {
import type {ReactNode} from 'react';
import type {PropVersionMetadata} from '@docusaurus/plugin-content-docs';
import type {RouteConfigComponentProps} from 'react-router-config';
import type {Required} from 'utility-types';
@ -629,14 +645,15 @@ declare module '@theme/DocVersionRoot' {
readonly version: PropVersionMetadata;
}
export default function DocVersionRoot(props: Props): JSX.Element;
export default function DocVersionRoot(props: Props): ReactNode;
}
declare module '@theme/DocRoot' {
import type {ReactNode} from 'react';
import type {RouteConfigComponentProps} from 'react-router-config';
import type {Required} from 'utility-types';
export interface Props extends Required<RouteConfigComponentProps, 'route'> {}
export default function DocRoot(props: Props): JSX.Element;
export default function DocRoot(props: Props): ReactNode;
}

View file

@ -75,6 +75,21 @@ export function getDocsDirPathLocalized({
});
}
export function getPluginDirPathLocalized({
localizationDir,
pluginId,
}: {
localizationDir: string;
pluginId: string;
}): string {
return getPluginI18nPath({
localizationDir,
pluginName: 'docusaurus-plugin-content-docs',
pluginId,
subPaths: [],
});
}
/** `community` => `[siteDir]/community_versions.json` */
export function getVersionsFilePath(siteDir: string, pluginId: string): string {
return path.join(siteDir, addPluginIdPrefix(VERSIONS_JSON_FILE, pluginId));