This commit is contained in:
2024-03-22 03:47:51 +05:30
parent 8bcf3d211e
commit 89819f6fe2
28440 changed files with 3211033 additions and 2 deletions

View File

@@ -0,0 +1,20 @@
/**
* 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 { RouteConfig, ReportingSeverity } from '@docusaurus/types';
type CollectedLinks = {
[pathname: string]: {
links: string[];
anchors: string[];
};
};
export declare function handleBrokenLinks({ collectedLinks, onBrokenLinks, onBrokenAnchors, routes, }: {
collectedLinks: CollectedLinks;
onBrokenLinks: ReportingSeverity;
onBrokenAnchors: ReportingSeverity;
routes: RouteConfig[];
}): Promise<void>;
export {};

276
node_modules/@docusaurus/core/lib/server/brokenLinks.js generated vendored Normal file
View File

@@ -0,0 +1,276 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.handleBrokenLinks = void 0;
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const react_router_config_1 = require("react-router-config");
const utils_1 = require("@docusaurus/utils");
const utils_2 = require("./utils");
function matchRoutes(routeConfig, pathname) {
// @ts-expect-error: React router types RouteConfig with an actual React
// component, but we load route components with string paths.
// We don't actually access component here, so it's fine.
return (0, react_router_config_1.matchRoutes)(routeConfig, pathname);
}
function createBrokenLinksHelper({ collectedLinks, routes, }) {
const validPathnames = new Set(collectedLinks.keys());
// IMPORTANT: this is an optimization
// See https://github.com/facebook/docusaurus/issues/9754
// Matching against the route array can be expensive
// If the route is already in the valid pathnames,
// we can avoid matching against it
const remainingRoutes = (function filterRoutes() {
// Goal: unit tests should behave the same with this enabled or disabled
const disableOptimization = false;
if (disableOptimization) {
return routes;
}
// We must consider the "exact" and "strict" match attribute
// We can only infer pre-validated pathnames from a route from exact routes
const [validPathnameRoutes, otherRoutes] = lodash_1.default.partition(routes, (route) => route.exact && validPathnames.has(route.path));
// If a route is non-strict (non-sensitive to trailing slashes)
// We must pre-validate all possible paths
validPathnameRoutes.forEach((validPathnameRoute) => {
if (!validPathnameRoute.strict) {
validPathnames.add((0, utils_1.addTrailingSlash)(validPathnameRoute.path));
validPathnames.add((0, utils_1.removeTrailingSlash)(validPathnameRoute.path));
}
});
return otherRoutes;
})();
function isPathnameMatchingAnyRoute(pathname) {
if (matchRoutes(remainingRoutes, pathname).length > 0) {
// IMPORTANT: this is an optimization
// See https://github.com/facebook/docusaurus/issues/9754
// Large Docusaurus sites have many routes!
// We try to minimize calls to a possibly expensive matchRoutes function
validPathnames.add(pathname);
return true;
}
return false;
}
function isPathBrokenLink(linkPath) {
const pathnames = [linkPath.pathname, decodeURI(linkPath.pathname)];
if (pathnames.some((p) => validPathnames.has(p))) {
return false;
}
if (pathnames.some(isPathnameMatchingAnyRoute)) {
return false;
}
return true;
}
function isAnchorBrokenLink(linkPath) {
const { pathname, hash } = linkPath;
// Link has no hash: it can't be a broken anchor link
if (hash === undefined) {
return false;
}
// Link has empty hash ("#", "/page#"...): we do not report it as broken
// Empty hashes are used for various weird reasons, by us and other users...
// See for example: https://github.com/facebook/docusaurus/pull/6003
if (hash === '') {
return false;
}
const targetPage = collectedLinks.get(pathname) || collectedLinks.get(decodeURI(pathname));
// link with anchor to a page that does not exist (or did not collect any
// link/anchor) is considered as a broken anchor
if (!targetPage) {
return true;
}
// it's a not broken anchor if the anchor exists on the target page
if (targetPage.anchors.has(hash) ||
targetPage.anchors.has(decodeURIComponent(hash))) {
return false;
}
return true;
}
return {
collectedLinks,
isPathBrokenLink,
isAnchorBrokenLink,
};
}
function getBrokenLinksForPage({ pagePath, helper, }) {
const pageData = helper.collectedLinks.get(pagePath);
const brokenLinks = [];
pageData.links.forEach((link) => {
const linkPath = (0, utils_1.parseURLPath)(link, pagePath);
if (helper.isPathBrokenLink(linkPath)) {
brokenLinks.push({
link,
resolvedLink: (0, utils_1.serializeURLPath)(linkPath),
anchor: false,
});
}
else if (helper.isAnchorBrokenLink(linkPath)) {
brokenLinks.push({
link,
resolvedLink: (0, utils_1.serializeURLPath)(linkPath),
anchor: true,
});
}
});
return brokenLinks;
}
/**
* The route defs can be recursive, and have a parent match-all route. We don't
* want to match broken links like /docs/brokenLink against /docs/*. For this
* reason, we only consider the "final routes" that do not have subroutes.
* We also need to remove the match-all 404 route
*/
function filterIntermediateRoutes(routesInput) {
const routesWithout404 = routesInput.filter((route) => route.path !== '*');
return (0, utils_2.getAllFinalRoutes)(routesWithout404);
}
function getBrokenLinks({ collectedLinks, routes, }) {
const filteredRoutes = filterIntermediateRoutes(routes);
const helper = createBrokenLinksHelper({
collectedLinks,
routes: filteredRoutes,
});
const result = {};
collectedLinks.forEach((_unused, pagePath) => {
try {
result[pagePath] = getBrokenLinksForPage({
pagePath,
helper,
});
}
catch (e) {
throw new Error(`Unable to get broken links for page ${pagePath}.`, {
cause: e,
});
}
});
return result;
}
function brokenLinkMessage(brokenLink) {
const showResolvedLink = brokenLink.link !== brokenLink.resolvedLink;
return `${brokenLink.link}${showResolvedLink ? ` (resolved as: ${brokenLink.resolvedLink})` : ''}`;
}
function createBrokenLinksMessage(pagePath, brokenLinks) {
const type = brokenLinks[0]?.anchor === true ? 'anchor' : 'link';
const anchorMessage = brokenLinks.length > 0
? `- Broken ${type} on source page path = ${pagePath}:
-> linking to ${brokenLinks
.map(brokenLinkMessage)
.join('\n -> linking to ')}`
: '';
return `${anchorMessage}`;
}
function createBrokenAnchorsMessage(brokenAnchors) {
if (Object.keys(brokenAnchors).length === 0) {
return undefined;
}
return `Docusaurus found broken anchors!
Please check the pages of your site in the list below, and make sure you don't reference any anchor that does not exist.
Note: it's possible to ignore broken anchors with the 'onBrokenAnchors' Docusaurus configuration, and let the build pass.
Exhaustive list of all broken anchors found:
${Object.entries(brokenAnchors)
.map(([pagePath, brokenLinks]) => createBrokenLinksMessage(pagePath, brokenLinks))
.join('\n')}
`;
}
function createBrokenPathsMessage(brokenPathsMap) {
if (Object.keys(brokenPathsMap).length === 0) {
return undefined;
}
/**
* If there's a broken link appearing very often, it is probably a broken link
* on the layout. Add an additional message in such case to help user figure
* this out. See https://github.com/facebook/docusaurus/issues/3567#issuecomment-706973805
*/
function getLayoutBrokenLinksHelpMessage() {
const flatList = Object.entries(brokenPathsMap).flatMap(([pagePage, brokenLinks]) => brokenLinks.map((brokenLink) => ({ pagePage, brokenLink })));
const countedBrokenLinks = lodash_1.default.countBy(flatList, (item) => item.brokenLink.link);
const FrequencyThreshold = 5; // Is this a good value?
const frequentLinks = Object.entries(countedBrokenLinks)
.filter(([, count]) => count >= FrequencyThreshold)
.map(([link]) => link);
if (frequentLinks.length === 0) {
return '';
}
return logger_1.default.interpolate `
It looks like some of the broken links we found appear in many pages of your site.
Maybe those broken links appear on all pages through your site layout?
We recommend that you check your theme configuration for such links (particularly, theme navbar and footer).
Frequent broken links are linking to:${frequentLinks}`;
}
return `Docusaurus found broken links!
Please check the pages of your site in the list below, and make sure you don't reference any path that does not exist.
Note: it's possible to ignore broken links with the 'onBrokenLinks' Docusaurus configuration, and let the build pass.${getLayoutBrokenLinksHelpMessage()}
Exhaustive list of all broken links found:
${Object.entries(brokenPathsMap)
.map(([pagePath, brokenPaths]) => createBrokenLinksMessage(pagePath, brokenPaths))
.join('\n')}
`;
}
function splitBrokenLinks(brokenLinks) {
const brokenPaths = {};
const brokenAnchors = {};
Object.entries(brokenLinks).forEach(([pathname, pageBrokenLinks]) => {
const [anchorBrokenLinks, pathBrokenLinks] = lodash_1.default.partition(pageBrokenLinks, (link) => link.anchor);
if (pathBrokenLinks.length > 0) {
brokenPaths[pathname] = pathBrokenLinks;
}
if (anchorBrokenLinks.length > 0) {
brokenAnchors[pathname] = anchorBrokenLinks;
}
});
return { brokenPaths, brokenAnchors };
}
function reportBrokenLinks({ brokenLinks, onBrokenLinks, onBrokenAnchors, }) {
// We need to split the broken links reporting in 2 for better granularity
// This is because we need to report broken path/anchors independently
// For v3.x retro-compatibility, we can't throw by default for broken anchors
// TODO Docusaurus v4: make onBrokenAnchors throw by default?
const { brokenPaths, brokenAnchors } = splitBrokenLinks(brokenLinks);
const pathErrorMessage = createBrokenPathsMessage(brokenPaths);
if (pathErrorMessage) {
logger_1.default.report(onBrokenLinks)(pathErrorMessage);
}
const anchorErrorMessage = createBrokenAnchorsMessage(brokenAnchors);
if (anchorErrorMessage) {
logger_1.default.report(onBrokenAnchors)(anchorErrorMessage);
}
}
// Users might use the useBrokenLinks() API in weird unexpected ways
// JS users might call "collectLink(undefined)" for example
// TS users might call "collectAnchor('#hash')" with/without #
// We clean/normalize the collected data to avoid obscure errors being thrown
// We also use optimized data structures for a faster algorithm
function normalizeCollectedLinks(collectedLinks) {
const result = new Map();
Object.entries(collectedLinks).forEach(([pathname, pageCollectedData]) => {
result.set(pathname, {
links: new Set(pageCollectedData.links.filter(lodash_1.default.isString)),
anchors: new Set(pageCollectedData.anchors
.filter(lodash_1.default.isString)
.map((anchor) => (anchor.startsWith('#') ? anchor.slice(1) : anchor))),
});
});
return result;
}
async function handleBrokenLinks({ collectedLinks, onBrokenLinks, onBrokenAnchors, routes, }) {
if (onBrokenLinks === 'ignore' && onBrokenAnchors === 'ignore') {
return;
}
const brokenLinks = getBrokenLinks({
routes,
collectedLinks: normalizeCollectedLinks(collectedLinks),
});
reportBrokenLinks({ brokenLinks, onBrokenLinks, onBrokenAnchors });
}
exports.handleBrokenLinks = handleBrokenLinks;

View File

@@ -0,0 +1,12 @@
/**
* 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 { LoadedPlugin } from '@docusaurus/types';
/**
* Runs the `getClientModules` lifecycle. The returned file paths are all
* absolute.
*/
export declare function loadClientModules(plugins: LoadedPlugin[]): string[];

View File

@@ -0,0 +1,20 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadClientModules = void 0;
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
/**
* Runs the `getClientModules` lifecycle. The returned file paths are all
* absolute.
*/
function loadClientModules(plugins) {
return plugins.flatMap((plugin) => plugin.getClientModules?.().map((p) => path_1.default.resolve(plugin.path, p)) ??
[]);
}
exports.loadClientModules = loadClientModules;

11
node_modules/@docusaurus/core/lib/server/config.d.ts generated vendored Normal file
View File

