UNPKG

@alauda/doom

Version:

Doctor Doom making docs.

383 lines (382 loc) 14.7 kB
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 { pluginLlms } from '@rspress/plugin-llms'; import { addLeadingSlash, addTrailingSlash, normalizeSlash, 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 { attributesPlugin } from '../plugins/attributes/index.js'; import { apiPlugin, autoSidebarPlugin, autoTocPlugin, createTransformerCallouts, directivesPlugin, globalPlugin, mermaidPlugin, permissionPlugin, replacePlugin, sitemapPlugin, } from '../plugins/index.js'; import { isExplicitlyUnversioned, 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', editLink: { text: '📝 Edit this page on GitHub', }, }, zh: { label: '简体中文', searchPlaceholderText: '搜索文档', searchNoResultsText: '未搜索到相关结果', searchSuggestedQueryText: '可更换不同的关键字后重试', outlineTitle: '本页概览', prevPageText: '上一页', nextPageText: '下一页', editLink: { text: '📝 在 GitHub 上编辑此页', }, }, ru: { label: 'Русский', searchPlaceholderText: 'Поиск документов', searchNoResultsText: 'Не найдено соответствующих результатов', searchSuggestedQueryText: 'Попробуйте изменить ключевые слова и повторить поиск', outlineTitle: 'Обзор страницы', prevPageText: 'Предыдущая страница', nextPageText: 'Следующая страница', editLink: { text: '📝 Редактировать эту страницу на GitHub', }, }, }; 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, }) => { 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') { 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], editLink: editRepoEnabled && editRepoBaseUrl ? { docRepoBaseUrl: editRepoBaseUrl, ...KNOWN_LOCALE_CONFIGS.en.editLink, ...KNOWN_LOCALE_CONFIGS[name]?.editLink, } : undefined, }); } } const unusedLanguages = difference(allLanguages, locales.map(({ lang }) => lang)); const { editLink, ...zhLocale } = KNOWN_LOCALE_CONFIGS.zh; const algoliaOptions = ((algolia && config.algolia) ?? (algolia === 'alauda' ? ALAUDA_ALGOLIA_OPTIONS : algolia === 'alauda-ru' && ALAUDA_RU_ALGOLIA_OPTIONS)) || null; 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: { shiki: { transformers: [ // builtin transformers transformerMetaHighlight(), transformerMetaWordHighlight(), transformerNotationDiff(), transformerNotationErrorLevel(), transformerNotationFocus(), transformerNotationHighlight(), transformerNotationWordHighlight(), transformerRemoveNotationEscape(), // custom transformers createTransformerCallouts(), ], }, }, themeConfig: { enableScrollToTop: true, // https://github.com/web-infra-dev/rspress/issues/2011 outline: true, localeRedirect: redirect, ...(fallbackToZh ? editRepoEnabled && editRepoBaseUrl ? { ...zhLocale, editLink: { ...editLink, docRepoBaseUrl: editRepoBaseUrl }, } : zhLocale : { locales }), }, plugins: [ algoliaOptions && pluginAlgolia(), pluginLlms(), apiPlugin({ localBasePath, }), attributesPlugin(), autoSidebarPlugin({ export: export_, ignore }), autoTocPlugin(), directivesPlugin(), globalPlugin({ version, download }), mermaidPlugin(), permissionPlugin({ localBasePath, }), replacePlugin({ lang: fallbackToZh ? null : (config.lang ?? 'en'), localBasePath, force, }), siteUrl && config.siteUrl && sitemapPlugin({ domain: config.siteUrl, }), ].filter(Boolean), search: algoliaOptions ? false : undefined, builderConfig: { dev: { lazyCompilation: lazy, }, performance: { buildCache: false, }, plugins: [pluginReact(), pluginSass(), pluginSvgr(), pluginYaml()], resolve: { alias: { classnames$: 'clsx', }, }, server: { open, }, tools: { rspack(rspackConfig, { mergeConfig, rspack }) { return mergeConfig(rspackConfig, { 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), }), ], }); }, }, }, }; }; 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, } = {}) { 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, }); base = commonConfig.base; const mergedConfig = mergeRsbuildConfig(commonConfig, config, { base, lang: commonConfig.lang, root: commonConfig.root, }); mergedConfig.export = config.export && (await Promise.all(config.export.map(async (item) => ({ ...item, scope: Array.isArray(item.scope) ? item.scope : [item.scope], flattenScope: (await glob(item.scope, { cwd: commonConfig.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'); }