@beforesemicolon/builder 1.8.11 → 1.8.18

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.
Files changed (40) hide show
  1. package/README.md +839 -142
  2. package/dist/cjs/docs/run.js +16 -14
  3. package/dist/cjs/docs/templates/fading-citrus/README.md +772 -0
  4. package/dist/cjs/docs/templates/fading-citrus/assets/logo.dark.svg +8 -0
  5. package/dist/cjs/docs/templates/fading-citrus/assets/logo.light.svg +8 -0
  6. package/dist/cjs/docs/templates/fading-citrus/assets/logo.svg +8 -0
  7. package/dist/cjs/docs/templates/fading-citrus/layouts/_footer.js +13 -6
  8. package/dist/cjs/docs/templates/fading-citrus/layouts/_head-meta.js +3 -2
  9. package/dist/cjs/docs/templates/fading-citrus/layouts/_header.js +9 -4
  10. package/dist/cjs/docs/templates/fading-citrus/layouts/document.js +3 -2
  11. package/dist/cjs/docs/templates/fading-citrus/layouts/landing.js +4 -2
  12. package/dist/cjs/docs/templates/fading-citrus/stylesheets/common.css +135 -50
  13. package/dist/cjs/docs/templates/fading-citrus/stylesheets/documentation.css +32 -24
  14. package/dist/cjs/docs/templates/fading-citrus/stylesheets/github-dark.hightlighter.css +12 -12
  15. package/dist/cjs/docs/templates/fading-citrus/stylesheets/github-light.hightlighter.css +22 -22
  16. package/dist/cjs/docs/templates/fading-citrus/stylesheets/landing.css +74 -87
  17. package/dist/cjs/docs/templates/fading-citrus/template.config.js +106 -15
  18. package/dist/cjs/docs/types.js +1 -1
  19. package/dist/esm/docs/run.js +15 -13
  20. package/dist/esm/docs/templates/fading-citrus/README.md +772 -0
  21. package/dist/esm/docs/templates/fading-citrus/assets/logo.dark.svg +8 -0
  22. package/dist/esm/docs/templates/fading-citrus/assets/logo.light.svg +8 -0
  23. package/dist/esm/docs/templates/fading-citrus/assets/logo.svg +8 -0
  24. package/dist/esm/docs/templates/fading-citrus/layouts/_footer.js +13 -6
  25. package/dist/esm/docs/templates/fading-citrus/layouts/_head-meta.js +3 -2
  26. package/dist/esm/docs/templates/fading-citrus/layouts/_header.js +9 -4
  27. package/dist/esm/docs/templates/fading-citrus/layouts/document.js +3 -2
  28. package/dist/esm/docs/templates/fading-citrus/layouts/landing.js +4 -2
  29. package/dist/esm/docs/templates/fading-citrus/stylesheets/common.css +135 -50
  30. package/dist/esm/docs/templates/fading-citrus/stylesheets/documentation.css +32 -24
  31. package/dist/esm/docs/templates/fading-citrus/stylesheets/github-dark.hightlighter.css +12 -12
  32. package/dist/esm/docs/templates/fading-citrus/stylesheets/github-light.hightlighter.css +22 -22
  33. package/dist/esm/docs/templates/fading-citrus/stylesheets/landing.css +74 -87
  34. package/dist/esm/docs/templates/fading-citrus/template.config.js +106 -15
  35. package/dist/types/docs/types.d.ts +4 -2
  36. package/package.json +1 -1
  37. package/dist/cjs/docs/templates/fading-citrus/assets/before-semicolon-logo.png +0 -0
  38. package/dist/cjs/docs/templates/fading-citrus/layouts/_logo.js +0 -31
  39. package/dist/esm/docs/templates/fading-citrus/assets/before-semicolon-logo.png +0 -0
  40. package/dist/esm/docs/templates/fading-citrus/layouts/_logo.js +0 -31
package/README.md CHANGED
@@ -1,219 +1,916 @@
1
1
  # @beforesemicolon/builder
2
2
 
3
- Utilities to build npm packages and a documentation website for Before Semicolon projects.
3
+ Utilities to build npm packages and static documentation websites for Before Semicolon projects.
4
4
 
5
- This package provides small, focused helpers to:
5
+ This package provides three public helpers:
6
6
 
7
- - Build server-friendly CommonJS and ESM module bundles (dist/cjs and dist/esm).
8
- - Produce a browser bundle (dist/client.js) for documentation or demos.
9
- - Render a static documentation website from Markdown files (output defaults to `website/`).
7
+ - `buildModules()` builds TypeScript sources into `dist/esm` and `dist/cjs`.
8
+ - `buildBrowser()` builds a browser bundle, usually for demos or docs.
9
+ - `buildDocs()` renders a static Markdown documentation site.
10
10
 
11
- Table of contents
11
+ The docs builder supports reusable templates, Markdown layout blocks, source-level template extension, generated SEO/AI files, theme variables, page scripts, assets, stylesheets, and custom `marked` options.
12
12
 
