docs: address fragment review feedback

This commit is contained in:
Turtle
2026-08-13 14:30:17 +08:00
parent 4201bbceca
commit 3a1a4e2cd8
7 changed files with 51 additions and 15 deletions

View File

@@ -607,7 +607,7 @@ function docSyncLeafGates(options: {
pnpmScript('translation-pairing', 'verify-translation-pairing', { label: 'translation pairing' }),
pnpmScript('doc-budgets', 'verify-doc-budgets', { label: 'doc budgets' }),
pnpmExec('docs-site-projection', ['vitest', 'run', 'scripts/project-doc-site.spec.ts', 'scripts/verify-doc-site-fragments.spec.ts'], {
label: 'documentation projection',
label: 'documentation site checks',
}),
// Keep the VitePress build itself in one gate because projection rewrites website/.generated.
pnpmScript('docs-site-build', options.docsBuildScript ?? 'docs:build', { label: 'documentation build' }),

View File

@@ -38,9 +38,29 @@ describe('inspectSiteFragments', () => {
it('resolves clean, encoded, and same-page routes', () => {
const root = fixture()
writeFileSync(join(root, 'guide/encoded.html'), '<h1 id="a b">Encoded</h1><a href="./encoded#a%20b">self</a>')
writeFileSync(
join(root, 'guide/encoded.html'),
'<h1 id="a b">Encoded</h1><h2 id="%">Literal</h2><a href="./encoded#a%20b">encoded</a><a href="#%">literal</a>',
)
expect(inspectSiteFragments(root)).toEqual({ checked: 5, broken: [] })
expect(inspectSiteFragments(root)).toEqual({ checked: 6, broken: [] })
})
it('rejects ambiguous built routes', () => {
const root = fixture()
writeFileSync(join(root, 'guide.html'), '<h1 id="flat">Flat</h1>')
writeFileSync(join(root, 'guide/index.html'), '<h1 id="index">Index</h1>')
expect(() => inspectSiteFragments(root)).toThrow('share route "/guide"')
})
it('rejects malformed fragment hrefs', () => {
const root = fixture()
writeFileSync(join(root, 'guide/invalid.html'), '<a href="http://[invalid]#fragment">invalid</a>')
expect(() => inspectSiteFragments(root)).toThrow(
'guide/invalid.html has invalid fragment href "http://[invalid]#fragment"',
)
})
it('reports missing ids and missing built routes', () => {

View File

@@ -3,7 +3,8 @@
* VitePress use different heading-slug algorithms, so source-link validation
* alone cannot prove that a published fragment exists.
*
* Run with `tsx scripts/verify-doc-site-fragments.ts` after `docs:build`.
* This runs as part of `docs:build` and can also run directly after a build
* with `tsx scripts/verify-doc-site-fragments.ts`.
*/
import { globSync, readFileSync } from 'node:fs'
@@ -61,7 +62,9 @@ function aliasesFor(page: BuiltPage): string[] {
function decodedFragment(hash: string): string {
try {
return decodeURIComponent(hash.slice(1))
} catch {
} catch (error) {
if (!(error instanceof URIError)) throw error
// URIError means malformed percent encoding; preserve the literal id for comparison.
return hash.slice(1)
}
}
@@ -90,7 +93,15 @@ export function inspectSiteFragments(distRoot: string): SiteFragmentReport {
const byRoute = new Map<string, BuiltPage>()
for (const page of pages) {
for (const alias of aliasesFor(page)) byRoute.set(alias, page)
for (const alias of aliasesFor(page)) {
const existing = byRoute.get(alias)
if (existing !== undefined && existing !== page) {
throw new Error(
`verify-doc-site-fragments: built pages ${existing.file} and ${page.file} share route ${JSON.stringify(alias)}.`,
)
}
byRoute.set(alias, page)
}
}
const origin = 'https://dsh-docs.invalid'
@@ -103,8 +114,11 @@ export function inspectSiteFragments(distRoot: string): SiteFragmentReport {
let targetUrl: URL
try {
targetUrl = new URL(href, `${origin}${page.route}`)
} catch {
continue
} catch (error) {
throw new Error(
`verify-doc-site-fragments: ${page.file} has invalid fragment href ${JSON.stringify(href)}.`,
{ cause: error },
)
}
if (targetUrl.origin !== origin || targetUrl.hash === '') continue
const fragment = decodedFragment(targetUrl.hash)