@blaaiz/docs-core 0.1.1 → 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/README.md +6 -6
- package/dist/{chunk-3ZX4WIE3.js → chunk-ICDBGTIJ.js} +89 -3
- package/dist/chunk-ICDBGTIJ.js.map +1 -0
- package/dist/generator.js +1 -1
- package/dist/index.cjs +87 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +74 -1
- package/dist/index.d.ts +74 -1
- package/dist/index.js +1 -1
- package/dist/ui.cjs +35 -1
- package/dist/ui.cjs.map +1 -1
- package/dist/ui.d.cts +76 -1
- package/dist/ui.d.ts +76 -1
- package/dist/ui.js +34 -2
- package/dist/ui.js.map +1 -1
- package/package.json +1 -1
- package/skills/AGENTS-section.md +3 -2
- package/skills/SKILL.md +138 -37
- package/styles/ask-ai.css +4 -0
- package/styles/docs.css +28 -0
- package/dist/chunk-3ZX4WIE3.js.map +0 -1
package/skills/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: docs-core
|
|
3
|
-
description: Work on a documentation site built with @blaaiz/docs-core. Use whenever a task touches docs.config.ts, content/docs.json, content/docs/**, or an import from @blaaiz/docs-core
|
|
3
|
+
description: Work on a documentation site built with @blaaiz/docs-core. Use whenever a task touches docs.config.ts, content/docs.json, content/docs/**, or an import from @blaaiz/docs-core: adding or reordering doc pages, tabs, and groups; publishing an OpenAPI endpoint with the try-it playground; changing theme colours, the logo, or the favicon; setting up public or workspace authentication; turning on Ask AI, copy-page, search, or SEO; or fixing an error the framework raises.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# docs-core
|
|
@@ -18,7 +18,7 @@ So: **never edit anything under `node_modules/@blaaiz/docs-core`.** Every change
|
|
|
18
18
|
you make lands in `docs.config.ts`, `content/docs.json`, a content file, or one
|
|
19
19
|
of the site's own wiring files. A change that seems to need a package edit is
|
|
20
20
|
either a config field you have not found yet, or a framework change that belongs
|
|
21
|
-
in the `docs-core` repository
|
|
21
|
+
in the `docs-core` repository. Say so instead of patching the install.
|
|
22
22
|
|
|
23
23
|
## The files that matter
|
|
24
24
|
|
|
@@ -30,20 +30,21 @@ in the `docs-core` repository — say so instead of patching the install.
|
|
|
30
30
|
| `content/**/*-openapi.json` | the site | Add or change an API endpoint's spec |
|
|
31
31
|
| `content/docs/**/meta.json` | see below | Sidebar order |
|
|
32
32
|
| `content/nav.json` | see below | Top-navigation links, one per tab |
|
|
33
|
-
| `content/api-methods.json` | the generator | Sidebar method badges
|
|
34
|
-
| `content/docs/api/**` | the generator | Generated endpoint pages
|
|
33
|
+
| `content/api-methods.json` | the generator | Sidebar method badges. Never by hand |
|
|
34
|
+
| `content/docs/api/**` | the generator | Generated endpoint pages. Never by hand |
|
|
35
35
|
| `app/**`, `lib/**`, `middleware.ts` | the site | Wiring: routes, layouts, the page tree |
|
|
36
36
|
|
|
37
37
|
`meta.json` and `nav.json` are hand-written on a prose-only site. On a site with
|
|
38
38
|
an API reference the generator writes them from `docs.json` on every run, so
|
|
39
|
-
hand edits are lost
|
|
39
|
+
hand edits are lost; change `docs.json` instead. A site runs the generator when
|
|
40
40
|
its `package.json` has a `predev` / `prebuild` script calling
|
|
41
41
|
`@blaaiz/docs-core/generator`; check that first.
|
|
42
42
|
|
|
43
43
|
## docs.config.ts
|
|
44
44
|
|
|
45
|
-
One call to `defineDocsConfig`. It
|
|
46
|
-
|
|
45
|
+
One call to `defineDocsConfig`. It validates the whole config and throws
|
|
46
|
+
`DocsConfigError` naming the exact field on a bad value; on success it returns
|
|
47
|
+
the config unchanged.
|
|
47
48
|
|
|
48
49
|
```ts
|
|
49
50
|
import { defineDocsConfig } from '@blaaiz/docs-core';
|
|
@@ -158,9 +159,9 @@ Naming a provider is a complete setup. Every other field has a default.
|
|
|
158
159
|
|
|
159
160
|
Allowed models:
|
|
160
161
|
|
|
161
|
-
- **anthropic
|
|
162
|
+
- **anthropic**: `claude-sonnet-5`, `claude-haiku-4-5`, `claude-opus-5`,
|
|
162
163
|
`claude-opus-4-8`, `claude-sonnet-4-6`
|
|
163
|
-
- **openai
|
|
164
|
+
- **openai**: `gpt-5-mini`, `gpt-5`, `gpt-5.2`, `gpt-5-nano`, `gpt-4.1`,
|
|
164
165
|
`gpt-4.1-mini`, `gpt-4o`, `gpt-4o-mini`
|
|
165
166
|
|
|
166
167
|
The whole `ai` block is validated when the route is built, so a typo fails the
|
|
@@ -205,7 +206,7 @@ The navigation, and the only thing that decides what is published.
|
|
|
205
206
|
Rules the parser enforces:
|
|
206
207
|
|
|
207
208
|
- A tab needs a string `tab` title. It carries either `pages` or `groups`, never
|
|
208
|
-
both
|
|
209
|
+
both; `groups` wins if both appear.
|
|
209
210
|
- A group needs a string `group` title and a `pages` array. Groups nest: a
|
|
210
211
|
`pages` array may hold group objects.
|
|
211
212
|
- A page string is either a content path relative to `content/docs`, without the
|
|
@@ -232,7 +233,7 @@ Rules the parser enforces:
|
|
|
232
233
|
3. Add the tab's folder to the root `content/docs/meta.json` `pages` array, and
|
|
233
234
|
give the folder its own `meta.json` with `"root": true`, a `title`, and an
|
|
234
235
|
`icon`. On an API site, re-run the generator instead.
|
|
235
|
-
4. Add the tab to `content/nav.json
|
|
236
|
+
4. Add the tab to `content/nav.json`: `{ text, url, icon }`, pointing at its
|
|
236
237
|
first page. The generator writes this file on an API site.
|
|
237
238
|
|
|
238
239
|
### Add an API endpoint
|
|
@@ -252,11 +253,57 @@ An operation that exists in a spec but is not declared in `docs.json` gets no
|
|
|
252
253
|
page. The navigation decides what is published. The API folder is deleted and
|
|
253
254
|
rebuilt on every run, so a removed endpoint leaves no stale page.
|
|
254
255
|
|
|
256
|
+
### Add, change, or remove the landing page
|
|
257
|
+
|
|
258
|
+
The site root (`app/page.tsx`) has two valid shapes, and switching between
|
|
259
|
+
them is a one-file change:
|
|
260
|
+
|
|
261
|
+
- **A landing page**: render the hero inside `HomeLayout` from
|
|
262
|
+
`fumadocs-ui/layouts/home`: the same top bar as the docs (logo, links,
|
|
263
|
+
search, theme toggle) with no sidebar. Hero content is plain elements
|
|
264
|
+
carrying the `home-*` classes from `@blaaiz/docs-core/styles/home.css`.
|
|
265
|
+
Clicking a link enters the sidebar shell under `/docs`. Sites scaffolded by
|
|
266
|
+
`create-docs-core` ship this shape.
|
|
267
|
+
- **No landing**: the file is a three-line `redirect('/docs')` and readers
|
|
268
|
+
land directly in the docs.
|
|
269
|
+
|
|
270
|
+
Do not put the landing inside `content/docs/`: a page there always renders
|
|
271
|
+
in the sidebar shell. For a wide page _inside_ the docs (sidebar kept, no
|
|
272
|
+
title row, no table of contents), set `full: true` in that page's
|
|
273
|
+
frontmatter instead. Mintlify's `mode: custom` frontmatter is not read by
|
|
274
|
+
this stack; `HomeLayout` at the root is its equivalent.
|
|
275
|
+
|
|
276
|
+
### The top-navigation links
|
|
277
|
+
|
|
278
|
+
Both layouts build their links the same way. Never map `nav.json` by hand
|
|
279
|
+
in a layout:
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
links={docsNavLinks(navLinks).map((link) => ({
|
|
283
|
+
type: 'custom' as const,
|
|
284
|
+
children: <TopNavLink key={link.url} link={link} />,
|
|
285
|
+
}))}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`docsNavLinks` (from `@blaaiz/docs-core/ui`) owns the policy: it prepends
|
|
289
|
+
Home pointing at the landing (`home: false` on a site without one, or a
|
|
290
|
+
custom entry to change its text or drop its icon), resolves icons (an entry
|
|
291
|
+
without `icon` renders text-only), and computes each link's `match` path:
|
|
292
|
+
exact for Home and the docs root, the section prefix for section links, so a
|
|
293
|
+
section stays lit on every page of its section. `TopNavLink` is a small
|
|
294
|
+
client component the site owns (like `components/logo.tsx`): it calls
|
|
295
|
+
`isDocsNavLinkActive(link, usePathname())` and renders an anchor with the
|
|
296
|
+
`topnav-link` class from `styles/docs.css`.
|
|
297
|
+
|
|
298
|
+
One name means one page: Home is always the landing. Give the docs root its
|
|
299
|
+
own tab in `docs.json` under a different name (Overview, Get started) rather
|
|
300
|
+
than a second Home, so no page is reachable only by typing its URL.
|
|
301
|
+
|
|
255
302
|
### Change theme colours
|
|
256
303
|
|
|
257
304
|
Edit `theme.colors` and `theme.darkColors` in `docs.config.ts`. Nothing else:
|
|
258
305
|
the site's layout already turns the palette into CSS variables. Set `primary` in
|
|
259
|
-
both palettes
|
|
306
|
+
both palettes; a light-theme accent rarely has enough contrast on a dark
|
|
260
307
|
background.
|
|
261
308
|
|
|
262
309
|
### Enable Ask AI
|
|
@@ -296,7 +343,15 @@ background.
|
|
|
296
343
|
```
|
|
297
344
|
|
|
298
345
|
`AskAi` accepts `endpoint`, `suggestions`, `label`, and `placeholder`, and all
|
|
299
|
-
four have defaults.
|
|
346
|
+
four have defaults. Write suggestions the corpus can actually answer. They
|
|
347
|
+
are the first thing a reader clicks.
|
|
348
|
+
|
|
349
|
+
On a site with an API reference, build the corpus with the merged OpenAPI
|
|
350
|
+
document so endpoints carry their fields: in `lib/ask-ai.ts`, pass
|
|
351
|
+
`openApiDocuments: { blaaiz: await getMergedSpec() }` to `mdxToMarkdown` (the
|
|
352
|
+
key must match the `document="…"` name in the generated pages). Retrieval
|
|
353
|
+
reads the last three user turns, so follow-ups keep their topic; answers
|
|
354
|
+
render Markdown tables.
|
|
300
355
|
|
|
301
356
|
### Enable copy page
|
|
302
357
|
|
|
@@ -305,8 +360,42 @@ four have defaults.
|
|
|
305
360
|
component, guarded by `config.features?.copyPage`.
|
|
306
361
|
3. Serve the Markdown: `createMarkdownRoute({ read })` in
|
|
307
362
|
`app/md/[[...slug]]/route.ts`, where `read` returns a page's raw MDX.
|
|
363
|
+
4. On a site with an API reference, also pass `loadOpenApiDocuments`: an async
|
|
364
|
+
loader returning the merged OpenAPI documents by name (usually
|
|
365
|
+
`{ blaaiz: await getMergedSpec() }`, matching the `document="…"` the
|
|
366
|
+
generated pages reference). Without it, an API page's Markdown is only its
|
|
367
|
+
title and `METHOD /path`; with it, parameters, body fields, enums, and
|
|
368
|
+
responses are rendered in. The same `openApiDocuments` option exists on
|
|
369
|
+
`mdxToMarkdown` and belongs in the Ask AI corpus builder for the same
|
|
370
|
+
reason (see the Enable Ask AI recipe).
|
|
371
|
+
|
|
372
|
+
### Wire search
|
|
373
|
+
|
|
374
|
+
Fumadocs search is one route file, `app/api/search/route.ts`, re-exporting
|
|
375
|
+
`createFromSource(source)` from `fumadocs-core/search/server`; the dialog comes
|
|
376
|
+
from `RootProvider` and needs nothing else. For a custom surface (a command
|
|
377
|
+
palette, a static index), the framework exports `buildSearchIndex` and
|
|
378
|
+
`searchIndex` from `@blaaiz/docs-core`: pure functions, BM25 scoring with a
|
|
379
|
+
light stemmer, safe to ship to the browser as JSON. Do not reimplement scoring.
|
|
380
|
+
|
|
381
|
+
### Wire SEO
|
|
382
|
+
|
|
383
|
+
Three framework builders read `docs.config.ts`; each mounts as one thin file:
|
|
384
|
+
|
|
385
|
+
1. `buildMetadata(config)` exported as `metadata` from `app/layout.tsx`, and
|
|
386
|
+
`buildMetadata(config, { title, description, path })` from the page's
|
|
387
|
+
`generateMetadata`. Without the per-page call, every page shares one title.
|
|
388
|
+
2. `buildRobots(config)` in `app/robots.ts`: `seo.noindex: true` disallows
|
|
389
|
+
crawling entirely; otherwise `/signin` and `/api/` are excluded.
|
|
390
|
+
3. `buildSitemap(entries, config)` in `app/sitemap.ts`, with entries from
|
|
391
|
+
`source.getPages()`.
|
|
392
|
+
|
|
393
|
+
`theme.favicon` flows through `buildMetadata`. Never add a
|
|
394
|
+
`<link rel="icon">` by hand.
|
|
395
|
+
|
|
396
|
+
### Set up workspace auth
|
|
308
397
|
|
|
309
|
-
|
|
398
|
+
Auth is config-only. The site's auth files ship installed and never change:
|
|
310
399
|
|
|
311
400
|
1. In `docs.config.ts`:
|
|
312
401
|
|
|
@@ -314,17 +403,29 @@ four have defaults.
|
|
|
314
403
|
auth: {
|
|
315
404
|
mode: 'workspace',
|
|
316
405
|
allowedDomains: ['example.com'],
|
|
317
|
-
providers: { secret: {
|
|
406
|
+
providers: { secret: {} },
|
|
318
407
|
}
|
|
319
408
|
```
|
|
320
409
|
|
|
321
|
-
2. Set `DOCS_SESSION_SECRET` (a long random value
|
|
322
|
-
`
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
410
|
+
2. Set `DOCS_SESSION_SECRET` (a long random value, for example
|
|
411
|
+
`openssl rand -hex 32`) and
|
|
412
|
+
`DOCS_ACCESS_SECRET` in the server environment. Restart. Never write new
|
|
413
|
+
auth files.
|
|
414
|
+
|
|
415
|
+
Providers combine freely: `email: true` (dev only), `secret`, and `google`
|
|
416
|
+
(needs `GOOGLE_CLIENT_ID`/`GOOGLE_CLIENT_SECRET`). A declared provider whose
|
|
417
|
+
env var is missing is offered as disabled, not broken; a missing session
|
|
418
|
+
secret fails at startup naming the variable.
|
|
419
|
+
|
|
420
|
+
On a site that adds docs-core to an existing Next.js app, mount
|
|
421
|
+
`createSiteAuth(config.auth)` once in `lib/auth.ts`
|
|
422
|
+
and re-export its handlers: `auth.gate` in `middleware.ts` (matcher must exempt
|
|
423
|
+
static images or the sign-in page's own logo request gets redirected),
|
|
424
|
+
`auth.secretSignIn` / `auth.emailSignIn` / `auth.google.start` /
|
|
425
|
+
`auth.google.callback` / `auth.signOut` as routes under `app/api/auth/`, and
|
|
426
|
+
`AuthScreen` (with `auth.secretEnabled` / `auth.googleEnabled`) on the sign-in
|
|
427
|
+
page. Import `@blaaiz/docs-core/styles/auth.css`. Consider
|
|
428
|
+
`seo: { noindex: true }` on a gated site.
|
|
328
429
|
|
|
329
430
|
## Verification habits
|
|
330
431
|
|
|
@@ -332,8 +433,8 @@ four have defaults.
|
|
|
332
433
|
usually fails at build time by design.
|
|
333
434
|
- After an API change, re-run the generator and confirm the page count it
|
|
334
435
|
reports matches the endpoints you declared.
|
|
335
|
-
- Never hand-edit `content/docs/api/**`, `content/api-methods.json`, or
|
|
336
|
-
site that runs the generator
|
|
436
|
+
- Never hand-edit `content/docs/api/**`, `content/api-methods.json`, or, on a
|
|
437
|
+
site that runs the generator, `meta.json` and `nav.json`. They are outputs.
|
|
337
438
|
- Check a content change in the browser at `/docs`: sidebar order, the top nav,
|
|
338
439
|
and search.
|
|
339
440
|
- A palette change needs a look in both light and dark mode.
|
|
@@ -342,19 +443,19 @@ four have defaults.
|
|
|
342
443
|
|
|
343
444
|
Each message below is raised verbatim by the framework.
|
|
344
445
|
|
|
345
|
-
| Message | Cause and fix
|
|
346
|
-
| ------------------------------------------------------------------------------------------ |
|
|
347
|
-
| `ai.provider must be one of 'anthropic', 'openai', got '…'.` | Typo in `docs.config.ts`. Use one of the two names
|
|
348
|
-
| `ai.model must be one of '…' for provider '…', got '…'.` | The model is not on that provider's allow-list. Use a listed id, or leave `model` out for the default
|
|
349
|
-
| `Ask AI is not configured: set the ANTHROPIC_API_KEY environment variable on the server.` | The route is mounted but the key is missing. Set the env var named by `ai.apiKeyEnv` where the server runs
|
|
350
|
-
| `Target origin is not allow-listed: https://api.example.com` | The try-it request targets an origin missing from `proxy.allowedOrigins`. Add it, exactly, scheme included
|
|
351
|
-
| `Blocked host: localhost` | The proxy refuses loopback, private, and link-local hosts on purpose. Point the playground at a reachable public origin
|
|
352
|
-
| `docs.json navigation must have a "tabs" array.` | `content/docs.json` is missing `navigation.tabs`. The older Mintlify `navigation` array form is not supported
|
|
353
|
-
| `Navigation entry at navigation.tabs[N].pages[M] must be a page string or a group object.` | That entry is neither a string nor an object with a `group` key. A common cause is `{ "page": "…" }`
|
|
354
|
-
| `Group at <path> is missing a string "group" title.` | Add the `group` title, or move a bare string into `pages`
|
|
355
|
-
| `Duplicate operation POST /api/x across inputs.` | Two OpenAPI files define the same method on the same route. The merge refuses to overwrite silently
|
|
356
|
-
| `Each OpenAPI input must have a "paths" object.` | One spec file is not a real OpenAPI document. Even a one-operation file needs `paths`
|
|
357
|
-
| `[docs-core] docs.json references POST /api/x, but <file> defines no such operation.` | The `docs.json` entry and the spec disagree on method or route. Fix whichever is wrong
|
|
446
|
+
| Message | Cause and fix |
|
|
447
|
+
| ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
448
|
+
| `ai.provider must be one of 'anthropic', 'openai', got '…'.` | Typo in `docs.config.ts`. Use one of the two names |
|
|
449
|
+
| `ai.model must be one of '…' for provider '…', got '…'.` | The model is not on that provider's allow-list. Use a listed id, or leave `model` out for the default |
|
|
450
|
+
| `Ask AI is not configured: set the ANTHROPIC_API_KEY environment variable on the server.` | The route is mounted but the key is missing. Set the env var named by `ai.apiKeyEnv` where the server runs, not in `docs.config.ts` |
|
|
451
|
+
| `Target origin is not allow-listed: https://api.example.com` | The try-it request targets an origin missing from `proxy.allowedOrigins`. Add it, exactly, scheme included |
|
|
452
|
+
| `Blocked host: localhost` | The proxy refuses loopback, private, and link-local hosts on purpose. Point the playground at a reachable public origin |
|
|
453
|
+
| `docs.json navigation must have a "tabs" array.` | `content/docs.json` is missing `navigation.tabs`. The older Mintlify `navigation` array form is not supported |
|
|
454
|
+
| `Navigation entry at navigation.tabs[N].pages[M] must be a page string or a group object.` | That entry is neither a string nor an object with a `group` key. A common cause is `{ "page": "…" }` |
|
|
455
|
+
| `Group at <path> is missing a string "group" title.` | Add the `group` title, or move a bare string into `pages` |
|
|
456
|
+
| `Duplicate operation POST /api/x across inputs.` | Two OpenAPI files define the same method on the same route. The merge refuses to overwrite silently. Delete one |
|
|
457
|
+
| `Each OpenAPI input must have a "paths" object.` | One spec file is not a real OpenAPI document. Even a one-operation file needs `paths` |
|
|
458
|
+
| `[docs-core] docs.json references POST /api/x, but <file> defines no such operation.` | The `docs.json` entry and the spec disagree on method or route. Fix whichever is wrong |
|
|
358
459
|
|
|
359
460
|
Error classes are exported, so a site can branch on them:
|
|
360
461
|
`InvalidNavigationError`, `OpenApiMergeError`, `ProxyTargetError`, and
|
package/styles/ask-ai.css
CHANGED
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
bottom: 1.5rem;
|
|
29
29
|
z-index: 9990;
|
|
30
30
|
display: inline-flex;
|
|
31
|
+
/* A floating pill is always content-sized. Without this, some environments
|
|
32
|
+
stretch a fixed-position button past its content and the shortcut chip is
|
|
33
|
+
left floating mid-pill. */
|
|
34
|
+
width: max-content;
|
|
31
35
|
align-items: center;
|
|
32
36
|
gap: 9px;
|
|
33
37
|
height: 42px;
|
package/styles/docs.css
CHANGED
|
@@ -245,3 +245,31 @@ body {
|
|
|
245
245
|
.page-head > h1 {
|
|
246
246
|
margin: 0;
|
|
247
247
|
}
|
|
248
|
+
|
|
249
|
+
/* Top-navigation links rendered by a site's TopNavLink component from
|
|
250
|
+
docsNavLinks data. Matches the fumadocs navbar look on theme tokens. */
|
|
251
|
+
.topnav-link {
|
|
252
|
+
display: inline-flex;
|
|
253
|
+
align-items: center;
|
|
254
|
+
gap: 6px;
|
|
255
|
+
padding: 6px 10px;
|
|
256
|
+
border-radius: 8px;
|
|
257
|
+
color: var(--color-fd-muted-foreground);
|
|
258
|
+
font-size: 14px;
|
|
259
|
+
font-weight: 500;
|
|
260
|
+
text-decoration: none;
|
|
261
|
+
transition: color 0.15s ease;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.topnav-link:hover {
|
|
265
|
+
color: var(--color-fd-accent-foreground, var(--color-fd-foreground));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.topnav-link[data-active='true'] {
|
|
269
|
+
color: var(--color-fd-primary);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.topnav-link svg {
|
|
273
|
+
width: 15px;
|
|
274
|
+
height: 15px;
|
|
275
|
+
}
|