13
- - Features
14
- - Requirements
15
- - Installation
16
- - Quick examples
17
- - Library usage (Node)
18
- - Building the browser bundle
19
- - Generating the documentation website
20
- - API
21
- - buildModules(options?)
22
- - buildBrowser(options?)
23
- - buildDocs(options?)
24
- - Documentation site layout and front-matter
25
- - Scripts (in package.json)
26
- - Development
27
- - Contributing
28
- - License
13
+ ## Requirements
29
14
 
30
- Features
15
+ - Node.js `>=18.16.0`
16
+ - ESM projects are supported directly.
17
+ - CommonJS consumers can use the package `require` export.
31
18
 
32
- - Builds all TypeScript sources into both ESM and CJS formats using esbuild.
33
- - Produces a single browser bundle for client-side documentation UI.
34
- - Static site generator for Markdown: supports layouts, assets, stylesheets, and scripts.
35
- - Uses marked + highlight.js for Markdown rendering, DOMPurify for sanitization and minifies output CSS/JS/HTML.
36
- - Simple, zero-config defaults plus options for common customization.
19
+ ## Installation
37
20
 
38
- Requirements
21
+ ```sh
22
+ npm install --save-dev @beforesemicolon/builder
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```js
28
+ import { buildModules, buildBrowser, buildDocs } from '@beforesemicolon/builder'
39
29
 
40
- - Node.js >= 18.16.0 (see `engines` in package.json)
30
+ await buildModules()
31
+ await buildBrowser()
32
+ await buildDocs()
33
+ ```
41
34
 
42
- Installation
35
+ Common project script:
43
36
 
44
- Install from npm (package name: `@beforesemicolon/builder`) or use the repo directly.
37
+ ```js
38
+ import { buildModules, buildBrowser, buildDocs } from '@beforesemicolon/builder'
45
39
 
40
+ const docsOptions = {
41
+ template: 'fading-citrus',
42
+ siteUrl: 'https://example.com',
43
+ generatedFiles: {
44
+ netlify: true,
45
+ },
46
+ }
47
+
48
+ const run = async () => {
49
+ await Promise.all([buildModules(), buildBrowser(), buildDocs(docsOptions)])
50
+ }
51
+
52
+ run()
46
53
  ```
47
- # npm
48
- npm install @beforesemicolon/builder
49
54
 
50
- # or using the repository
51
- git clone https://github.com/beforesemicolon/builder.git
52
- cd builder
53
- npm install
55
+ ## API
56
+
57
+ ### buildModules(options?)
58
+
59
+ ```ts
60
+ buildModules(options?: {
61
+ directoryPath?: string
62
+ }): Promise<void>
63
+ ```
64
+
65
+ Builds source files into server-friendly ESM and CommonJS output.
66
+
67
+ Defaults:
68
+
69
+ - `directoryPath`: `process.cwd()/src`
70
+ - ESM output: `dist/esm`
71
+ - CommonJS output: `dist/cjs`
72
+
73
+ Behavior:
74
+
75
+ - Recursively scans the source directory.
76
+ - Skips files ending in `.spec.ts`.
77
+ - Skips `/client.ts` from module builds.
78
+ - Uses `esbuild`.
79
+ - Minifies output.
80
+ - Keeps symbol names for better stack traces.
81
+
82
+ ### buildBrowser(options?)
83
+
84
+ ```ts
85
+ buildBrowser(options?: {
86
+ entry?: string
87
+ out?: string
88
+ }): Promise<void>
54
89
  ```
55
90
 
56
- Quick examples
91
+ Builds a single browser bundle.
57
92
 
58
- Library usage (Node / programmatic)
93
+ Defaults:
94
+
95
+ - `entry`: `src/client`
96
+ - `out`: `dist/client.js`
97
+
98
+ Behavior:
99
+
100
+ - Uses `esbuild`.
101
+ - Generates sourcemaps.
102
+ - Minifies output.
103
+ - Includes a small internal plugin that removes the `Doc` export from `@beforesemicolon/html-parser` when bundling.
104
+
105
+ ### buildDocs(options?)
106
+
107
+ ```ts
108
+ buildDocs(options?: {
109
+ srcDir?: string
110
+ publicDir?: string
111
+ markedOptions?: MarkedExtension
112
+ template?: string
113
+ siteUrl?: string
114
+ generatedFiles?:
115
+ | boolean
116
+ | {
117
+ sitemap?: boolean
118
+ robots?: boolean
119
+ llms?: boolean
120
+ llmsFull?: boolean
121
+ netlify?: boolean
122
+ }
123
+ }): Promise<void>
124
+ ```
125
+
126
+ Builds a static documentation site from Markdown.
127
+
128
+ Defaults:
129
+
130
+ - `srcDir`: `process.cwd()/docs`
131
+ - `publicDir`: `process.cwd()/website`
132
+ - `template`: no named template, uses the built-in `default` layout
133
+ - `generatedFiles`: enabled for `sitemap`, `robots`, `llms`, and `llmsFull`
134
+ - `generatedFiles.netlify`: `false`
135
+
136
+ Example:
59
137
 
60
138
  ```js
