diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-09-01 14:48:45 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-09-01 14:48:45 +0300 |
| commit | 8a6d697308162874a236c84aac0b0f8c7ac01f83 (patch) | |
| tree | ed7dc7e8c6f50d66bde721fae78576aede929f66 /website/scripts/prepare-docs.mjs | |
| parent | b54a78aa510b87d1e3b56c619b72eaeb59e4a2a1 (diff) | |
refactor: separate website presentation from runtime
Diffstat (limited to 'website/scripts/prepare-docs.mjs')
| -rw-r--r-- | website/scripts/prepare-docs.mjs | 479 |
1 files changed, 0 insertions, 479 deletions
diff --git a/website/scripts/prepare-docs.mjs b/website/scripts/prepare-docs.mjs deleted file mode 100644 index 1fc8bc56..00000000 --- a/website/scripts/prepare-docs.mjs +++ /dev/null @@ -1,479 +0,0 @@ -import { cp, mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises'; -import { dirname, extname, join, relative, resolve } from 'node:path'; -import { fileURLToPath } from 'node:url'; -import process from 'node:process'; - -const websiteDir = dirname(fileURLToPath(import.meta.url)); -const repoRoot = process.env.HERDR_DOCS_REPO_ROOT - ? resolve(process.env.HERDR_DOCS_REPO_ROOT) - : resolve(websiteDir, '../..'); -const publicDir = resolve(repoRoot, 'website/public'); -const stableDocsDir = resolve(repoRoot, 'website/src/content/docs'); -const previewDocsDir = resolve(stableDocsDir, 'preview'); -const generatedVersionsDocsDir = resolve(stableDocsDir, '_versions'); -const versionsDir = resolve(repoRoot, 'docs/versions'); -const versionsManifestPath = resolve(versionsDir, 'manifest.json'); -const previewManifestPath = resolve(repoRoot, 'website/preview.json'); -const generatedVersionsDataPath = resolve(repoRoot, 'website/src/data/docs-versions.json'); -const stableConfigReferenceDestination = resolve( - repoRoot, - 'website/src/data/config-reference.json', -); -const previewConfigReferenceDestination = resolve( - repoRoot, - 'website/src/data/config-reference-preview.json', -); -const generatedVersionReferencesPath = resolve( - repoRoot, - 'website/src/data/config-reference-versions.json', -); - -if (process.argv[2] === '--rewrite-preview-doc-fixture') { - const chunks = []; - for await (const chunk of process.stdin) chunks.push(chunk); - process.stdout.write( - rewritePreviewDocContent(Buffer.concat(chunks).toString('utf8'), '', { - buildId: '2026-07-29-44b3adb12552', - commit: '44b3adb125524ea9a55739eee3776f922f2115ad', - }), - ); -} else if (process.argv[2] === '--rewrite-version-doc-fixture') { - const chunks = []; - for await (const chunk of process.stdin) chunks.push(chunk); - process.stdout.write( - rewriteVersionDocContent(Buffer.concat(chunks).toString('utf8'), { - version: process.argv[3] ?? '0.7.5', - tag: `v${process.argv[3] ?? '0.7.5'}`, - relativePath: 'index.mdx', - }), - ); -} else { - const supported = new Set(['--docs-only', '--draft']); - const unsupported = process.argv.slice(2).filter((argument) => !supported.has(argument)); - if (unsupported.length > 0) { - throw new Error('usage: node website/scripts/prepare-docs.mjs [--docs-only] [--draft]'); - } - const docsOnly = process.argv.includes('--docs-only'); - if (!docsOnly) await preparePublicAssets(); - await prepareDocs({ - draft: process.argv.includes('--draft'), - publishAgentIndexes: !docsOnly, - }); -} - -async function preparePublicAssets() { - await rm(publicDir, { recursive: true, force: true }); - await mkdir(publicDir, { recursive: true }); - - for (const file of [ - 'install.sh', - 'install.ps1', - 'install.cmd', - 'agent-guide.md', - 'latest.json', - 'preview.json', - 'robots.txt', - 'favicon.ico', - '_headers', - '_redirects', - ]) { - const source = resolve(repoRoot, 'website', file); - try { - await cp(source, resolve(publicDir, file)); - } catch (error) { - if (file !== 'preview.json' || error.code !== 'ENOENT') throw error; - } - } - - for (const directory of ['assets', 'css', 'agent-detection']) { - await cp(resolve(repoRoot, 'website', directory), resolve(publicDir, directory), { - recursive: true, - }); - } -} - -async function prepareDocs({ draft, publishAgentIndexes }) { - const manifest = JSON.parse(await readFile(versionsManifestPath, 'utf8')); - if ( - manifest.schema_version !== 1 || - typeof manifest.current !== 'string' || - !['legacy', 'snapshot'].includes(manifest.stable_source ?? 'legacy') - ) { - throw new Error(`${versionsManifestPath} has an unsupported schema`); - } - const currentEntry = manifest.versions.find((entry) => entry.version === manifest.current); - if (!currentEntry) throw new Error(`current docs version ${manifest.current} has no snapshot`); - - const previewManifest = JSON.parse(await readFile(previewManifestPath, 'utf8')); - if (!/^[0-9a-f]{40}$/.test(previewManifest.commit ?? '')) { - throw new Error(`${previewManifestPath} must contain a full preview commit SHA`); - } - const previewWebsiteRoot = resolve(repoRoot, draft ? 'docs/next/website' : 'docs/preview/website'); - const previewDocsSourceDir = resolve(previewWebsiteRoot, 'src/content/docs'); - const previewConfigReferenceSource = resolve( - previewWebsiteRoot, - 'src/data/config-reference.json', - ); - - if ((manifest.stable_source ?? 'legacy') === 'snapshot') { - await rm(stableDocsDir, { recursive: true, force: true }); - const currentSnapshotRoot = resolve(versionsDir, manifest.current, 'website'); - await copyPreparedDocs( - resolve(currentSnapshotRoot, 'src/content/docs'), - stableDocsDir, - (content, relativePath) => - rewriteStableDocContent(content, { - version: currentEntry.version, - tag: currentEntry.tag, - relativePath, - }), - ); - const currentReference = resolve(currentSnapshotRoot, 'src/data/config-reference.json'); - try { - await cp(currentReference, stableConfigReferenceDestination); - } catch (error) { - if (error.code !== 'ENOENT') throw error; - await rm(stableConfigReferenceDestination, { force: true }); - } - } else { - await rm(previewDocsDir, { recursive: true, force: true }); - await rm(generatedVersionsDocsDir, { recursive: true, force: true }); - } - - await copyPreparedDocs(previewDocsSourceDir, previewDocsDir, (content, relativePath) => - rewritePreviewDocContent(content, relativePath, { - buildId: draft ? 'draft' : previewManifest.build_id, - commit: draft ? 'master' : previewManifest.commit, - }), - ); - await cp(previewConfigReferenceSource, previewConfigReferenceDestination); - - const scopes = { - stable: await collectDocsScope( - (manifest.stable_source ?? 'legacy') === 'snapshot' - ? resolve(versionsDir, manifest.current, 'website/src/content/docs') - : stableDocsDir, - new Set(['preview', '_versions']), - ), - preview: await collectDocsScope(previewDocsSourceDir), - }; - const configReferences = {}; - - for (const entry of manifest.versions) { - const version = entry.version; - const snapshotDocsRoot = resolve(versionsDir, version, 'website/src/content/docs'); - const destinationRoot = resolve(generatedVersionsDocsDir, version); - await copyPreparedDocs(snapshotDocsRoot, destinationRoot, (content, relativePath) => - rewriteVersionDocContent(content, { - version, - tag: entry.tag, - relativePath, - }), - ); - scopes[version] = await collectDocsScope(snapshotDocsRoot); - - const referencePath = resolve( - versionsDir, - version, - 'website/src/data/config-reference.json', - ); - try { - configReferences[version] = JSON.parse(await readFile(referencePath, 'utf8')); - } catch (error) { - if (error.code !== 'ENOENT') throw error; - } - } - - await writeFile( - generatedVersionsDataPath, - `${JSON.stringify({ - ...manifest, - preview: { - build_id: draft ? 'draft' : previewManifest.build_id, - commit: draft ? 'master' : previewManifest.commit, - }, - scopes, - }, null, 2)}\n`, - 'utf8', - ); - await writeFile( - generatedVersionReferencesPath, - `${JSON.stringify(configReferences, null, 2)}\n`, - 'utf8', - ); - - if (publishAgentIndexes) { - if (typeof currentEntry.tag !== 'string' || typeof currentEntry.source !== 'string') { - throw new Error(`current docs version ${manifest.current} is missing its tag or source`); - } - const stableSourceDir = resolve(versionsDir, manifest.current, 'website/src/content/docs'); - const previewRef = draft ? 'master' : previewManifest.commit; - const previewBuild = draft ? 'draft' : previewManifest.build_id; - await Promise.all([ - writeAgentDocsIndex({ - output: resolve(publicDir, 'llms.txt'), - heading: 'Herdr stable documentation index', - summary: `Current stable release: ${manifest.current}.`, - sourceDir: stableSourceDir, - ref: currentEntry.tag, - repositoryPath: currentEntry.source, - extraSections: [ - '## Optional bundles', - '- [Abridged documentation](https://herdr.dev/llms-small.txt)', - '- [Complete documentation](https://herdr.dev/llms-full.txt)', - '', - '## Other resources', - '- [Agent guide](https://herdr.dev/agent-guide.md): help a human understand, set up, or troubleshoot Herdr', - '- [Preview documentation](https://herdr.dev/llms-preview.txt): use only for the preview channel', - ], - }), - writeAgentDocsIndex({ - output: resolve(publicDir, 'llms-preview.txt'), - heading: 'Herdr preview documentation index', - summary: `Active preview build: ${previewBuild} (${previewRef.slice(0, 12)}).`, - sourceDir: previewDocsSourceDir, - ref: previewRef, - repositoryPath: 'docs/next/website/src/content/docs', - extraSections: [ - '## Other resources', - '- [Stable documentation](https://herdr.dev/llms.txt): use unless the human is running the preview channel', - '- [Agent guide](https://herdr.dev/agent-guide.md): help a human understand, set up, or troubleshoot Herdr', - ], - }), - ]); - } -} - -async function writeAgentDocsIndex({ - output, - heading, - summary, - sourceDir, - ref, - repositoryPath, - extraSections, -}) { - const pages = await collectAgentDocs(sourceDir); - const rawRoot = `https://raw.githubusercontent.com/herdrdev/herdr/${ref}`; - const rawBase = `${rawRoot}/${repositoryPath}`; - const configDataPath = repositoryPath.replace( - /\/content\/docs$/, - '/data/config-reference.json', - ); - if (configDataPath === repositoryPath) { - throw new Error(`cannot derive config reference path from ${repositoryPath}`); - } - const configDataUrl = `${rawRoot}/${configDataPath}`; - - const lines = [ - `# ${heading}`, - '', - `> ${summary}`, - '', - 'Open only the pages relevant to the current task. Links return raw source files from the exact documented revision. For another topic, return to this index instead of following `/docs/` links inside a page, which serve human-facing HTML.', - '', - '## Documentation', - '', - ...pages.map(({ title, description, path }) => { - const url = /^config-reference\.mdx?$/.test(path) ? configDataUrl : `${rawBase}/${path}`; - return `- [${title}](${url})${description ? `: ${description}` : ''}`; - }), - '', - '## Using the config reference', - '', - 'The Config reference link above is a large structured JSON file. When command tools are available, fetch and filter it by exact key instead of loading the whole file:', - '', - '```sh', - `curl -fsSL '${configDataUrl}' | jq --arg key 'ui.sidebar_width' \\`, - " '.sections[].keys[] | select(.key == $key)'", - '```', - '', - ...extraSections, - '', - ]; - await writeFile(output, lines.join('\n'), 'utf8'); -} - -async function collectAgentDocs(sourceDir) { - const pages = []; - - async function walk(directory, prefix = '') { - for (const entry of await readdir(directory, { withFileTypes: true })) { - if (!prefix && entry.isDirectory() && ['ja', 'zh-cn'].includes(entry.name)) continue; - const path = join(directory, entry.name); - const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name; - if (entry.isDirectory()) { - await walk(path, relativePath); - continue; - } - if (!entry.isFile() || !['.md', '.mdx'].includes(extname(entry.name).toLowerCase())) { - continue; - } - const content = await readFile(path, 'utf8'); - const title = frontmatterValue(content, 'title'); - if (!title) throw new Error(`${path} is missing a frontmatter title`); - pages.push({ - title, - description: frontmatterValue(content, 'description'), - path: relativePath, - }); - } - } - - await walk(sourceDir); - pages.sort((a, b) => { - if (a.path === 'index.mdx' || a.path === 'index.md') return -1; - if (b.path === 'index.mdx' || b.path === 'index.md') return 1; - return a.title.localeCompare(b.title, 'en'); - }); - return pages; -} - -function frontmatterValue(content, key) { - const frontmatter = content.match(/^---\n([\s\S]*?)\n---(?:\n|$)/)?.[1]; - const value = frontmatter?.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'))?.[1]?.trim(); - if (!value) return undefined; - const quote = value[0]; - return (quote === '"' || quote === "'") && value.endsWith(quote) - ? value.slice(1, -1) - : value; -} - -async function copyPreparedDocs(sourceDir, destinationDir, rewrite, pathPrefix = '') { - await mkdir(destinationDir, { recursive: true }); - for (const entry of await readdir(sourceDir, { withFileTypes: true })) { - const source = join(sourceDir, entry.name); - const destination = join(destinationDir, entry.name); - const relativePath = pathPrefix ? `${pathPrefix}/${entry.name}` : entry.name; - if (entry.isDirectory()) { - await copyPreparedDocs(source, destination, rewrite, relativePath); - continue; - } - if (!entry.isFile()) continue; - - if (!['.md', '.mdx'].includes(extname(entry.name).toLowerCase())) { - await cp(source, destination); - continue; - } - const content = await readFile(source, 'utf8'); - await writeFile(destination, rewrite(content, relativePath), 'utf8'); - } -} - -async function collectDocsScope(sourceDir, excludedDirectories = new Set()) { - const locales = { root: [], ja: [], 'zh-cn': [] }; - - async function walk(directory, prefix = '') { - for (const entry of await readdir(directory, { withFileTypes: true })) { - if (entry.isDirectory() && excludedDirectories.has(entry.name)) continue; - const path = join(directory, entry.name); - const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name; - if (entry.isDirectory()) { - await walk(path, relativePath); - continue; - } - if (!entry.isFile() || !['.md', '.mdx'].includes(extname(entry.name))) continue; - - const segments = relativePath.replace(/\.(md|mdx)$/i, '').split('/'); - const locale = segments[0] === 'ja' || segments[0] === 'zh-cn' ? segments.shift() : 'root'; - const page = segments.join('/').replace(/(^|\/)index$/, '').replace(/\/$/, ''); - locales[locale].push(page); - } - } - - await walk(sourceDir); - for (const pages of Object.values(locales)) pages.sort(); - for (const locale of ['ja', 'zh-cn']) { - if (locales[locale].length === 0) locales[locale] = [...locales.root]; - } - return { locales }; -} - -export function rewritePreviewDocContent( - content, - relativePath = '', - { buildId, commit } = { - buildId: 'preview', - commit: 'master', - }, -) { - const taggedContent = rewriteRepositoryLinks( - content.replaceAll('/docs/', '/docs/preview/'), - commit, - ); - const rewritten = rewriteRelativeDocPaths(taggedContent, 1); - const withSourceLink = setGeneratedEditUrl( - rewritten, - `https://github.com/herdrdev/herdr/blob/${commit}/docs/next/website/src/content/docs/${relativePath}`, - ); - return insertPreviewNotice(withSourceLink, relativePath, { buildId, commit }); -} - -export function rewriteStableDocContent(content, { version, tag, relativePath }) { - const taggedContent = rewriteRepositoryLinks(content, tag); - return setGeneratedEditUrl(taggedContent, versionedDocSourceUrl(version, relativePath)); -} - -export function rewriteVersionDocContent(content, { version, tag, relativePath }) { - const taggedContent = rewriteRepositoryLinks( - content.replaceAll('/docs/', `/docs/${version}/`), - tag, - ); - const rewritten = rewriteRelativeDocPaths(taggedContent, 2); - return setGeneratedEditUrl(rewritten, versionedDocSourceUrl(version, relativePath)); -} - -function versionedDocSourceUrl(version, relativePath) { - return `https://github.com/herdrdev/herdr/blob/master/docs/versions/${version}/website/src/content/docs/${relativePath}`; -} - -function rewriteRepositoryLinks(content, ref) { - return content - .replaceAll( - 'https://github.com/herdrdev/herdr/blob/master/', - `https://github.com/herdrdev/herdr/blob/${ref}/`, - ) - .replaceAll( - 'https://raw.githubusercontent.com/herdrdev/herdr/master/', - `https://raw.githubusercontent.com/herdrdev/herdr/${ref}/`, - ); -} - -function rewriteRelativeDocPaths(content, extraDepth) { - const parents = '../'.repeat(extraDepth); - return content - .replace(/((?:\.\.\/)+)(?=public\/)/g, `$1${parents}`) - .replace(/^(import .*from\s+['"])(?=(?:\.\.\/)+components\/)/gm, `$1${parents}`); -} - -function setGeneratedEditUrl(content, editUrl) { - if (!content.startsWith('---\n') || /^editUrl:/m.test(content)) return content; - return content.replace(/^---\n/, `---\neditUrl: ${editUrl}\n`); -} - -function insertPreviewNotice(content, relativePath, { buildId, commit }) { - const source = commit === 'master' ? '`master` draft' : `[${commit.slice(0, 12)}](https://github.com/herdrdev/herdr/commit/${commit})`; - const notice = [ - `> Preview build \`${buildId}\`, published from ${source}. Stable docs remain at [/docs/](/docs/).`, - '', - '', - ].join('\n'); - const indexPrefix = - relativePath === 'index.mdx' - ? content.replace('title: Herdr documentation', 'title: Herdr preview documentation') - : content; - const frontmatter = indexPrefix.match(/^---\n[\s\S]*?\n---\n/); - if (!frontmatter) { - return insertNoticeAfterImports(indexPrefix, notice); - } - const body = indexPrefix.slice(frontmatter[0].length); - return `${frontmatter[0]}\n${insertNoticeAfterImports(body, notice)}`; -} - -function insertNoticeAfterImports(content, notice) { - const imports = content.match(/^(\s*import .+?;\n)+\s*/); - if (!imports) { - return `${notice}${content}`; - } - return `${imports[0]}${notice}${content.slice(imports[0].length)}`; -} |
