UNPKG

@alauda/doom

Version:

Doctor Doom making docs.

395 lines (394 loc) 14.8 kB
import fs_ from 'node:fs'; import fs from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { pluginReact } from '@rsbuild/plugin-react'; import { pluginSass } from '@rsbuild/plugin-sass'; import { pluginSvgr } from '@rsbuild/plugin-svgr'; import { pluginYaml } from '@rsbuild/plugin-yaml'; import { logger } from '@rspress/core'; import { pluginAlgolia } from '@rspress/plugin-algolia'; import { pluginSitemap } from '@rspress/plugin-sitemap'; import { addLeadingSlash, addTrailingSlash, removeLeadingSlash, } from '@rspress/shared'; import { transformerMetaHighlight, transformerMetaWordHighlight, transformerNotationDiff, transformerNotationErrorLevel, transformerNotationFocus, transformerNotationHighlight, transformerNotationWordHighlight, transformerRemoveNotationEscape, } from '@shikijs/transformers'; import { difference } from 'es-toolkit'; import { glob } from 'tinyglobby'; import { cyan } from 'yoctocolors'; import { apiPlugin, autoSidebarPlugin, autoTocPlugin, createTransformerCallouts, directivesPlugin, globalPlugin, mermaidPlugin, permissionPlugin, replacePlugin, } from "../plugins/index.js"; import { isExplicitlyUnversioned, normalizeSlash, UNVERSIONED, } from "../shared/index.js"; import { pathExists, pkgResolve, resolveStaticConfig } from "../utils/index.js"; import { DEFAULT_CONFIG_NAME, DEFAULT_EXTENSIONS, I18N_FILE, SITES_FILE, YAML_EXTENSIONS, } from "./constants.js"; import { defaultGitHubUrl, isDoc } from "./helpers.js"; const DEFAULT_LOGO = '/logo.svg'; const KNOWN_LOCALE_CONFIGS = { en: { label: 'English', }, zh: { label: '简体中文', }, ru: { label: 'Русский', }, }; const ALAUDA_ALGOLIA_OPTIONS = { appId: 'XN35Q5JLSV', apiKey: '487fb969bdcda09dd4950468d7d8b61e', indexName: 'docs_alauda_io_xn35q5jlsv_pages', }; const ALAUDA_RU_ALGOLIA_OPTIONS = { appId: 'GBNIJIGQ50', apiKey: '786fa6da36fe76777d688387c0520c5b', indexName: 'docs_alauda_io_gbnijigq50_pages', }; const getCommonConfig = async ({ config, root, configFilePath, base, version, download, export: export_, ignore, force, open, lazy, include, exclude, redirect, editRepo, algolia, siteUrl, lang, }) => { if (lang != null) { config.lang = lang; } const fallbackToZh = 'lang' in config && !config.lang; root = resolveDocRoot(root, config.root); const localBasePath = configFilePath ? path.dirname(configFilePath) : root; const userBase = (base = addLeadingSlash(addTrailingSlash(base || config.base || '/'))); if (version && !isExplicitlyUnversioned(version)) { base = userBase + `${version}/`; } let { editRepoBaseUrl } = config; const editRepoEnabled = !!editRepo; if (typeof editRepo === 'string') { if (editRepo.includes('/')) { editRepoBaseUrl = editRepo; } else if (editRepoBaseUrl) { editRepoBaseUrl = addTrailingSlash(editRepoBaseUrl); if (!editRepoBaseUrl.endsWith(`/${editRepo}/`)) { editRepoBaseUrl += editRepo; } } } if (editRepoEnabled) { if (editRepoBaseUrl) { editRepoBaseUrl = defaultGitHubUrl(editRepoBaseUrl); } else { logger.error(`The \`${cyan('-R, --edit-repo')}\` flag is enabled specifically, but no \`${cyan('editRepoBaseUrl')}\` found, it will take no effect`); } } const allLanguages = []; const locales = []; if (!fallbackToZh) { const dirents = await fs.readdir(root, { withFileTypes: true }); for (const dirent of dirents) { const { name } = dirent; if (!dirent.isDirectory() || ['dist', 'node_modules', 'public', 'shared'].includes(name)) { continue; } allLanguages.push(name); if (include?.length) { if (!include.includes(name)) { continue; } } else if (exclude?.length) { if (exclude.includes(name)) { continue; } } locales.push({ lang: name, label: name, ...KNOWN_LOCALE_CONFIGS[name], }); } } const unusedLanguages = difference(allLanguages, locales.map(({ lang }) => lang)); const zhLocale = KNOWN_LOCALE_CONFIGS.zh; const algoliaOptions = ((algolia && config.algolia) ?? (algolia === 'alauda' ? ALAUDA_ALGOLIA_OPTIONS : algolia === 'alauda-ru' && ALAUDA_RU_ALGOLIA_OPTIONS)) || null; const publicPath = path.resolve(root, 'public'); return { userBase, root, base: addTrailingSlash(base), lang: fallbackToZh ? 'zh' : config.lang || 'en', title: '', route: { include: ignore ? config.onlyIncludeRoutes : undefined, exclude: [ 'dist/**/*', 'public/**/*', 'shared/**/*', 'doom.config.*', '**/assets/**/*', '**/*.d.ts', ...((ignore && config.internalRoutes) || []), ...unusedLanguages.map((lang) => `${lang}/**/*`), ], }, markdown: { defaultWrapCode: export_, link: { checkDeadLinks: { excludes(url) { if (!url.startsWith('/')) { return false; } const { pathname } = new URL(url, 'https://example.com'); return fs_.existsSync(path.resolve(publicPath, pathname.slice(1))); }, }, }, shiki: { transformers: [ // builtin transformers transformerMetaHighlight(), transformerMetaWordHighlight(), transformerNotationDiff(), transformerNotationErrorLevel(), transformerNotationFocus(), transformerNotationHighlight(), transformerNotationWordHighlight(), transformerRemoveNotationEscape(), // custom transformers createTransformerCallouts(), ], }, }, themeConfig: { enableScrollToTop: true, localeRedirect: redirect, llmsUI: false, ...(editRepoEnabled && editRepoBaseUrl && { editLink: { docRepoBaseUrl: editRepoBaseUrl }, }), ...(fallbackToZh ? zhLocale : { locales }), }, plugins: [ algoliaOptions && pluginAlgolia(), siteUrl && config.siteUrl && pluginSitemap({ siteUrl: config.siteUrl, }), apiPlugin({ localBasePath, }), autoSidebarPlugin({ export: export_, ignore }), autoTocPlugin(), directivesPlugin(), globalPlugin({ version, download }), mermaidPlugin(), permissionPlugin({ localBasePath, }), replacePlugin({ lang: fallbackToZh ? null : (config.lang ?? 'en'), localBasePath, force, }), ].filter(Boolean), search: algoliaOptions ? false : undefined, builderConfig: { dev: { lazyCompilation: lazy, }, performance: { buildCache: false, }, plugins: [pluginReact(), pluginSass(), pluginSvgr(), pluginYaml()], resolve: { alias: { classnames$: 'clsx', }, }, server: { open, proxy: { '/smart/api': 'https://docs.alauda.cn', }, }, tools: { rspack(rspackConfig, { mergeConfig, rspack }) { return mergeConfig(rspackConfig, { ignoreWarnings: [ { module: /\b(flexsearch|vscode-languageserver-types)\b/, }, ], resolve: { extensionAlias: { '.js': ['.ts', '.tsx', '.js'], }, }, plugins: [ new rspack.DefinePlugin({ 'process.env.ALGOLIA_APP_ID': JSON.stringify(algoliaOptions?.appId), 'process.env.ALGOLIA_API_KEY': JSON.stringify(algoliaOptions?.apiKey), 'process.env.ALGOLIA_INDEX_NAME': JSON.stringify(algoliaOptions?.indexName), }), ], }); }, }, }, ssg: { experimentalWorker: true, }, llms: true, }; }; const findConfig = async (basePath) => { for (const configFile of DEFAULT_EXTENSIONS.map((ext) => basePath + ext)) { if (await pathExists(configFile, 'file')) { return configFile; } } }; export async function loadConfig(root, { config: configFile, base, prefix, v: version, download, export: export_, ignore, force, open, lazy, include, exclude, outDir, redirect, editRepo, algolia, siteUrl, lang, } = {}) { let configFilePath; if (configFile) { configFilePath = path.resolve(configFile); } else { if (root) { root = root.toString(); if (root.startsWith('file:')) { root = fileURLToPath(root); } configFilePath = await findConfig(path.resolve(root, DEFAULT_CONFIG_NAME)); } if (!configFilePath) { configFilePath = await findConfig(path.resolve(DEFAULT_CONFIG_NAME)); } // when root is not specified, try to find config in docs folder if (!root && !configFilePath) { configFilePath = await findConfig(path.resolve('docs', DEFAULT_CONFIG_NAME)); } } let config; const { loadConfig, mergeRsbuildConfig } = await import('@rsbuild/core'); if (!configFilePath) { logger.info(`No doom config file found in ${process.cwd()}`); } else { try { if (YAML_EXTENSIONS.includes(path.extname(configFilePath))) { config = await resolveStaticConfig(configFilePath); } else { const { content } = await loadConfig({ cwd: path.dirname(configFilePath), path: configFilePath, }); config = content; } } catch { logger.error(`Failed to load config from ${configFilePath}`); } } config ??= {}; if (config.sites) { logger.error('Use separate `sites.yaml` config in repository root instead'); } else { const sitesConfigFilePath = path.resolve(SITES_FILE); if (await pathExists(sitesConfigFilePath, 'file')) { config.sites = await resolveStaticConfig(sitesConfigFilePath); } } version ||= ''; const commonConfig = await getCommonConfig({ config, configFilePath, root: root, base, version, download, export: export_, ignore, force, open, lazy, include, exclude, redirect, editRepo, algolia, siteUrl, lang, }); base = commonConfig.base; lang = commonConfig.lang; root = commonConfig.root; const mergedConfig = mergeRsbuildConfig(commonConfig, config, { base, lang, root, }); const hasLocales = !!commonConfig.themeConfig.locales?.length; const apiEntries = await glob(`${hasLocales ? commonConfig.lang + '/' : ''}apis/{advanced_apis,crds,kubernetes_apis}/*/index.{md,mdx}`, { cwd: root }); const apiExports = apiEntries.map((entry) => { const scope = entry.replace(/index\.mdx?$/, ''); return { name: path.basename(path.dirname(entry)) + '_apis', scope: hasLocales ? scope.replace(lang, '*') : scope, }; }); mergedConfig.export = await Promise.all([...(config.export ?? []), ...apiExports].map(async (item) => ({ ...item, scope: Array.isArray(item.scope) ? item.scope : [item.scope], flattenScope: (await glob(item.scope, { cwd: root })).filter(isDoc), }))); if (base && prefix) { mergedConfig.base = (mergedConfig.prefix = normalizeSlash(prefix)) + base; } let ensureDefaultLogo = false; if (!mergedConfig.logo) { mergedConfig.logo = DEFAULT_LOGO; ensureDefaultLogo = true; } if (!mergedConfig.icon) { mergedConfig.icon = DEFAULT_LOGO; ensureDefaultLogo = true; } if (ensureDefaultLogo) { const publicPath = path.resolve(mergedConfig.root, 'public'); await fs.mkdir(publicPath, { recursive: true }); const logoPath = path.resolve(publicPath, removeLeadingSlash(DEFAULT_LOGO)); if (!(await pathExists(logoPath))) { await fs.copyFile(pkgResolve(`assets${DEFAULT_LOGO}`), logoPath); } } if (mergedConfig.i18nSourcePath) { if (!path.isAbsolute(mergedConfig.i18nSourcePath)) { mergedConfig.i18nSourcePath = path.resolve(configFilePath ? path.dirname(configFilePath) : mergedConfig.root, mergedConfig.i18nSourcePath); } } else { mergedConfig.i18nSourcePath = path.resolve(mergedConfig.root, I18N_FILE); } outDir ||= mergedConfig.outDir; mergedConfig.outDir = `dist${(outDir ? addLeadingSlash(addTrailingSlash(outDir)) : base) + (isExplicitlyUnversioned(version) ? UNVERSIONED : outDir ? version : '')}`; return { config: mergedConfig, configFilePath: configFilePath || '', }; } export function resolveDocRoot(cliRoot, configRoot) { // CLI root has highest priority if (cliRoot) { return path.resolve(cliRoot); } // Config root is next in priority if (configRoot) { return path.isAbsolute(configRoot) ? configRoot : path.resolve(configRoot); } // Default to 'docs' if no root is specified return path.resolve('docs'); }