@@ -0,0 +1,11 @@
/**
* 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 { LoadContext } from '@docusaurus/types';
export declare function loadSiteConfig({ siteDir, customConfigFilePath, }: {
siteDir: string;
customConfigFilePath?: string;
}): Promise<Pick<LoadContext, 'siteConfig' | 'siteConfigPath'>>;

42
node_modules/@docusaurus/core/lib/server/config.js generated vendored Normal file
View File

@@ -0,0 +1,42 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadSiteConfig = void 0;
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const utils_1 = require("@docusaurus/utils");
const configValidation_1 = require("./configValidation");
async function findConfig(siteDir) {
// We could support .mjs, .ts, etc. in the future
const candidates = ['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs'].map((ext) => utils_1.DEFAULT_CONFIG_FILE_NAME + ext);
const configPath = await (0, utils_1.findAsyncSequential)(candidates.map((file) => path_1.default.join(siteDir, file)), fs_extra_1.default.pathExists);
if (!configPath) {
logger_1.default.error('No config file found.');
logger_1.default.info `Expected one of:${candidates}
You can provide a custom config path with the code=${'--config'} option.`;
throw new Error();
}
return configPath;
}
async function loadSiteConfig({ siteDir, customConfigFilePath, }) {
const siteConfigPath = customConfigFilePath
? path_1.default.resolve(siteDir, customConfigFilePath)
: await findConfig(siteDir);
if (!(await fs_extra_1.default.pathExists(siteConfigPath))) {
throw new Error(`Config file at "${siteConfigPath}" not found.`);
}
const importedConfig = await (0, utils_1.loadFreshModule)(siteConfigPath);
const loadedConfig = typeof importedConfig === 'function'
? await importedConfig()
: await importedConfig;
const siteConfig = (0, configValidation_1.validateConfig)(loadedConfig, path_1.default.relative(siteDir, siteConfigPath));
return { siteConfig, siteConfigPath };
}
exports.loadSiteConfig = loadSiteConfig;

View File

@@ -0,0 +1,13 @@
/**
* 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 { Joi } from '@docusaurus/utils-validation';
import type { DocusaurusConfig, I18nConfig, MarkdownConfig } from '@docusaurus/types';
export declare const DEFAULT_I18N_CONFIG: I18nConfig;
export declare const DEFAULT_MARKDOWN_CONFIG: MarkdownConfig;
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'>;
export declare const ConfigSchema: Joi.ObjectSchema<DocusaurusConfig>;
export declare function validateConfig(config: unknown, siteConfigPath: string): DocusaurusConfig;

View File

@@ -0,0 +1,268 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateConfig = exports.ConfigSchema = exports.DEFAULT_CONFIG = exports.DEFAULT_MARKDOWN_CONFIG = exports.DEFAULT_I18N_CONFIG = void 0;
const utils_1 = require("@docusaurus/utils");
const utils_validation_1 = require("@docusaurus/utils-validation");
const DEFAULT_I18N_LOCALE = 'en';
exports.DEFAULT_I18N_CONFIG = {
defaultLocale: DEFAULT_I18N_LOCALE,
path: utils_1.DEFAULT_I18N_DIR_NAME,
locales: [DEFAULT_I18N_LOCALE],
localeConfigs: {},
};
exports.DEFAULT_MARKDOWN_CONFIG = {
format: 'mdx',
mermaid: false,
preprocessor: undefined,
parseFrontMatter: utils_1.DEFAULT_PARSE_FRONT_MATTER,
mdx1Compat: {
comments: true,
admonitions: true,
headingIds: true,
},
remarkRehypeOptions: undefined,
};
exports.DEFAULT_CONFIG = {
i18n: exports.DEFAULT_I18N_CONFIG,
onBrokenLinks: 'throw',
onBrokenAnchors: 'warn',
onBrokenMarkdownLinks: 'warn',
onDuplicateRoutes: 'warn',
plugins: [],
themes: [],
presets: [],
headTags: [],
stylesheets: [],
scripts: [],
clientModules: [],
customFields: {},
themeConfig: {},
titleDelimiter: '|',
noIndex: false,
tagline: '',
baseUrlIssueBanner: true,
staticDirectories: [utils_1.DEFAULT_STATIC_DIR_NAME],
markdown: exports.DEFAULT_MARKDOWN_CONFIG,
};
function createPluginSchema(theme) {
return utils_validation_1.Joi.alternatives()
.try(utils_validation_1.Joi.function(), utils_validation_1.Joi.array()
.ordered(utils_validation_1.Joi.function().required(), utils_validation_1.Joi.object().required())
.length(2), utils_validation_1.Joi.string(), utils_validation_1.Joi.array()
.ordered(utils_validation_1.Joi.string().required(), utils_validation_1.Joi.object().required())
.length(2), utils_validation_1.Joi.any().valid(false, null))
.error((errors) => {
errors.forEach((error) => {
const validConfigExample = theme
? `Example valid theme config:
{
themes: [
["@docusaurus/theme-classic",options],
"./myTheme",
["./myTheme",{someOption: 42}],
function myTheme() { },
[function myTheme() { },options]
],
};`
: `Example valid plugin config:
{
plugins: [
["@docusaurus/plugin-content-docs",options],
"./myPlugin",
["./myPlugin",{someOption: 42}],
function myPlugin() { },
[function myPlugin() { },options]
],
};`;
error.message = ` => Bad Docusaurus ${theme ? 'theme' : 'plugin'} value ${error.path.reduce((acc, cur) => typeof cur === 'string' ? `${acc}.${cur}` : `${acc}[${cur}]`)}.
${validConfigExample}
`;
});
return errors;
});
}
const PluginSchema = createPluginSchema(false);
const ThemeSchema = createPluginSchema(true);
const PresetSchema = utils_validation_1.Joi.alternatives()
.try(utils_validation_1.Joi.string(), utils_validation_1.Joi.array()
.items(utils_validation_1.Joi.string().required(), utils_validation_1.Joi.object().required())
.length(2), utils_validation_1.Joi.any().valid(false, null))
.messages({
'alternatives.types': `{#label} does not look like a valid preset config. A preset config entry should be one of:
- A tuple of [presetName, options], like \`["classic", \\{ blog: false \\}]\`, or
- A simple string, like \`"classic"\``,
});
const LocaleConfigSchema = utils_validation_1.Joi.object({
label: utils_validation_1.Joi.string(),
htmlLang: utils_validation_1.Joi.string(),
direction: utils_validation_1.Joi.string().equal('ltr', 'rtl').default('ltr'),
calendar: utils_validation_1.Joi.string(),
path: utils_validation_1.Joi.string(),
});
const I18N_CONFIG_SCHEMA = utils_validation_1.Joi.object({
defaultLocale: utils_validation_1.Joi.string().required(),
path: utils_validation_1.Joi.string().default(exports.DEFAULT_I18N_CONFIG.path),
locales: utils_validation_1.Joi.array().items().min(1).items(utils_validation_1.Joi.string().required()).required(),
localeConfigs: utils_validation_1.Joi.object()
.pattern(/.*/, LocaleConfigSchema)
.default(exports.DEFAULT_I18N_CONFIG.localeConfigs),
})
.optional()
.default(exports.DEFAULT_I18N_CONFIG);
const SiteUrlSchema = utils_validation_1.Joi.string()
.required()
.custom((value, helpers) => {
try {
const { pathname } = new URL(value);
if (pathname !== '/') {
return helpers.error('docusaurus.subPathError', { pathname });
}
}
catch {
return helpers.error('any.invalid');
}
return (0, utils_1.removeTrailingSlash)(value);
})
.messages({
'any.invalid': '"{#value}" does not look like a valid URL. Make sure it has a protocol; for example, "https://example.com".',
'docusaurus.subPathError': 'The url is not supposed to contain a sub-path like "{#pathname}". Please use the baseUrl field for sub-paths.',
});
// TODO move to @docusaurus/utils-validation
exports.ConfigSchema = utils_validation_1.Joi.object({
baseUrl: utils_validation_1.Joi
// Weird Joi trick needed, otherwise value '' is not normalized...
.alternatives()
.try(utils_validation_1.Joi.string().required().allow(''))
.required()
.custom((value) => (0, utils_1.addLeadingSlash)((0, utils_1.addTrailingSlash)(value))),
baseUrlIssueBanner: utils_validation_1.Joi.boolean().default(exports.DEFAULT_CONFIG.baseUrlIssueBanner),
favicon: utils_validation_1.Joi.string().optional(),
title: utils_validation_1.Joi.string().required(),
url: SiteUrlSchema,
trailingSlash: utils_validation_1.Joi.boolean(),
i18n: I18N_CONFIG_SCHEMA,
onBrokenLinks: utils_validation_1.Joi.string()
.equal('ignore', 'log', 'warn', 'throw')
.default(exports.DEFAULT_CONFIG.onBrokenLinks),
onBrokenAnchors: utils_validation_1.Joi.string()
.equal('ignore', 'log', 'warn', 'throw')
.default(exports.DEFAULT_CONFIG.onBrokenAnchors),
onBrokenMarkdownLinks: utils_validation_1.Joi.string()
.equal('ignore', 'log', 'warn', 'throw')
.default(exports.DEFAULT_CONFIG.onBrokenMarkdownLinks),
onDuplicateRoutes: utils_validation_1.Joi.string()
.equal('ignore', 'log', 'warn', 'throw')
.default(exports.DEFAULT_CONFIG.onDuplicateRoutes),
organizationName: utils_validation_1.Joi.string().allow(''),
staticDirectories: utils_validation_1.Joi.array()
.items(utils_validation_1.Joi.string())
.default(exports.DEFAULT_CONFIG.staticDirectories),
projectName: utils_validation_1.Joi.string().allow(''),
deploymentBranch: utils_validation_1.Joi.string().optional(),
customFields: utils_validation_1.Joi.object().unknown().default(exports.DEFAULT_CONFIG.customFields),
githubHost: utils_validation_1.Joi.string(),
githubPort: utils_validation_1.Joi.string(),
plugins: utils_validation_1.Joi.array().items(PluginSchema).default(exports.DEFAULT_CONFIG.plugins),
themes: utils_validation_1.Joi.array().items(ThemeSchema).default(exports.DEFAULT_CONFIG.themes),
presets: utils_validation_1.Joi.array().items(PresetSchema).default(exports.DEFAULT_CONFIG.presets),
themeConfig: utils_validation_1.Joi.object().unknown().default(exports.DEFAULT_CONFIG.themeConfig),
scripts: utils_validation_1.Joi.array()
.items(utils_validation_1.Joi.string(), utils_validation_1.Joi.object({
src: utils_validation_1.Joi.string().required(),
async: utils_validation_1.Joi.bool(),
defer: utils_validation_1.Joi.bool(),
})
// See https://github.com/facebook/docusaurus/issues/3378
.unknown())
.messages({
'array.includes': '{#label} is invalid. A script must be a plain string (the src), or an object with at least a "src" property.',
})
.default(exports.DEFAULT_CONFIG.scripts),
ssrTemplate: utils_validation_1.Joi.string(),
headTags: utils_validation_1.Joi.array()
.items(utils_validation_1.Joi.object({
tagName: utils_validation_1.Joi.string().required(),
attributes: utils_validation_1.Joi.object()
.pattern(/[\w-]+/, utils_validation_1.Joi.string())
.required(),
}).unknown())
.messages({
'array.includes': '{#label} is invalid. A headTag must be an object with at least a "tagName" and an "attributes" property.',
})
.default(exports.DEFAULT_CONFIG.headTags),
stylesheets: utils_validation_1.Joi.array()
.items(utils_validation_1.Joi.string(), utils_validation_1.Joi.object({
href: utils_validation_1.Joi.string().required(),
type: utils_validation_1.Joi.string(),
}).unknown())
.messages({
'array.includes': '{#label} is invalid. A stylesheet must be a plain string (the href), or an object with at least a "href" property.',
})
.default(exports.DEFAULT_CONFIG.stylesheets),
clientModules: utils_validation_1.Joi.array()
.items(utils_validation_1.Joi.string())
.default(exports.DEFAULT_CONFIG.clientModules),
tagline: utils_validation_1.Joi.string().allow('').default(exports.DEFAULT_CONFIG.tagline),
titleDelimiter: utils_validation_1.Joi.string().default(exports.DEFAULT_CONFIG.titleDelimiter),
noIndex: utils_validation_1.Joi.bool().default(exports.DEFAULT_CONFIG.noIndex),
webpack: utils_validation_1.Joi.object({
jsLoader: utils_validation_1.Joi.alternatives()
.try(utils_validation_1.Joi.string().equal('babel'), utils_validation_1.Joi.function())
.optional(),
}).optional(),
markdown: utils_validation_1.Joi.object({
format: utils_validation_1.Joi.string()
.equal('mdx', 'md', 'detect')
.default(exports.DEFAULT_CONFIG.markdown.format),
parseFrontMatter: utils_validation_1.Joi.function().default(() => exports.DEFAULT_CONFIG.markdown.parseFrontMatter),
mermaid: utils_validation_1.Joi.boolean().default(exports.DEFAULT_CONFIG.markdown.mermaid),
preprocessor: utils_validation_1.Joi.function()
.arity(1)
.optional()
.default(() => exports.DEFAULT_CONFIG.markdown.preprocessor),
mdx1Compat: utils_validation_1.Joi.object({
comments: utils_validation_1.Joi.boolean().default(exports.DEFAULT_CONFIG.markdown.mdx1Compat.comments),
admonitions: utils_validation_1.Joi.boolean().default(exports.DEFAULT_CONFIG.markdown.mdx1Compat.admonitions),
headingIds: utils_validation_1.Joi.boolean().default(exports.DEFAULT_CONFIG.markdown.mdx1Compat.headingIds),
}).default(exports.DEFAULT_CONFIG.markdown.mdx1Compat),
remarkRehypeOptions:
// add proper external options validation?
// Not sure if it's a good idea, validation is likely to become stale
// See https://github.com/remarkjs/remark-rehype#options
utils_validation_1.Joi.object().unknown(),
}).default(exports.DEFAULT_CONFIG.markdown),
}).messages({
'docusaurus.configValidationWarning': 'Docusaurus config validation warning. Field {#label}: {#warningMessage}',
});
// TODO move to @docusaurus/utils-validation
function validateConfig(config, siteConfigPath) {
const { error, warning, value } = exports.ConfigSchema.validate(config, {
abortEarly: false,
});
(0, utils_validation_1.printWarning)(warning);
if (error) {
const unknownFields = error.details.reduce((formattedError, err) => {
if (err.type === 'object.unknown') {
return `${formattedError}"${err.path.reduce((acc, cur) => typeof cur === 'string' ? `${acc}.${cur}` : `${acc}[${cur}]`)}",`;
}
return formattedError;
}, '');
let formattedError = error.details.reduce((accumulatedErr, err) => err.type !== 'object.unknown'
? `${accumulatedErr}${err.message}\n`
: accumulatedErr, '');
formattedError = unknownFields
? `${formattedError}These field(s) (${unknownFields}) are not recognized in ${siteConfigPath}.\nIf you still want these fields to be in your configuration, put them in the "customFields" field.\nSee https://docusaurus.io/docs/api/docusaurus-config/#customfields`
: formattedError;
throw new Error(formattedError);
}
else {
return value;
}
}
exports.validateConfig = validateConfig;