61
- // ESM
62
- import { buildModules, buildBrowser, buildDocs } from '@beforesemicolon/builder'
139
+ await buildDocs({
140
+ template: 'fading-citrus',
141
+ siteUrl: 'https://docs.example.com',
142
+ generatedFiles: {
143
+ netlify: true,
144
+ },
145
+ })
146
+ ```
147
+
148
+ ## Docs Directory Structure
149
+
150
+ The default source directory is `docs/`.
151
+
152
+ ```txt
153
+ docs/
154
+ index.md
155
+ guide/
156
+ getting-started.md
157
+ assets/
158
+ stylesheets/
159
+ scripts/
160
+ _layouts/
161
+ _template/
162
+ template.config.js
163
+ assets/
164
+ stylesheets/
165
+ scripts/
166
+ layouts/
167
+ robots.txt
168
+ sitemap.xml
169
+ llms.txt
170
+ llms-full.txt
171
+ _redirects
172
+ netlify.toml
173
+ ```
63
174
 
64
- // Build server-side modules (dist/esm and dist/cjs)
65
- await buildModules({ directoryPath: 'src' })
175
+ Supported folders:
66
176
 
67
- // Build single browser bundle (defaults to src/client -> dist/client.js)
68
- await buildBrowser({ entry: 'src/client', out: 'dist/client.js' })
177
+ - `assets/`: copied to the same relative location in the output directory.
178
+ - `stylesheets/`: CSS files are minified and copied to output.
179
+ - `scripts/`: JS files are minified and copied to output.
180
+ - `_layouts/`: page layout modules. Each file default-exports a page layout function.
181
+ - `_template/`: source-level extension for the selected template.
182
+ - `_template/assets/`: copied into `publicDir/assets`, overriding or extending template assets.
183
+ - `_template/stylesheets/`: copied into `publicDir/stylesheets`, overriding or extending template styles.
184
+ - `_template/scripts/`: copied into `publicDir/scripts`, overriding or extending template scripts.
185
+ - `_template/layouts/`: custom page layouts that can override or extend selected template layouts.
186
+ - `_template/template.config.js`: source-level template config merged with the selected template config.
69
187
 
70
- // Generate static docs (defaults: docs -> website)
71
- await buildDocs({ srcDir: 'docs', publicDir: 'website' })
188
+ Files and folders starting with `.` or `_` are skipped during Markdown page discovery. `_template` and `_layouts` are used explicitly by the docs builder.
189
+
190
+ ## Page Front Matter
191
+
192
+ Each Markdown page can include front matter:
193
+
194
+ ```md
195
+ ---
196
+ name: Get Started
197
+ title: Get Started with Example
198
+ description: Learn how to install and use Example.
199
+ order: 1
200
+ layout: document
201
+ ---
202
+
203
+ # Get Started
72
204
  ```
73
205
 
74
- CommonJS
206
+ Common fields:
207
+
208
+ - `name`: label used in the generated site map.
209
+ - `title`: HTML title and generated metadata title.
210
+ - `description`: meta description and generated metadata description.
211
+ - `order`: numeric sort order for site map and generated files.
212
+ - `layout`: page layout name. Defaults to `default`.
213
+
214
+ The final page props include:
215
+
216
+ ```ts
217
+ interface PageProps {
218
+ name?: string
219
+ path?: string
220
+ order?: number
221
+ title?: string
222
+ description?: string
223
+ content?: string
224
+ siteMap?: SiteMap
225
+ tableOfContent?: Array<{
226
+ path: string
227
+ label: string
228
+ level: string
229
+ }>
230
+ projectMeta?: {
231
+ name: string
232
+ version: string
233
+ [key: string]: unknown
234
+ }
235
+ renderMarkdown?: (markdown: string) => string
236
+ scripts?: string[]
237
+ themeStylesheet?: string
238
+ }
239
+ ```
240
+
241
+ ## Page Layouts
242
+
243
+ Page layouts render complete HTML documents. A layout file must default-export a function that receives `PageProps` and returns an HTML string.
244
+
245
+ Example `docs/_layouts/document.js`:
75
246
 
76
247
  ```js
