Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions scripts/prerender.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -590,12 +590,20 @@ async function emitMarkdownRoutes() {
// route '/foo' → dist/foo.md
// route '/foo/bar' → dist/foo/bar.md
// route '/' → dist/index.md
async function writeRouteMd(route, content) {
//
// includeInAggregate (default true): whether the route's content is also
// concatenated into /llms-full.txt. The per-section docs mirrors pass false
// because their content already appears verbatim in the /docs.md
// concatenated mirror — including them too would duplicate every docs
// section in the one-shot aggregate.
async function writeRouteMd(route, content, includeInAggregate = true) {
const fileSubpath = route === '/' ? 'index.md' : route.replace(/^\//, '') + '.md'
const outPath = resolve(DIST, fileSubpath)
await mkdir(dirname(outPath), { recursive: true })
await writeFile(outPath, content, 'utf-8')
out.push({ route: route === '/' ? '/index.md' : route + '.md', content })
if (includeInAggregate) {
out.push({ route: route === '/' ? '/index.md' : route + '.md', content })
}
}

// 1. React-only pages — read from .content/pages/<name>.md
Expand Down Expand Up @@ -657,6 +665,32 @@ async function emitMarkdownRoutes() {
if (docsFiles.length > 0) {
const docsPage = await buildDocsPage(docsDir, docsFiles)
await writeRouteMd('/docs', docsPage)

// 6b. Per-section .md mirrors — /docs/<slug>.md for every docs section.
//
// The /docs HTML page renders all sections under one route with anchor
// ids equal to the source filename slug (buildDocsPage sets s.id =
// filename, matching the `#troubleshooting-deploys` HTML anchor). The
// /llms.txt agent contract links the section directly as
// `/docs/<slug>.md` (e.g. the troubleshooting-deploys auto-debug guide).
// Without these per-section mirrors that URL 404s — only /docs.md (the
// concatenated mirror) and the legal /docs/public/*.md statics resolved.
//
// Each mirror is the standalone section: an H1 title (from frontmatter)
// + the section body with its YAML frontmatter stripped. This mirrors the
// blog/use-case per-slug .md pattern above so an agent can fetch ONE
// section instead of the whole concatenated doc. GH Pages serves the
// nested file directly (proven by the existing /docs/public/*.md statics).
for (const f of docsFiles) {
const slug = f.replace(/\.md$/, '')
const src = await readFile(resolve(docsDir, f), 'utf-8')
const meta = parseFrontmatter(src)
const body = src.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '').trim()
const title = meta.title || slug
// includeInAggregate=false: the content is already in /docs.md (the
// concatenated mirror that IS in the aggregate).
await writeRouteMd(`/docs/${slug}`, `# ${title}\n\n${body}\n`, false)
}
}

return out
Expand Down
34 changes: 34 additions & 0 deletions src/prerender.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,37 @@ describe('prerender.mjs Step 4.6 — authShellRoutes (UI-3)', () => {
expect(src).toMatch(/['"]\/app\/billing['"]:\s*{[^}]*title:/)
})
})

/* Bug-3 (2026-06-06): the agent-facing troubleshooting page is linked from
* llms.txt as https://instanode.dev/docs/troubleshooting-deploys.md, but that
* nested per-section .md route 404'd — only /docs.md (concatenated) and the
* legal /docs/public/*.md statics resolved. Step 6b of emitMarkdownRoutes now
* emits /docs/<slug>.md for every docs section so the contract URL 200s.
*
* These are source guards (deterministic, no filesystem/build dependency) so
* they run identically under `npm run gate` (build-then-vitest) and the
* coverage job (vitest only, no build) — every line is always executed. The
* real-file emission is exercised by `npm run build` in the gate; the existing
* llmsContract.test.ts already pins the `troubleshooting-deploys` reference in
* public/llms.txt, so URL and contract stay in lock-step. */
describe('prerender.mjs Step 6b — per-section docs .md mirrors (Bug-3)', () => {
const src = readFileSync(PRERENDER, 'utf-8')

it('emits /docs/<slug>.md from each .content/docs section file', () => {
// The loop derives the slug from the docs filename and writes /docs/<slug>.
expect(src).toMatch(/writeRouteMd\(`\/docs\/\$\{slug\}`/)
})

it('excludes per-section docs from the aggregate (content already in /docs.md)', () => {
// The per-section mirror call passes includeInAggregate=false (third arg)
// so /llms-full.txt does not duplicate every docs section.
expect(src).toMatch(/writeRouteMd\(`\/docs\/\$\{slug\}`,[^)]*,\s*false\)/)
})

it('builds each section mirror from the section title + frontmatter-stripped body', () => {
// The mirror is the standalone section: an H1 from frontmatter title and
// the body with its YAML frontmatter removed (mirrors buildDocsPage).
expect(src).toMatch(/const title = meta\.title \|\| slug/)
expect(src).toMatch(/`# \$\{title\}\\n\\n\$\{body\}\\n`/)
})
})
Loading