UNPKG

@alauda/doom

Version:

Doctor Doom making docs.

216 lines (215 loc) 9.29 kB
import fs from 'node:fs/promises'; import path from 'node:path'; import { generatePdf, } from '@alauda/doom-export'; import { logger, serve } from '@rspress/core'; import { removeLeadingSlash } from '@rspress/shared'; import { Command } from 'commander'; import { groupBy } from 'es-toolkit'; import { cyan, yellow } from 'yoctocolors'; import { autoSidebar, } from '../plugins/index.js'; import { getPdfName } from '../shared/index.js'; import { pathExists, setNodeEnv } from '../utils/index.js'; import { loadConfig } from './load-config.js'; const collectPages = (sidebarItems, base) => { const pages = new Map(); for (const item of sidebarItems) { if ('link' in item && item.link) { const link = removeLeadingSlash(item.link); pages.set(link, { key: link, path: base + link + '.html?print', title: item.text, }); } if ('items' in item) { for (const page of collectPages(item.items, base)) { if (!pages.has(page.key)) { pages.set(page.key, page); } } } } return [...pages.values()]; }; export const exportCommand = new Command('export') .description('Export the documentation as PDF, `apis/**` and `*/apis/**` routes will be ignored automatically') .argument('[root]', 'Root directory of the documentation') .option('-H, --host [host]', 'Serve host name', 'localhost') .option('-P, --port [port]', 'Serve port number', '4173') .action(async function (root) { setNodeEnv('production'); const { port, host, ...globalOptions } = this.optsWithGlobals(); let { config, configFilePath } = await loadConfig(root, { ...globalOptions, export: true, }); const outDir = config.outDir; if (!(await pathExists(path.resolve(outDir, 'index.html'), 'file'))) { logger.error('Please build the documentation first'); process.exit(1); } config = await autoSidebar(config, { ignore: globalOptions.ignore, export: true, }); config.builderConfig.server.open = false; // make sure it won't be overridden by `serve` const themeConfig = { ...config.themeConfig }; logger.start('Serving...'); await serve({ config, configFilePath, host, port }); const tempDir = path.resolve(outDir, '.doom'); const commonOptions = { tempDir, port: port, host: host, outDir, pdfOptions: { margin: { top: 40, right: 40, bottom: 40, left: 40, }, printBackground: true, headerTemplate: /* HTML */ `<div style="margin-top: -0.4cm; height: 70%; width: 100%; display: flex; justify-content: center; align-items: center; color: lightgray; border-bottom: solid lightgray 1px; font-size: 10px;" > <span class="title"></span> </div>`, footerTemplate: /* HTML */ `<div style="margin-bottom: -0.4cm; height: 70%; width: 100%; display: flex; justify-content: flex-start; align-items: center; color: lightgray; border-top: solid lightgray 1px; font-size: 10px;" ></div>`, }, printerOptions: { ignoreHTTPSErrors: true, initScripts: [ // eslint-disable-next-line @typescript-eslint/require-await async () => { localStorage.setItem('rspress-theme-appearance', 'light'); localStorage.setItem('rspress-visited', '1'); }, ], outlineContainerSelector: '.rp-doc', outlineExcludeSelector: '.rp-toc-exclude *', }, }; const exportPdf = async (sidebarItems, lang = config.lang, { includeApisPages, overrideOutlines, ...options } = {}) => { let pages = collectPages(sidebarItems, config.base); if (!includeApisPages) { pages = pages.filter((page) => themeConfig.locales?.length ? !themeConfig.locales.some((l) => page.key.startsWith(l.lang === config.lang ? 'apis/' : `${l.lang}/apis/`)) : !page.key.startsWith('apis/')); } const pdfOptions = { pages, outFile: getPdfName(lang, config.userBase, config.title), cleanupTempDir: false, customOutlines: overrideOutlines ? (pages) => { const pageMap = new Map(); let pageIndex = 0; for (const page of pages) { pageMap.set(page.url.split('?')[0], { title: page.title, pageIndex, }); pageIndex += page.count; } const generateOutlinesFromSidebar = (sidebarItems) => { const result = []; for (const item of sidebarItems) { let children; if ('items' in item) { children = generateOutlinesFromSidebar(item.items); } if ('link' in item && item.link) { const page = pageMap.get(`${config.base}${removeLeadingSlash(item.link)}.html`); if (page != null) { result.push({ title: page.title || item.text, to: page.pageIndex, children, }); } } } return result; }; return generateOutlinesFromSidebar(sidebarItems); } : undefined, ...commonOptions, ...options, }; logger.start(`Exporting ${lang} language documents with ${pages.length} pages into ${yellow(pdfOptions.outFile)}...`); return generatePdf(pdfOptions); }; const findScopeFactory = (scope) => function findScope(sidebarItems) { const found = new Set(); for (const item of sidebarItems) { if (!('_fileKey' in item) || !item._fileKey) { continue; } if (scope.includes(item._fileKey)) { found.add(item); } if ('items' in item) { const found_ = findScope(item.items); if (found_?.size) { found_.forEach((i) => found.add(i)); } } } return found; }; const exportItems = config.export || []; // eslint-disable-next-line @typescript-eslint/no-useless-default-assignment const { apis = [], nonApis = [] } = groupBy(exportItems, (item) => item.scope.some((s) => s.startsWith('apis/') || s.startsWith('*/apis/')) ? 'apis' : 'nonApis'); const exportEntries = async (sidebarItems, allOutlines, lang = config.lang) => { for (const item of nonApis) { const found = findScopeFactory(item.flattenScope)(sidebarItems); if (!found?.size) { logger.warn(`Cannot find matched scope \`${cyan(item.scope.join(', '))}\` for lang ${lang}, skip exporting`); continue; } const foundSidebarItems = [...found]; await exportPdf(foundSidebarItems, lang, { allOutlines, outFile: `${item.name || (foundSidebarItems.find((it) => it.link?.endsWith('/index')) ?? foundSidebarItems[0]).text}-${lang}.pdf`, }); } for (const item of apis) { const found = findScopeFactory(item.flattenScope)(sidebarItems); if (!found?.size) { logger.warn(`Cannot find matched scope \`${cyan(item.scope.join(', '))}\` for lang ${lang}, skip exporting`); continue; } const foundSidebarItems = [...found]; await exportPdf(foundSidebarItems, lang, { includeApisPages: true, outFile: `${item.name || (foundSidebarItems.find((it) => it.link?.endsWith('/index')) ?? foundSidebarItems[0]).text}-${lang}.pdf`, }); } }; if (themeConfig.locales?.length) { for (const { lang, sidebar } of themeConfig.locales) { const sidebarItems = sidebar[config.lang === lang ? '/' : `/${lang}`]; const { allOutlines } = await exportPdf(sidebarItems, lang, { overrideOutlines: true, }); await exportEntries(sidebarItems, allOutlines, lang); } } else { const sidebarItems = themeConfig.sidebar['/']; const { allOutlines } = await exportPdf(sidebarItems, undefined, { overrideOutlines: true, }); await exportEntries(sidebarItems, allOutlines); } await fs.rm(tempDir, { force: true, recursive: true }); logger.ready('Closing the server'); process.exit(0); });