77
- const {
78
- buildModules,
79
- buildBrowser,
80
- buildDocs,
81
- } = require('@beforesemicolon/builder')
248
+ export default ({
249
+ title,
250
+ description,
251
+ content,
252
+ scripts = [],
253
+ }) => `<!doctype html>
254
+ <html>
255
+ <head>
256
+ <meta charset="utf-8">
257
+ <meta name="description" content="${description || ''}">
258
+ <title>${title || ''}</title>
259
+ </head>
260
+ <body>
261
+ ${content || ''}
262
+ ${scripts.join('')}
263
+ </body>
264
+ </html>`
265
+ ```
266
+
267
+ Layout lookup order:
268
+
269
+ 1. Built-in layouts.
270
+ 2. Selected template layouts.
271
+ 3. `docs/_layouts`.
272
+ 4. `docs/_template/layouts`.
273
+
274
+ Later layout files with the same basename override earlier ones.
82
275
 
83
- ;(async () => {
84
- await buildModules()
85
- await buildBrowser()
86
- await buildDocs()
87
- })()
276
+ ## Templates
277
+
278
+ Named templates are loaded from:
279
+
280
+ ```txt
281
+ src/docs/templates/<template-name>/
88
282
  ```
89
283
 
90
- CLI / npm scripts
284
+ Available templates:
285
+
286
+ - `fading-citrus`: a complete landing and documentation template with Markdown layout handlers, theme variables, assets, and page scripts. See [fading-citrus template README](./src/docs/templates/fading-citrus/README.md).
91
287
 
92
- The repo ships with a `build` script that will emit types and run the TypeScript build entry (`build.ts`).
288
+ A template can provide:
93
289
 
290
+ ```txt
291
+ template.config.js
292
+ assets/
293
+ stylesheets/
294
+ scripts/
295
+ layouts/
94
296
  ```
95
- npm run build
297
+
298
+ The selected template is a complete out-of-the-box docs site shell. A docs source can extend it through `docs/_template`. Template-specific layouts, assets, options, and assumptions should be documented by each template.
299
+
300
+ ## Template Config
301
+
302
+ A template config exports an object:
303
+
304
+ ```js
305
+ export default {
306
+ markedOptions: {},
307
+ markdownLayouts: {},
308
+ scripts: {},
309
+ theme: {
310
+ light: {},
311
+ dark: {},
312
+ },
313
+ }
314
+ ```
315
+
316
+ Config from `docs/_template/template.config.js` is merged into the selected template config.
317
+
318
+ Merge behavior:
319
+
320
+ - `markdownLayouts` are shallow-merged by layout name.
321
+ - `scripts` are shallow-merged by script name.
322
+ - `theme.light` and `theme.dark` are shallow-merged by CSS variable name.
323
+ - Other top-level config values use the docs source config value when provided.
324
+
325
+ Example docs source extension:
326
+
327
+ ```js
328
+ import pricingCards from './layouts/pricing-cards.js'
329
+
330
+ export default {
331
+ markdownLayouts: {
332
+ 'pricing-cards': pricingCards,
333
+ },
334
+ theme: {
335
+ light: {
336
+ '--primary': 'oklch(0.62 0.18 250)',
337
+ },
338
+ dark: {
339
+ '--primary': 'oklch(0.76 0.16 250)',
340
+ },
341
+ },
342
+ }
343
+ ```
344
+
345
+ ## Markdown Layout Syntax
346
+
347
+ The docs renderer extends `marked` with a custom block syntax:
348
+
349
+ ```md
350
+ ::: layout <type> [options]
351
+
352
+ === <name> [options]
353
+
354
+ Markdown content for this part.
355
+
356
+ === <name> [options]
357
+
358
+ More Markdown content.
359
+
360
+ :::
361
+ ```
362
+
363
+ Example:
364
+
365
+ ```md
366
+ ::: layout grid columns=3 gap=lg
367
+
368
+ === card span=2
369
+
370
+ ## First card
371
+
372
+ Markdown content.
373
+
374
+ === card sticky
375
+
376
+ ## Second card
377
+
378
+ More Markdown content.
379
+
380
+ ===
381
+
382
+ Unnamed item.
383
+
384
+ :::
96
385
  ```
97
386
 
98
- This runs TypeScript declaration emission and then executes `build.ts` which calls `buildModules()` to produce the `dist/` outputs.
387
+ Header parsing:
99
388
 
100
- API
389
+ ```txt
390
+ ::: layout grid columns=3 gap=lg
391
+ ```
101
392
 
102
- - buildModules(options?: { directoryPath?: string }) => Promise
103
- - Scans the given directory (default: `process.cwd()/src`) recursively for files and builds them into:
104
- - `dist/esm` (ESM format)
105
- - `dist/cjs` (CommonJS format)
106
- - Files ending with `.spec.ts` or `/client.ts` are ignored from the module build set.
107
- - Uses `esbuild` with minification and keeps symbol names for better stack traces.
393
+ Produces:
108
394
 
109
- - buildBrowser(options?: { entry?: string; out?: string }) => Promise
110
- - Bundles a single browser file using `esbuild`.
111
- - Defaults: `entry` -> `src/client`, `out` -> `dist/client.js`.
112
- - Includes a small plugin to remove the `Doc` export from `@beforesemicolon/html-parser` during bundling (keeps bundle size smaller when that export isn't used).
113
- - Generates sourcemaps and minifies the output.
395
+ ```js
396
+ {
397
+ type: 'grid',
398
+ options: {
399
+ columns: 3,
400
+ gap: 'lg',
401
+ },
402
+ }
403
+ ```
114
404
 
115
- - buildDocs(options?: { srcDir?: string; publicDir?: string }) => Promise
116
- - Generates a static documentation website from a Markdown `docs` directory.
117
- - Defaults: `srcDir` -> `docs`, `publicDir` -> `website`.
118
- - Supported docs structure (defaults used by the generator):
119
- - `_layouts/` — custom templates (each module should default-export a function matching `PageProps`)
120
- - `assets/` — copied to the site output
121
- - `stylesheets/` — CSS files are minified and copied
122
- - `scripts/` — JS files are minified and copied
123
- - `*.md` — Markdown pages with front-matter to control metadata
124
- - Pages use `marked` with a custom renderer (heading IDs, code blocks, links) and `highlight.js` for syntax highlighting.
125
- - HTML is sanitized with DOMPurify and minified with `html-minifier`.
405
+ Item header parsing:
126
406
 
127
- Documentation site layout and front-matter
407
+ ```txt
408
+ === hero span=2 sticky
409
+ ```
410
+
411
+ Produces:
128
412
 
129
- Markdown pages should contain front-matter (YAML) with properties compatible with the site's `PageProps`:
413
+ ```js
414
+ {
415
+ name: 'hero',
416
+ options: {
417
+ span: 2,
418
+ sticky: true,
419
+ },
420
+ }
421
+ ```
130
422
 
131
- - title: string — page title (used in the HTML title)
132
- - description: string — meta description
133
- - order: number — numeric order used when building the site map and sorting pages
134
- - layout: string — layout name; corresponds to a template in `_layouts` (default: `default`)
423
+ Unnamed items are supported:
424
+
425
+ ```md
426
+ ===
427
+
428
+ Content
429
+ ```
135
430
 
136
- PageProps (shape used by layouts)
431
+ Produces:
137
432
 
138
- - name: string
139
- - path: string (page path, e.g. `/guide/getting-started.html`)
140
- - order: number
141
- - title: string
142
- - description: string
143
- - content: string (HTML produced from Markdown)
144
- - siteMap: Map representing the site structure
145
- - tableOfContent: array of { path, label } entries generated from headings
433
+ ```js
434
+ {
435
+ name: null,
436
+ options: {},
437
+ }
438
+ ```
439
+
440
+ Option parsing rules:
441
+
442
+ - `key=value` becomes a keyed option.
443
+ - Bare words become boolean `true`.
444
+ - Numeric values become numbers.
445
+ - `true` and `false` become booleans.
446
+ - Quoted values are supported.
447
+
448
+ Examples:
449
+
450
+ ```txt
451
+ columns=3
452
+ gap=lg
453
+ sticky
454
+ label="Get Started"
455
+ enabled=false
456
+ ```
457
+
458
+ Nested layout blocks are supported. Nested blocks are preserved inside the parent item body and rendered through the same Markdown renderer.
459
+
460
+ ## Markdown Layout Handlers
461
+
462
+ Markdown layout handlers are registered through `template.config.js`:
463
+
464
+ ```js
465
+ import pricingCards from './layouts/pricing-cards.js'
466
+
467
+ export default {
468
+ markdownLayouts: {
469
+ 'pricing-cards': pricingCards,
470
+ },
471
+ }
472
+ ```
146
473
 
147
- A minimal docs layout example (the package ships a simple `default` template):
474
+ A handler receives parsed layout data and a rendering context:
148
475
 
149
476
  ```ts
