@readme/cli 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/git-format.js +80 -0
- package/package.json +23 -4
- package/src/commands/import.js +12 -3
- package/src/commands/oas-sync.js +185 -78
- package/src/commands/pretty.js +14 -4
- package/src/dev/lib/docs.js +2 -2
- package/src/utils/containment.js +87 -0
- package/src/utils/reporter.js +1 -4
- package/src/validators/content.js +12 -5
- package/src/validators/frontmatter.js +4 -10
- package/src/validators/nesting.js +9 -4
- package/src/validators/oas-reference.js +18 -4
- package/vendor/git-format/CLAUDE.md +352 -0
- package/vendor/git-format/frontmatter.schema.json +199 -0
package/src/utils/reporter.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
-
import { createRequire } from 'node:module';
|
|
4
3
|
import path from 'node:path';
|
|
5
4
|
import { fileURLToPath } from 'node:url';
|
|
6
5
|
|
|
@@ -19,8 +18,6 @@ import {
|
|
|
19
18
|
import * as styles from './styles.js';
|
|
20
19
|
import { getRandomTip } from './tips.js';
|
|
21
20
|
|
|
22
|
-
const require = createRequire(import.meta.url);
|
|
23
|
-
|
|
24
21
|
const isRunningInClaude = !!process.env.CLAUDECODE;
|
|
25
22
|
|
|
26
23
|
const CATEGORY_LABELS = {
|
|
@@ -178,7 +175,7 @@ export function createHumanReporter() {
|
|
|
178
175
|
if (isRunningInClaude && unfixed.length > 0) {
|
|
179
176
|
const cliRoot = path.join(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
180
177
|
|
|
181
|
-
const gitFormatDir = path.
|
|
178
|
+
const gitFormatDir = path.join(cliRoot, 'vendor/git-format');
|
|
182
179
|
const claudeMdPath = path.join(gitFormatDir, 'CLAUDE.md');
|
|
183
180
|
const toolsMdPath = path.join(cliRoot, 'vendor/TOOLS.md');
|
|
184
181
|
const schemaPath = path.join(gitFormatDir, 'frontmatter.schema.json');
|
|
@@ -28,12 +28,19 @@ export function validate({ filePath, content, relativePath }) {
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
if (trimmed.length === 0) {
|
|
31
|
-
// index.md files that act as parent pages (have child
|
|
31
|
+
// index.md files that act as parent pages (have child page siblings or nested page
|
|
32
|
+
// directories) can be empty. The backend accepts .md and .mdx in any case.
|
|
32
33
|
if (path.basename(filePath) === 'index.md') {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
const dir = path.dirname(filePath);
|
|
35
|
+
const isPage = f => /\.mdx?$/i.test(f);
|
|
36
|
+
const hasChildren = fs
|
|
37
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
38
|
+
.some(
|
|
39
|
+
e =>
|
|
40
|
+
(!e.isDirectory() && isPage(e.name) && !/^index\.mdx?$/i.test(e.name)) ||
|
|
41
|
+
(e.isDirectory() && fs.readdirSync(path.join(dir, e.name)).some(isPage)),
|
|
42
|
+
);
|
|
43
|
+
if (hasChildren) return null;
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
return {
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
|
-
import path from 'node:path';
|
|
4
2
|
|
|
5
3
|
import Ajv from 'ajv';
|
|
6
4
|
|
|
5
|
+
import frontmatterSchema from '../../vendor/git-format/frontmatter.schema.json' with { type: 'json' };
|
|
7
6
|
import { parseFrontmatter } from '../utils/markdown.js';
|
|
8
7
|
|
|
9
|
-
const require = createRequire(import.meta.url);
|
|
10
|
-
|
|
11
8
|
export const name = 'frontmatter';
|
|
12
9
|
|
|
13
10
|
function getNestedValue(obj, instancePath) {
|
|
@@ -77,18 +74,15 @@ const DIR_SCHEMA_INDEX = {
|
|
|
77
74
|
custom_blocks: 4,
|
|
78
75
|
};
|
|
79
76
|
|
|
80
|
-
//
|
|
81
|
-
const schemaPath = require.resolve('@readmeio/git-format/frontmatter.schema.json');
|
|
82
|
-
const fullSchema = JSON.parse(fs.readFileSync(schemaPath, 'utf-8'));
|
|
83
|
-
|
|
77
|
+
// Compile a validator for each directory type from the vendored schema.
|
|
84
78
|
const ajv = new Ajv({ allErrors: true, strict: false, logger: false, verbose: true });
|
|
85
79
|
|
|
86
80
|
const validators = {};
|
|
87
81
|
const knownProperties = {};
|
|
88
82
|
for (const [dir, index] of Object.entries(DIR_SCHEMA_INDEX)) {
|
|
89
|
-
const subSchema = { defs:
|
|
83
|
+
const subSchema = { defs: frontmatterSchema.defs, ...frontmatterSchema.oneOf[index] };
|
|
90
84
|
validators[dir] = ajv.compile(subSchema);
|
|
91
|
-
knownProperties[dir] = collectPropertyNames(subSchema,
|
|
85
|
+
knownProperties[dir] = collectPropertyNames(subSchema, frontmatterSchema.defs);
|
|
92
86
|
}
|
|
93
87
|
|
|
94
88
|
export function validate({ content, filePath, relativePath, fix }) {
|
|
@@ -4,9 +4,14 @@ export const name = 'nesting';
|
|
|
4
4
|
|
|
5
5
|
// Max folder nesting per top-level content dir. Anything deeper still syncs
|
|
6
6
|
// but won't appear in the sidebar.
|
|
7
|
+
//
|
|
8
|
+
// For `docs` and `reference` the category is the first folder and each page
|
|
9
|
+
// level below it adds one, so this is the number of page levels the sidebar
|
|
10
|
+
// renders under a category. It mirrors our publicly documented sidebar levels.
|
|
11
|
+
// Keep in sync with src/dev/lib/docs.js.
|
|
7
12
|
const MAX_DEPTH = {
|
|
8
|
-
docs:
|
|
9
|
-
reference:
|
|
13
|
+
docs: 5,
|
|
14
|
+
reference: 5,
|
|
10
15
|
recipes: 0,
|
|
11
16
|
custom_pages: 0,
|
|
12
17
|
custom_blocks: 0,
|
|
@@ -23,8 +28,8 @@ export function validateAll(files) {
|
|
|
23
28
|
if (max === undefined) continue;
|
|
24
29
|
|
|
25
30
|
// Depth = number of folders between the top dir and the file.
|
|
26
|
-
// docs/a/b/c/page.md → parts.length
|
|
27
|
-
// docs/a/b/c/d/page.md → parts.length
|
|
31
|
+
// docs/a/b/c/d/e/page.md → parts.length 7 → depth 5
|
|
32
|
+
// docs/a/b/c/d/e/f/page.md → parts.length 8 → depth 6
|
|
28
33
|
const depth = parts.length - 2;
|
|
29
34
|
if (depth <= max) continue;
|
|
30
35
|
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
operationKey,
|
|
10
10
|
findIgnoredInternalExtensions,
|
|
11
11
|
} from '../commands/oas-sync.js';
|
|
12
|
+
import { PathEscapeError } from '../utils/containment.js';
|
|
12
13
|
import { parseFrontmatter } from '../utils/markdown.js';
|
|
13
14
|
|
|
14
15
|
export const name = 'oas-reference';
|
|
@@ -103,11 +104,24 @@ export function validateAll(files, gitRoot, { fix } = {}) {
|
|
|
103
104
|
// reference files, which is far too much to do on the strength of an
|
|
104
105
|
// unfixable warning (`x-readme.internal`, a page pointing at a missing spec).
|
|
105
106
|
if (fix && results.some(r => r.fixable)) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
try {
|
|
108
|
+
const syncResults = syncOas(gitRoot);
|
|
109
|
+
if (syncResults) {
|
|
110
|
+
for (const r of results) {
|
|
111
|
+
if (r.fixable) r.message += ' (fixed)';
|
|
112
|
+
}
|
|
110
113
|
}
|
|
114
|
+
} catch (err) {
|
|
115
|
+
if (!(err instanceof PathEscapeError)) throw err;
|
|
116
|
+
|
|
117
|
+
// Don't mark the other fixes applied — sync stopped before it could.
|
|
118
|
+
results.push({
|
|
119
|
+
file: path.relative(gitRoot, err.target) || 'reference',
|
|
120
|
+
rule: name,
|
|
121
|
+
severity: 'error',
|
|
122
|
+
message: err.message,
|
|
123
|
+
fixable: false,
|
|
124
|
+
});
|
|
111
125
|
}
|
|
112
126
|
}
|
|
113
127
|
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# ReadMe Git-Based Docs Format
|
|
2
|
+
|
|
3
|
+
This repo is the reference implementation of ReadMe's Git-based documentation format. It defines how documentation is structured when syncing with ReadMe via Git.
|
|
4
|
+
|
|
5
|
+
Any LLM modifying a repo that uses this format should reference this file and the examples in this repo.
|
|
6
|
+
|
|
7
|
+
## Directory structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
your-repo/
|
|
11
|
+
├── frontmatter.schema.json # Frontmatter validation schema
|
|
12
|
+
├── docs/ # Guides and documentation pages
|
|
13
|
+
│ ├── {Category Name}/ # Category folders (Title Case with spaces)
|
|
14
|
+
│ │ ├── _order.yaml # Page ordering within this category
|
|
15
|
+
│ │ ├── page-slug.md # Documentation pages (kebab-case filenames)
|
|
16
|
+
│ │ └── parent-page/ # Parent page with children
|
|
17
|
+
│ │ ├── index.md # The parent page content
|
|
18
|
+
│ │ └── child-page.md # Child page nested under parent
|
|
19
|
+
│ └── {Another Category}/
|
|
20
|
+
│ └── ...
|
|
21
|
+
├── reference/ # API reference documentation
|
|
22
|
+
│ ├── oas.json # OpenAPI spec file(s)
|
|
23
|
+
│ └── {API Title}/ # Matches info.title from OAS
|
|
24
|
+
│ └── {Tag}/ # Matches OAS operation tags
|
|
25
|
+
│ ├── index.md # Optional category landing page
|
|
26
|
+
│ └── operationId.md # Endpoint page linked to OAS operation
|
|
27
|
+
├── recipes/ # Code recipe/tutorial pages
|
|
28
|
+
│ ├── _order.yaml
|
|
29
|
+
│ └── recipe-slug.md
|
|
30
|
+
├── custom_pages/ # Standalone pages (markdown or HTML)
|
|
31
|
+
│ └── page-slug.md
|
|
32
|
+
├── custom_blocks/ # Reusable content snippets and components
|
|
33
|
+
│ ├── snippet.md # Markdown snippet
|
|
34
|
+
│ └── Component.mdx # React component (single-file)
|
|
35
|
+
└── reusable_content/ # (also valid top-level directory)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Valid top-level directories:** `docs/`, `reference/`, `custom_pages/`, `recipes/`, `custom_blocks/`, `reusable_content/`
|
|
39
|
+
|
|
40
|
+
## Frontmatter
|
|
41
|
+
|
|
42
|
+
Every page uses YAML frontmatter. The full schema is in `frontmatter.schema.json`. Here's a summary by content type:
|
|
43
|
+
|
|
44
|
+
### Doc pages (`docs/`)
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
---
|
|
48
|
+
title: Page Title # Required
|
|
49
|
+
excerpt: One-sentence summary
|
|
50
|
+
hidden: true # Hides page from navigation
|
|
51
|
+
deprecated: true # Marks page as deprecated
|
|
52
|
+
icon: star # Icon identifier
|
|
53
|
+
metadata:
|
|
54
|
+
title: SEO Title
|
|
55
|
+
description: SEO description
|
|
56
|
+
keywords: [keyword1, keyword2]
|
|
57
|
+
image: https://example.com/image.png
|
|
58
|
+
robots: noindex # "index" or "noindex"
|
|
59
|
+
next:
|
|
60
|
+
description: What's next
|
|
61
|
+
pages:
|
|
62
|
+
- slug: page-slug
|
|
63
|
+
title: Display Title
|
|
64
|
+
type: basic # basic, endpoint, changelog, custom_page
|
|
65
|
+
- title: External Link
|
|
66
|
+
type: link
|
|
67
|
+
url: https://example.com
|
|
68
|
+
link:
|
|
69
|
+
url: https://example.com # Redirect page to this URL
|
|
70
|
+
new_tab: true
|
|
71
|
+
---
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Only `title` is required. All other fields are optional — don't add fields you don't need.
|
|
75
|
+
|
|
76
|
+
### API reference pages (`reference/`)
|
|
77
|
+
|
|
78
|
+
Same fields as doc pages, plus:
|
|
79
|
+
|
|
80
|
+
```yaml
|
|
81
|
+
---
|
|
82
|
+
title: Get a Pet
|
|
83
|
+
excerpt: Fetch a specific pet by ID
|
|
84
|
+
api:
|
|
85
|
+
file: oas.json # Path to OAS file (relative to reference/)
|
|
86
|
+
operationId: getPet # Matches operationId in the OAS spec
|
|
87
|
+
webhook: false # Set true for webhook operations
|
|
88
|
+
api_config: authentication # Special page type: "authentication", "getting-started", or "my-requests"
|
|
89
|
+
recipes: # Related recipe slugs
|
|
90
|
+
- publish-all-docs-in-category
|
|
91
|
+
---
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Set `api` to `null` to detach a reference page from its API operation (makes it a plain page within `reference/`).
|
|
95
|
+
|
|
96
|
+
### Recipe pages (`recipes/`)
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
---
|
|
100
|
+
title: Publish all Docs in Category # Required
|
|
101
|
+
description: Update each doc as published. # Optional
|
|
102
|
+
hidden: true
|
|
103
|
+
recipe: # Required
|
|
104
|
+
color: '#018FF4' # Hex color, required
|
|
105
|
+
icon: circle-play # Icon string, required
|
|
106
|
+
---
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Custom pages (`custom_pages/`)
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
---
|
|
113
|
+
title: My Custom Page # Required
|
|
114
|
+
hidden: true
|
|
115
|
+
fullscreen: true # Renders page without sidebar/nav chrome
|
|
116
|
+
metadata:
|
|
117
|
+
description: ...
|
|
118
|
+
keywords: ...
|
|
119
|
+
title: ...
|
|
120
|
+
image: ...
|
|
121
|
+
---
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Custom pages support markdown (`.md`) and HTML (`.html`). They cannot use `deprecated`.
|
|
125
|
+
|
|
126
|
+
### Custom blocks (`custom_blocks/`)
|
|
127
|
+
|
|
128
|
+
Markdown snippets (`.md`):
|
|
129
|
+
```yaml
|
|
130
|
+
---
|
|
131
|
+
name: my-snippet # Required
|
|
132
|
+
---
|
|
133
|
+
Content here...
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
React components (`.mdx`) — export a component and include a usage example:
|
|
137
|
+
```yaml
|
|
138
|
+
---
|
|
139
|
+
name: MyComponent # Required
|
|
140
|
+
---
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Ordering with `_order.yaml`
|
|
144
|
+
|
|
145
|
+
Every directory containing pages needs an `_order.yaml` to control display order.
|
|
146
|
+
|
|
147
|
+
**Category ordering** — lists folder names (Title Case with spaces):
|
|
148
|
+
```yaml
|
|
149
|
+
# docs/_order.yaml (if it existed — currently categories display alphabetically without one)
|
|
150
|
+
- Documentation
|
|
151
|
+
- More Documentation
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Page ordering within a category** — lists page slugs (kebab-case, no `.md`):
|
|
155
|
+
```yaml
|
|
156
|
+
# docs/Documentation/_order.yaml
|
|
157
|
+
- hello-world
|
|
158
|
+
- parent-page
|
|
159
|
+
- another-page
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Recipe ordering:**
|
|
163
|
+
```yaml
|
|
164
|
+
# recipes/_order.yaml
|
|
165
|
+
- publish-all-docs-in-category
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Parent-child pages
|
|
169
|
+
|
|
170
|
+
Create a folder named after the parent page slug, with `index.md` as the parent and additional `.md` files as children:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
docs/Documentation/
|
|
174
|
+
└── parent-page/
|
|
175
|
+
├── index.md # Parent page (title: "Parent Page")
|
|
176
|
+
└── child-page.md # Child page nested underneath
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Linking between pages
|
|
180
|
+
|
|
181
|
+
### Published URL structure
|
|
182
|
+
|
|
183
|
+
On the published ReadMe site, pages have flat URL paths based on their content type:
|
|
184
|
+
|
|
185
|
+
| Content type | Published URL |
|
|
186
|
+
|---|---|
|
|
187
|
+
| Doc pages (`docs/`) | `/docs/{slug}` |
|
|
188
|
+
| API reference (`reference/`) | `/reference/{slug}` |
|
|
189
|
+
| Recipes (`recipes/`) | `/recipes/{slug}` |
|
|
190
|
+
| Custom pages (`custom_pages/`) | `/page/{slug}` |
|
|
191
|
+
|
|
192
|
+
The slug comes from the filename (without `.md`). Category folders do **not** appear in published URLs — `docs/Getting Started/quickstart.md` becomes `/docs/quickstart`. Child pages nest under their parent: `docs/Category/parent-page/child-page.md` becomes `/docs/parent-page/child-page`.
|
|
193
|
+
|
|
194
|
+
**Important:** Site URLs may include a prefix (e.g., `/lang-en/docs/slug`), so never use absolute paths when linking between pages. Use relative links instead.
|
|
195
|
+
|
|
196
|
+
### Relative links (preferred)
|
|
197
|
+
|
|
198
|
+
Always use relative links based on the published URL structure above (not the file structure in this repo). Do not include `.md` extensions in links.
|
|
199
|
+
|
|
200
|
+
```markdown
|
|
201
|
+
<!-- Link to a page in the same section -->
|
|
202
|
+
[Link text](./other-page)
|
|
203
|
+
|
|
204
|
+
<!-- Link from a doc page to a reference page -->
|
|
205
|
+
[Link text](../reference/some-endpoint)
|
|
206
|
+
|
|
207
|
+
<!-- Link from a child page to a sibling -->
|
|
208
|
+
[Link text](./sibling-page)
|
|
209
|
+
|
|
210
|
+
<!-- Link from a child page up to another doc -->
|
|
211
|
+
[Link text](../other-page)
|
|
212
|
+
|
|
213
|
+
<!-- HTML anchor tags also work with relative links -->
|
|
214
|
+
<a href="./other-page">Link text</a>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### `doc:` protocol links
|
|
218
|
+
|
|
219
|
+
ReadMe supports a special `doc:slug` protocol that resolves to doc pages regardless of URL prefix. This is convenient but only works in markdown links (not in `<a>` tags or component `href` attributes):
|
|
220
|
+
|
|
221
|
+
```markdown
|
|
222
|
+
[Link text](doc:hello-world)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Use relative links as the default.** The `doc:` protocol is an acceptable alternative for markdown-only links to doc pages.
|
|
226
|
+
|
|
227
|
+
## Markdown content conventions
|
|
228
|
+
|
|
229
|
+
- Write in active voice, address the reader as "you"
|
|
230
|
+
- Use sentence case for headings
|
|
231
|
+
- Category folder names: Title Case with spaces (e.g., `Getting Started`, `Core Features`)
|
|
232
|
+
- Page filenames: lowercase kebab-case (e.g., `quickstart.md`, `error-handling.md`)
|
|
233
|
+
|
|
234
|
+
### MDX components
|
|
235
|
+
|
|
236
|
+
These built-in components are available in markdown content:
|
|
237
|
+
|
|
238
|
+
**Tabs** — for non-code tabbed content:
|
|
239
|
+
```markdown
|
|
240
|
+
<Tabs>
|
|
241
|
+
<Tab title="First Tab">
|
|
242
|
+
Content for tab 1
|
|
243
|
+
</Tab>
|
|
244
|
+
<Tab title="Second Tab">
|
|
245
|
+
Content for tab 2
|
|
246
|
+
</Tab>
|
|
247
|
+
</Tabs>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Tabbed code blocks** — place fenced code blocks back-to-back with NO blank lines between them:
|
|
251
|
+
|
|
252
|
+
````markdown
|
|
253
|
+
```javascript
|
|
254
|
+
const pet = await sdk.getPet({ petId: 123 });
|
|
255
|
+
```
|
|
256
|
+
```python
|
|
257
|
+
pet = sdk.get_pet(pet_id=123)
|
|
258
|
+
```
|
|
259
|
+
````
|
|
260
|
+
|
|
261
|
+
**Accordion:**
|
|
262
|
+
```markdown
|
|
263
|
+
<Accordion title="Click to expand" icon="info">
|
|
264
|
+
Hidden content here
|
|
265
|
+
</Accordion>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Cards:**
|
|
269
|
+
```markdown
|
|
270
|
+
<Cards columns={3}>
|
|
271
|
+
<Card title="Getting Started" icon="rocket" href="./getting-started">
|
|
272
|
+
Quick intro to the platform
|
|
273
|
+
</Card>
|
|
274
|
+
<Card title="API Reference" icon="code" href="../reference/">
|
|
275
|
+
Explore the API
|
|
276
|
+
</Card>
|
|
277
|
+
</Cards>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Columns:**
|
|
281
|
+
```markdown
|
|
282
|
+
<Columns>
|
|
283
|
+
<Column>
|
|
284
|
+
Left side content
|
|
285
|
+
</Column>
|
|
286
|
+
<Column>
|
|
287
|
+
Right side content
|
|
288
|
+
</Column>
|
|
289
|
+
</Columns>
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
Only these components are valid. Convert any other components (e.g., `<Callout>`, `<Note>`, `<Info>`, `<Steps>`) to plain markdown or blockquotes.
|
|
293
|
+
|
|
294
|
+
### Recipe line highlighting
|
|
295
|
+
|
|
296
|
+
Recipes can highlight specific code lines per language using HTML comments:
|
|
297
|
+
|
|
298
|
+
```markdown
|
|
299
|
+
<!-- node@L0-L4,L6 -->
|
|
300
|
+
<!-- python@L2-L3,L9 -->
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
These comments go after a markdown heading and before the explanatory text for that step. They reference the code blocks at the top of the recipe.
|
|
304
|
+
|
|
305
|
+
## API reference structure
|
|
306
|
+
|
|
307
|
+
OAS (OpenAPI) spec files go in `reference/`. The directory structure maps to the API:
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
reference/
|
|
311
|
+
├── oas.json # The OpenAPI spec
|
|
312
|
+
└── Simple Pet API/ # Matches spec's info.title
|
|
313
|
+
├── Pet/ # Matches a tag name
|
|
314
|
+
│ ├── index.md # Optional: landing page for this tag
|
|
315
|
+
│ └── getPet.md # Endpoint page (filename = operationId)
|
|
316
|
+
└── Pets/
|
|
317
|
+
├── index.md
|
|
318
|
+
└── randomPet.md
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
If you only need the auto-generated endpoint pages (no custom markdown), you can just have the OAS file with no subdirectories — ReadMe creates "virtual" categories from the spec's tags.
|
|
322
|
+
|
|
323
|
+
Endpoint pages link to their OAS operation via frontmatter:
|
|
324
|
+
```yaml
|
|
325
|
+
api:
|
|
326
|
+
file: oas.json
|
|
327
|
+
operationId: getPet
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
You can also add **conceptual guide pages** inside `reference/` (authentication, error handling, pagination, etc.) by creating pages without an `api` frontmatter field, or by setting `api: null`.
|
|
331
|
+
|
|
332
|
+
## Validation rules
|
|
333
|
+
|
|
334
|
+
When attempting to validate the repo, these are the common issues to check for and how to fix them:
|
|
335
|
+
|
|
336
|
+
- **frontmatter** — Invalid or unknown frontmatter properties. Unknown properties should either be removed or prefixed with `x-` (e.g., `category` → `x-category`). Typos are flagged with the closest valid property name.
|
|
337
|
+
- **ordering** — Every file and subdirectory should be listed in its directory's `_order.yaml`. Slugs that YAML interprets as non-strings (numbers like `404`, booleans like `true`/`false`/`yes`/`no`) must be quoted (e.g., `- "404"`). Never remove quotes from existing entries.
|
|
338
|
+
- **numbering** — Files ending in `-1`, `-2`, etc. where no base name exists should be renamed (e.g., `setup-1.md` → `setup.md`). These suffixes are holdovers from an old bug.
|
|
339
|
+
- **duplicates** — Slugs must be unique across all `docs/` and `reference/` pages. Never rename files inside `ReadMeConfig/` — these have special internal names.
|
|
340
|
+
- **oas-reference** — Reference pages with `api.file` frontmatter must point to a valid OAS file. The `api.operationId` must exist in the spec.
|
|
341
|
+
- **oas-schema** — OAS files must be valid OpenAPI or Swagger specs. Error messages include line numbers and context.
|
|
342
|
+
- **components** — MDX component tags (`<Name>`) must reference a built-in or custom block. Custom block `.md` snippets should use PascalCase filenames (e.g., `MySnippet.md` for `<MySnippet />`).
|
|
343
|
+
- **recipes** — Must have code blocks at the top, then `# Heading` sections. Snippet references (`<!-- lang@lines -->`) must reference existing code blocks with valid line ranges.
|
|
344
|
+
|
|
345
|
+
## Key rules when editing
|
|
346
|
+
|
|
347
|
+
1. **Don't add frontmatter fields not in the schema.** Check `frontmatter.schema.json` if unsure.
|
|
348
|
+
2. **Every directory with pages needs `_order.yaml`** to control ordering.
|
|
349
|
+
3. **Category folders use Title Case with spaces.** Page files use kebab-case.
|
|
350
|
+
4. **Don't modify auto-generated endpoint files** in `reference/` unless adding custom markdown to existing ones. You can add new conceptual pages alongside them.
|
|
351
|
+
5. **Prefer more focused pages over fewer long ones.** Split distinct concepts into separate files.
|
|
352
|
+
6. **Don't create changelog/release-notes content** — ReadMe handles that separately.
|