diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-08-04 03:58:48 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-08-04 03:59:22 +0300 |
| commit | cffc6c4b50178d404fbb939346dab7a33e5ed8d4 (patch) | |
| tree | 7ca1cdfd9b669ca1bbd23d0c800c4a1047913e99 /website/scripts | |
| parent | b12da23918ff94099e7fcedd7db79f3d2fa8fa6f (diff) | |
fix(docs): allow corrections to published versions
refs #916
Diffstat (limited to 'website/scripts')
| -rw-r--r-- | website/scripts/check-built-docs.mjs | 4 | ||||
| -rw-r--r-- | website/scripts/docs-snapshot.mjs | 4 | ||||
| -rw-r--r-- | website/scripts/docs-versions.integration.test.ts | 75 | ||||
| -rw-r--r-- | website/scripts/docs-versions.mjs | 51 | ||||
| -rw-r--r-- | website/scripts/prepare-docs.mjs | 22 |
5 files changed, 100 insertions, 56 deletions
diff --git a/website/scripts/check-built-docs.mjs b/website/scripts/check-built-docs.mjs index 8841bf74..66e904e4 100644 --- a/website/scripts/check-built-docs.mjs +++ b/website/scripts/check-built-docs.mjs @@ -48,7 +48,7 @@ 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'); + throw new Error('the current documentation version 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'); @@ -68,7 +68,7 @@ if (previous) { 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'); + throw new Error('preview or versioned documentation URLs must not appear in the sitemap'); } const build = await inspectFiles(distDir); diff --git a/website/scripts/docs-snapshot.mjs b/website/scripts/docs-snapshot.mjs index b85557a1..e9eaf92c 100644 --- a/website/scripts/docs-snapshot.mjs +++ b/website/scripts/docs-snapshot.mjs @@ -73,7 +73,7 @@ export async function compareGitTree(git, ref, sourceRoot, snapshotRoot) { assertRelativePath(relativePath, entry.path); expected.set(relativePath, entry); } - const actualPaths = await listSnapshotPaths(snapshotRoot); + const actualPaths = await listDocumentationPaths(snapshotRoot); const expectedPaths = [...expected.keys()].sort(); if (JSON.stringify(actualPaths) !== JSON.stringify(expectedPaths)) { throw new Error(`${snapshotRoot} file list differs from ${ref}:${sourceRoot}`); @@ -89,7 +89,7 @@ export async function compareGitTree(git, ref, sourceRoot, snapshotRoot) { } } -async function listSnapshotPaths(root) { +export async function listDocumentationPaths(root) { const paths = []; async function walk(directory) { for (const entry of await readdir(directory, { withFileTypes: true })) { diff --git a/website/scripts/docs-versions.integration.test.ts b/website/scripts/docs-versions.integration.test.ts index 5e2d36a5..052d9ca2 100644 --- a/website/scripts/docs-versions.integration.test.ts +++ b/website/scripts/docs-versions.integration.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, test } from 'bun:test'; import { execFileSync } from 'node:child_process'; -import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'; +import { mkdtemp, mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises'; import { join, resolve } from 'node:path'; import { tmpdir } from 'node:os'; @@ -21,7 +21,8 @@ describe('documentation release publishing', () => { await write(root, 'website/latest.json', '{"version":"0.9.0"}\n'); await write(root, 'README.md', 'stable readme\n'); await write(root, 'README.zh-CN.md', 'stable readme zh-cn\n'); - await write(root, 'docs/next/website/src/content/docs/index.mdx', 'next docs\n'); + const nextDocs = '---\ntitle: Documentation\n---\n\nnext docs\n'; + await write(root, 'docs/next/website/src/content/docs/index.mdx', nextDocs); await write(root, 'docs/next/website/src/data/config-reference.json', '{"next":true}\n'); await write(root, 'docs/next/README.md', 'next readme\n'); await write(root, 'docs/next/README.zh-CN.md', 'next readme zh-cn\n'); @@ -40,7 +41,7 @@ describe('documentation release publishing', () => { await expect(read(root, 'website/src/data/config-reference.json')).rejects.toThrow(); expect(await read(root, 'README.md')).toBe('next readme\n'); expect(await read(root, 'README.zh-CN.md')).toBe('next readme zh-cn\n'); - expect(await read(root, 'docs/versions/1.0.0/website/src/content/docs/index.mdx')).toBe('next docs\n'); + expect(await read(root, 'docs/versions/1.0.0/website/src/content/docs/index.mdx')).toBe(nextDocs); const manifest = JSON.parse(await read(root, 'docs/versions/manifest.json')); expect(manifest.current).toBe('1.0.0'); @@ -65,7 +66,19 @@ describe('documentation release publishing', () => { `${JSON.stringify({ build_id: 'preview-test', commit: previewCommit })}\n`, ); runPrepare(root); - expect(await read(root, 'website/src/content/docs/index.mdx')).toBe('next docs\n'); + const stableDocs = await read(root, 'website/src/content/docs/index.mdx'); + expect(stableDocs).toContain('next docs'); + expect(stableDocs).toContain( + 'editUrl: https://github.com/herdrdev/herdr/blob/master/docs/versions/1.0.0/website/src/content/docs/index.mdx', + ); + const versionedDocs = await read( + root, + 'website/src/content/docs/_versions/1.0.0/index.mdx', + ); + expect(versionedDocs).toContain('next docs'); + expect(versionedDocs).toContain( + 'editUrl: https://github.com/herdrdev/herdr/blob/master/docs/versions/1.0.0/website/src/content/docs/index.mdx', + ); expect(await read(root, 'website/src/content/docs/preview/index.mdx')).toContain( 'Preview build `preview-test`', ); @@ -74,10 +87,36 @@ describe('documentation release publishing', () => { await write(root, 'website/latest.json', '{"version":"1.0.0"}\n'); runScript(root, ['check']); + const correctedDocs = '---\ntitle: Documentation\n---\n\ncorrected docs\n'; + await write( + root, + 'docs/versions/1.0.0/website/src/content/docs/index.mdx', + correctedDocs, + ); + await write( + root, + 'docs/versions/1.0.0/website/src/data/config-reference.json', + '{"corrected":true}\n', + ); + runScript(root, ['check']); + await write(root, 'README.md', 'post-release correction\n'); runScript(root, ['publish', 'v1.0.0']); expect(await read(root, 'README.md')).toBe('post-release correction\n'); + runPrepare(root); + expect(await read(root, 'website/src/content/docs/index.mdx')).toContain('corrected docs'); + expect(await read(root, 'website/src/content/docs/_versions/1.0.0/index.mdx')).toContain( + 'corrected docs', + ); + expect(await read(root, 'website/src/data/config-reference.json')).toBe( + '{"corrected":true}\n', + ); + const versionReferences = JSON.parse( + await read(root, 'website/src/data/config-reference-versions.json'), + ); + expect(versionReferences['1.0.0']).toEqual({ corrected: true }); + runScript(root, ['publish', 'v0.9.0']); const archivedManifest = JSON.parse(await read(root, 'docs/versions/manifest.json')); expect(archivedManifest.current).toBe('1.0.0'); @@ -85,6 +124,34 @@ describe('documentation release publishing', () => { expect(await read(root, 'README.md')).toBe('post-release correction\n'); runScript(root, ['check']); + const correctedArchivedDocs = '---\ntitle: Documentation\n---\n\ncorrected archived docs\n'; + await write( + root, + 'docs/versions/0.9.0/website/src/content/docs/index.mdx', + correctedArchivedDocs, + ); + runScript(root, ['publish', 'v0.9.0']); + expect(await read(root, 'docs/versions/0.9.0/website/src/content/docs/index.mdx')).toBe( + correctedArchivedDocs, + ); + + const archivedDocsRoot = resolve( + root, + 'docs/versions/0.9.0/website/src/content/docs', + ); + await rm(archivedDocsRoot, { recursive: true, force: true }); + expect(() => runScript(root, ['check'])).toThrow(); + await write( + root, + 'docs/versions/0.9.0/website/src/content/docs/index.mdx', + correctedArchivedDocs, + ); + const linkedPage = resolve(archivedDocsRoot, 'linked.mdx'); + await symlink('index.mdx', linkedPage); + expect(() => runScript(root, ['check'])).toThrow(); + await rm(linkedPage); + runScript(root, ['check']); + delete archivedManifest.versions[0].commit; await write(root, 'docs/versions/manifest.json', `${JSON.stringify(archivedManifest)}\n`); expect(() => runScript(root, ['check'])).toThrow(); diff --git a/website/scripts/docs-versions.mjs b/website/scripts/docs-versions.mjs index 30cb099d..0923d683 100644 --- a/website/scripts/docs-versions.mjs +++ b/website/scripts/docs-versions.mjs @@ -2,11 +2,11 @@ import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'; import { dirname, resolve } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { - compareGitTree, createGit, extractGitTree, gitPathExists, gitTreesEqual, + listDocumentationPaths, resolveCommit, } from './docs-snapshot.mjs'; @@ -167,38 +167,26 @@ export async function checkVersions() { 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 sourceRoot = entry.source; - const snapshotRoot = resolve(versionsDir, entry.version, 'website/src/content/docs'); - await compareGitTree(git, entry.tag, sourceRoot, snapshotRoot); - const referenceSource = sourceRoot.includes('docs/next/') - ? 'docs/next/website/src/data/config-reference.json' - : 'website/src/data/config-reference.json'; - const referenceSnapshot = resolve( - versionsDir, - entry.version, - 'website/src/data/config-reference.json', + const versionRoot = resolve(versionsDir, entry.version, 'website'); + const documentationPaths = await listDocumentationPaths( + resolve(versionRoot, 'src/content/docs'), ); - if (gitPathExists(git, entry.tag, referenceSource)) { - const actualReference = await readFile(referenceSnapshot); - const taggedReference = git(['show', `${entry.tag}:${referenceSource}`], { binary: true }); - if (!actualReference.equals(taggedReference)) { - throw new Error(`${entry.version} config reference differs from ${entry.tag}:${referenceSource}`); - } - } else { - try { - await readFile(referenceSnapshot); - throw new Error(`${entry.version} has an unexpected config reference snapshot`); - } catch (error) { - if (error.code !== 'ENOENT') throw error; - } + 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 snapshots\n`); + process.stdout.write(`validated ${manifest.versions.length} documentation versions\n`); } export async function publishVersion(tag) { @@ -217,8 +205,7 @@ export async function publishVersion(tag) { if (!existing?.commit || existing.commit !== release.commit) { throw new Error(`published documentation ${release.version} has mismatched commit provenance`); } - await compareGitTree(git, release.commit, existing.source, resolve(versionsDir, release.version, 'website/src/content/docs')); - process.stdout.write(`documentation snapshot ${release.tag} is already published\n`); + process.stdout.write(`documentation ${release.tag} is already published\n`); return; } if (manifest.current && compareVersions(release.version, manifest.current) < 0) { @@ -229,13 +216,7 @@ export async function publishVersion(tag) { if (existing.commit && existing.commit !== release.commit) { throw new Error(`version ${release.version} has mismatched commit provenance`); } - await compareGitTree( - git, - release.commit, - existing.source, - resolve(versionsDir, release.version, 'website/src/content/docs'), - ); - process.stdout.write(`documentation snapshot ${release.tag} is already archived\n`); + process.stdout.write(`documentation ${release.tag} is already archived\n`); return; } const archived = await snapshotTag(tag, 'docs/next/website/src/content/docs'); @@ -251,7 +232,7 @@ export async function publishVersion(tag) { return; } if (existing) { - throw new Error(`version ${release.version} already has a documentation snapshot`); + throw new Error(`version ${release.version} already has published documentation`); } const metadata = await snapshotTag(tag, 'docs/next/website/src/content/docs'); diff --git a/website/scripts/prepare-docs.mjs b/website/scripts/prepare-docs.mjs index a96e2c41..074ef36d 100644 --- a/website/scripts/prepare-docs.mjs +++ b/website/scripts/prepare-docs.mjs @@ -44,7 +44,6 @@ if (process.argv[2] === '--rewrite-preview-doc-fixture') { rewriteVersionDocContent(Buffer.concat(chunks).toString('utf8'), { version: process.argv[3] ?? '0.7.5', tag: `v${process.argv[3] ?? '0.7.5'}`, - sourceRoot: 'docs/next/website/src/content/docs', relativePath: 'index.mdx', }), ); @@ -118,8 +117,8 @@ async function prepareDocs({ draft }) { stableDocsDir, (content, relativePath) => rewriteStableDocContent(content, { + version: currentEntry.version, tag: currentEntry.tag, - sourceRoot: currentEntry.source, relativePath, }), ); @@ -162,7 +161,6 @@ async function prepareDocs({ draft }) { rewriteVersionDocContent(content, { version, tag: entry.tag, - sourceRoot: entry.source, relativePath, }), ); @@ -269,24 +267,22 @@ export function rewritePreviewDocContent( return insertPreviewNotice(withSourceLink, relativePath, { buildId, commit }); } -export function rewriteStableDocContent(content, { tag, sourceRoot, relativePath }) { +export function rewriteStableDocContent(content, { version, tag, relativePath }) { const taggedContent = rewriteRepositoryLinks(content, tag); - return setGeneratedEditUrl( - taggedContent, - `https://github.com/herdrdev/herdr/blob/${tag}/${sourceRoot}/${relativePath}`, - ); + return setGeneratedEditUrl(taggedContent, versionedDocSourceUrl(version, relativePath)); } -export function rewriteVersionDocContent(content, { version, tag, sourceRoot, 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, - `https://github.com/herdrdev/herdr/blob/${tag}/${sourceRoot}/${relativePath}`, - ); + 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) { |