150
- export default ({ title, description, content }) => `
477
+ type MarkdownLayoutHandler = (
478
+ layout: {
479
+ type: string
480
+ options: Record<string, string | number | boolean>
481
+ parts: Array<{
482
+ name: string | null
483
+ options: Record<string, string | number | boolean>
484
+ body: string
485
+ html: string
486
+ }>
487
+ raw: string
488
+ },
489
+ context: {
490
+ renderMarkdown(markdown: string): string
491
+ renderDefault(node): string
492
+ renderParts(node): Array<{ html: string }>
493
+ }
494
+ ) => string
495
+ ```
496
+
497
+ Each part body is rendered from Markdown to HTML before the handler receives it. Use `part.html` when injecting content.
498
+
499
+ Example handler:
500
+
501
+ ```js
502
+ export default ({ parts, options }) => {
503
+ const tierClass = options.featured ? ' pricing-cards-featured' : ''
504
+
505
+ return `<div class="pricing-cards${tierClass}">
506
+ ${parts
507
+ .map(
508
+ (
509
+ part,
510
+ index
511
+ ) => `<section class="pricing-card option-${index + 1}">
512
+ ${part.html}
513
+ </section>`
514
+ )
515
+ .join('')}
516
+ </div>`
517
+ }
518
+ ```
519
+
520
+ Markdown:
521
+
522
+ ```md
523
+ ::: layout pricing-cards featured
524
+
525
+ ===
526
+
527
+ ## Starter
528
+
529
+ $10/month
530
+
531
+ ===
532
+
533
+ ## Pro
534
+
535
+ $30/month
536
+
537
+ :::
538
+ ```
539
+
540
+ Generated HTML is entirely controlled by the handler.
541
+
542
+ ## Default Markdown Layout Rendering
543
+
544
+ If a layout type has no custom handler, builder renders a generic structure:
545
+
546
+ ```html
547
+ <div
548
+ class="bfs-layout bfs-layout-grid"
549
+ data-layout="grid"
550
+ style="--columns: 3; --gap: lg;"
551
+ >
552
+ <section class="bfs-layout-item" data-name="card" style="--span: 2;">
553
+ ...
554
+ </section>
555
+ </div>
556
+ ```
557
+
558
+ Boolean options are omitted from inline styles. Non-boolean options are converted to CSS custom properties.
559
+
560
+ ## marked Options
561
+
562
+ The docs builder uses `marked`, `marked-highlight`, and a custom renderer for headings, code, and links.
563
+
564
+ You can extend `marked` globally for docs generation:
565
+
566
+ ```js
567
+ await buildDocs({
568
+ markedOptions: {
569
+ renderer: {
570
+ codespan({ text }) {
571
+ return `<code data-inline>${text}</code>`
572
+ },
573
+ },
574
+ },
575
+ })
576
+ ```
577
+
578
+ Templates can also provide `markedOptions` through `template.config.js`.
579
+
580
+ ## Page Scripts
581
+
582
+ Template scripts are declared in `template.config.js`.
583
+
584
+ ```js
585
+ import { renderCodeCopyScript } from './layouts/_code-snippet.js'
586
+
587
+ export default {
588
+ scripts: {
589
+ 'code-copy': {
590
+ match: 'code-copy-btn',
591
+ render: renderCodeCopyScript,
592
+ },
593
+ },
594
+ }
595
+ ```
596
+
597
+ Script definitions:
598
+
599
+ ```ts
600
+ type DocsScriptMatcher =
601
+ | string
602
+ | string[]
603
+ | RegExp
604
+ | ((html: string) => boolean)
605
+
606
+ interface DocsScriptDefinition {
607
+ match?: DocsScriptMatcher
608
+ render: () => string
609
+ }
610
+
611
+ type DocsScriptRegistry = Record<
612
+ string,
613
+ false | DocsScriptDefinition | (() => string)
614
+ >
615
+ ```
616
+
617
+ Behavior:
618
+
619
+ - Scripts are rendered per page after Markdown has been rendered.
620
+ - If `match` is omitted, the script is included on every page.
621
+ - A string matcher checks `html.includes(match)`.
622
+ - An array matcher checks whether any string is present.
623
+ - A RegExp matcher tests the rendered page HTML.
624
+ - A function matcher receives the rendered page HTML and returns a boolean.
625
+ - A script can be disabled by setting its registry value to `false` in an extending config.
626
+
627
+ Layouts receive scripts through `props.scripts` and must insert them where appropriate, usually before `</body>`.
628
+
629
+ ```js
630
+ export default (props) => `
151
631
  <!doctype html>
