Skip to content

Commit e679e1b

Browse files
committed
docs: close the code fences that swallowed three pages
`docs/basics/routing.md`, `models.md` and `actions.md` each opened a code block that never closed, so from that point everything rendered as code - headings, prose, the lot. Roughly half of each page. The cause is the same in all three: the OPENING fence went missing, leaving the snippet bare under the frontmatter with a stray closer further down. In `routing.md` it is clear where it went - the fence was swallowed into the frontmatter, whose `description` field was two lines of TypeScript: description: "// Points to app/Controllers/ComingSoonController.ts, index method\nroute.get('/coming-soon', 'Controllers/ComingSoonController@index')" That page now describes itself, and all three open their snippet properly. Nothing caught this. `docs:links:check` reads links and the count checks read counts; an unclosed fence is neither, which is why it sat there. `docs-code-fences-close.test.ts` pairs the fences in every doc, and separately looks for the shape that produced these: a line ending in `{`, `(` or `;` immediately after the frontmatter is code that lost its fence, not a sentence. Verified by removing the opener from `routing.md` again - the test names the file.
1 parent 932b743 commit e679e1b

4 files changed

Lines changed: 74 additions & 1 deletion

File tree

docs/basics/actions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
title: Server Actions
33
description: Learn how to create and use Actions in Stacks applications
44
---
5+
```typescript
56
// Get specific keys only
67
const credentials = request.only<{ email: string; password: string }>([
78
'email',

docs/basics/models.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ title: Data Models
33
description: Learn how to define and use ORM models in Stacks applications
44
---
55

6+
```typescript
67
attributes: {
78
name: {
89
factory: faker => faker.person.fullName(),

docs/basics/routing.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
---
22
title: Application Routing
3-
description: "// Points to app/Controllers/ComingSoonController.ts, index method\nroute.get('/coming-soon', 'Controllers/ComingSoonController@index')"
3+
description: Learn how routes map to Actions and Controllers in Stacks applications
44
---
5+
```typescript
56
// Points to app/Controllers/ComingSoonController.ts, index method
67
route.get('/coming-soon', 'Controllers/ComingSoonController@index')
78

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Every fenced code block in the docs is closed.
3+
*
4+
* Three pages under `docs/basics/` opened a block that never closed, so from
5+
* that point the rest of the page rendered as code - headings, prose and all.
6+
* The cause is the same in each: the opening fence went missing and the code
7+
* sat bare under the frontmatter, with a stray closer further down. In
8+
* `routing.md` the fence had been swallowed into the frontmatter itself, whose
9+
* `description` was two lines of TypeScript.
10+
*
11+
* Nothing caught it. `docs:links:check` reads links, and the count checks read
12+
* counts; an unclosed fence is neither.
13+
*/
14+
import { describe, expect, it } from 'bun:test'
15+
import { readdirSync, readFileSync, statSync } from 'node:fs'
16+
import { join } from 'node:path'
17+
18+
const root = new URL('../../../../../', import.meta.url).pathname
19+
20+
function markdownFiles(dir: string, found: string[] = []): string[] {
21+
for (const entry of readdirSync(dir)) {
22+
if (entry.startsWith('.'))
23+
continue
24+
25+
const full = join(dir, entry)
26+
if (statSync(full).isDirectory())
27+
markdownFiles(full, found)
28+
else if (entry.endsWith('.md'))
29+
found.push(full)
30+
}
31+
32+
return found
33+
}
34+
35+
describe('documentation code fences', () => {
36+
it('are all closed', () => {
37+
const unclosed = markdownFiles(join(root, 'docs')).filter((file) => {
38+
let open = false
39+
for (const line of readFileSync(file, 'utf-8').split('\n')) {
40+
// Only a fence at the start of a line delimits a block; an indented
41+
// one inside a block is content.
42+
if (line.startsWith('```'))
43+
open = !open
44+
}
45+
return open
46+
})
47+
48+
expect(unclosed.map(file => file.replace(root, '')).sort()).toEqual([])
49+
})
50+
51+
it('never leave code bare under the frontmatter', () => {
52+
// How all three broke: the opener vanished and the snippet became prose.
53+
const bare = markdownFiles(join(root, 'docs')).filter((file) => {
54+
const lines = readFileSync(file, 'utf-8').split('\n')
55+
if (lines[0]?.trim() !== '---')
56+
return false
57+
58+
const close = lines.findIndex((line, index) => index > 0 && line.trim() === '---')
59+
if (close < 0)
60+
return false
61+
62+
const first = lines.slice(close + 1).find(line => line.trim())
63+
// A line ending in `{`, `(` or `;` right after the frontmatter is code
64+
// that lost its fence, not a sentence.
65+
return !!first && /[{(;]$/.test(first.trim()) && !first.startsWith('```')
66+
})
67+
68+
expect(bare.map(file => file.replace(root, '')).sort()).toEqual([])
69+
})
70+
})

0 commit comments

Comments
 (0)