View File

@@ -0,0 +1,14 @@
/**
* 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.
*/
export type HostPortOptions = {
host?: string;
port?: string;
};
export declare function getHostPort(options: HostPortOptions): Promise<{
host: string;
port: number | null;
}>;

View File

@@ -0,0 +1,82 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getHostPort = void 0;
const tslib_1 = require("tslib");
const child_process_1 = require("child_process");
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const detect_port_1 = tslib_1.__importDefault(require("detect-port"));
const utils_1 = require("@docusaurus/utils");
const prompts_1 = tslib_1.__importDefault(require("prompts"));
const execOptions = {
encoding: 'utf8',
stdio: [/* stdin */ 'pipe', /* stdout */ 'pipe', /* stderr */ 'ignore'],
};
function clearConsole() {
process.stdout.write(process.platform === 'win32' ? '\x1B[2J\x1B[0f' : '\x1B[2J\x1B[3J\x1B[H');
}
function getProcessForPort(port) {
try {
const processId = (0, child_process_1.execSync)(`lsof -i:${port} -P -t -sTCP:LISTEN`, execOptions)
.split('\n')[0]
.trim();
const directory = (0, child_process_1.execSync)(`lsof -p ${processId} | awk '$4=="cwd" {for (i=9; i<=NF; i++) printf "%s ", $i}'`, execOptions).trim();
const command = (0, child_process_1.execSync)(`ps -o command -p ${processId} | sed -n 2p`, execOptions).replace(/\n$/, '');
return logger_1.default.interpolate `code=${command} subdue=${`(pid ${processId})`} in path=${directory}`;
}
catch {
return null;
}
}
/**
* Detects if program is running on port, and prompts user to choose another if
* port is already being used. This feature was heavily inspired by
* create-react-app and uses many of the same utility functions to implement it.
*/
async function choosePort(host, defaultPort) {
try {
const port = await (0, detect_port_1.default)({
port: defaultPort,
...(host !== 'localhost' && { hostname: host }),
});
if (port === defaultPort) {
return port;
}
const isRoot = process.getuid?.() === 0;
const isInteractive = process.stdout.isTTY;
const message = process.platform !== 'win32' && defaultPort < 1024 && !isRoot
? `Admin permissions are required to run a server on a port below 1024.`
: `Something is already running on port ${defaultPort}.`;
if (!isInteractive) {
logger_1.default.error(message);
return null;
}
clearConsole();
const existingProcess = getProcessForPort(defaultPort);
const { shouldChangePort } = (await (0, prompts_1.default)({
type: 'confirm',
name: 'shouldChangePort',
message: logger_1.default.yellow(`${logger_1.default.bold('[WARNING]')} ${message}${existingProcess ? ` Probably:\n ${existingProcess}` : ''}
Would you like to run the app on another port instead?`),
initial: true,
}));
return shouldChangePort ? port : null;
}
catch (err) {
logger_1.default.error `Could not find an open port at ${host}.`;
throw err;
}
}
async function getHostPort(options) {
const host = options.host ?? 'localhost';
const basePort = options.port ? parseInt(options.port, 10) : utils_1.DEFAULT_PORT;
const port = await choosePort(host, basePort);
return { host, port };
}
exports.getHostPort = getHostPort;

12
node_modules/@docusaurus/core/lib/server/htmlTags.d.ts generated vendored Normal file
View File

@@ -0,0 +1,12 @@
/**
* 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 { Props, LoadedPlugin } from '@docusaurus/types';
/**
* Runs the `injectHtmlTags` lifecycle, and aggregates all plugins' tags into
* directly render-able HTML markup.
*/
export declare function loadHtmlTags(plugins: LoadedPlugin[]): Pick<Props, 'headTags' | 'preBodyTags' | 'postBodyTags'>;

62
node_modules/@docusaurus/core/lib/server/htmlTags.js generated vendored Normal file
View File

@@ -0,0 +1,62 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadHtmlTags = void 0;
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const html_tags_1 = tslib_1.__importDefault(require("html-tags"));
const void_1 = tslib_1.__importDefault(require("html-tags/void"));
const escape_html_1 = tslib_1.__importDefault(require("escape-html"));
function assertIsHtmlTagObject(val) {
if (typeof val !== 'object' || !val) {
throw new Error(`"${val}" is not a valid HTML tag object.`);
}
if (typeof val.tagName !== 'string') {
throw new Error(`${JSON.stringify(val)} is not a valid HTML tag object. "tagName" must be defined as a string.`);
}
if (!html_tags_1.default.includes(val.tagName)) {
throw new Error(`Error loading ${JSON.stringify(val)}, "${val.tagName}" is not a valid HTML tag.`);
}
}
function htmlTagObjectToString(tag) {
assertIsHtmlTagObject(tag);
const isVoidTag = void_1.default.includes(tag.tagName);
const tagAttributes = tag.attributes ?? {};
const attributes = Object.keys(tagAttributes)
.map((attr) => {
const value = tagAttributes[attr];
if (typeof value === 'boolean') {
return value ? attr : undefined;
}
return `${attr}="${(0, escape_html_1.default)(value)}"`;
})
.filter((str) => Boolean(str));
const openingTag = `<${[tag.tagName].concat(attributes).join(' ')}>`;
const innerHTML = (!isVoidTag && tag.innerHTML) || '';
const closingTag = isVoidTag ? '' : `</${tag.tagName}>`;
return openingTag + innerHTML + closingTag;
}
function createHtmlTagsString(tags) {
return (Array.isArray(tags) ? tags : [tags])
.filter(Boolean)
.map((val) => (typeof val === 'string' ? val : htmlTagObjectToString(val)))
.join('\n');
}
/**
* Runs the `injectHtmlTags` lifecycle, and aggregates all plugins' tags into
* directly render-able HTML markup.
*/
function loadHtmlTags(plugins) {
const pluginHtmlTags = plugins.map((plugin) => plugin.injectHtmlTags?.({ content: plugin.content }) ?? {});
const tagTypes = ['headTags', 'preBodyTags', 'postBodyTags'];
return Object.fromEntries(lodash_1.default.zip(tagTypes, tagTypes.map((type) => pluginHtmlTags
.map((tags) => createHtmlTagsString(tags[type]))
.join('\n')
.trim())));
}
exports.loadHtmlTags = loadHtmlTags;

10
node_modules/@docusaurus/core/lib/server/i18n.d.ts generated vendored Normal file
View File

@@ -0,0 +1,10 @@
/**
* 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 { I18n, DocusaurusConfig, I18nLocaleConfig } from '@docusaurus/types';
import type { LoadContextOptions } from './index';
export declare function getDefaultLocaleConfig(locale: string): I18nLocaleConfig;
export declare function loadI18n(config: DocusaurusConfig, options: Pick<LoadContextOptions, 'locale'>): Promise<I18n>;

53
node_modules/@docusaurus/core/lib/server/i18n.js generated vendored Normal file
View File

@@ -0,0 +1,53 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadI18n = exports.getDefaultLocaleConfig = void 0;
const tslib_1 = require("tslib");
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const rtl_detect_1 = require("rtl-detect");
function getDefaultLocaleLabel(locale) {
const languageName = new Intl.DisplayNames(locale, { type: 'language' }).of(locale);
return (languageName.charAt(0).toLocaleUpperCase(locale) + languageName.substring(1));
}
function getDefaultLocaleConfig(locale) {
return {
label: getDefaultLocaleLabel(locale),
direction: (0, rtl_detect_1.getLangDir)(locale),
htmlLang: locale,
// If the locale name includes -u-ca-xxx the calendar will be defined
calendar: new Intl.Locale(locale).calendar ?? 'gregory',
path: locale,
};
}
exports.getDefaultLocaleConfig = getDefaultLocaleConfig;
async function loadI18n(config, options) {
const { i18n: i18nConfig } = config;
const currentLocale = options.locale ?? i18nConfig.defaultLocale;
if (!i18nConfig.locales.includes(currentLocale)) {
logger_1.default.warn `The locale name=${currentLocale} was not found in your site configuration: Available locales are: ${i18nConfig.locales}
Note: Docusaurus only support running one locale at a time.`;
}
const locales = i18nConfig.locales.includes(currentLocale)
? i18nConfig.locales
: i18nConfig.locales.concat(currentLocale);
function getLocaleConfig(locale) {
return {
...getDefaultLocaleConfig(locale),
...i18nConfig.localeConfigs[locale],
};
}
const localeConfigs = Object.fromEntries(locales.map((locale) => [locale, getLocaleConfig(locale)]));
return {
defaultLocale: i18nConfig.defaultLocale,
locales,
path: i18nConfig.path,
currentLocale,
localeConfigs,
};
}
exports.loadI18n = loadI18n;

38
node_modules/@docusaurus/core/lib/server/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,38 @@
/**
* 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 { LoadContext, Props } from '@docusaurus/types';
export type LoadContextOptions = {
/** Usually the CWD; can be overridden with command argument. */
siteDir: string;
/** Custom output directory. Can be customized with `--out-dir` option */
outDir?: string;
/** Custom config path. Can be customized with `--config` option */
config?: string;
/** Default is `i18n.defaultLocale` */
locale?: string;
/**
* `true` means the paths will have the locale prepended; `false` means they
* won't (useful for `yarn build -l zh-Hans` where the output should be
* emitted into `build/` instead of `build/zh-Hans/`); `undefined` is like the
* "smart" option where only non-default locale paths are localized
*/
localizePath?: boolean;
};
/**
* Loading context is the very first step in site building. Its options are
* directly acquired from CLI options. It mainly loads `siteConfig` and the i18n
* context (which includes code translations). The `LoadContext` will be passed
* to plugin constructors.
*/
export declare function loadContext(options: LoadContextOptions): Promise<LoadContext>;
/**
* This is the crux of the Docusaurus server-side. It reads everything it needs—
* code translations, config file, plugin modules... Plugins then use their
* lifecycles to generate content and other data. It is side-effect-ful because
* it generates temp files in the `.docusaurus` folder for the bundler.
*/
export declare function load(options: LoadContextOptions): Promise<Props>;

154
node_modules/@docusaurus/core/lib/server/index.js generated vendored Normal file
View File

