diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-22 14:58:26 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-22 14:59:09 +0300 |
| commit | c234f221f9be698bc0501c60459e1ae8e67df6c3 (patch) | |
| tree | 944b4dec10fada8d93456dc5a85e6bfdb36a8b8b /website/scripts/check-built-docs.mjs | |
| parent | ef85fa0c7ebb48bb59fe7593af5a67f3e02ff3d4 (diff) | |
feat(docs): publish versioned release documentation
Diffstat (limited to 'website/scripts/check-built-docs.mjs')
| -rw-r--r-- | website/scripts/check-built-docs.mjs | 100 |
1 files changed, 100 insertions, 0 deletions
diff --git a/website/scripts/check-built-docs.mjs b/website/scripts/check-built-docs.mjs new file mode 100644 index 00000000..6f5dfa0b --- /dev/null +++ b/website/scripts/check-built-docs.mjs @@ -0,0 +1,100 @@ +import { access, readFile, readdir, stat } from 'node:fs/promises'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const websiteDir = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const distDir = resolve(websiteDir, 'dist'); +const nonCanonicalDocsUrl = /https:\/\/herdr\.dev\/(?:ja\/|zh-cn\/)?docs\/(?:preview|\d+\.\d+\.\d+)(?:\/|<)/; +const versions = JSON.parse( + await readFile(resolve(websiteDir, 'src/data/docs-versions.json'), 'utf8'), +); + +for (const entry of versions.versions) { + const scope = versions.scopes[entry.version]; + if (!scope) throw new Error(`missing generated scope for ${entry.version}`); + for (const [locale, pages] of Object.entries(scope.locales)) { + const localePrefix = locale === 'root' ? '' : `${locale}/`; + for (const page of pages) { + const output = resolve( + distDir, + localePrefix, + 'docs', + entry.version, + page, + 'index.html', + ); + await access(output); + } + } +} + +const stable = await readFile(resolve(distDir, 'docs/index.html'), 'utf8'); +const preview = await readFile(resolve(distDir, 'docs/preview/index.html'), 'utf8'); +const archived = await readFile( + resolve(distDir, 'docs', versions.current, 'index.html'), + 'utf8', +); + +assertIncludes(stable, 'data-pagefind-filter="version[content]" content="stable"'); +if (stable.includes('name="robots" content="noindex')) { + throw new Error('stable docs must remain indexable'); +} +assertIncludes(preview, 'data-pagefind-filter="version[content]" content="preview"'); +assertIncludes(preview, 'name="robots" content="noindex, nofollow"'); +assertIncludes(archived, `data-pagefind-filter="version[content]" content="${versions.current}"`); +assertIncludes(archived, 'name="robots" content="noindex, nofollow"'); +if (archived.includes(`This page documents Herdr ${versions.current}`)) { + throw new Error('the current immutable snapshot must not be labeled as outdated'); +} +const versionSelect = stable.match(/<select[^>]*aria-label="Documentation version"[^>]*>([\s\S]*?)<\/select>/)?.[1]; +if (!versionSelect) throw new Error('stable docs are missing the version selector'); +if (versionSelect.includes(`value="/docs/${versions.current}/"`)) { + throw new Error('the current release is duplicated in the version selector'); +} + +const previous = versions.versions.find((entry) => entry.version !== versions.current); +if (previous) { + const previousArchive = await readFile( + resolve(distDir, 'docs', previous.version, 'index.html'), + 'utf8', + ); + assertIncludes(previousArchive, `This page documents Herdr ${previous.version}`); +} + +const sitemap = await readFile(resolve(distDir, 'sitemap-0.xml'), 'utf8'); +assertIncludes(sitemap, 'https://herdr.dev/docs/'); +if (nonCanonicalDocsUrl.test(sitemap)) { + throw new Error('preview or immutable documentation URLs must not appear in the sitemap'); +} + +const build = await inspectFiles(distDir); +if (build.count > 20_000) { + throw new Error(`website build has ${build.count} files, exceeding the Cloudflare Pages free-plan limit`); +} +if (build.largest.bytes > 25 * 1024 * 1024) { + throw new Error(`website asset ${build.largest.path} is ${build.largest.bytes} bytes, exceeding Cloudflare Pages' 25 MiB limit`); +} +process.stdout.write(`validated ${versions.versions.length} documentation versions in ${build.count} website files\n`); + +function assertIncludes(content, expected) { + if (!content.includes(expected)) throw new Error(`built documentation is missing ${expected}`); +} + +async function inspectFiles(directory, root = directory) { + const result = { count: 0, largest: { path: '', bytes: 0 } }; + for (const entry of await readdir(directory, { withFileTypes: true })) { + const path = join(directory, entry.name); + if (entry.isDirectory()) { + const nested = await inspectFiles(path, root); + result.count += nested.count; + if (nested.largest.bytes > result.largest.bytes) result.largest = nested.largest; + } else if (entry.isFile()) { + result.count += 1; + const { size } = await stat(path); + if (size > result.largest.bytes) { + result.largest = { path: path.slice(root.length + 1), bytes: size }; + } + } + } + return result; +} |