152
632
  <html>
153
- <head>
154
- <meta charset="utf-8" />
155
- <meta name="description" content="${description}" />
156
- <title>${title}</title>
157
- </head>
158
- <body>
159
- ${content}
160
- </body>
633
+ <body>
634
+ ${props.content}
635
+ ${props.scripts?.join('') || ''}
636
+ </body>
161
637
  </html>`
162
638
  ```
163
639
 
164
- Scripts (from package.json)
640
+ ## Theme Variables
165
641
 
166
- - `build` removes `dist`, emits TypeScript declarations, then runs `build.ts` (which calls `buildModules`).
167
- - `lint` — runs ESLint and Prettier check
168
- - `format` — runs ESLint autofix and Prettier write
642
+ Templates can define theme variables in `template.config.js`:
169
643
 
170
- Development
644
+ ```js
645
+ export default {
646
+ theme: {
647
+ light: {
648
+ '--background': 'oklch(0.98 0.006 250)',
649
+ '--foreground': 'oklch(0.18 0.015 250)',
650
+ '--primary': 'oklch(0.66 0.18 45)',
651
+ },
652
+ dark: {
653
+ '--background': 'oklch(0.18 0.015 250)',
654
+ '--foreground': 'oklch(0.96 0.005 250)',
655
+ '--primary': 'oklch(0.74 0.18 45)',
656
+ },
657
+ },
658
+ }
659
+ ```
171
660
 
172
- - Ensure you use Node >= 18.16.0
173
- - Install dependencies:
661
+ Builder converts theme variables into:
174
662
 