@@ -0,0 +1,154 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.load = exports.loadContext = void 0;
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const utils_1 = require("@docusaurus/utils");
const config_1 = require("./config");
const clientModules_1 = require("./clientModules");
const plugins_1 = require("./plugins");
const routes_1 = require("./routes");
const htmlTags_1 = require("./htmlTags");
const siteMetadata_1 = require("./siteMetadata");
const i18n_1 = require("./i18n");
const translations_1 = require("./translations/translations");
/**
* Loading context is the very first step in site building. Its options are
* directly acquired from CLI options. It mainly loads `siteConfig` and the i18n
* context (which includes code translations). The `LoadContext` will be passed
* to plugin constructors.
*/
async function loadContext(options) {
const { siteDir, outDir: baseOutDir = utils_1.DEFAULT_BUILD_DIR_NAME, locale, config: customConfigFilePath, } = options;
const generatedFilesDir = path_1.default.resolve(siteDir, utils_1.GENERATED_FILES_DIR_NAME);
const { siteConfig: initialSiteConfig, siteConfigPath } = await (0, config_1.loadSiteConfig)({
siteDir,
customConfigFilePath,
});
const i18n = await (0, i18n_1.loadI18n)(initialSiteConfig, { locale });
const baseUrl = (0, utils_1.localizePath)({
path: initialSiteConfig.baseUrl,
i18n,
options,
pathType: 'url',
});
const outDir = (0, utils_1.localizePath)({
path: path_1.default.resolve(siteDir, baseOutDir),
i18n,
options,
pathType: 'fs',
});
const siteConfig = { ...initialSiteConfig, baseUrl };
const localizationDir = path_1.default.resolve(siteDir, i18n.path, i18n.localeConfigs[i18n.currentLocale].path);
const codeTranslationFileContent = (await (0, translations_1.readCodeTranslationFileContent)({ localizationDir })) ?? {};
// We only need key->message for code translations
const codeTranslations = lodash_1.default.mapValues(codeTranslationFileContent, (value) => value.message);
return {
siteDir,
generatedFilesDir,
localizationDir,
siteConfig,
siteConfigPath,
outDir,
baseUrl,
i18n,
codeTranslations,
};
}
exports.loadContext = loadContext;
/**
* This is the crux of the Docusaurus server-side. It reads everything it needs—
* code translations, config file, plugin modules... Plugins then use their
* lifecycles to generate content and other data. It is side-effect-ful because
* it generates temp files in the `.docusaurus` folder for the bundler.
*/
async function load(options) {
const { siteDir } = options;
const context = await loadContext(options);
const { generatedFilesDir, siteConfig, siteConfigPath, outDir, baseUrl, i18n, localizationDir, codeTranslations: siteCodeTranslations, } = context;
const { plugins, pluginsRouteConfigs, globalData } = await (0, plugins_1.loadPlugins)(context);
const clientModules = (0, clientModules_1.loadClientModules)(plugins);
const { headTags, preBodyTags, postBodyTags } = (0, htmlTags_1.loadHtmlTags)(plugins);
const { registry, routesChunkNames, routesConfig, routesPaths } = (0, routes_1.loadRoutes)(pluginsRouteConfigs, baseUrl, siteConfig.onDuplicateRoutes);
const codeTranslations = {
...(await (0, translations_1.getPluginsDefaultCodeTranslationMessages)(plugins)),
...siteCodeTranslations,
};
const siteMetadata = await (0, siteMetadata_1.loadSiteMetadata)({ plugins, siteDir });
// === Side-effects part ===
const genWarning = (0, utils_1.generate)(generatedFilesDir,
// cSpell:ignore DONT
'DONT-EDIT-THIS-FOLDER', `This folder stores temp files that Docusaurus' client bundler accesses.
DO NOT hand-modify files in this folder because they will be overwritten in the
next build. You can clear all build artifacts (including this folder) with the
\`docusaurus clear\` command.
`);
const genSiteConfig = (0, utils_1.generate)(generatedFilesDir, `${utils_1.DEFAULT_CONFIG_FILE_NAME}.mjs`, `/*
* AUTOGENERATED - DON'T EDIT
* Your edits in this file will be overwritten in the next build!
* Modify the docusaurus.config.js file at your site's root instead.
*/
export default ${JSON.stringify(siteConfig, null, 2)};
`);
const genClientModules = (0, utils_1.generate)(generatedFilesDir, 'client-modules.js', `export default [
${clientModules
// Use `require()` because `import()` is async but client modules can have CSS
// and the order matters for loading CSS.
.map((clientModule) => ` require("${(0, utils_1.escapePath)(clientModule)}"),`)
.join('\n')}
];
`);
const genRegistry = (0, utils_1.generate)(generatedFilesDir, 'registry.js', `export default {
${Object.entries(registry)
.sort((a, b) => a[0].localeCompare(b[0]))
.map(([chunkName, modulePath]) =>
// modulePath is already escaped by escapePath
` "${chunkName}": [() => import(/* webpackChunkName: "${chunkName}" */ "${modulePath}"), "${modulePath}", require.resolveWeak("${modulePath}")],`)
.join('\n')}};
`);
const genRoutesChunkNames = (0, utils_1.generate)(generatedFilesDir, 'routesChunkNames.json', JSON.stringify(routesChunkNames, null, 2));
const genRoutes = (0, utils_1.generate)(generatedFilesDir, 'routes.js', routesConfig);
const genGlobalData = (0, utils_1.generate)(generatedFilesDir, 'globalData.json', JSON.stringify(globalData, null, 2));
const genI18n = (0, utils_1.generate)(generatedFilesDir, 'i18n.json', JSON.stringify(i18n, null, 2));
const genCodeTranslations = (0, utils_1.generate)(generatedFilesDir, 'codeTranslations.json', JSON.stringify(codeTranslations, null, 2));
const genSiteMetadata = (0, utils_1.generate)(generatedFilesDir, 'site-metadata.json', JSON.stringify(siteMetadata, null, 2));
await Promise.all([
genWarning,
genClientModules,
genSiteConfig,
genRegistry,
genRoutesChunkNames,
genRoutes,
genGlobalData,
genSiteMetadata,
genI18n,
genCodeTranslations,
]);
return {
siteConfig,
siteConfigPath,
siteMetadata,
siteDir,
outDir,
baseUrl,
i18n,
localizationDir,
generatedFilesDir,
routes: pluginsRouteConfigs,
routesPaths,
plugins,
headTags,
preBodyTags,
postBodyTags,
codeTranslations,
};
}
exports.load = load;

View File

@@ -0,0 +1,51 @@
/**
* 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 { LoadContext, PluginModule, PluginOptions } from '@docusaurus/types';
type ImportedPluginModule = PluginModule & {
default?: PluginModule;
};
export type NormalizedPluginConfig = {
/**
* The default export of the plugin module, or alternatively, what's provided
* in the config file as inline plugins. Note that if a file is like:
*
* ```ts
* export default plugin() {...}
* export validateOptions() {...}
* ```
*
* Then the static methods may not exist here. `pluginModule.module` will
* always take priority.
*/
plugin: PluginModule;
/** Options as they are provided in the config, not validated yet. */
options: PluginOptions;
/** Only available when a string is provided in config. */
pluginModule?: {
/**
* Raw module name as provided in the config. Shorthands have been resolved,
* so at least it's directly `require.resolve`able.
*/
path: string;
/** Whatever gets imported with `require`. */
module: ImportedPluginModule;
};
/**
* Different from `pluginModule.path`, this one is always an absolute path,
* used to resolve relative paths returned from lifecycles. If it's an inline
* plugin, it will be path to the config file.
*/
entryPath: string;
};
/**
* Reads the site config's `presets`, `themes`, and `plugins`, imports them, and
* normalizes the return value. Plugin configs are ordered, mostly for theme
* alias shadowing. Site themes have the highest priority, and preset plugins
* are the lowest.
*/
export declare function loadPluginConfigs(context: LoadContext): Promise<NormalizedPluginConfig[]>;
export {};

View File

@@ -0,0 +1,100 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadPluginConfigs = void 0;
const module_1 = require("module");
const utils_1 = require("@docusaurus/utils");
const presets_1 = require("./presets");
const moduleShorthand_1 = require("./moduleShorthand");
async function normalizePluginConfig(pluginConfig, configPath, pluginRequire) {
// plugins: ["./plugin"]
if (typeof pluginConfig === 'string') {
const pluginModuleImport = pluginConfig;
const pluginPath = pluginRequire.resolve(pluginModuleImport);
const pluginModule = (await (0, utils_1.loadFreshModule)(pluginPath));
return {
plugin: pluginModule.default ?? pluginModule,
options: {},
pluginModule: {
path: pluginModuleImport,
module: pluginModule,
},
entryPath: pluginPath,
};
}
// plugins: [() => {...}]
if (typeof pluginConfig === 'function') {
return {
plugin: pluginConfig,
options: {},
entryPath: configPath,
};
}
// plugins: [
// ["./plugin",options],
// ]
if (typeof pluginConfig[0] === 'string') {
const pluginModuleImport = pluginConfig[0];
const pluginPath = pluginRequire.resolve(pluginModuleImport);
const pluginModule = (await (0, utils_1.loadFreshModule)(pluginPath));
return {
plugin: pluginModule.default ?? pluginModule,
options: pluginConfig[1],
pluginModule: {
path: pluginModuleImport,
module: pluginModule,
},
entryPath: pluginPath,
};
}
// plugins: [
// [() => {...}, options],
// ]
return {
plugin: pluginConfig[0],
options: pluginConfig[1],
entryPath: configPath,
};
}
/**
* Reads the site config's `presets`, `themes`, and `plugins`, imports them, and
* normalizes the return value. Plugin configs are ordered, mostly for theme
* alias shadowing. Site themes have the highest priority, and preset plugins
* are the lowest.
*/
async function loadPluginConfigs(context) {
const preset = await (0, presets_1.loadPresets)(context);
const { siteConfig, siteConfigPath } = context;
const pluginRequire = (0, module_1.createRequire)(siteConfigPath);
function normalizeShorthand(pluginConfig, pluginType) {
if (typeof pluginConfig === 'string') {
return (0, moduleShorthand_1.resolveModuleName)(pluginConfig, pluginRequire, pluginType);
}
else if (Array.isArray(pluginConfig) &&
typeof pluginConfig[0] === 'string') {
return [
(0, moduleShorthand_1.resolveModuleName)(pluginConfig[0], pluginRequire, pluginType),
pluginConfig[1] ?? {},
];
}
return pluginConfig;
}
preset.plugins = preset.plugins.map((plugin) => normalizeShorthand(plugin, 'plugin'));
preset.themes = preset.themes.map((theme) => normalizeShorthand(theme, 'theme'));
const standalonePlugins = siteConfig.plugins.map((plugin) => normalizeShorthand(plugin, 'plugin'));
const standaloneThemes = siteConfig.themes.map((theme) => normalizeShorthand(theme, 'theme'));
const pluginConfigs = [
...preset.plugins,
...preset.themes,
// Site config should be the highest priority.
...standalonePlugins,
...standaloneThemes,
].filter((x) => Boolean(x));
return Promise.all(pluginConfigs.map((pluginConfig) => normalizePluginConfig(pluginConfig, context.siteConfigPath, pluginRequire)));
}
exports.loadPluginConfigs = loadPluginConfigs;

View File

@@ -0,0 +1,18 @@
/**
* 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 { LoadContext, RouteConfig, GlobalData, LoadedPlugin } from '@docusaurus/types';
/**
* Initializes the plugins, runs `loadContent`, `translateContent`,
* `contentLoaded`, and `translateThemeConfig`. Because `contentLoaded` is
* side-effect-ful (it generates temp files), so is this function. This function
* would also mutate `context.siteConfig.themeConfig` to translate it.
*/
export declare function loadPlugins(context: LoadContext): Promise<{
plugins: LoadedPlugin[];
pluginsRouteConfigs: RouteConfig[];
globalData: GlobalData;
}>;

View File

