@readme/cli 0.2.0 → 0.3.0
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/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/oas-reference.js +18 -4
- package/vendor/git-format/CLAUDE.md +352 -0
- package/vendor/git-format/frontmatter.schema.json +199 -0
|
@@ -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 }) {
|
|
@@ -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.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"title": "ReadMe frontmatter shapes",
|
|
4
|
+
"oneOf": [
|
|
5
|
+
{
|
|
6
|
+
"title": "Page",
|
|
7
|
+
"description": "This is the shape for files within the `docs/` directory.",
|
|
8
|
+
"$ref": "#/defs/filetype-page"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"title": "Custom Page",
|
|
12
|
+
"description": "This is the shape for files within the `custom_pages/` directory.",
|
|
13
|
+
"allOf": [
|
|
14
|
+
{ "$ref": "#/defs/common-page" },
|
|
15
|
+
{ "type": "object", "properties": { "fullscreen": { "type": ["boolean", "null"] } } }
|
|
16
|
+
],
|
|
17
|
+
"required": ["title"],
|
|
18
|
+
"not": {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": { "deprecated": { "type": "boolean" } },
|
|
21
|
+
"description": "Custom pages have everything that a common page has except that they can't be deprecated."
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"title": "API Reference",
|
|
26
|
+
"description": "This is the shape for files within the `reference/` directory.",
|
|
27
|
+
"allOf": [
|
|
28
|
+
{ "$ref": "#/defs/filetype-page" },
|
|
29
|
+
{
|
|
30
|
+
"type": "object",
|
|
31
|
+
"properties": {
|
|
32
|
+
"api": {
|
|
33
|
+
"anyOf": [
|
|
34
|
+
{
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {
|
|
37
|
+
"file": { "type": "string" },
|
|
38
|
+
"operationId": { "type": "string" },
|
|
39
|
+
"webhook": { "type": ["boolean", "null"] }
|
|
40
|
+
},
|
|
41
|
+
"required": ["file", "operationId"]
|
|
42
|
+
},
|
|
43
|
+
{ "type": "null" }
|
|
44
|
+
],
|
|
45
|
+
"description": "Information about the API operation that this page is documenting. Setting this to `null` will detach it from the API operation and make it a standard page."
|
|
46
|
+
},
|
|
47
|
+
"api_config": {
|
|
48
|
+
"anyOf": [
|
|
49
|
+
{ "type": "string", "enum": ["authentication", "getting-started", "my-requests"] },
|
|
50
|
+
{ "type": "null" }
|
|
51
|
+
],
|
|
52
|
+
"description": "Describes a Reference config page of a particular type."
|
|
53
|
+
},
|
|
54
|
+
"recipes": {
|
|
55
|
+
"anyOf": [{ "type": "array", "items": { "type": "string" } }, { "type": "null" }],
|
|
56
|
+
"description": "A collection of recipes that are related to this API operation."
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"title": "Recipe",
|
|
64
|
+
"description": "This is the shape for files within the `recipes/` directory.",
|
|
65
|
+
"allOf": [
|
|
66
|
+
{ "$ref": "#/defs/core" },
|
|
67
|
+
{
|
|
68
|
+
"type": "object",
|
|
69
|
+
"properties": {
|
|
70
|
+
"description": { "type": ["string", "null"] },
|
|
71
|
+
"recipe": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"properties": {
|
|
74
|
+
"color": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$" },
|
|
75
|
+
"icon": { "type": "string" }
|
|
76
|
+
},
|
|
77
|
+
"required": ["color", "icon"]
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
"required": ["title", "recipe"]
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"title": "Custom Block",
|
|
86
|
+
"description": "This is the shape for custom blocks in the `custom_blocks/` directory.",
|
|
87
|
+
"type": "object",
|
|
88
|
+
"properties": { "name": { "type": ["string", "null"] } },
|
|
89
|
+
"required": ["name"]
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
"defs": {
|
|
93
|
+
"core": {
|
|
94
|
+
"title": "Core properties that are available on all file types.",
|
|
95
|
+
"type": "object",
|
|
96
|
+
"properties": { "hidden": { "type": ["boolean", "null"] }, "title": { "type": "string" } }
|
|
97
|
+
},
|
|
98
|
+
"metadata": {
|
|
99
|
+
"title": "Properties that control file metadata.",
|
|
100
|
+
"type": "object",
|
|
101
|
+
"properties": {
|
|
102
|
+
"description": { "type": ["string", "null"] },
|
|
103
|
+
"image": { "type": ["string", "null"] },
|
|
104
|
+
"keywords": { "anyOf": [{ "type": "array", "items": { "type": "string" } }, { "type": "null" }] },
|
|
105
|
+
"robots": { "anyOf": [{ "type": "string", "enum": ["index", "noindex"] }, { "type": "null" }] },
|
|
106
|
+
"title": { "type": ["string", "null"], "description": "This is the title that's used for SEO metadata." }
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"common-page": {
|
|
110
|
+
"allOf": [
|
|
111
|
+
{ "$ref": "#/defs/core" },
|
|
112
|
+
{
|
|
113
|
+
"type": "object",
|
|
114
|
+
"properties": {
|
|
115
|
+
"deprecated": { "type": ["boolean", "null"] },
|
|
116
|
+
"excerpt": { "type": ["string", "null"] },
|
|
117
|
+
"icon": { "type": ["string", "null"] },
|
|
118
|
+
"table_of_contents": { "type": ["boolean", "null"] },
|
|
119
|
+
"x-import": {
|
|
120
|
+
"type": ["string", "null"],
|
|
121
|
+
"description": "Set during the docs site import flow. This is a link to the external page that needs to be imported and signifies this page has not yet been imported. It is removed once the page content is imported."
|
|
122
|
+
},
|
|
123
|
+
"metadata": { "$ref": "#/defs/metadata" },
|
|
124
|
+
"next": {
|
|
125
|
+
"type": "object",
|
|
126
|
+
"properties": {
|
|
127
|
+
"description": { "type": ["string", "null"] },
|
|
128
|
+
"pages": {
|
|
129
|
+
"anyOf": [
|
|
130
|
+
{
|
|
131
|
+
"type": "array",
|
|
132
|
+
"items": {
|
|
133
|
+
"anyOf": [
|
|
134
|
+
{
|
|
135
|
+
"type": "object",
|
|
136
|
+
"properties": {
|
|
137
|
+
"slug": { "type": "string" },
|
|
138
|
+
"title": { "type": ["string", "null"] },
|
|
139
|
+
"type": { "type": "string", "enum": ["basic", "endpoint", "changelog", "custom_page"] }
|
|
140
|
+
},
|
|
141
|
+
"description": "A page within the developer site. Does not support multiguides and is generally deprecated in favor of `type: page`."
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"type": "object",
|
|
145
|
+
"properties": {
|
|
146
|
+
"slug": { "type": "string" },
|
|
147
|
+
"directory": {
|
|
148
|
+
"type": "string",
|
|
149
|
+
"pattern": "^[a-z0-9][a-z0-9_-]{0,29}$",
|
|
150
|
+
"description": "The section directory of the page."
|
|
151
|
+
},
|
|
152
|
+
"title": { "type": ["string", "null"] },
|
|
153
|
+
"type": { "type": "string", "const": "page" }
|
|
154
|
+
},
|
|
155
|
+
"description": "A page within the developer site. Multiguides compatible."
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"type": "object",
|
|
159
|
+
"properties": {
|
|
160
|
+
"title": { "type": ["string", "null"] },
|
|
161
|
+
"type": { "type": "string", "const": "link" },
|
|
162
|
+
"url": { "type": "string" }
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
{ "type": "null" }
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
},
|
|
177
|
+
"filetype-page": {
|
|
178
|
+
"allOf": [
|
|
179
|
+
{ "$ref": "#/defs/common-page" },
|
|
180
|
+
{
|
|
181
|
+
"type": "object",
|
|
182
|
+
"properties": {
|
|
183
|
+
"link": {
|
|
184
|
+
"anyOf": [
|
|
185
|
+
{
|
|
186
|
+
"type": "object",
|
|
187
|
+
"properties": { "new_tab": { "type": ["boolean", "null"] }, "url": { "type": ["string", "null"] } }
|
|
188
|
+
},
|
|
189
|
+
{ "type": "null" }
|
|
190
|
+
],
|
|
191
|
+
"description": "Information about where this page should redirect to. Setting this to `null` will make the page a standard page."
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
],
|
|
196
|
+
"required": ["title"]
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|