663
+ ```txt
664
+ website/stylesheets/theme.css
175
665
  ```
176
- npm install
666
+
667
+ The generated file includes:
668
+
669
+ - `:root` variables.
670
+ - `@media (prefers-color-scheme: dark)` variables.
671
+ - `[data-theme="light"]` variables.
672
+ - `[data-theme="dark"]` variables.
673
+
674
+ A theme mode can be disabled with `false`:
675
+
676
+ ```js
677
+ export default {
678
+ theme: {
679
+ light: false,
680
+ },
681
+ }
177
682
  ```
178
683
 
179
- - Run the build pipeline locally:
684
+ When one mode is disabled, the remaining mode is emitted as `:root`. For example, `light: false` makes the dark theme the default theme and skips light-mode selectors and `prefers-color-scheme` switching.
180
685
 
686
+ Page layouts receive:
687
+
688
+ ```ts
689
+ themeStylesheet?: string
181
690
  ```
182
- npm run build
691
+
692
+ Templates should include it in `<head>`:
693
+
694
+ ```js
695
+ ${props.themeStylesheet ? `<link rel="stylesheet" href="${props.themeStylesheet}">` : ''}
696
+ ```
697
+
698
+ Docs sources can override only variable values by adding `docs/_template/template.config.js`.
699
+
700
+ ## Generated Files
701
+
702
+ `buildDocs()` can generate common root-level files into `publicDir`.
703
+
704
+ Defaults:
705
+
706
+ ```js
707
+ generatedFiles: {
708
+ sitemap: true,
709
+ robots: true,
710
+ llms: true,
711
+ llmsFull: true,
712
+ netlify: false,
713
+ }
183
714
  ```
184
715
 
185
- - Lint and format:
716
+ Disable all generated files:
186
717
 
718
+ ```js
719
+ await buildDocs({
720
+ generatedFiles: false,
721
+ })
187
722
  ```
188
- npm run lint
189
- npm run format
723
+
724
+ Enable Netlify files:
725
+
726
+ ```js
727
+ await buildDocs({
728
+ siteUrl: 'https://docs.example.com',
729
+ generatedFiles: {
730
+ netlify: true,
731
+ },
732
+ })
733
+ ```
734
+
735
+ Generated files:
736
+
737
+ - `sitemap.xml`: generated from discovered Markdown pages. Requires `siteUrl`.
738
+ - `robots.txt`: generated with `Allow: /` and a sitemap URL when `siteUrl` is provided.
739
+ - `llms.txt`: generated page index for AI tools.
740
+ - `llms-full.txt`: generated expanded page index with source paths, descriptions, and summaries.
741
+ - `_redirects`: generated only when `generatedFiles.netlify` is `true`.
742
+ - `netlify.toml`: generated only when `generatedFiles.netlify` is `true`.
743
+
744
+ Source-first behavior:
745
+
746
+ - If `docs/sitemap.xml` exists, it is copied to `publicDir/sitemap.xml` instead of generated.
747
+ - If `docs/robots.txt` exists, it is copied to `publicDir/robots.txt` instead of generated.
748
+ - If `docs/llms.txt` exists, it is copied to `publicDir/llms.txt` instead of generated.
749
+ - If `docs/llms-full.txt` exists, it is copied to `publicDir/llms-full.txt` instead of generated.
750
+ - If `generatedFiles.netlify` is `true` and `docs/_redirects` exists, it is copied to `publicDir/_redirects` instead of generated.
751
+ - If `generatedFiles.netlify` is `true` and `docs/netlify.toml` exists, it is copied to `publicDir/netlify.toml` instead of generated.
752
+
753
+ Netlify notes:
754
+
755
+ - `_redirects` is treated as Netlify-specific.
756
+ - `netlify.toml` is written to `publicDir`, not the project root.
757
+ - Generated `netlify.toml` defaults to `command = "node build-docs.js"` and `publish = "website"` unless `publicDir` has a different basename.
758
+
759
+ ## llms-full.txt Content
760
+
761
+ The generated `llms-full.txt` is derived from discovered Markdown pages.
762
+
763
+ For each page it uses:
764
+
765
+ - `title`: front matter `title`, fallback to `name`, fallback to `Documentation`.
766
+ - `description`: front matter `description`, fallback to stripped Markdown body text.
767
+ - `URL`: file-derived page URL joined with `siteUrl`.
768
+ - `Source`: relative Markdown source path.
769
+ - `summary`: first 320 characters of stripped Markdown body text.
770
+
771
+ The body summary is not the final rendered HTML. It is a lightweight Markdown text extraction used for AI-facing page discovery.
772
+
773
+ ## Extension Example
774
+
775
+ Project docs:
776
+
777
+ ```txt
778
+ docs/
779
+ index.md
780
+ _template/
781
+ template.config.js
782
+ assets/
783
+ logo.svg
784
+ layouts/
785
+ pricing-cards.js
786
+ ```
787
+
788
+ `docs/_template/template.config.js`:
789
+
790
+ ```js
791
+ import pricingCards from './layouts/pricing-cards.js'
792
+
793
+ export default {
794
+ markdownLayouts: {
795
+ 'pricing-cards': pricingCards,
796
+ },
797
+ scripts: {
798
+ analytics: {
799
+ match: '<main',
800
+ render: () => `<script>console.log('page viewed')</script>`,
801
+ },
802
+ },
803
+ theme: {
804
+ light: {
805
+ '--primary': 'oklch(0.62 0.18 250)',
806
+ },
807
+ dark: {
808
+ '--primary': 'oklch(0.78 0.16 250)',
809
+ },
810
+ },
811
+ }
190
812
  ```