@@ -0,0 +1,106 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadPlugins = void 0;
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const utils_1 = require("@docusaurus/utils");
const init_1 = require("./init");
const synthetic_1 = require("./synthetic");
const translations_1 = require("../translations/translations");
const routeConfig_1 = require("./routeConfig");
/**
* Initializes the plugins, runs `loadContent`, `translateContent`,
* `contentLoaded`, and `translateThemeConfig`. Because `contentLoaded` is
* side-effect-ful (it generates temp files), so is this function. This function
* would also mutate `context.siteConfig.themeConfig` to translate it.
*/
async function loadPlugins(context) {
// 1. Plugin Lifecycle - Initialization/Constructor.
const plugins = await (0, init_1.initPlugins)(context);
plugins.push((0, synthetic_1.createBootstrapPlugin)(context), (0, synthetic_1.createMDXFallbackPlugin)(context));
// 2. Plugin Lifecycle - loadContent.
// Currently plugins run lifecycle methods in parallel and are not
// order-dependent. We could change this in future if there are plugins which
// need to run in certain order or depend on others for data.
// This would also translate theme config and content upfront, given the
// translation files that the plugin declares.
const loadedPlugins = await Promise.all(plugins.map(async (plugin) => {
const content = await plugin.loadContent?.();
const rawTranslationFiles = (await plugin.getTranslationFiles?.({ content })) ?? [];
const translationFiles = await Promise.all(rawTranslationFiles.map((translationFile) => (0, translations_1.localizePluginTranslationFile)({
localizationDir: context.localizationDir,
translationFile,
plugin,
})));
const translatedContent = plugin.translateContent?.({ content, translationFiles }) ?? content;
const translatedThemeConfigSlice = plugin.translateThemeConfig?.({
themeConfig: context.siteConfig.themeConfig,
translationFiles,
});
// Side-effect to merge theme config translations. A plugin should only
// translate its own slice of theme config and should make no assumptions
// about other plugins' keys, so this is safe to run in parallel.
Object.assign(context.siteConfig.themeConfig, translatedThemeConfigSlice);
return { ...plugin, content: translatedContent };
}));
const allContent = lodash_1.default.chain(loadedPlugins)
.groupBy((item) => item.name)
.mapValues((nameItems) => lodash_1.default.chain(nameItems)
.groupBy((item) => item.options.id)
.mapValues((idItems) => idItems[0].content)
.value())
.value();
// 3. Plugin Lifecycle - contentLoaded.
const pluginsRouteConfigs = [];
const globalData = {};
await Promise.all(loadedPlugins.map(async ({ content, ...plugin }) => {
if (!plugin.contentLoaded) {
return;
}
const pluginId = plugin.options.id;
// Plugins data files are namespaced by pluginName/pluginId
const dataDir = path_1.default.join(context.generatedFilesDir, plugin.name, pluginId);
const pluginRouteContextModulePath = path_1.default.join(dataDir, `${(0, utils_1.docuHash)('pluginRouteContextModule')}.json`);
const pluginRouteContext = {
name: plugin.name,
id: pluginId,
};
await (0, utils_1.generate)('/', pluginRouteContextModulePath, JSON.stringify(pluginRouteContext, null, 2));
const actions = {
addRoute(initialRouteConfig) {
// Trailing slash behavior is handled generically for all plugins
const finalRouteConfig = (0, routeConfig_1.applyRouteTrailingSlash)(initialRouteConfig, context.siteConfig);
pluginsRouteConfigs.push({
...finalRouteConfig,
context: {
...(finalRouteConfig.context && { data: finalRouteConfig.context }),
plugin: pluginRouteContextModulePath,
},
});
},
async createData(name, data) {
const modulePath = path_1.default.join(dataDir, name);
await (0, utils_1.generate)(dataDir, name, data);
return modulePath;
},
setGlobalData(data) {
var _a;
globalData[_a = plugin.name] ?? (globalData[_a] = {});
globalData[plugin.name][pluginId] = data;
},
};
await plugin.contentLoaded({ content, actions, allContent });
}));
// Sort the route config. This ensures that route with nested
// routes are always placed last.
(0, routeConfig_1.sortConfig)(pluginsRouteConfigs, context.siteConfig.baseUrl);
return { plugins: loadedPlugins, pluginsRouteConfigs, globalData };
}
exports.loadPlugins = loadPlugins;

View File

@@ -0,0 +1,12 @@
/**
* 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 { LoadContext, InitializedPlugin } from '@docusaurus/types';
/**
* Runs the plugin constructors and returns their return values. It would load
* plugin configs from `plugins`, `themes`, and `presets`.
*/
export declare function initPlugins(context: LoadContext): Promise<InitializedPlugin[]>;

View File

@@ -0,0 +1,95 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.initPlugins = void 0;
const tslib_1 = require("tslib");
const module_1 = require("module");
const path_1 = tslib_1.__importDefault(require("path"));
const utils_1 = require("@docusaurus/utils");
const utils_validation_1 = require("@docusaurus/utils-validation");
const siteMetadata_1 = require("../siteMetadata");
const pluginIds_1 = require("./pluginIds");
const configs_1 = require("./configs");
function getOptionValidationFunction(normalizedPluginConfig) {
if (normalizedPluginConfig.pluginModule) {
// Support both CommonJS and ES modules
return (normalizedPluginConfig.pluginModule.module.default?.validateOptions ??
normalizedPluginConfig.pluginModule.module.validateOptions);
}
return normalizedPluginConfig.plugin.validateOptions;
}
function getThemeValidationFunction(normalizedPluginConfig) {
if (normalizedPluginConfig.pluginModule) {
// Support both CommonJS and ES modules
return (normalizedPluginConfig.pluginModule.module.default?.validateThemeConfig ??
normalizedPluginConfig.pluginModule.module.validateThemeConfig);
}
return normalizedPluginConfig.plugin.validateThemeConfig;
}
/**
* Runs the plugin constructors and returns their return values. It would load
* plugin configs from `plugins`, `themes`, and `presets`.
*/
async function initPlugins(context) {
// We need to resolve plugins from the perspective of the site config, as if
// we are using `require.resolve` on those module names.
const pluginRequire = (0, module_1.createRequire)(context.siteConfigPath);
const pluginConfigs = await (0, configs_1.loadPluginConfigs)(context);
async function doGetPluginVersion(normalizedPluginConfig) {
if (normalizedPluginConfig.pluginModule?.path) {
const pluginPath = pluginRequire.resolve(normalizedPluginConfig.pluginModule.path);
return (0, siteMetadata_1.getPluginVersion)(pluginPath, context.siteDir);
}
return { type: 'local' };
}
function doValidateThemeConfig(normalizedPluginConfig) {
const validateThemeConfig = getThemeValidationFunction(normalizedPluginConfig);
if (validateThemeConfig) {
return validateThemeConfig({
validate: utils_validation_1.normalizeThemeConfig,
themeConfig: context.siteConfig.themeConfig,
});
}
return context.siteConfig.themeConfig;
}
function doValidatePluginOptions(normalizedPluginConfig) {
const validateOptions = getOptionValidationFunction(normalizedPluginConfig);
if (validateOptions) {
return validateOptions({
validate: utils_validation_1.normalizePluginOptions,
options: normalizedPluginConfig.options,
});
}
// Important to ensure all plugins have an id
// as we don't go through the Joi schema that adds it
return {
...normalizedPluginConfig.options,
id: normalizedPluginConfig.options.id ?? utils_1.DEFAULT_PLUGIN_ID,
};
}
async function initializePlugin(normalizedPluginConfig) {
const pluginVersion = await doGetPluginVersion(normalizedPluginConfig);
const pluginOptions = doValidatePluginOptions(normalizedPluginConfig);
// Side-effect: merge the normalized theme config in the original one
context.siteConfig.themeConfig = {
...context.siteConfig.themeConfig,
...doValidateThemeConfig(normalizedPluginConfig),
};
const pluginInstance = await normalizedPluginConfig.plugin(context, pluginOptions);
return {
...pluginInstance,
options: pluginOptions,
version: pluginVersion,
path: path_1.default.dirname(normalizedPluginConfig.entryPath),
};
}
const plugins = await Promise.all(pluginConfigs.map(initializePlugin));
(0, pluginIds_1.ensureUniquePluginInstanceIds)(plugins);
return plugins;
}
exports.initPlugins = initPlugins;

View File

@@ -0,0 +1,9 @@
/**
* 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.
*/
/// <reference types="node" />
export declare function getNamePatterns(moduleName: string, moduleType: 'preset' | 'theme' | 'plugin'): string[];
export declare function resolveModuleName(moduleName: string, moduleRequire: NodeRequire, moduleType: 'preset' | 'theme' | 'plugin'): string;

View File

@@ -0,0 +1,46 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveModuleName = exports.getNamePatterns = void 0;
function getNamePatterns(moduleName, moduleType) {
if (moduleName.startsWith('@')) {
// Pure scope: `@scope` => `@scope/docusaurus-plugin`
if (!moduleName.includes('/')) {
return [`${moduleName}/docusaurus-${moduleType}`];
}
const [scope, packageName] = moduleName.split(/\/(?<rest>.*)/);
return [
`${scope}/${packageName}`,
`${scope}/docusaurus-${moduleType}-${packageName}`,
];
}
return [
moduleName,
`@docusaurus/${moduleType}-${moduleName}`,
`docusaurus-${moduleType}-${moduleName}`,
];
}
exports.getNamePatterns = getNamePatterns;
function resolveModuleName(moduleName, moduleRequire, moduleType) {
const modulePatterns = getNamePatterns(moduleName, moduleType);
const module = modulePatterns.find((m) => {
try {
moduleRequire.resolve(m);
return true;
}
catch {
return false;
}
});
if (!module) {
throw new Error(`Docusaurus was unable to resolve the "${moduleName}" ${moduleType}. Make sure one of the following packages are installed:
${modulePatterns.map((m) => `- ${m}`).join('\n')}`);
}
return module;
}
exports.resolveModuleName = resolveModuleName;

View File

@@ -0,0 +1,12 @@
/**
* 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 { InitializedPlugin } from '@docusaurus/types';
/**
* It is forbidden to have 2 plugins of the same name sharing the same ID.
* This is required to support multi-instance plugins without conflict.
*/
export declare function ensureUniquePluginInstanceIds(plugins: InitializedPlugin[]): void;

View File

