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/docs-versions.mjs | |
| parent | b54a78aa510b87d1e3b56c619b72eaeb59e4a2a1 (diff) | |
refactor: separate website presentation from runtime
Diffstat (limited to 'website/scripts/docs-versions.mjs')
| -rw-r--r-- | website/scripts/docs-versions.mjs | 286 |
1 files changed, 0 insertions, 286 deletions
diff --git a/website/scripts/docs-versions.mjs b/website/scripts/docs-versions.mjs deleted file mode 100644 index 0923d683..00000000 --- a/website/scripts/docs-versions.mjs +++ /dev/null @@ -1,286 +0,0 @@ -import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'; -import { dirname, resolve } from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; -import { - createGit, - extractGitTree, - gitPathExists, - gitTreesEqual, - listDocumentationPaths, - resolveCommit, -} from './docs-snapshot.mjs'; - -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 versionsDir = resolve(repoRoot, 'docs/versions'); -const manifestPath = resolve(versionsDir, 'manifest.json'); -const stableDocsDir = resolve(repoRoot, 'website/src/content/docs'); -const stableReferencePath = resolve(repoRoot, 'website/src/data/config-reference.json'); -const git = createGit(repoRoot); - -const VERSION_PATTERN = /^v?(\d+\.\d+\.\d+)$/; - -export function normalizeVersion(value) { - const match = VERSION_PATTERN.exec(value); - if (!match) throw new Error(`version must look like 0.7.5 or v0.7.5, got ${value}`); - return match[1]; -} - -export function sortVersionsNewestFirst(versions) { - return [...versions].sort((left, right) => compareVersions(right.version, left.version)); -} - -function compareVersions(left, right) { - const a = left.split('.').map(Number); - const b = right.split('.').map(Number); - for (let index = 0; index < 3; index += 1) { - if (a[index] !== b[index]) return a[index] - b[index]; - } - return 0; -} - -async function readManifest() { - try { - return JSON.parse(await readFile(manifestPath, 'utf8')); - } catch (error) { - if (error.code === 'ENOENT') { - return { schema_version: 1, stable_source: 'legacy', current: null, versions: [] }; - } - throw error; - } -} - -async function writeManifest(manifest) { - manifest.versions = sortVersionsNewestFirst(manifest.versions).map( - ({ version, tag, commit, source }) => ({ - version, - tag, - ...(commit ? { commit } : {}), - source, - }), - ); - await mkdir(dirname(manifestPath), { recursive: true }); - await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8'); -} - -function releaseMetadata(tag) { - const version = normalizeVersion(tag); - const normalizedTag = `v${version}`; - return { version, tag: normalizedTag, commit: resolveCommit(git, normalizedTag) }; -} - -async function snapshotTag(tag, sourceRoot) { - const metadata = { ...releaseMetadata(tag), source: sourceRoot }; - const destination = resolve(versionsDir, metadata.version); - await rm(destination, { recursive: true, force: true }); - await extractGitTree(git, tag, sourceRoot, resolve(destination, 'website/src/content/docs')); - - const referenceSource = sourceRoot.includes('docs/next/') - ? 'docs/next/website/src/data/config-reference.json' - : 'website/src/data/config-reference.json'; - if (gitPathExists(git, tag, referenceSource)) { - const referenceDestination = resolve(destination, 'website/src/data/config-reference.json'); - await mkdir(dirname(referenceDestination), { recursive: true }); - await writeFile(referenceDestination, git(['show', `${tag}:${referenceSource}`], { binary: true })); - } - - return metadata; -} - -export async function backfillVersions() { - const tags = git(['tag', '--list', 'v*', '--sort=v:refname']).split('\n').filter(Boolean); - const manifest = await readManifest(); - const entries = new Map(manifest.versions.map((entry) => [entry.version, entry])); - - for (const tag of tags) { - const version = normalizeVersion(tag); - if (entries.has(version)) continue; - const nextRoot = 'docs/next/website/src/content/docs'; - const legacyRoot = 'website/src/content/docs'; - const hasNext = gitPathExists(git, tag, nextRoot); - const hasLegacy = gitPathExists(git, tag, legacyRoot); - if (!hasNext && !hasLegacy) continue; - const sourceRoot = - hasNext && (!hasLegacy || !gitTreesEqual(git, tag, legacyRoot, nextRoot)) - ? nextRoot - : legacyRoot; - const metadata = await snapshotTag(tag, sourceRoot); - entries.set(metadata.version, metadata); - process.stdout.write(`snapshotted ${tag}\n`); - } - - const current = JSON.parse(await readFile(resolve(repoRoot, 'website/latest.json'), 'utf8')).version; - await writeManifest({ - schema_version: 1, - stable_source: manifest.stable_source ?? 'legacy', - current: normalizeVersion(current), - versions: [...entries.values()], - }); -} - -export async function checkVersions() { - const manifest = await readManifest(); - if ( - manifest.schema_version !== 1 || - typeof manifest.current !== 'string' || - !['legacy', 'snapshot'].includes(manifest.stable_source ?? 'legacy') - ) { - throw new Error(`${manifestPath} has an unsupported schema`); - } - - const latest = JSON.parse(await readFile(resolve(repoRoot, 'website/latest.json'), 'utf8')).version; - if (manifest.current !== normalizeVersion(latest)) { - throw new Error(`docs current version ${manifest.current} does not match website latest ${latest}`); - } - - const expectedOrder = sortVersionsNewestFirst(manifest.versions).map(({ version }) => version); - if (JSON.stringify(manifest.versions.map(({ version }) => version)) !== JSON.stringify(expectedOrder)) { - throw new Error('documentation versions are not sorted newest first'); - } - - const seen = new Set(); - for (const entry of manifest.versions) { - if (seen.has(entry.version)) throw new Error(`duplicate docs version ${entry.version}`); - seen.add(entry.version); - if (normalizeVersion(entry.tag) !== entry.version) { - throw new Error(`docs version ${entry.version} has mismatched tag ${entry.tag}`); - } - const requiresCommit = - entry.source === 'docs/next/website/src/content/docs' || - ((manifest.stable_source ?? 'legacy') === 'snapshot' && entry.version === manifest.current); - if (requiresCommit && !entry.commit) { - throw new Error(`docs version ${entry.version} is missing commit provenance`); - } - if (entry.commit) { - if (!/^[0-9a-f]{40}$/.test(entry.commit)) { - throw new Error(`docs version ${entry.version} has invalid commit ${entry.commit}`); - } - const taggedCommit = resolveCommit(git, entry.tag); - if (taggedCommit !== entry.commit) { - throw new Error( - `docs version ${entry.version} tag ${entry.tag} moved from ${entry.commit} to ${taggedCommit}`, - ); - } - } - if (!['website/src/content/docs', 'docs/next/website/src/content/docs'].includes(entry.source)) { - throw new Error(`docs version ${entry.version} has unsupported source ${entry.source}`); - } - - const versionRoot = resolve(versionsDir, entry.version, 'website'); - const documentationPaths = await listDocumentationPaths( - resolve(versionRoot, 'src/content/docs'), - ); - if (!documentationPaths.includes('index.md') && !documentationPaths.includes('index.mdx')) { - throw new Error(`docs version ${entry.version} has no root index`); - } - - try { - JSON.parse(await readFile(resolve(versionRoot, 'src/data/config-reference.json'), 'utf8')); - } catch (error) { - if (error.code !== 'ENOENT') throw error; - } - } - - if (!seen.has(manifest.current)) { - throw new Error(`current docs version ${manifest.current} has no snapshot`); - } - process.stdout.write(`validated ${manifest.versions.length} documentation versions\n`); -} - -export async function publishVersion(tag) { - const release = releaseMetadata(tag); - if (!gitPathExists(git, tag, 'docs/next/website/src/content/docs')) { - throw new Error(`${tag} does not contain staged website documentation`); - } - - const manifest = await readManifest(); - const existing = manifest.versions.find((entry) => entry.version === release.version); - if (manifest.current === release.version) { - if ((manifest.stable_source ?? 'legacy') === 'legacy') { - process.stdout.write(`documentation ${release.tag} is already published with the legacy stable source\n`); - return; - } - if (!existing?.commit || existing.commit !== release.commit) { - throw new Error(`published documentation ${release.version} has mismatched commit provenance`); - } - process.stdout.write(`documentation ${release.tag} is already published\n`); - return; - } - if (manifest.current && compareVersions(release.version, manifest.current) < 0) { - if (existing) { - if (existing.tag !== release.tag) { - throw new Error(`version ${release.version} is already associated with ${existing.tag}`); - } - if (existing.commit && existing.commit !== release.commit) { - throw new Error(`version ${release.version} has mismatched commit provenance`); - } - process.stdout.write(`documentation ${release.tag} is already archived\n`); - return; - } - const archived = await snapshotTag(tag, 'docs/next/website/src/content/docs'); - const archivedEntries = new Map(manifest.versions.map((entry) => [entry.version, entry])); - archivedEntries.set(archived.version, archived); - await writeManifest({ - ...manifest, - versions: [...archivedEntries.values()], - }); - process.stdout.write( - `archived documentation snapshot ${release.tag} without changing current ${manifest.current}\n`, - ); - return; - } - if (existing) { - throw new Error(`version ${release.version} already has published documentation`); - } - - const metadata = await snapshotTag(tag, 'docs/next/website/src/content/docs'); - await rm(stableDocsDir, { recursive: true, force: true }); - await rm(stableReferencePath, { force: true }); - - for (const readme of ['README.md', 'README.zh-CN.md']) { - const nextReadme = `docs/next/${readme}`; - if (gitPathExists(git, tag, nextReadme)) { - await writeFile(resolve(repoRoot, readme), git(['show', `${tag}:${nextReadme}`], { binary: true })); - } - } - - const entries = new Map(manifest.versions.map((entry) => [entry.version, entry])); - entries.set(metadata.version, metadata); - await writeManifest({ - schema_version: 1, - stable_source: 'snapshot', - current: metadata.version, - versions: [...entries.values()], - }); - process.stdout.write(`published documentation snapshot ${metadata.tag}\n`); -} - -async function main() { - const [command, value] = process.argv.slice(2); - if (command === 'backfill' && !value) { - await backfillVersions(); - return; - } - if (command === 'publish' && value) { - await publishVersion(value); - return; - } - if (command === 'check' && !value) { - await checkVersions(); - return; - } - if (command === 'current' && !value) { - const manifest = await readManifest(); - process.stdout.write(`${manifest.current ?? ''}\n`); - return; - } - throw new Error( - 'usage: node website/scripts/docs-versions.mjs backfill | check | current | publish <tag>', - ); -} - -if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { - await main(); -} |
