@zebradil/docs-kit 0.0.0-stage → 0.10.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 +21 -0
- package/README.md +136 -2
- package/package.json +41 -4
- package/packages/docs-kit/Cascade.astro +205 -0
- package/packages/docs-kit/Diff.astro +134 -0
- package/packages/docs-kit/Landing.astro +372 -0
- package/packages/docs-kit/Tree.astro +139 -0
- package/packages/docs-kit/content.mjs +5 -0
- package/packages/docs-kit/index.mjs +77 -0
- package/packages/docs-kit/kit.test.mjs +45 -0
- package/packages/docs-kit/layout.css +5 -0
- package/packages/docs-kit/llms.txt.mjs +31 -0
- package/packages/docs-kit/page.md.mjs +42 -0
- package/packages/docs-kit/text.mjs +6 -0
- package/packages/docs-kit/themes/default.css +12 -0
- package/packages/docs-kit/themes/mono.css +30 -0
- package/packages/docs-kit/themes/porridge.css +42 -0
- package/packages/docs-kit/themes/sieve.css +49 -0
- package/packages/docs-kit/themes/vortex.css +45 -0
- package/refgen/help2md.mjs +85 -0
- package/schema/site.schema.json +537 -0
- package/schema/validate.mjs +63 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro';
|
|
4
|
+
import { Code, LinkCard, TabItem, Tabs } from '@astrojs/starlight/components';
|
|
5
|
+
import site from 'virtual:docs-kit/site';
|
|
6
|
+
import { escape, inline } from './text.mjs';
|
|
7
|
+
import Cascade from './Cascade.astro';
|
|
8
|
+
import Diff from './Diff.astro';
|
|
9
|
+
import Tree from './Tree.astro';
|
|
10
|
+
|
|
11
|
+
// The hero's text column is Starlight's: the eyebrow rides in the tagline slot and CSS moves it above the title.
|
|
12
|
+
const lede = `<span class="kit-lede">${inline(site.description ?? site.tagline)}</span>`;
|
|
13
|
+
const eyebrow = site.eyebrow ? `<span class="kit-eyebrow">${escape(site.eyebrow)}</span>` : '';
|
|
14
|
+
|
|
15
|
+
// Illustrations are project content, inlined so they can follow the color scheme and animate.
|
|
16
|
+
// Paths are relative to the docs directory, the working directory of a build.
|
|
17
|
+
const svg = (path: string) => readFileSync(path, 'utf8');
|
|
18
|
+
const art = site.art && `<div class="kit-art" aria-hidden="true">${svg(site.art)}</div>`;
|
|
19
|
+
|
|
20
|
+
// The flow panel fills Starlight's hero image slot, which only takes an HTML string, unless `art` takes it;
|
|
21
|
+
// then the panel opens a section below the hero. With `flow.art` the rows stay for screen readers only.
|
|
22
|
+
const arrows = { forward: '→', back: '←', both: '↔' };
|
|
23
|
+
const node = (name: string) =>
|
|
24
|
+
`<span class="kit-flow-node${name === site.flow?.highlight ? ' kit-flow-node-hl' : ''}">${escape(name)}</span>`;
|
|
25
|
+
const flow =
|
|
26
|
+
site.flow &&
|
|
27
|
+
[
|
|
28
|
+
`<div class="kit-flow">`,
|
|
29
|
+
art ? '' : `<p class="kit-flow-title">${escape(site.flow.title ?? 'How it fits')}</p>`,
|
|
30
|
+
site.flow.art ? `<div class="kit-flow-art" aria-hidden="true">${svg(site.flow.art)}</div><div class="sr-only">` : '',
|
|
31
|
+
...site.flow.steps.map(
|
|
32
|
+
(s) =>
|
|
33
|
+
`<div class="kit-flow-row">${node(s.from)}` +
|
|
34
|
+
`<span class="kit-flow-edge" data-arrow="${s.arrow ?? 'forward'}"><span class="kit-flow-label">${escape(s.label)}</span>` +
|
|
35
|
+
`<span class="sr-only">${arrows[s.arrow ?? 'forward']}</span></span>${node(s.to)}</div>`,
|
|
36
|
+
),
|
|
37
|
+
site.flow.art ? '</div>' : '',
|
|
38
|
+
site.flow.note && !art ? `<p class="kit-flow-note">${inline(site.flow.note)}</p>` : '',
|
|
39
|
+
'</div>',
|
|
40
|
+
].join('');
|
|
41
|
+
|
|
42
|
+
const frontmatter = {
|
|
43
|
+
title: site.name,
|
|
44
|
+
description: site.tagline,
|
|
45
|
+
template: 'splash' as const,
|
|
46
|
+
hero: {
|
|
47
|
+
title: escape(site.headline ?? site.name),
|
|
48
|
+
tagline: eyebrow + lede,
|
|
49
|
+
...((art || flow) && { image: { html: art || flow } }),
|
|
50
|
+
actions: [
|
|
51
|
+
{ text: 'Get started', link: `${import.meta.env.BASE_URL.replace(/\/$/, '')}/getting-started/`, icon: 'right-arrow' },
|
|
52
|
+
{ text: 'GitHub', link: site.repo, icon: 'github', variant: 'minimal' },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
const number = (i: number) => String(i + 1).padStart(2, '0');
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
<StarlightPage frontmatter={frontmatter}>
|
|
60
|
+
<div class="kit-landing not-content">
|
|
61
|
+
<section class="kit-install" aria-label="Install">
|
|
62
|
+
<Tabs>
|
|
63
|
+
{site.install.map((m) => <TabItem label={m.label}><Code code={m.code} lang="sh" /></TabItem>)}
|
|
64
|
+
</Tabs>
|
|
65
|
+
</section>
|
|
66
|
+
|
|
67
|
+
{site.diff && <Diff />}
|
|
68
|
+
|
|
69
|
+
{
|
|
70
|
+
art && flow && (
|
|
71
|
+
<section class="kit-flow-section">
|
|
72
|
+
<div>
|
|
73
|
+
<h2>{site.flow.title ?? 'How it fits'}</h2>
|
|
74
|
+
{site.flow.note && <p set:html={inline(site.flow.note)} />}
|
|
75
|
+
</div>
|
|
76
|
+
<Fragment set:html={flow} />
|
|
77
|
+
</section>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
{site.tree && <Tree />}
|
|
82
|
+
|
|
83
|
+
{site.cascade && <Cascade />}
|
|
84
|
+
|
|
85
|
+
<section>
|
|
86
|
+
<h2>Features</h2>
|
|
87
|
+
<div class="kit-features">
|
|
88
|
+
{
|
|
89
|
+
site.features.map((f, i) => (
|
|
90
|
+
<article class="kit-feature">
|
|
91
|
+
<span class="kit-number">{number(i)}</span>
|
|
92
|
+
<h3 set:html={escape(f.title)} />
|
|
93
|
+
<p set:html={inline(f.body)} />
|
|
94
|
+
</article>
|
|
95
|
+
))
|
|
96
|
+
}
|
|
97
|
+
</div>
|
|
98
|
+
</section>
|
|
99
|
+
|
|
100
|
+
{
|
|
101
|
+
site.components && (
|
|
102
|
+
<section class="kit-components">
|
|
103
|
+
<div>
|
|
104
|
+
<h2>{site.components.title ?? 'Components'}</h2>
|
|
105
|
+
{site.components.intro && <p set:html={inline(site.components.intro)} />}
|
|
106
|
+
</div>
|
|
107
|
+
<dl>
|
|
108
|
+
{site.components.items.map((c) => (
|
|
109
|
+
<div>
|
|
110
|
+
<dt>
|
|
111
|
+
<code>{c.name}</code>
|
|
112
|
+
</dt>
|
|
113
|
+
<dd set:html={inline(c.body)} />
|
|
114
|
+
</div>
|
|
115
|
+
))}
|
|
116
|
+
</dl>
|
|
117
|
+
</section>
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
{
|
|
122
|
+
!!site.links?.length && (
|
|
123
|
+
<section>
|
|
124
|
+
<h2>Links</h2>
|
|
125
|
+
<div class="kit-links">
|
|
126
|
+
{site.links.map((l) => <LinkCard title={l.label} href={l.href} />)}
|
|
127
|
+
</div>
|
|
128
|
+
</section>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
</div>
|
|
132
|
+
</StarlightPage>
|
|
133
|
+
|
|
134
|
+
<style is:global>
|
|
135
|
+
/* Hero: Starlight's markup in the kit's layout, scoped to the landing page with :has(). */
|
|
136
|
+
:root:has(.kit-landing) {
|
|
137
|
+
--kit-display: var(--kit-font-display, var(--sl-font));
|
|
138
|
+
}
|
|
139
|
+
:root:has(.kit-landing) .hero .tagline {
|
|
140
|
+
display: contents;
|
|
141
|
+
}
|
|
142
|
+
:root:has(.kit-landing) .hero h1 {
|
|
143
|
+
font-family: var(--kit-display);
|
|
144
|
+
letter-spacing: -0.03em;
|
|
145
|
+
line-height: 1.05;
|
|
146
|
+
}
|
|
147
|
+
.kit-eyebrow {
|
|
148
|
+
order: -1;
|
|
149
|
+
font-family: var(--sl-font-mono);
|
|
150
|
+
font-size: var(--sl-text-xs);
|
|
151
|
+
letter-spacing: 0.06em;
|
|
152
|
+
text-transform: uppercase;
|
|
153
|
+
color: var(--sl-color-text-accent);
|
|
154
|
+
}
|
|
155
|
+
.kit-lede {
|
|
156
|
+
font-size: clamp(var(--sl-text-base), calc(0.0625rem + 2vw), var(--sl-text-xl));
|
|
157
|
+
line-height: 1.55;
|
|
158
|
+
color: var(--sl-color-gray-2);
|
|
159
|
+
}
|
|
160
|
+
:root:has(.kit-landing) .hero > .hero-html {
|
|
161
|
+
order: 2;
|
|
162
|
+
width: 100%;
|
|
163
|
+
}
|
|
164
|
+
@media (min-width: 50rem) {
|
|
165
|
+
:root:has(.kit-landing) .hero:has(> .hero-html) {
|
|
166
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
167
|
+
gap: 4rem;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Illustrations: project SVGs scaled to their column. */
|
|
172
|
+
.kit-art {
|
|
173
|
+
display: grid;
|
|
174
|
+
place-items: center;
|
|
175
|
+
width: 100%;
|
|
176
|
+
}
|
|
177
|
+
.kit-art svg {
|
|
178
|
+
width: min(100%, 26rem);
|
|
179
|
+
height: auto;
|
|
180
|
+
}
|
|
181
|
+
.kit-flow-art svg {
|
|
182
|
+
display: block;
|
|
183
|
+
width: 100%;
|
|
184
|
+
height: auto;
|
|
185
|
+
}
|
|
186
|
+
.kit-flow-section {
|
|
187
|
+
display: grid;
|
|
188
|
+
grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
|
|
189
|
+
gap: 2rem 3rem;
|
|
190
|
+
align-items: center;
|
|
191
|
+
}
|
|
192
|
+
.kit-flow-section > div > p {
|
|
193
|
+
margin: 0;
|
|
194
|
+
line-height: 1.55;
|
|
195
|
+
color: var(--sl-color-gray-2);
|
|
196
|
+
}
|
|
197
|
+
@media (max-width: 50rem) {
|
|
198
|
+
.kit-flow-section {
|
|
199
|
+
grid-template-columns: minmax(0, 1fr);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* Flow panel */
|
|
204
|
+
.kit-flow {
|
|
205
|
+
width: 100%;
|
|
206
|
+
display: flex;
|
|
207
|
+
flex-direction: column;
|
|
208
|
+
gap: 1.125rem;
|
|
209
|
+
padding: 1.75rem;
|
|
210
|
+
border: 1px solid var(--sl-color-hairline-light);
|
|
211
|
+
border-radius: 1.25rem;
|
|
212
|
+
background: var(--sl-color-bg-nav);
|
|
213
|
+
text-align: start;
|
|
214
|
+
}
|
|
215
|
+
.kit-flow-title {
|
|
216
|
+
margin: 0;
|
|
217
|
+
font-family: var(--sl-font-mono);
|
|
218
|
+
font-size: var(--sl-text-2xs);
|
|
219
|
+
letter-spacing: 0.08em;
|
|
220
|
+
text-transform: uppercase;
|
|
221
|
+
color: var(--sl-color-gray-3);
|
|
222
|
+
}
|
|
223
|
+
.kit-flow-row {
|
|
224
|
+
display: grid;
|
|
225
|
+
grid-template-columns: minmax(0, 9rem) minmax(4rem, 1fr) minmax(0, 9rem);
|
|
226
|
+
align-items: center;
|
|
227
|
+
gap: 0.75rem;
|
|
228
|
+
}
|
|
229
|
+
.kit-flow-node {
|
|
230
|
+
display: flex;
|
|
231
|
+
align-items: center;
|
|
232
|
+
justify-content: center;
|
|
233
|
+
min-height: 3.25rem;
|
|
234
|
+
padding: 0.25rem 0.75rem;
|
|
235
|
+
border: 1.5px solid var(--sl-color-gray-5);
|
|
236
|
+
border-radius: 0.75rem;
|
|
237
|
+
font-size: var(--sl-text-sm);
|
|
238
|
+
font-weight: 600;
|
|
239
|
+
line-height: 1.25;
|
|
240
|
+
text-align: center;
|
|
241
|
+
color: var(--sl-color-white);
|
|
242
|
+
}
|
|
243
|
+
.kit-flow-node-hl {
|
|
244
|
+
border-color: var(--sl-color-white);
|
|
245
|
+
background: var(--sl-color-white);
|
|
246
|
+
color: var(--sl-color-black);
|
|
247
|
+
}
|
|
248
|
+
.kit-flow-edge {
|
|
249
|
+
position: relative;
|
|
250
|
+
display: flex;
|
|
251
|
+
justify-content: center;
|
|
252
|
+
padding-bottom: 0.625rem;
|
|
253
|
+
border-bottom: 2px solid var(--sl-color-gray-3);
|
|
254
|
+
}
|
|
255
|
+
.kit-flow-edge::before,
|
|
256
|
+
.kit-flow-edge::after {
|
|
257
|
+
position: absolute;
|
|
258
|
+
bottom: -7px;
|
|
259
|
+
border-block: 6px solid transparent;
|
|
260
|
+
}
|
|
261
|
+
.kit-flow-edge:is([data-arrow='forward'], [data-arrow='both'])::after {
|
|
262
|
+
content: '';
|
|
263
|
+
right: -2px;
|
|
264
|
+
border-left: 9px solid var(--sl-color-gray-3);
|
|
265
|
+
}
|
|
266
|
+
.kit-flow-edge:is([data-arrow='back'], [data-arrow='both'])::before {
|
|
267
|
+
content: '';
|
|
268
|
+
left: -2px;
|
|
269
|
+
border-right: 9px solid var(--sl-color-gray-3);
|
|
270
|
+
}
|
|
271
|
+
.kit-flow-label {
|
|
272
|
+
font-family: var(--sl-font-mono);
|
|
273
|
+
font-size: var(--sl-text-2xs);
|
|
274
|
+
line-height: 1.3;
|
|
275
|
+
text-align: center;
|
|
276
|
+
color: var(--sl-color-gray-2);
|
|
277
|
+
}
|
|
278
|
+
.kit-flow-note {
|
|
279
|
+
margin: 0;
|
|
280
|
+
padding-top: 0.875rem;
|
|
281
|
+
border-top: 1px dashed var(--sl-color-hairline-light);
|
|
282
|
+
font-size: var(--sl-text-sm);
|
|
283
|
+
color: var(--sl-color-gray-3);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/* Sections below the hero */
|
|
287
|
+
.kit-landing {
|
|
288
|
+
display: flex;
|
|
289
|
+
flex-direction: column;
|
|
290
|
+
gap: 4.5rem;
|
|
291
|
+
padding-bottom: 3rem;
|
|
292
|
+
}
|
|
293
|
+
.kit-landing h2 {
|
|
294
|
+
margin: 0 0 1.5rem;
|
|
295
|
+
font-family: var(--kit-display);
|
|
296
|
+
font-size: clamp(var(--sl-text-3xl), calc(1rem + 2.5vw), var(--sl-text-5xl));
|
|
297
|
+
letter-spacing: -0.025em;
|
|
298
|
+
line-height: 1.1;
|
|
299
|
+
color: var(--sl-color-white);
|
|
300
|
+
}
|
|
301
|
+
.kit-features {
|
|
302
|
+
display: grid;
|
|
303
|
+
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
|
|
304
|
+
gap: 1.25rem;
|
|
305
|
+
}
|
|
306
|
+
.kit-feature {
|
|
307
|
+
display: flex;
|
|
308
|
+
flex-direction: column;
|
|
309
|
+
gap: 0.75rem;
|
|
310
|
+
padding: 1.75rem;
|
|
311
|
+
border: 1px solid var(--sl-color-hairline-light);
|
|
312
|
+
border-radius: 1rem;
|
|
313
|
+
background: var(--sl-color-bg-nav);
|
|
314
|
+
}
|
|
315
|
+
.kit-feature h3 {
|
|
316
|
+
margin: 0;
|
|
317
|
+
font-family: var(--kit-display);
|
|
318
|
+
font-size: var(--sl-text-xl);
|
|
319
|
+
letter-spacing: -0.015em;
|
|
320
|
+
color: var(--sl-color-white);
|
|
321
|
+
}
|
|
322
|
+
.kit-feature p {
|
|
323
|
+
margin: 0;
|
|
324
|
+
line-height: 1.55;
|
|
325
|
+
color: var(--sl-color-gray-2);
|
|
326
|
+
}
|
|
327
|
+
.kit-number {
|
|
328
|
+
font-family: var(--sl-font-mono);
|
|
329
|
+
font-size: var(--sl-text-xs);
|
|
330
|
+
color: var(--sl-color-text-accent);
|
|
331
|
+
}
|
|
332
|
+
.kit-components {
|
|
333
|
+
display: grid;
|
|
334
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
|
|
335
|
+
gap: 2rem 3rem;
|
|
336
|
+
padding: clamp(1.75rem, 4vw, 3.5rem);
|
|
337
|
+
border-radius: 1.5rem;
|
|
338
|
+
background: var(--sl-color-gray-6);
|
|
339
|
+
}
|
|
340
|
+
.kit-components p {
|
|
341
|
+
margin: 0;
|
|
342
|
+
line-height: 1.55;
|
|
343
|
+
color: var(--sl-color-gray-2);
|
|
344
|
+
}
|
|
345
|
+
.kit-components dl {
|
|
346
|
+
display: grid;
|
|
347
|
+
grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
|
|
348
|
+
gap: 1.25rem 3rem;
|
|
349
|
+
margin: 0;
|
|
350
|
+
}
|
|
351
|
+
@media (max-width: 50rem) {
|
|
352
|
+
.kit-components {
|
|
353
|
+
grid-template-columns: minmax(0, 1fr);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
.kit-components dt code {
|
|
357
|
+
padding: 0;
|
|
358
|
+
background: none;
|
|
359
|
+
font-size: var(--sl-text-sm);
|
|
360
|
+
font-weight: 600;
|
|
361
|
+
color: var(--sl-color-white);
|
|
362
|
+
}
|
|
363
|
+
.kit-components dd {
|
|
364
|
+
margin: 0.25rem 0 0;
|
|
365
|
+
font-size: var(--sl-text-sm);
|
|
366
|
+
color: var(--sl-color-gray-2);
|
|
367
|
+
}
|
|
368
|
+
.kit-links {
|
|
369
|
+
display: grid;
|
|
370
|
+
gap: 1rem;
|
|
371
|
+
}
|
|
372
|
+
</style>
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The inheritance tree: one tab per node with an `id`, numbering the nodes it inherits from, root first.
|
|
3
|
+
import { TabItem, Tabs } from '@astrojs/starlight/components';
|
|
4
|
+
import site from 'virtual:docs-kit/site';
|
|
5
|
+
import { escape, inline } from './text.mjs';
|
|
6
|
+
|
|
7
|
+
const tree = site.tree;
|
|
8
|
+
const chains = [];
|
|
9
|
+
const walk = (node, chain) => {
|
|
10
|
+
const here = [...chain, node];
|
|
11
|
+
if (node.id) chains.push(here);
|
|
12
|
+
node.children?.forEach((child) => walk(child, here));
|
|
13
|
+
};
|
|
14
|
+
walk(tree.root, []);
|
|
15
|
+
|
|
16
|
+
const render = (node, chain) => {
|
|
17
|
+
const step = chain.indexOf(node) + 1;
|
|
18
|
+
const row =
|
|
19
|
+
`<div class="kit-tree-row"${step ? ` data-step="${step}"` : ''}>` +
|
|
20
|
+
`<span class="kit-tree-step">${step || ''}</span>` +
|
|
21
|
+
`<code>${escape(node.name)}</code>` +
|
|
22
|
+
(node.file ? `<span class="kit-tree-file">${escape(node.file)}</span>` : '') +
|
|
23
|
+
(node.note ? `<span class="kit-tree-note">${inline(node.note)}</span>` : '') +
|
|
24
|
+
'</div>';
|
|
25
|
+
const children = node.children ? `<ul>${node.children.map((c) => render(c, chain)).join('')}</ul>` : '';
|
|
26
|
+
return `<li>${row}${children}</li>`;
|
|
27
|
+
};
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
<section class="kit-tree" aria-labelledby="kit-tree-title">
|
|
31
|
+
<div>
|
|
32
|
+
<h2 id="kit-tree-title">{tree.title ?? 'How settings inherit'}</h2>
|
|
33
|
+
{tree.intro && <p set:html={inline(tree.intro)} />}
|
|
34
|
+
</div>
|
|
35
|
+
<div class="kit-tree-panel">
|
|
36
|
+
<Tabs>
|
|
37
|
+
{
|
|
38
|
+
chains.map((chain) => (
|
|
39
|
+
<TabItem label={chain.at(-1).id}>
|
|
40
|
+
<ul class="kit-tree-list" set:html={render(tree.root, chain)} />
|
|
41
|
+
</TabItem>
|
|
42
|
+
))
|
|
43
|
+
}
|
|
44
|
+
</Tabs>
|
|
45
|
+
</div>
|
|
46
|
+
</section>
|
|
47
|
+
|
|
48
|
+
<style is:global>
|
|
49
|
+
.kit-tree {
|
|
50
|
+
display: grid;
|
|
51
|
+
grid-template-columns: minmax(0, 2fr) minmax(0, 3fr);
|
|
52
|
+
gap: 2rem 3rem;
|
|
53
|
+
align-items: start;
|
|
54
|
+
}
|
|
55
|
+
@media (max-width: 50rem) {
|
|
56
|
+
.kit-tree {
|
|
57
|
+
grid-template-columns: minmax(0, 1fr);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
.kit-tree > div > p {
|
|
61
|
+
margin: 0;
|
|
62
|
+
line-height: 1.55;
|
|
63
|
+
color: var(--sl-color-gray-2);
|
|
64
|
+
}
|
|
65
|
+
.kit-tree-panel {
|
|
66
|
+
min-width: 0;
|
|
67
|
+
padding: 0.5rem 1.25rem 1.25rem;
|
|
68
|
+
border: 1px solid var(--sl-color-hairline-light);
|
|
69
|
+
border-radius: 1rem;
|
|
70
|
+
background: var(--sl-color-bg-nav);
|
|
71
|
+
}
|
|
72
|
+
.kit-tree starlight-tabs {
|
|
73
|
+
display: block;
|
|
74
|
+
}
|
|
75
|
+
.kit-tree-list,
|
|
76
|
+
.kit-tree-list ul {
|
|
77
|
+
margin: 0;
|
|
78
|
+
padding: 0;
|
|
79
|
+
list-style: none;
|
|
80
|
+
}
|
|
81
|
+
.kit-tree-list {
|
|
82
|
+
margin-top: 1rem;
|
|
83
|
+
}
|
|
84
|
+
.kit-tree-list ul {
|
|
85
|
+
margin-left: 0.875rem;
|
|
86
|
+
padding-left: 1rem;
|
|
87
|
+
border-left: 1px solid var(--sl-color-gray-5);
|
|
88
|
+
}
|
|
89
|
+
.kit-tree-list li {
|
|
90
|
+
margin: 0;
|
|
91
|
+
}
|
|
92
|
+
.kit-tree-row {
|
|
93
|
+
display: flex;
|
|
94
|
+
flex-wrap: wrap;
|
|
95
|
+
align-items: center;
|
|
96
|
+
gap: 0.25rem 0.625rem;
|
|
97
|
+
padding: 0.375rem 0;
|
|
98
|
+
font-size: var(--sl-text-sm);
|
|
99
|
+
color: var(--sl-color-gray-3);
|
|
100
|
+
}
|
|
101
|
+
.kit-tree-row > code {
|
|
102
|
+
padding: 0;
|
|
103
|
+
background: none;
|
|
104
|
+
font-size: var(--sl-text-sm);
|
|
105
|
+
color: var(--sl-color-gray-3);
|
|
106
|
+
}
|
|
107
|
+
.kit-tree-step {
|
|
108
|
+
display: inline-grid;
|
|
109
|
+
place-items: center;
|
|
110
|
+
width: 1.5rem;
|
|
111
|
+
height: 1.5rem;
|
|
112
|
+
border: 1px dashed var(--sl-color-gray-5);
|
|
113
|
+
border-radius: 50%;
|
|
114
|
+
font-family: var(--sl-font-mono);
|
|
115
|
+
font-size: var(--sl-text-xs);
|
|
116
|
+
}
|
|
117
|
+
.kit-tree-file {
|
|
118
|
+
font-family: var(--sl-font-mono);
|
|
119
|
+
font-size: var(--sl-text-xs);
|
|
120
|
+
}
|
|
121
|
+
.kit-tree-note {
|
|
122
|
+
flex-basis: 100%;
|
|
123
|
+
padding-left: 2.125rem;
|
|
124
|
+
font-size: var(--sl-text-xs);
|
|
125
|
+
}
|
|
126
|
+
.kit-tree-row[data-step] {
|
|
127
|
+
color: var(--sl-color-gray-2);
|
|
128
|
+
}
|
|
129
|
+
.kit-tree-row[data-step] > code {
|
|
130
|
+
font-weight: 600;
|
|
131
|
+
color: var(--sl-color-white);
|
|
132
|
+
}
|
|
133
|
+
.kit-tree-row[data-step] .kit-tree-step {
|
|
134
|
+
border: 0;
|
|
135
|
+
background: var(--sl-color-text-accent);
|
|
136
|
+
color: var(--sl-color-black);
|
|
137
|
+
font-weight: 600;
|
|
138
|
+
}
|
|
139
|
+
</style>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { docsLoader } from '@astrojs/starlight/loaders';
|
|
2
|
+
import { docsSchema } from '@astrojs/starlight/schema';
|
|
3
|
+
import { defineCollection } from 'astro:content';
|
|
4
|
+
|
|
5
|
+
export const collections = { docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }) };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import starlight from '@astrojs/starlight';
|
|
4
|
+
import { defineConfig } from 'astro/config';
|
|
5
|
+
import { parse } from 'yaml';
|
|
6
|
+
import { validateSite } from '../../schema/validate.mjs';
|
|
7
|
+
|
|
8
|
+
const kitPath = (path) => fileURLToPath(new URL(path, import.meta.url));
|
|
9
|
+
|
|
10
|
+
/** Builds the whole Astro config of a consuming project from its site.yaml (path relative to the working directory). */
|
|
11
|
+
export default function docsKit({ site: sitePath = 'site.yaml' } = {}) {
|
|
12
|
+
const site = parse(readFileSync(sitePath, 'utf8'));
|
|
13
|
+
const errors = validateSite(site);
|
|
14
|
+
if (errors.length) throw new Error(`${sitePath} is invalid:\n ${errors.join('\n ')}`);
|
|
15
|
+
|
|
16
|
+
// Hosting is GitHub Pages per repo: https://github.com/<owner>/<repo> is served at https://<owner>.github.io/<repo>,
|
|
17
|
+
// or at the root of its custom domain.
|
|
18
|
+
const [owner, repo] = new URL(site.repo).pathname.split('/').filter(Boolean);
|
|
19
|
+
const base = site.domain ? '' : `/${repo}`;
|
|
20
|
+
|
|
21
|
+
// Starlight links public/favicon.svg itself; these are linked only when the project ships them.
|
|
22
|
+
const icons = [
|
|
23
|
+
['favicon.ico', { rel: 'icon', sizes: '32x32' }],
|
|
24
|
+
['apple-touch-icon.png', { rel: 'apple-touch-icon' }],
|
|
25
|
+
]
|
|
26
|
+
.filter(([file]) => existsSync(`public/${file}`))
|
|
27
|
+
.map(([file, attrs]) => ({ tag: 'link', attrs: { ...attrs, href: `${base}/${file}` } }));
|
|
28
|
+
|
|
29
|
+
return defineConfig({
|
|
30
|
+
site: site.domain ? `https://${site.domain}` : `https://${owner.toLowerCase()}.github.io`,
|
|
31
|
+
base: base || '/',
|
|
32
|
+
vite: { plugins: [siteModule(site)] },
|
|
33
|
+
integrations: [
|
|
34
|
+
starlight({
|
|
35
|
+
title: site.name,
|
|
36
|
+
description: site.tagline,
|
|
37
|
+
logo: site.logo && (site.logo.dark ? { light: site.logo.light, dark: site.logo.dark } : { src: site.logo.light }),
|
|
38
|
+
head: [
|
|
39
|
+
...icons,
|
|
40
|
+
...(site.analytics
|
|
41
|
+
? [{ tag: 'script', attrs: { src: site.analytics.script, defer: true, ...site.analytics.attributes } }]
|
|
42
|
+
: []),
|
|
43
|
+
],
|
|
44
|
+
social: [{ icon: 'github', label: 'GitHub', href: site.repo }],
|
|
45
|
+
editLink: { baseUrl: `${site.repo}/edit/main/docs/` },
|
|
46
|
+
customCss: [kitPath('layout.css'), kitPath(`themes/${site.theme ?? 'default'}.css`)],
|
|
47
|
+
sidebar: [
|
|
48
|
+
{ label: 'Getting started', slug: 'getting-started' },
|
|
49
|
+
...['Guides', 'Concepts', 'Reference'].map((label) => ({
|
|
50
|
+
label,
|
|
51
|
+
items: [{ autogenerate: { directory: label.toLowerCase() } }],
|
|
52
|
+
})),
|
|
53
|
+
],
|
|
54
|
+
}),
|
|
55
|
+
{
|
|
56
|
+
name: 'docs-kit',
|
|
57
|
+
hooks: {
|
|
58
|
+
'astro:config:setup': ({ injectRoute }) => {
|
|
59
|
+
injectRoute({ pattern: '/', entrypoint: kitPath('Landing.astro') });
|
|
60
|
+
injectRoute({ pattern: '/[...slug].md', entrypoint: kitPath('page.md.mjs') });
|
|
61
|
+
injectRoute({ pattern: '/[name].txt', entrypoint: kitPath('llms.txt.mjs') });
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Exposes the validated site.yaml to Landing.astro as `virtual:docs-kit/site`. */
|
|
70
|
+
function siteModule(site) {
|
|
71
|
+
const id = 'virtual:docs-kit/site';
|
|
72
|
+
return {
|
|
73
|
+
name: 'docs-kit-site',
|
|
74
|
+
resolveId: (source) => (source === id ? `\0${id}` : undefined),
|
|
75
|
+
load: (resolved) => (resolved === `\0${id}` ? `export default ${JSON.stringify(site)};` : undefined),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdtempSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { test } from 'node:test';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import docsKit from './index.mjs';
|
|
8
|
+
|
|
9
|
+
const path = (p) => fileURLToPath(new URL(p, import.meta.url));
|
|
10
|
+
|
|
11
|
+
test('schema theme enum matches the shipped theme files', () => {
|
|
12
|
+
const schema = JSON.parse(readFileSync(path('../../schema/site.schema.json'), 'utf8'));
|
|
13
|
+
const themes = readdirSync(path('themes')).map((f) => f.replace(/\.css$/, ''));
|
|
14
|
+
assert.deepEqual(themes.sort(), schema.properties.theme.enum.sort());
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('serves the site at <owner>.github.io/<repo>', () => {
|
|
18
|
+
const config = docsKit({ site: path('../../examples/site.yaml') });
|
|
19
|
+
assert.equal(config.site, 'https://zebradil.github.io');
|
|
20
|
+
assert.equal(config.base, '/kasha');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('serves the site at the root of a custom domain', () => {
|
|
24
|
+
const dir = mkdtempSync(join(tmpdir(), 'docs-kit-'));
|
|
25
|
+
const site = join(dir, 'site.yaml');
|
|
26
|
+
writeFileSync(site, `${readFileSync(path('../../examples/site.yaml'), 'utf8')}\ndomain: kasha.dev\n`);
|
|
27
|
+
const config = docsKit({ site });
|
|
28
|
+
assert.equal(config.site, 'https://kasha.dev');
|
|
29
|
+
assert.equal(config.base, '/');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('accepts analytics with data-* attributes only', () => {
|
|
33
|
+
const dir = mkdtempSync(join(tmpdir(), 'docs-kit-'));
|
|
34
|
+
const site = join(dir, 'site.yaml');
|
|
35
|
+
const withAttribute = (attr) =>
|
|
36
|
+
`${readFileSync(path('../../examples/site.yaml'), 'utf8')}\nanalytics:\n script: https://stats.example.dev/app.js\n attributes:\n ${attr}\n`;
|
|
37
|
+
writeFileSync(site, withAttribute('data-website-id: abc'));
|
|
38
|
+
assert.doesNotThrow(() => docsKit({ site }));
|
|
39
|
+
writeFileSync(site, withAttribute('onload: alert(1)'));
|
|
40
|
+
assert.throws(() => docsKit({ site }), /\/analytics\/attributes/);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('rejects an invalid site.yaml with readable errors', () => {
|
|
44
|
+
assert.throws(() => docsKit({ site: path('../../schema/testdata/broken.yaml') }), /must be one of: default, mono, porridge/);
|
|
45
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import site from 'virtual:docs-kit/site';
|
|
2
|
+
import { docsSections, pageMarkdown, siteRoot } from './page.md.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Serves llms.txt (https://llmstxt.org): an index linking every page's Markdown, and llms-full.txt: every page in one
|
|
6
|
+
* file, for pasting a project's whole docs into a model's context.
|
|
7
|
+
*/
|
|
8
|
+
export function getStaticPaths() {
|
|
9
|
+
return [{ params: { name: 'llms' } }, { params: { name: 'llms-full' } }];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function GET(context) {
|
|
13
|
+
const sections = await docsSections();
|
|
14
|
+
const root = siteRoot(context);
|
|
15
|
+
const head = `# ${site.name}\n\n> ${site.tagline}`;
|
|
16
|
+
|
|
17
|
+
if (context.params.name === 'llms-full') {
|
|
18
|
+
const pages = sections.flatMap(({ pages }) => pages.map((page) => pageMarkdown(page, root)));
|
|
19
|
+
return new Response([`${head}\n`, ...pages].join('\n'));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const links = sections.map(
|
|
23
|
+
({ label, pages }) =>
|
|
24
|
+
`## ${label}\n\n` +
|
|
25
|
+
pages
|
|
26
|
+
.map(({ id, data }) => `- [${data.title}](${new URL(`${id}.md`, root)})${data.description ? `: ${data.description}` : ''}`)
|
|
27
|
+
.join('\n'),
|
|
28
|
+
);
|
|
29
|
+
const intro = `Each page below links to its Markdown source. All pages in one file: ${new URL('llms-full.txt', root)}`;
|
|
30
|
+
return new Response([head, intro, ...links].join('\n\n') + '\n');
|
|
31
|
+
}
|