@kitr/kit-docs 0.0.0-stage → 0.4.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Platon Podkopov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,11 @@
1
- # Temporary Holding Version
1
+ # @kitr/kit-docs
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Docs kit.** Start a docs site from markdown files: a sidebar, search, dark mode and copies for coding agents, prerendered to static HTML.
4
+
5
+ ```sh
6
+ pnpm dlx @kitr/kitr new my-app --kit docs
7
+ ```
8
+
9
+ Read the guide at https://kitr.dev/docs/cli.
10
+
11
+ Part of [kitr](https://kitr.dev), the TypeScript framework where everything is an add-on. MIT licensed.
package/dist/kitr.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import { AddContext } from "@kitr/core/package";
2
+ /**
3
+ * `kitr new --kit docs`: a static docs site from markdown. `content` renders \`content/docs\`,
4
+ * `seo` gives every page its tags, `tailwind` and `theme` the look, and the registry components
5
+ * the sidebar, search and package-manager commands.
6
+ */
7
+ declare const _default: {
8
+ readonly name: "docs";
9
+ readonly kind: "kit";
10
+ readonly kit: {
11
+ readonly description: "A static docs site from markdown: sidebar, search, package-manager commands, dark mode and llms.txt.";
12
+ readonly platforms: {
13
+ readonly site: {
14
+ readonly type: "web";
15
+ readonly options: {
16
+ readonly mode: "static";
17
+ };
18
+ };
19
+ };
20
+ readonly packages: ["content", "seo", "tailwind", "theme", "testing"];
21
+ readonly components: ["icon", "button", "theme-toggle", "command", "search-dialog", "doc-layout"];
22
+ readonly files: (ctx: AddContext, name: string) => void;
23
+ };
24
+ };
25
+ export { _default as default };
package/dist/kitr.js ADDED
@@ -0,0 +1,358 @@
1
+ import { definePackage, displayName, removeFiles, startDocument, writeTemplate } from "@kitr/core/package";
2
+ // src/kitr.ts
3
+ const navJson = `[
4
+ {
5
+ "title": "Start here",
6
+ "description": "Run the site and write your first pages.",
7
+ "pages": ["getting-started", "writing"]
8
+ }
9
+ ]
10
+ `;
11
+ const gettingStarted = `---
12
+ title: Getting started
13
+ description: Run the docs site, add a page and publish it.
14
+ ---
15
+
16
+ # Getting started
17
+
18
+ This site is markdown in \`content/docs/\`, rendered by \`kitr generate\` and served by a static web platform. Every page is plain HTML after \`kitr build\`, so any static host can serve it.
19
+
20
+ > [!SUMMARY]
21
+ > Write a page in \`content/docs/<slug>.md\`, list its slug in \`content/docs/nav.json\`, and it appears at \`/docs/<slug>\` with the sidebar, search and an \`llms.txt\` entry.
22
+
23
+ ## 1. Run the site
24
+
25
+ Start the dev server. It renders the markdown on start and again whenever you save a page.
26
+
27
+ \`\`\`sh
28
+ kitr dev
29
+ \`\`\`
30
+
31
+ Open the address it prints. The page you are reading is \`content/docs/getting-started.md\`.
32
+
33
+ ## 2. Add a page
34
+
35
+ Create a markdown file. The file name is the page's slug and the last part of its URL.
36
+
37
+ \`\`\`md
38
+ <!-- content/docs/deploy.md -->
39
+ ---
40
+ title: Deploy
41
+ description: Publish the site to any static host.
42
+ ---
43
+
44
+ The build writes plain HTML to \`dist/site/client\`.
45
+ \`\`\`
46
+
47
+ Then list the slug in the group it belongs to. The order in \`nav.json\` is the order of the sidebar and of the previous and next links.
48
+
49
+ \`\`\`json
50
+ // content/docs/nav.json
51
+ [
52
+ {
53
+ "title": "Start here",
54
+ "pages": ["getting-started", "writing", "deploy"]
55
+ }
56
+ ]
57
+ \`\`\`
58
+
59
+ ## 3. Publish it
60
+
61
+ Build the site, then upload \`dist/site/client\` to any static host.
62
+
63
+ \`\`\`sh
64
+ kitr build
65
+ \`\`\`
66
+
67
+ The build prerenders every page linked from the home page and writes \`llms.txt\`, \`llms-full.txt\` and a \`.md\` copy of each page beside the HTML, so coding agents can read the docs without a browser.
68
+
69
+ > [!TIP]
70
+ > \`kitr start\` serves the built folder locally, so you can check the result before you upload it.
71
+
72
+ ## Next steps
73
+
74
+ - [Write pages](./writing.md): frontmatter, callouts, code captions and commands.
75
+ - [Content package](https://kitr.dev/packages/content): every option of \`config/content.ts\`.
76
+ `;
77
+ const writing = `---
78
+ title: Writing pages
79
+ description: Frontmatter, callouts, code captions and kitr commands.
80
+ ---
81
+
82
+ # Writing pages
83
+
84
+ Pages are GitHub-flavoured markdown with a few additions. \`kitr generate\` turns them into HTML at build time, so none of this costs the reader anything.
85
+
86
+ ## Frontmatter
87
+
88
+ Every page starts with a title and a one-sentence description. The title is the page's heading and the browser tab; the description is the line under the heading, the search result and the share preview.
89
+
90
+ \`\`\`md
91
+ ---
92
+ title: Writing pages
93
+ description: Frontmatter, callouts, code captions and kitr commands.
94
+ ---
95
+ \`\`\`
96
+
97
+ \`nav.json\` can override both for one page: \`{ "slug": "cli", "title": "The CLI" }\`.
98
+
99
+ ## Callouts
100
+
101
+ A blockquote that starts with a marker becomes a callout. Text after the marker on the same line is its title.
102
+
103
+ \`\`\`md
104
+ > [!NOTE]
105
+ > Plain information the reader may want.
106
+
107
+ > [!WARNING] Deletes data
108
+ > Something that loses data or breaks production.
109
+ \`\`\`
110
+
111
+ > [!NOTE]
112
+ > This is a note. \`[!TIP]\`, \`[!WARNING]\` and \`[!SUMMARY]\` work the same way.
113
+
114
+ ## Code blocks
115
+
116
+ Code is highlighted at build time in a light and a dark theme. When the first line is a comment naming a file, it becomes the block's caption.
117
+
118
+ \`\`\`ts
119
+ // app/routes.ts
120
+ import { loadPage } from '#kitr/content'
121
+
122
+ const page = await loadPage('docs', 'writing')
123
+ \`\`\`
124
+
125
+ ## Commands
126
+
127
+ A shell block whose every line starts with \`kitr \` becomes a command with tabs for npm, pnpm, yarn and bun. The reader's choice is remembered on every page.
128
+
129
+ \`\`\`sh
130
+ kitr add content
131
+ \`\`\`
132
+
133
+ ## Steps
134
+
135
+ Number the H2 headings (\`## 1. Install\`) and the page shows them as steps, like [Getting started](./getting-started.md).
136
+
137
+ ## Links
138
+
139
+ Link another page by its file, as you would on GitHub: \`[Getting started](./getting-started.md)\` becomes a link to \`/docs/getting-started\`. Links to other sites open in a new tab.
140
+
141
+ | Write | The reader gets |
142
+ | --- | --- |
143
+ | \`[Getting started](./getting-started.md)\` | \`/docs/getting-started\` |
144
+ | \`[Writing](./writing.md#callouts)\` | \`/docs/writing#callouts\` |
145
+ | \`[kitr](https://kitr.dev)\` | a new tab |
146
+
147
+ ## Next steps
148
+
149
+ - [Getting started](./getting-started.md): run, add a page and publish.
150
+ - [Content package](https://kitr.dev/packages/content): every option and the React components.
151
+ `;
152
+ const files = (ctx, name) => {
153
+ const brand = displayName(name);
154
+ removeFiles(ctx, "app/pages/home.tsx", "app/pages/about.tsx", "test/home.test.tsx");
155
+ writeTemplate(ctx, "content/docs/nav.json", navJson);
156
+ writeTemplate(ctx, "content/docs/getting-started.md", gettingStarted);
157
+ writeTemplate(ctx, "content/docs/writing.md", writing);
158
+ writeTemplate(ctx, "config/seo.ts", `import { defineConfig } from '@kitr/seo'
159
+
160
+ export default defineConfig({
161
+ site: { name: '${brand}' },
162
+ // The static platform prerenders every page linked from \`/\`; list pages nothing links to.
163
+ prerender: [],
164
+ })
165
+ `);
166
+ writeTemplate(ctx, "app/document.tsx", startDocument({
167
+ imports: `import contentCss from '@kitr/content/styles.css?url'
168
+ import { Link } from '@kitr/router'
169
+ import { seo } from '@kitr/seo'
170
+ import { loadSearch } from '#kitr/content'
171
+ import { SearchDialog } from '#components/ui/search-dialog'
172
+ import { ThemeToggle } from '#components/ui/theme-toggle'
173
+ `,
174
+ headParams: "ctx: object",
175
+ head: ` meta: [
176
+ { charSet: 'utf-8' },
177
+ { name: 'viewport', content: 'width=device-width, initial-scale=1' },
178
+ ...seo({ title: '${brand}', description: 'Documentation built with kitr.' }, ctx).meta,
179
+ ],`,
180
+ body: ` <SiteHeader />
181
+ <Outlet />`,
182
+ extra: `
183
+ function SiteHeader() {
184
+ return (
185
+ <header className="sticky top-0 z-20 border-b border-border bg-background/80 backdrop-blur">
186
+ <div className="mx-auto flex h-14 max-w-6xl items-center gap-4 px-4">
187
+ <Link to="/" className="font-semibold">
188
+ ${brand}
189
+ </Link>
190
+ <Link to="/docs" className="text-sm text-muted-foreground hover:text-foreground">
191
+ Docs
192
+ </Link>
193
+ <div className="flex-1" />
194
+ <SearchDialog load={loadSearch} />
195
+ <ThemeToggle />
196
+ </div>
197
+ </header>
198
+ )
199
+ }
200
+ `
201
+ }).replace("links: [{ rel: 'stylesheet', href: css }],", "links: [\n { rel: 'stylesheet', href: css },\n // The look of rendered markdown, over the theme tokens. Drop it to style .prose yourself.\n { rel: 'stylesheet', href: contentCss },\n ],"));
202
+ writeTemplate(ctx, "app/routes.ts", `import { defineRoutes } from '@kitr/kitr/app'
203
+ import { notFound } from '@kitr/router'
204
+ import router from '@kitr/router/routes'
205
+ import { seo } from '@kitr/seo'
206
+ import { loadPage } from '#kitr/content'
207
+ import { head, RootDocument } from './document'
208
+
209
+ /**
210
+ * Every page of the site. The docs pages are the markdown in content/docs, rendered by
211
+ * \`kitr generate\`; the loader reads one and \`seo()\` takes its title and description.
212
+ */
213
+ export const routes = defineRoutes([router], (r) =>
214
+ r
215
+ .root({ head, component: RootDocument })
216
+ .page('/', { lazy: () => import('./pages/docs') })
217
+ .page('/docs', {
218
+ head: (ctx) => seo({ title: 'Docs' }, ctx),
219
+ lazy: () => import('./pages/docs'),
220
+ })
221
+ .page('/docs/$slug', {
222
+ loader: async ({ params }) => {
223
+ const page = await loadPage('docs', params.slug)
224
+ if (!page) throw notFound()
225
+ return page
226
+ },
227
+ head: (ctx) => seo({ type: 'article' }, ctx),
228
+ lazy: () => import('./pages/docs/slug'),
229
+ }),
230
+ )
231
+
232
+ export type AppRoutes = typeof routes.$types
233
+ `);
234
+ writeTemplate(ctx, "app/pages/docs.tsx", `import { Link } from '@kitr/router'
235
+ import { nav } from '#kitr/content'
236
+
237
+ /** The docs home: every group from content/docs/nav.json, each page with its description. */
238
+ export default function DocsIndexPage() {
239
+ return (
240
+ <main className="mx-auto max-w-4xl px-4 py-12">
241
+ <h1 className="text-3xl font-bold tracking-tight">${brand}</h1>
242
+ <p className="mt-2 text-lg text-muted-foreground">
243
+ Everything about ${brand}, from the first run to the last option.
244
+ </p>
245
+ {nav.docs.map((group) => (
246
+ <section key={group.title} className="mt-10">
247
+ <h2 className="text-lg font-semibold">{group.title}</h2>
248
+ {group.description && <p className="text-sm text-muted-foreground">{group.description}</p>}
249
+ <div className="mt-4 grid gap-3 sm:grid-cols-2">
250
+ {group.items.map((item) => (
251
+ <Link
252
+ key={item.slug}
253
+ to="/docs/$slug"
254
+ params={{ slug: item.slug }}
255
+ className="flex flex-col gap-1 rounded-lg border border-border p-4 hover:border-primary"
256
+ >
257
+ <span className="font-medium">{item.title}</span>
258
+ {item.description && (
259
+ <span className="text-sm text-muted-foreground">{item.description}</span>
260
+ )}
261
+ </Link>
262
+ ))}
263
+ </div>
264
+ </section>
265
+ ))}
266
+ </main>
267
+ )
268
+ }
269
+ `);
270
+ writeTemplate(ctx, "app/pages/docs/slug.tsx", `import { definePage, getRouteApi, Link } from '@kitr/router'
271
+ import { nav } from '#kitr/content'
272
+ import { DocLayout } from '#components/ui/doc-layout'
273
+
274
+ const route = getRouteApi('/docs/$slug')
275
+
276
+ /** One docs page, and what renders when the loader in app/routes.ts throws notFound(). */
277
+ export default definePage({
278
+ component: DocPage,
279
+ notFoundComponent: () => (
280
+ <main className="mx-auto max-w-3xl px-4 py-12">
281
+ <h1 className="text-2xl font-semibold">No such page</h1>
282
+ <Link to="/docs" className="mt-4 inline-block underline">
283
+ All docs
284
+ </Link>
285
+ </main>
286
+ ),
287
+ })
288
+
289
+ function DocPage() {
290
+ const page = route.useLoaderData()
291
+ const { slug } = route.useParams()
292
+ return <DocLayout page={page} nav={nav.docs} base="/docs" slug={slug} />
293
+ }
294
+ `);
295
+ writeTemplate(ctx, "test/docs.test.tsx", `import { createMemoryHistory, createRootRoute, createRouter, RouterProvider } from '@kitr/router'
296
+ import { renderWithKitr } from '@kitr/testing/react'
297
+ import { within } from '@testing-library/react'
298
+ import { expect, it } from 'vitest'
299
+ import { app } from '#app/app'
300
+ import { loadPage, nav } from '#kitr/content'
301
+ import { DocLayout } from '#components/ui/doc-layout'
302
+
303
+ it('renders a docs page with its title, the sidebar and the next page', async () => {
304
+ await app.boot()
305
+ const page = await loadPage('docs', 'getting-started')
306
+ if (!page) throw new Error('kitr generate has not rendered content/docs/getting-started.md')
307
+ const root = createRootRoute({
308
+ component: () => <DocLayout page={page} nav={nav.docs} base="/docs" slug="getting-started" />,
309
+ })
310
+ const router = createRouter({
311
+ routeTree: root,
312
+ history: createMemoryHistory({ initialEntries: ['/docs/getting-started'] }),
313
+ })
314
+ const view = renderWithKitr(<RouterProvider router={router} />, { app })
315
+ expect((await view.findByRole('heading', { level: 1 })).textContent).toBe('Getting started')
316
+ expect(view.getByRole('navigation', { name: 'Docs' })).toBeTruthy()
317
+ const pager = view.getByRole('navigation', { name: 'Previous and next' })
318
+ expect(within(pager).getByRole('link').getAttribute('href')).toBe('/docs/writing')
319
+ })
320
+
321
+ it('has no page for an unknown slug', async () => {
322
+ expect(await loadPage('docs', 'no-such-page')).toBeNull()
323
+ })
324
+ `);
325
+ };
326
+ /**
327
+ * `kitr new --kit docs`: a static docs site from markdown. `content` renders \`content/docs\`,
328
+ * `seo` gives every page its tags, `tailwind` and `theme` the look, and the registry components
329
+ * the sidebar, search and package-manager commands.
330
+ */
331
+ var kitr_default = definePackage({
332
+ name: "docs",
333
+ kind: "kit",
334
+ kit: {
335
+ description: "A static docs site from markdown: sidebar, search, package-manager commands, dark mode and llms.txt.",
336
+ platforms: { site: {
337
+ type: "web",
338
+ options: { mode: "static" }
339
+ } },
340
+ packages: [
341
+ "content",
342
+ "seo",
343
+ "tailwind",
344
+ "theme",
345
+ "testing"
346
+ ],
347
+ components: [
348
+ "icon",
349
+ "button",
350
+ "theme-toggle",
351
+ "command",
352
+ "search-dialog",
353
+ "doc-layout"
354
+ ],
355
+ files
356
+ }
357
+ });
358
+ export { kitr_default as default };
package/package.json CHANGED
@@ -1,6 +1,54 @@
1
1
  {
2
2
  "name": "@kitr/kit-docs",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.4.0",
4
+ "description": "kitr new --kit docs: A static docs site from markdown: sidebar, search, package-manager commands, dark mode and llms.txt.",
5
+ "kitr": {
6
+ "name": "docs",
7
+ "title": "Docs kit",
8
+ "summary": "Start a docs site from markdown files: a sidebar, search, dark mode and copies for coding agents, prerendered to static HTML.",
9
+ "category": "kits",
10
+ "runsOn": "both",
11
+ "kind": "kit",
12
+ "setup": "./kitr"
13
+ },
14
+ "type": "module",
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/kitrjs/kitr.git",
19
+ "directory": "packages/kit-docs"
20
+ },
21
+ "homepage": "https://kitr.dev/docs/cli",
22
+ "bugs": "https://github.com/kitrjs/kitr/issues",
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "sideEffects": false,
27
+ "exports": {
28
+ "./kitr": {
29
+ "types": "./dist/kitr.d.ts",
30
+ "default": "./dist/kitr.js"
31
+ },
32
+ "./package.json": "./package.json"
33
+ },
34
+ "files": [
35
+ "dist"
36
+ ],
37
+ "keywords": [
38
+ "kitr",
39
+ "kitr-package",
40
+ "kitr-kit"
41
+ ],
42
+ "dependencies": {
43
+ "@kitr/core": "0.4.0"
44
+ },
45
+ "devDependencies": {
46
+ "tsdown": "^0.23.7",
47
+ "typescript": "^5.9.3",
48
+ "@types/node": "^22.19.0"
49
+ },
50
+ "scripts": {
51
+ "build": "tsdown",
52
+ "typecheck": "tsc -p tsconfig.json"
53
+ }
6
54
  }