@@ -0,0 +1,30 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ensureUniquePluginInstanceIds = void 0;
const tslib_1 = require("tslib");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const utils_1 = require("@docusaurus/utils");
/**
* It is forbidden to have 2 plugins of the same name sharing the same ID.
* This is required to support multi-instance plugins without conflict.
*/
function ensureUniquePluginInstanceIds(plugins) {
const pluginsByName = lodash_1.default.groupBy(plugins, (p) => p.name);
Object.entries(pluginsByName).forEach(([pluginName, pluginInstances]) => {
const pluginInstancesById = lodash_1.default.groupBy(pluginInstances, (p) => p.options.id ?? utils_1.DEFAULT_PLUGIN_ID);
Object.entries(pluginInstancesById).forEach(([pluginId, pluginInstancesWithId]) => {
if (pluginInstancesWithId.length !== 1) {
throw new Error(`Plugin "${pluginName}" is used ${pluginInstancesWithId.length} times with ID "${pluginId}".\nTo use the same plugin multiple times on a Docusaurus site, you need to assign a unique ID to each plugin instance.${pluginId === utils_1.DEFAULT_PLUGIN_ID
? `\n\nThe plugin ID is "${utils_1.DEFAULT_PLUGIN_ID}" by default. It's possible that the preset you are using already includes a plugin instance, in which case you either want to disable the plugin in the preset (to use a single instance), or assign another ID to your extra plugin instance (to use multiple instances).`
: ''}`);
}
});
});
}
exports.ensureUniquePluginInstanceIds = ensureUniquePluginInstanceIds;

View File

@@ -0,0 +1,12 @@
/**
* 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 { LoadContext, DocusaurusConfig } from '@docusaurus/types';
/**
* Calls preset functions, aggregates each of their return values, and returns
* the plugin and theme configs.
*/
export declare function loadPresets(context: LoadContext): Promise<Pick<DocusaurusConfig, 'plugins' | 'themes'>>;

View File

@@ -0,0 +1,42 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadPresets = void 0;
const module_1 = require("module");
const utils_1 = require("@docusaurus/utils");
const moduleShorthand_1 = require("./moduleShorthand");
/**
* Calls preset functions, aggregates each of their return values, and returns
* the plugin and theme configs.
*/
async function loadPresets(context) {
// We need to resolve plugins from the perspective of the site config, as if
// we are using `require.resolve` on those module names.
const presetRequire = (0, module_1.createRequire)(context.siteConfigPath);
const presets = context.siteConfig.presets.filter((p) => !!p);
async function loadPreset(presetItem) {
let presetModuleImport;
let presetOptions = {};
if (typeof presetItem === 'string') {
presetModuleImport = presetItem;
}
else {
[presetModuleImport, presetOptions] = presetItem;
}
const presetName = (0, moduleShorthand_1.resolveModuleName)(presetModuleImport, presetRequire, 'preset');
const presetPath = presetRequire.resolve(presetName);
const presetModule = (await (0, utils_1.loadFreshModule)(presetPath));
const presetFunction = presetModule.default ?? presetModule;
return presetFunction(context, presetOptions);
}
const loadedPresets = await Promise.all(presets.map(loadPreset));
const plugins = loadedPresets.flatMap((preset) => preset.plugins ?? []);
const themes = loadedPresets.flatMap((preset) => preset.themes ?? []);
return { plugins, themes };
}
exports.loadPresets = loadPresets;

View File

@@ -0,0 +1,11 @@
/**
* 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 ApplyTrailingSlashParams } from '@docusaurus/utils-common';
import type { RouteConfig } from '@docusaurus/types';
/** Recursively applies trailing slash config to all nested routes. */
export declare function applyRouteTrailingSlash(route: RouteConfig, params: ApplyTrailingSlashParams): RouteConfig;
export declare function sortConfig(routeConfigs: RouteConfig[], baseUrl?: string): void;

View File

@@ -0,0 +1,56 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.sortConfig = exports.applyRouteTrailingSlash = void 0;
const utils_common_1 = require("@docusaurus/utils-common");
/** Recursively applies trailing slash config to all nested routes. */
function applyRouteTrailingSlash(route, params) {
return {
...route,
path: (0, utils_common_1.applyTrailingSlash)(route.path, params),
...(route.routes && {
routes: route.routes.map((subroute) => applyRouteTrailingSlash(subroute, params)),
}),
};
}
exports.applyRouteTrailingSlash = applyRouteTrailingSlash;
function sortConfig(routeConfigs, baseUrl = '/') {
// Sort the route config. This ensures that route with nested
// routes is always placed last.
routeConfigs.sort((a, b) => {
// Root route should get placed last.
if (a.path === baseUrl && b.path !== baseUrl) {
return 1;
}
if (a.path !== baseUrl && b.path === baseUrl) {
return -1;
}
if (a.routes && !b.routes) {
return 1;
}
if (!a.routes && b.routes) {
return -1;
}
// Higher priority get placed first.
if (a.priority || b.priority) {
const priorityA = a.priority ?? 0;
const priorityB = b.priority ?? 0;
const score = priorityB - priorityA;
if (score !== 0) {
return score;
}
}
return a.path.localeCompare(b.path);
});
routeConfigs.forEach((routeConfig) => {
if (routeConfig.routes) {
sortConfig(routeConfig.routes, baseUrl);
}
});
}
exports.sortConfig = sortConfig;

View File

@@ -0,0 +1,20 @@
/**
* 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 { LoadedPlugin, LoadContext } from '@docusaurus/types';
/**
* Make a synthetic plugin to:
* - Inject site client modules
* - Inject scripts/stylesheets
*/
export declare function createBootstrapPlugin({ siteDir, siteConfig, }: LoadContext): LoadedPlugin;
/**
* Configure Webpack fallback mdx loader for md/mdx files out of content-plugin
* folders. Adds a "fallback" mdx loader for mdx files that are not processed by
* content plugins. This allows to do things such as importing repo/README.md as
* a partial from another doc. Not ideal solution, but good enough for now
*/
export declare function createMDXFallbackPlugin({ siteDir, siteConfig, }: LoadContext): LoadedPlugin;

View File

@@ -0,0 +1,111 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createMDXFallbackPlugin = exports.createBootstrapPlugin = void 0;
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
/**
* Make a synthetic plugin to:
* - Inject site client modules
* - Inject scripts/stylesheets
*/
function createBootstrapPlugin({ siteDir, siteConfig, }) {
const { stylesheets, scripts, headTags, clientModules: siteConfigClientModules, } = siteConfig;
return {
name: 'docusaurus-bootstrap-plugin',
content: null,
options: {
id: 'default',
},
version: { type: 'synthetic' },
path: siteDir,
getClientModules() {
return siteConfigClientModules;
},
injectHtmlTags: () => {
const stylesheetsTags = stylesheets.map((source) => typeof source === 'string'
? `<link rel="stylesheet" href="${source}">`
: {
tagName: 'link',
attributes: {
rel: 'stylesheet',
...source,
},
});
const scriptsTags = scripts.map((source) => typeof source === 'string'
? `<script src="${source}"></script>`
: {
tagName: 'script',
attributes: {
...source,
},
});
return {
headTags: [...headTags, ...stylesheetsTags, ...scriptsTags],
};
},
};
}
exports.createBootstrapPlugin = createBootstrapPlugin;
/**
* Configure Webpack fallback mdx loader for md/mdx files out of content-plugin
* folders. Adds a "fallback" mdx loader for mdx files that are not processed by
* content plugins. This allows to do things such as importing repo/README.md as
* a partial from another doc. Not ideal solution, but good enough for now
*/
function createMDXFallbackPlugin({ siteDir, siteConfig, }) {
return {
name: 'docusaurus-mdx-fallback-plugin',
content: null,
options: {
id: 'default',
},
version: { type: 'synthetic' },
// Synthetic, the path doesn't matter much
path: '.',
configureWebpack(config) {
// We need the mdx fallback loader to exclude files that were already
// processed by content plugins mdx loaders. This works, but a bit
// hacky... Not sure there's a way to handle that differently in webpack
function getMDXFallbackExcludedPaths() {
const rules = config.module?.rules;
return rules.flatMap((rule) => {
const isMDXRule = rule.test instanceof RegExp && rule.test.test('x.mdx');
return isMDXRule ? rule.include : [];
});
}
const mdxLoaderOptions = {
admonitions: true,
staticDirs: siteConfig.staticDirectories.map((dir) => path_1.default.resolve(siteDir, dir)),
siteDir,
// External MDX files are always meant to be imported as partials
isMDXPartial: () => true,
// External MDX files might have front matter, just disable the warning
isMDXPartialFrontMatterWarningDisabled: true,
markdownConfig: siteConfig.markdown,
};
return {
module: {
rules: [
{
test: /\.mdx?$/i,
exclude: getMDXFallbackExcludedPaths(),
use: [
{
loader: require.resolve('@docusaurus/mdx-loader'),
options: mdxLoaderOptions,
},
],
},
],
},
};
},
};
}
exports.createMDXFallbackPlugin = createMDXFallbackPlugin;

51
node_modules/@docusaurus/core/lib/server/routes.d.ts generated vendored Normal file
View File

@@ -0,0 +1,51 @@
/**
* 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 { RouteConfig, RouteChunkNames, ReportingSeverity } from '@docusaurus/types';
type LoadedRoutes = {
/** Serialized routes config that can be directly emitted into temp file. */
routesConfig: string;
/** @see {ChunkNames} */
routesChunkNames: RouteChunkNames;
/**
* A map from chunk name to module paths. Module paths would have backslash
* escaped already, so they can be directly printed.
*/
registry: {
[chunkName: string]: string;
};
/**
* Collect all page paths for injecting it later in the plugin lifecycle.
* This is useful for plugins like sitemaps, redirects etc... Only collects
* "actual" pages, i.e. those without subroutes, because if a route has
* subroutes, it is probably a wrapper.
*/
routesPaths: string[];
};
/**
* Generates a unique chunk name that can be used in the chunk registry.
*
* @param modulePath A path to generate chunk name from. The actual value has no
* semantic significance.
* @param prefix A prefix to append to the chunk name, to avoid name clash.
* @param preferredName Chunk names default to `modulePath`, and this can supply
* a more human-readable name.
* @param shortId When `true`, the chunk name would only be a hash without any
* other characters. Useful for bundle size. Defaults to `true` in production.
*/
export declare function genChunkName(modulePath: string, prefix?: string, preferredName?: string, shortId?: boolean): string;
export declare function handleDuplicateRoutes(pluginsRouteConfigs: RouteConfig[], onDuplicateRoutes: ReportingSeverity): void;
/**
* Routes are prepared into three temp files:
*
* - `routesConfig`, the route config passed to react-router. This file is kept
* minimal, because it can't be code-splitted.
* - `routesChunkNames`, a mapping from route paths (hashed) to code-splitted
* chunk names.
* - `registry`, a mapping from chunk names to options for react-loadable.
*/
export declare function loadRoutes(routeConfigs: RouteConfig[], baseUrl: string, onDuplicateRoutes: ReportingSeverity): LoadedRoutes;
export {};

205
node_modules/@docusaurus/core/lib/server/routes.js generated vendored Normal file
View File

@@ -0,0 +1,205 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadRoutes = exports.handleDuplicateRoutes = exports.genChunkName = void 0;
const tslib_1 = require("tslib");
const querystring_1 = tslib_1.__importDefault(require("querystring"));
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const utils_1 = require("@docusaurus/utils");
const utils_2 = require("./utils");
/** Indents every line of `str` by one level. */
function indent(str) {
return ` ${str.replace(/\n/g, `\n `)}`;
}
const chunkNameCache = new Map();
const chunkNameCount = new Map();
/**
* Generates a unique chunk name that can be used in the chunk registry.
*
* @param modulePath A path to generate chunk name from. The actual value has no
* semantic significance.
* @param prefix A prefix to append to the chunk name, to avoid name clash.
* @param preferredName Chunk names default to `modulePath`, and this can supply
* a more human-readable name.
* @param shortId When `true`, the chunk name would only be a hash without any
* other characters. Useful for bundle size. Defaults to `true` in production.
*/
function genChunkName(modulePath, prefix, preferredName, shortId = process.env.NODE_ENV === 'production') {
let chunkName = chunkNameCache.get(modulePath);
if (!chunkName) {
if (shortId) {
chunkName = (0, utils_1.simpleHash)(modulePath, 8);
}
else {
let str = modulePath;
if (preferredName) {
const shortHash = (0, utils_1.simpleHash)(modulePath, 3);
str = `${preferredName}${shortHash}`;
}
const name = (0, utils_1.docuHash)(str);
chunkName = prefix ? `${prefix}---${name}` : name;
}
const seenCount = (chunkNameCount.get(chunkName) ?? 0) + 1;
if (seenCount > 1) {
chunkName += seenCount.toString(36);
}
chunkNameCache.set(modulePath, chunkName);
chunkNameCount.set(chunkName, seenCount);
}
return chunkName;
}
exports.genChunkName = genChunkName;
/**
* Takes a piece of route config, and serializes it into raw JS code. The shape
* is the same as react-router's `RouteConfig`. Formatting is similar to
* `JSON.stringify` but without all the quotes.
*/
function serializeRouteConfig({ routePath, routeHash, exact, subroutesCodeStrings, props, }) {
const parts = [
`path: '${routePath}'`,
`component: ComponentCreator('${routePath}', '${routeHash}')`,
];
if (exact) {
parts.push(`exact: true`);
}
if (subroutesCodeStrings) {
parts.push(`routes: [
${indent(subroutesCodeStrings.join(',\n'))}
]`);
}
Object.entries(props).forEach(([propName, propValue]) => {
const isIdentifier = /^[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*$/u.test(propName);
const key = isIdentifier ? propName : JSON.stringify(propName);
parts.push(`${key}: ${JSON.stringify(propValue)}`);
});
return `{
${indent(parts.join(',\n'))}
}`;
}
const isModule = (value) => typeof value === 'string' ||
(typeof value === 'object' &&
// eslint-disable-next-line no-underscore-dangle
!!value?.__import);
/**
* Takes a {@link Module} (which is nothing more than a path plus some metadata
* like query) and returns the string path it represents.
*/
function getModulePath(target) {
if (typeof target === 'string') {
return target;
}
const queryStr = target.query ? `?${querystring_1.default.stringify(target.query)}` : '';
return `${target.path}${queryStr}`;
}
function genChunkNames(routeModule, prefix, name, res) {
if (isModule(routeModule)) {
// This is a leaf node, no need to recurse
const modulePath = getModulePath(routeModule);
const chunkName = genChunkName(modulePath, prefix, name);
res.registry[chunkName] = (0, utils_1.escapePath)(modulePath);
return chunkName;
}
if (Array.isArray(routeModule)) {
return routeModule.map((val, index) => genChunkNames(val, `${index}`, name, res));
}
return lodash_1.default.mapValues(routeModule, (v, key) => genChunkNames(v, key, name, res));
}
function handleDuplicateRoutes(pluginsRouteConfigs, onDuplicateRoutes) {
if (onDuplicateRoutes === 'ignore') {
return;
}
const allRoutes = (0, utils_2.getAllFinalRoutes)(pluginsRouteConfigs).map((routeConfig) => routeConfig.path);
const seenRoutes = new Set();
const duplicatePaths = allRoutes.filter((route) => {
if (seenRoutes.has(route)) {
return true;
}
seenRoutes.add(route);
return false;
});
if (duplicatePaths.length > 0) {
logger_1.default.report(onDuplicateRoutes) `Duplicate routes found!${duplicatePaths.map((duplicateRoute) => logger_1.default.interpolate `Attempting to create page at url=${duplicateRoute}, but a page already exists at this route.`)}
This could lead to non-deterministic routing behavior.`;
}
}
exports.handleDuplicateRoutes = handleDuplicateRoutes;
/**
* This is the higher level overview of route code generation. For each route
* config node, it returns the node's serialized form, and mutates `registry`,
* `routesPaths`, and `routesChunkNames` accordingly.
*/
function genRouteCode(routeConfig, res) {
const { path: routePath, component, modules = {}, context, routes: subroutes, priority, exact, ...props } = routeConfig;
if (typeof routePath !== 'string' || !component) {
throw new Error(`Invalid route config: path must be a string and component is required.
${JSON.stringify(routeConfig)}`);
}
if (!subroutes) {
res.routesPaths.push(routePath);
}
const routeHash = (0, utils_1.simpleHash)(JSON.stringify(routeConfig), 3);
res.routesChunkNames[`${routePath}-${routeHash}`] = {
// Avoid clash with a prop called "component"
...genChunkNames({ __comp: component }, 'component', component, res),
...(context &&
genChunkNames({ __context: context }, 'context', routePath, res)),
...genChunkNames(modules, 'module', routePath, res),
};
return serializeRouteConfig({
routePath: routePath.replace(/'/g, "\\'"),
routeHash,
subroutesCodeStrings: subroutes?.map((r) => genRouteCode(r, res)),
exact,
props,
});
}
/**
* Old stuff
* As far as I understand, this is what permits to SSG the 404.html file
* This is rendered through the catch-all ComponentCreator("*") route
* Note CDNs only understand the 404.html file by convention
* The extension probably permits to avoid emitting "/404/index.html"
*/
const NotFoundRoutePath = '/404.html';
/**
* Routes are prepared into three temp files:
*
* - `routesConfig`, the route config passed to react-router. This file is kept
* minimal, because it can't be code-splitted.
* - `routesChunkNames`, a mapping from route paths (hashed) to code-splitted
* chunk names.
* - `registry`, a mapping from chunk names to options for react-loadable.
*/
function loadRoutes(routeConfigs, baseUrl, onDuplicateRoutes) {
handleDuplicateRoutes(routeConfigs, onDuplicateRoutes);
const res = {
// To be written by `genRouteCode`
routesConfig: '',
routesChunkNames: {},
registry: {},
routesPaths: [(0, utils_1.normalizeUrl)([baseUrl, NotFoundRoutePath])],
};
// `genRouteCode` would mutate `res`
const routeConfigSerialized = routeConfigs
.map((r) => genRouteCode(r, res))
.join(',\n');
res.routesConfig = `import React from 'react';
import ComponentCreator from '@docusaurus/ComponentCreator';
export default [
${indent(routeConfigSerialized)},
{
path: '*',
component: ComponentCreator('*'),
},
];
`;
return res;
}
exports.loadRoutes = loadRoutes;

View File

@@ -0,0 +1,12 @@
/**
* 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 { LoadedPlugin, PluginVersionInformation, SiteMetadata } from '@docusaurus/types';
export declare function getPluginVersion(pluginPath: string, siteDir: string): Promise<PluginVersionInformation>;
export declare function loadSiteMetadata({ plugins, siteDir, }: {
plugins: LoadedPlugin[];
siteDir: string;
}): Promise<SiteMetadata>;

View File

@@ -0,0 +1,79 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadSiteMetadata = exports.getPluginVersion = void 0;
const tslib_1 = require("tslib");
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const path_1 = tslib_1.__importDefault(require("path"));
const utils_1 = require("@docusaurus/utils");
async function getPackageJsonVersion(packageJsonPath) {
if (await fs_extra_1.default.pathExists(packageJsonPath)) {
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require
return require(packageJsonPath).version;
}
return undefined;
}
async function getPackageJsonName(packageJsonPath) {
// eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-dynamic-require, global-require
return require(packageJsonPath).name;
}
async function getPluginVersion(pluginPath, siteDir) {
let potentialPluginPackageJsonDirectory = path_1.default.dirname(pluginPath);
while (potentialPluginPackageJsonDirectory !== '/') {
const packageJsonPath = path_1.default.join(potentialPluginPackageJsonDirectory, 'package.json');
if ((await fs_extra_1.default.pathExists(packageJsonPath)) &&
(await fs_extra_1.default.lstat(packageJsonPath)).isFile()) {
if (potentialPluginPackageJsonDirectory === siteDir) {
// If the plugin belongs to the same docusaurus project, we classify it
// as local plugin.
return { type: 'project' };
}
return {
type: 'package',
name: await getPackageJsonName(packageJsonPath),
version: await getPackageJsonVersion(packageJsonPath),
};
}
potentialPluginPackageJsonDirectory = path_1.default.dirname(potentialPluginPackageJsonDirectory);
}
// In the case where a plugin is a path where no parent directory contains
// package.json, we can only classify it as local. Could happen if one puts a
// script in the parent directory of the site.
return { type: 'local' };
}
exports.getPluginVersion = getPluginVersion;
/**
* We want all `@docusaurus/*` packages to have the exact same version!
* @see https://github.com/facebook/docusaurus/issues/3371
* @see https://github.com/facebook/docusaurus/pull/3386
*/
function checkDocusaurusPackagesVersion(siteMetadata) {
const { docusaurusVersion } = siteMetadata;
Object.entries(siteMetadata.pluginVersions).forEach(([plugin, versionInfo]) => {
if (versionInfo.type === 'package' &&
versionInfo.name?.startsWith('@docusaurus/') &&
versionInfo.version &&
versionInfo.version !== docusaurusVersion) {
throw new Error(`Invalid name=${plugin} version number=${versionInfo.version}.
All official @docusaurus/* packages should have the exact same version as @docusaurus/core (number=${docusaurusVersion}).
Maybe you want to check, or regenerate your yarn.lock or package-lock.json file?`);
}
});
}
async function loadSiteMetadata({ plugins, siteDir, }) {
const siteMetadata = {
docusaurusVersion: utils_1.DOCUSAURUS_VERSION,
siteVersion: await getPackageJsonVersion(path_1.default.join(siteDir, 'package.json')),
pluginVersions: Object.fromEntries(plugins
.filter(({ version: { type } }) => type !== 'synthetic')
.map(({ name, version }) => [name, version])),
};
checkDocusaurusPackagesVersion(siteMetadata);
return siteMetadata;
}
exports.loadSiteMetadata = loadSiteMetadata;

View File

@@ -0,0 +1,31 @@
/**
* 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 { TranslationFileContent, TranslationFile, CodeTranslations, InitializedPlugin } from '@docusaurus/types';
export type WriteTranslationsOptions = {
override?: boolean;
messagePrefix?: string;
};
type TranslationContext = {
localizationDir: string;
};
export declare function readCodeTranslationFileContent(context: TranslationContext): Promise<TranslationFileContent | undefined>;
export declare function writeCodeTranslations(context: TranslationContext, content: TranslationFileContent, options: WriteTranslationsOptions): Promise<void>;
export declare function writePluginTranslations({ localizationDir, plugin, translationFile, options, }: TranslationContext & {
plugin: InitializedPlugin;
translationFile: TranslationFile;
options?: WriteTranslationsOptions;
}): Promise<void>;
export declare function localizePluginTranslationFile({ localizationDir, plugin, translationFile, }: TranslationContext & {
plugin: InitializedPlugin;
translationFile: TranslationFile;
}): Promise<TranslationFile>;
export declare function getPluginsDefaultCodeTranslationMessages(plugins: InitializedPlugin[]): Promise<CodeTranslations>;
export declare function applyDefaultCodeTranslations({ extractedCodeTranslations, defaultCodeMessages, }: {
extractedCodeTranslations: TranslationFileContent;
defaultCodeMessages: CodeTranslations;
}): TranslationFileContent;
export {};

View File

@@ -0,0 +1,164 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.applyDefaultCodeTranslations = exports.getPluginsDefaultCodeTranslationMessages = exports.localizePluginTranslationFile = exports.writePluginTranslations = exports.writeCodeTranslations = exports.readCodeTranslationFileContent = void 0;
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const utils_1 = require("@docusaurus/utils");
const utils_validation_1 = require("@docusaurus/utils-validation");
const TranslationFileContentSchema = utils_validation_1.Joi.object()
.pattern(utils_validation_1.Joi.string(), utils_validation_1.Joi.object({
message: utils_validation_1.Joi.string().allow('').required(),
description: utils_validation_1.Joi.string().optional(),
}))
.required();
function ensureTranslationFileContent(content) {
utils_validation_1.Joi.attempt(content, TranslationFileContentSchema, {
abortEarly: false,
allowUnknown: false,
convert: false,
});
}
async function readTranslationFileContent(filePath) {
if (await fs_extra_1.default.pathExists(filePath)) {
try {
const content = await fs_extra_1.default.readJSON(filePath);
ensureTranslationFileContent(content);
return content;
}
catch (err) {
logger_1.default.error `Invalid translation file at path=${filePath}.`;
throw err;
}
}
return undefined;
}
function mergeTranslationFileContent({ existingContent = {}, newContent, options, }) {
// Apply messagePrefix to all messages
const newContentTransformed = lodash_1.default.mapValues(newContent, (value) => ({
...value,
message: `${options.messagePrefix ?? ''}${value.message}`,
}));
const result = { ...existingContent };
// We only add missing keys here, we don't delete existing ones
Object.entries(newContentTransformed).forEach(([key, { message, description }]) => {
result[key] = {
// If messages already exist, we don't override them (unless requested)
message: options.override
? message
: existingContent[key]?.message ?? message,
description,
};
});
return result;
}
async function writeTranslationFileContent({ filePath, content: newContent, options = {}, }) {
const existingContent = await readTranslationFileContent(filePath);
// Warn about potential legacy keys
const unknownKeys = lodash_1.default.difference(Object.keys(existingContent ?? {}), Object.keys(newContent));
if (unknownKeys.length > 0) {
logger_1.default.warn `Some translation keys looks unknown to us in file path=${filePath}.
Maybe you should remove them? ${unknownKeys}`;
}
const mergedContent = mergeTranslationFileContent({
existingContent,
newContent,
options,
});
// Avoid creating empty translation files
if (Object.keys(mergedContent).length > 0) {
logger_1.default.info `number=${Object.keys(mergedContent).length} translations will be written at path=${(0, utils_1.toMessageRelativeFilePath)(filePath)}.`;
await fs_extra_1.default.outputFile(filePath, `${JSON.stringify(mergedContent, null, 2)}\n`);
}
}
function getCodeTranslationsFilePath(context) {
return path_1.default.join(context.localizationDir, utils_1.CODE_TRANSLATIONS_FILE_NAME);
}
async function readCodeTranslationFileContent(context) {
return readTranslationFileContent(getCodeTranslationsFilePath(context));
}
exports.readCodeTranslationFileContent = readCodeTranslationFileContent;
async function writeCodeTranslations(context, content, options) {
return writeTranslationFileContent({
filePath: getCodeTranslationsFilePath(context),
content,
options,
});
}
exports.writeCodeTranslations = writeCodeTranslations;
// We ask users to not provide any extension on purpose:
// maybe some day we'll want to support multiple FS formats?
// (json/yaml/toml/xml...)
function addTranslationFileExtension(translationFilePath) {
if (translationFilePath.endsWith('.json')) {
throw new Error(`Translation file path at "${translationFilePath}" does not need to end with ".json", we add the extension automatically.`);
}
return `${translationFilePath}.json`;
}
function getPluginTranslationFilePath({ localizationDir, plugin, translationFilePath, }) {
const dirPath = (0, utils_1.getPluginI18nPath)({
localizationDir,
pluginName: plugin.name,
pluginId: plugin.options.id,
});
const filePath = addTranslationFileExtension(translationFilePath);
return path_1.default.join(dirPath, filePath);
}
async function writePluginTranslations({ localizationDir, plugin, translationFile, options, }) {
const filePath = getPluginTranslationFilePath({
plugin,
localizationDir,
translationFilePath: translationFile.path,
});
await writeTranslationFileContent({
filePath,
content: translationFile.content,
options,
});
}
exports.writePluginTranslations = writePluginTranslations;
async function localizePluginTranslationFile({ localizationDir, plugin, translationFile, }) {
const filePath = getPluginTranslationFilePath({
plugin,
localizationDir,
translationFilePath: translationFile.path,
});
const localizedContent = await readTranslationFileContent(filePath);
if (localizedContent) {
// Localized messages "override" default unlocalized messages
return {
path: translationFile.path,
content: {
...translationFile.content,
...localizedContent,
},
};
}
return translationFile;
}
exports.localizePluginTranslationFile = localizePluginTranslationFile;
async function getPluginsDefaultCodeTranslationMessages(plugins) {
const pluginsMessages = await Promise.all(plugins.map((plugin) => plugin.getDefaultCodeTranslationMessages?.() ?? {}));
return pluginsMessages.reduce((allMessages, pluginMessages) => ({ ...allMessages, ...pluginMessages }), {});
}
exports.getPluginsDefaultCodeTranslationMessages = getPluginsDefaultCodeTranslationMessages;
function applyDefaultCodeTranslations({ extractedCodeTranslations, defaultCodeMessages, }) {
const unusedDefaultCodeMessages = lodash_1.default.difference(Object.keys(defaultCodeMessages), Object.keys(extractedCodeTranslations));
if (unusedDefaultCodeMessages.length > 0) {
logger_1.default.warn `Unused default message codes found.
Please report this Docusaurus issue. name=${unusedDefaultCodeMessages}`;
}
return lodash_1.default.mapValues(extractedCodeTranslations, (messageTranslation, messageId) => ({
...messageTranslation,
message: defaultCodeMessages[messageId] ?? messageTranslation.message,
}));
}
exports.applyDefaultCodeTranslations = applyDefaultCodeTranslations;

View File

@@ -0,0 +1,18 @@
/**
* 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 TransformOptions } from '@babel/core';
import type { InitializedPlugin, TranslationFileContent } from '@docusaurus/types';
export declare function globSourceCodeFilePaths(dirPaths: string[]): Promise<string[]>;
export declare function extractSiteSourceCodeTranslations(siteDir: string, plugins: InitializedPlugin[], babelOptions: TransformOptions, extraSourceCodeFilePaths?: string[]): Promise<TranslationFileContent>;
type SourceCodeFileTranslations = {
sourceCodeFilePath: string;
translations: TranslationFileContent;
warnings: string[];
};
export declare function extractAllSourceCodeFileTranslations(sourceCodeFilePaths: string[], babelOptions: TransformOptions): Promise<SourceCodeFileTranslations[]>;
export declare function extractSourceCodeFileTranslations(sourceCodeFilePath: string, babelOptions: TransformOptions): Promise<SourceCodeFileTranslations>;
export {};

View File

@@ -0,0 +1,255 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractSourceCodeFileTranslations = exports.extractAllSourceCodeFileTranslations = exports.extractSiteSourceCodeTranslations = exports.globSourceCodeFilePaths = void 0;
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
const traverse_1 = tslib_1.__importDefault(require("@babel/traverse"));
const generator_1 = tslib_1.__importDefault(require("@babel/generator"));
const core_1 = require("@babel/core");
const utils_1 = require("@docusaurus/utils");
const utils_2 = require("../utils");
// We only support extracting source code translations from these kind of files
const TranslatableSourceCodeExtension = new Set([
'.js',
'.jsx',
'.ts',
'.tsx',
// TODO support md/mdx too? (may be overkill)
// need to compile the MDX to JSX first and remove front matter
// '.md',
// '.mdx',
]);
function isTranslatableSourceCodePath(filePath) {
return TranslatableSourceCodeExtension.has(path_1.default.extname(filePath));
}
function getSiteSourceCodeFilePaths(siteDir) {
return [path_1.default.join(siteDir, utils_1.SRC_DIR_NAME)];
}
function getPluginSourceCodeFilePaths(plugin) {
// The getPathsToWatch() generally returns the js/jsx/ts/tsx/md/mdx file paths
// We can use this method as well to know which folders we should try to
// extract translations from. Hacky/implicit, but do we want to introduce a
// new lifecycle method just for that???
const codePaths = plugin.getPathsToWatch?.() ?? [];
// We also include theme code
const themePath = plugin.getThemePath?.();
if (themePath) {
codePaths.push(themePath);
}
return codePaths.map((p) => path_1.default.resolve(plugin.path, p));
}
async function globSourceCodeFilePaths(dirPaths) {
const filePaths = await (0, utils_2.safeGlobby)(dirPaths);
return filePaths.filter(isTranslatableSourceCodePath);
}
exports.globSourceCodeFilePaths = globSourceCodeFilePaths;
async function getSourceCodeFilePaths(siteDir, plugins) {
const sitePaths = getSiteSourceCodeFilePaths(siteDir);
// The getPathsToWatch() generally returns the js/jsx/ts/tsx/md/mdx file paths
// We can use this method as well to know which folders we should try to
// extract translations from. Hacky/implicit, but do we want to introduce a
// new lifecycle method for that???
const pluginsPaths = plugins.flatMap(getPluginSourceCodeFilePaths);
const allPaths = [...sitePaths, ...pluginsPaths];
return globSourceCodeFilePaths(allPaths);
}
async function extractSiteSourceCodeTranslations(siteDir, plugins, babelOptions, extraSourceCodeFilePaths = []) {
// Should we warn here if the same translation "key" is found in multiple
// source code files?
function toTranslationFileContent(sourceCodeFileTranslations) {
return sourceCodeFileTranslations.reduce((acc, item) => ({ ...acc, ...item.translations }), {});
}
const sourceCodeFilePaths = await getSourceCodeFilePaths(siteDir, plugins);
const allSourceCodeFilePaths = [
...sourceCodeFilePaths,
...extraSourceCodeFilePaths,
];
const sourceCodeFilesTranslations = await extractAllSourceCodeFileTranslations(allSourceCodeFilePaths, babelOptions);
logSourceCodeFileTranslationsWarnings(sourceCodeFilesTranslations);
return toTranslationFileContent(sourceCodeFilesTranslations);
}
exports.extractSiteSourceCodeTranslations = extractSiteSourceCodeTranslations;
function logSourceCodeFileTranslationsWarnings(sourceCodeFilesTranslations) {
sourceCodeFilesTranslations.forEach(({ sourceCodeFilePath, warnings }) => {
if (warnings.length > 0) {
logger_1.default.warn `Translation extraction warnings for file path=${sourceCodeFilePath}: ${warnings}`;
}
});
}
async function extractAllSourceCodeFileTranslations(sourceCodeFilePaths, babelOptions) {
return Promise.all(sourceCodeFilePaths.flatMap((sourceFilePath) => extractSourceCodeFileTranslations(sourceFilePath, babelOptions)));
}
exports.extractAllSourceCodeFileTranslations = extractAllSourceCodeFileTranslations;
async function extractSourceCodeFileTranslations(sourceCodeFilePath, babelOptions) {
try {
const code = await fs_extra_1.default.readFile(sourceCodeFilePath, 'utf8');
const ast = (0, core_1.parse)(code, {
...babelOptions,
ast: true,
// filename is important, because babel does not process the same files
// according to their js/ts extensions.
// See https://twitter.com/NicoloRibaudo/status/1321130735605002243
filename: sourceCodeFilePath,
});
const translations = extractSourceCodeAstTranslations(ast, sourceCodeFilePath);
return translations;
}
catch (err) {
logger_1.default.error `Error while attempting to extract Docusaurus translations from source code file at path=${sourceCodeFilePath}.`;
throw err;
}
}
exports.extractSourceCodeFileTranslations = extractSourceCodeFileTranslations;
/*
Need help understanding this?
Useful resources:
https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md
https://github.com/formatjs/formatjs/blob/main/packages/babel-plugin-formatjs/index.ts
https://github.com/pugjs/babel-walk
*/
function extractSourceCodeAstTranslations(ast, sourceCodeFilePath) {
function sourceWarningPart(node) {
return `File: ${sourceCodeFilePath} at line ${node.loc?.start.line ?? '?'}
Full code: ${(0, generator_1.default)(node).code}`;
}
const translations = {};
const warnings = [];
let translateComponentName;
let translateFunctionName;
// First pass: find import declarations of Translate / translate.
// If not found, don't process the rest to avoid false positives
(0, traverse_1.default)(ast, {
ImportDeclaration(path) {
if (path.node.importKind === 'type' ||
path.get('source').node.value !== '@docusaurus/Translate') {
return;
}
const importSpecifiers = path.get('specifiers');
const defaultImport = importSpecifiers.find((specifier) => specifier.node.type === 'ImportDefaultSpecifier');
const callbackImport = importSpecifiers.find((specifier) => specifier.node.type === 'ImportSpecifier' &&
(specifier.get('imported')
.node.name === 'translate' ||
specifier.get('imported')
.node.value === 'translate'));
translateComponentName = defaultImport?.get('local').node.name;
translateFunctionName = callbackImport?.get('local').node.name;
},
});
(0, traverse_1.default)(ast, {
...(translateComponentName && {
JSXElement(path) {
if (!path
.get('openingElement')
.get('name')
.isJSXIdentifier({ name: translateComponentName })) {
return;
}
function evaluateJSXProp(propName) {
const attributePath = path
.get('openingElement.attributes')
.find((attr) => attr.isJSXAttribute() &&
attr.get('name').isJSXIdentifier({ name: propName }));
if (attributePath) {
const attributeValue = attributePath.get('value');
const attributeValueEvaluated = attributeValue.isJSXExpressionContainer()
? attributeValue.get('expression').evaluate()
: attributeValue.evaluate();
if (attributeValueEvaluated.confident &&
typeof attributeValueEvaluated.value === 'string') {
return attributeValueEvaluated.value;
}
warnings.push(`<Translate> prop=${propName} should be a statically evaluable object.
Example: <Translate id="optional id" description="optional description">Message</Translate>
Dynamically constructed values are not allowed, because they prevent translations to be extracted.
${sourceWarningPart(path.node)}`);
}
return undefined;
}
const id = evaluateJSXProp('id');
const description = evaluateJSXProp('description');
let message;
const childrenPath = path.get('children');
// Handle empty content
if (!childrenPath.length) {
if (!id) {
warnings.push(`<Translate> without children must have id prop.
Example: <Translate id="my-id" />
${sourceWarningPart(path.node)}`);
}
else {
translations[id] = {
message: id,
...(description && { description }),
};
}
return;
}
// Handle single non-empty content
const singleChildren = childrenPath
// Remove empty/useless text nodes that might be around our
// translation! Makes the translation system more reliable to JSX
// formatting issues
.filter((children) => !(children.isJSXText() &&
children.node.value.replace('\n', '').trim() === ''))
.pop();
const isJSXText = singleChildren?.isJSXText();
const isJSXExpressionContainer = singleChildren?.isJSXExpressionContainer() &&
singleChildren.get('expression').evaluate().confident;
if (isJSXText || isJSXExpressionContainer) {
message = isJSXText
? singleChildren.node.value.trim().replace(/\s+/g, ' ')
: String(singleChildren.get('expression').evaluate().value);
translations[id ?? message] = {
message,
...(description && { description }),
};
}
else {
warnings.push(`Translate content could not be extracted. It has to be a static string and use optional but static props, like <Translate id="my-id" description="my-description">text</Translate>.
${sourceWarningPart(path.node)}`);
}
},
}),
...(translateFunctionName && {
CallExpression(path) {
if (!path.get('callee').isIdentifier({ name: translateFunctionName })) {
return;
}
const args = path.get('arguments');
if (args.length === 1 || args.length === 2) {
const firstArgPath = args[0];
// translate("x" + "y"); => translate("xy");
const firstArgEvaluated = firstArgPath.evaluate();
if (firstArgEvaluated.confident &&
typeof firstArgEvaluated.value === 'object') {
const { message, id, description } = firstArgEvaluated.value;
translations[String(id ?? message)] = {
message: String(message ?? id),
...(Boolean(description) && { description: String(description) }),
};
}
else {
warnings.push(`translate() first arg should be a statically evaluable object.
Example: translate({message: "text",id: "optional.id",description: "optional description"}
Dynamically constructed values are not allowed, because they prevent translations to be extracted.
${sourceWarningPart(path.node)}`);
}
}
else {
warnings.push(`translate() function only takes 1 or 2 args
${sourceWarningPart(path.node)}`);
}
},
}),
});
return { sourceCodeFilePath, translations, warnings };
}

10
node_modules/@docusaurus/core/lib/server/utils.d.ts generated vendored Normal file
View File

@@ -0,0 +1,10 @@
/**
* 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 { Globby } from '@docusaurus/utils';
import type { RouteConfig } from '@docusaurus/types';
export declare function getAllFinalRoutes(routeConfig: RouteConfig[]): RouteConfig[];
export declare function safeGlobby(patterns: string[], options?: Globby.GlobbyOptions): Promise<string[]>;

29
node_modules/@docusaurus/core/lib/server/utils.js generated vendored Normal file
View File

@@ -0,0 +1,29 @@
"use strict";
/**
* 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.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.safeGlobby = exports.getAllFinalRoutes = void 0;
const tslib_1 = require("tslib");
const path_1 = tslib_1.__importDefault(require("path"));
const utils_1 = require("@docusaurus/utils");
// Recursively get the final routes (routes with no subroutes)
function getAllFinalRoutes(routeConfig) {
function getFinalRoutes(route) {
return route.routes ? route.routes.flatMap(getFinalRoutes) : [route];
}
return routeConfig.flatMap(getFinalRoutes);
}
exports.getAllFinalRoutes = getAllFinalRoutes;
// Globby that fix Windows path patterns
// See https://github.com/facebook/docusaurus/pull/4222#issuecomment-795517329
async function safeGlobby(patterns, options) {
// Required for Windows support, as paths using \ should not be used by globby
// (also using the windows hard drive prefix like c: is not a good idea)
const globPaths = patterns.map((dirPath) => (0, utils_1.posixPath)(path_1.default.relative(process.cwd(), dirPath)));
return (0, utils_1.Globby)(globPaths, options);
}
exports.safeGlobby = safeGlobby;