191
813
 
192
- - The source entry points are in `src/`. `src/build-modules.ts`, `src/build-browser.ts` and `src/docs/run.ts` provide the public functionality.
814
+ `docs/index.md`:
815
+
816
+ ```md
817
+ ---
818
+ title: Example
819
+ description: Example documentation.
820
+ layout: landing
821
+ ---
822
+
823
+ ::: layout pricing-cards featured
824
+
825
+ ===
826
+
827
+ ## Starter
828
+
829
+ For small teams.
830
+
831
+ ===
832
+
833
+ ## Pro
193
834
 
194
- Testing and validation
835
+ For growing teams.
836
+
837
+ :::
838
+ ```
839
+
840
+ ## Build Output
841
+
842
+ Given default options, output is written to:
843
+
844
+ ```txt
845
+ website/
846
+ index.html
847
+ guide/
848
+ getting-started.html
849
+ assets/
850
+ stylesheets/
851
+ scripts/
852
+ robots.txt
853
+ sitemap.xml
854
+ llms.txt
855
+ llms-full.txt
856
+ ```
857
+
858
+ If `generatedFiles.netlify` is `true`, output also includes:
859
+
860
+ ```txt
861
+ website/
862
+ _redirects
863
+ netlify.toml
864
+ ```
195
865
 
196
- This repository does not include automated tests by default. If you add tests, consider using the existing devDependencies (TypeScript + ts-jest + tinybench if you want benchmarks).
866
+ ## Package Scripts
197
867
 
198
- Contributing
868
+ This repo provides:
199
869
 
200
- Contributions are welcome. A good workflow is:
870
+ - `npm run build`: removes `dist`, emits TypeScript declarations, then builds package outputs.
871
+ - `npm run lint`: runs ESLint and Prettier checks.
872
+ - `npm run format`: runs ESLint autofix and Prettier write.
873
+ - `npm test`: runs the Markdown layout parser/renderer tests.
201
874
 
202
- 1. Fork the repository and create a feature branch.
203
- 2. Add or modify code in `src/`.
204
- 3. Run `npm run build` to check the build output.
205
- 4. Run `npm run lint` or `npm run format` to keep code style consistent.
206
- 5. Open a pull request describing the change.
875
+ ## Development
207
876
 
208
- License
877
+ Install dependencies:
878
+
879
+ ```sh
880
+ npm install
881
+ ```
882
+
883
+ Run checks:
884
+
885
+ ```sh
886
+ npm run lint
887
+ npm test
888
+ npm run build
889
+ ```
890
+
891
+ Pack locally:
892
+
893
+ ```sh
894
+ npm pack
895
+ ```
896
+
897
+ Install the packed artifact into a sibling project:
898
+
899
+ ```sh
900
+ npm install ../builder/beforesemicolon-builder-<version>.tgz
901
+ ```
209
902
 
210
- BSD-3-Clause see `package.json` for author and license metadata.
903
+ ## Implementation Notes
211
904
 
212
- Acknowledgements and internals
905
+ - Markdown rendering uses `marked`.
906
+ - Syntax highlighting uses `marked-highlight` and `highlight.js`.
907
+ - Front matter parsing uses `front-matter`.
908
+ - HTML is sanitized with `isomorphic-dompurify`.
909
+ - HTML output is minified with `html-minifier`.
910
+ - CSS output is minified with `clean-css`.
911
+ - JS output copied from docs script folders is minified with `@putout/minify`.
912
+ - Static module and browser builds use `esbuild`.
213
913
 
214
- - Uses `esbuild` for fast bundling and minification.
215
- - Markdown rendering powered by `marked` with a custom renderer and `marked-highlight` plugin using `highlight.js`.
216
- - Front-matter parsing via `front-matter`.
217
- - DOM sanitization by `isomorphic-dompurify` and HTML/CSS/JS minification using `html-minifier`, `clean-css`, and `@putout/minify` respectively.
914
+ ## License
218
915
 
219
- If you'd like any sections expanded (examples, advanced options, or a CONTRIBUTING.md), tell me which parts to elaborate and I'll add them.
916
+ BSD-3-Clause. See `package.json`.