kitfly 0.1.2 → 0.2.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/CHANGELOG.md +34 -0
- package/README.md +63 -16
- package/VERSION +1 -1
- package/dist/_raw/content/deployment/preflight.md +134 -0
- package/dist/_raw/content/deployment/recipes/aws-s3.md +128 -0
- package/dist/_raw/content/deployment/recipes/cloudflare-pages.md +73 -0
- package/dist/_raw/content/deployment/recipes/cloudflare-r2.md +156 -0
- package/dist/_raw/content/deployment/recipes/fly-io.md +57 -0
- package/dist/_raw/content/deployment/recipes/github-pages.md +112 -0
- package/dist/_raw/content/deployment/recipes/netlify.md +99 -0
- package/dist/_raw/content/deployment/recipes/vercel.md +88 -0
- package/dist/_raw/content/deployment/secrets-and-env-vars.md +75 -0
- package/dist/_raw/content/deployment.md +128 -0
- package/dist/_raw/content/guide/approaches.md +182 -0
- package/dist/_raw/content/guide/features.md +121 -0
- package/dist/_raw/content/guide/getting-started.md +112 -0
- package/dist/_raw/content/guide/kitfly-overview.md +209 -0
- package/dist/_raw/content/reference/configuration.md +259 -0
- package/dist/_raw/content/reference/design-catalog.md +167 -0
- package/dist/_raw/content/reference/environment-variables.md +66 -0
- package/dist/_raw/content/reference/glossary.md +92 -0
- package/dist/_raw/content/reference/key-concepts.md +118 -0
- package/dist/_raw/content/reference/plugins.md +220 -0
- package/dist/_raw/content/reference/structure.md +166 -0
- package/dist/_raw/content/reference.md +19 -0
- package/dist/_raw/content/templates/crucible.md +192 -0
- package/dist/_raw/content/templates/handbook.md +83 -0
- package/dist/_raw/content/templates/minimal.md +138 -0
- package/dist/_raw/content/templates/overview.md +187 -0
- package/dist/_raw/content/templates/pipeline.md +151 -0
- package/dist/_raw/content/templates/productbook.md +187 -0
- package/dist/_raw/content/templates/runbook.md +193 -0
- package/dist/_raw/content/templates/servicebook.md +163 -0
- package/dist/_raw/docs/decisions/ADR-0001-minimalist-site-code.md +118 -0
- package/dist/_raw/docs/decisions/ADR-0002-ai-accessibility.md +153 -0
- package/dist/_raw/docs/decisions/ADR-0003-single-file-bundle.md +93 -0
- package/dist/_raw/docs/decisions/ADR-0004-bun-runtime.md +98 -0
- package/dist/_raw/docs/decisions/ADR-0005-plugin-contract-and-distribution.md +110 -0
- package/dist/_raw/docs/decisions/DDR-0001-viewport-locked-layout.md +111 -0
- package/dist/_raw/docs/decisions/DDR-0002-theme-system.md +131 -0
- package/dist/_raw/docs/decisions/DDR-0003-bounded-logo-slot.md +106 -0
- package/dist/_raw/docs/decisions/DDR-0004-slides-rendering-model.md +113 -0
- package/dist/_raw/docs/decisions/DDR-0005-deterministic-layout-boundary.md +107 -0
- package/dist/_raw/docs/userguide/cli/build.md +85 -0
- package/dist/_raw/docs/userguide/cli/bundle.md +81 -0
- package/dist/_raw/docs/userguide/cli/dev.md +92 -0
- package/dist/_raw/docs/userguide/cli/init.md +116 -0
- package/dist/_raw/docs/userguide/cli/servers.md +69 -0
- package/dist/_raw/docs/userguide/cli/stop.md +76 -0
- package/dist/_raw/docs/userguide/cli/update.md +78 -0
- package/dist/_raw/docs/userguide/cli/version.md +65 -0
- package/dist/_raw/docs/userguide/cli.md +34 -0
- package/dist/_raw/docs/userguide/sharing.md +94 -0
- package/dist/_raw/schemas/plugin-schemas-notes.md +71 -0
- package/dist/_raw/schemas.md +42 -0
- package/dist/assets/brand/kitfly-favicon-32.png +0 -0
- package/dist/assets/brand/kitfly-icon-64.png +0 -0
- package/dist/assets/brand/kitfly-logo-128.png +0 -0
- package/dist/assets/brand/kitfly-logo-512.png +0 -0
- package/dist/assets/brand/kitfly-logo.svg +12132 -0
- package/dist/assets/brand/kitfly-neon-128.png +0 -0
- package/dist/assets/brand/kitfly-neon-192.png +0 -0
- package/dist/assets/brand/kitfly-neon-256.png +0 -0
- package/dist/assets/brand/kitfly-neon.png +0 -0
- package/dist/assets/brand/palette.md +75 -0
- package/dist/content/deployment/index.html +11 -0
- package/dist/content/deployment/preflight.html +418 -0
- package/dist/content/deployment/recipes/aws-s3.html +421 -0
- package/dist/content/deployment/recipes/cloudflare-pages.html +372 -0
- package/dist/content/deployment/recipes/cloudflare-r2.html +443 -0
- package/dist/content/deployment/recipes/fly-io.html +356 -0
- package/dist/content/deployment/recipes/github-pages.html +414 -0
- package/dist/content/deployment/recipes/index.html +11 -0
- package/dist/content/deployment/recipes/netlify.html +394 -0
- package/dist/content/deployment/recipes/vercel.html +382 -0
- package/dist/content/deployment/secrets-and-env-vars.html +380 -0
- package/dist/content/deployment.html +426 -0
- package/dist/content/guide/approaches.html +501 -0
- package/dist/content/guide/features.html +436 -0
- package/dist/content/guide/getting-started.html +403 -0
- package/dist/content/guide/index.html +11 -0
- package/dist/content/guide/kitfly-overview.html +544 -0
- package/dist/content/index.html +11 -0
- package/dist/content/reference/configuration.html +580 -0
- package/dist/content/reference/design-catalog.html +449 -0
- package/dist/content/reference/environment-variables.html +367 -0
- package/dist/content/reference/glossary.html +368 -0
- package/dist/content/reference/index.html +11 -0
- package/dist/content/reference/key-concepts.html +399 -0
- package/dist/content/reference/plugins.html +491 -0
- package/dist/content/reference/structure.html +463 -0
- package/dist/content/reference.html +334 -0
- package/dist/content/templates/crucible.html +546 -0
- package/dist/content/templates/handbook.html +405 -0
- package/dist/content/templates/index.html +11 -0
- package/dist/content/templates/minimal.html +447 -0
- package/dist/content/templates/overview.html +558 -0
- package/dist/content/templates/pipeline.html +494 -0
- package/dist/content/templates/productbook.html +540 -0
- package/dist/content/templates/runbook.html +543 -0
- package/dist/content/templates/servicebook.html +523 -0
- package/dist/content-index.json +540 -0
- package/dist/docs/decisions/ADR-0001-minimalist-site-code.html +491 -0
- package/dist/docs/decisions/ADR-0002-ai-accessibility.html +434 -0
- package/dist/docs/decisions/ADR-0003-single-file-bundle.html +412 -0
- package/dist/docs/decisions/ADR-0004-bun-runtime.html +409 -0
- package/dist/docs/decisions/ADR-0005-plugin-contract-and-distribution.html +402 -0
- package/dist/docs/decisions/DDR-0001-viewport-locked-layout.html +459 -0
- package/dist/docs/decisions/DDR-0002-theme-system.html +452 -0
- package/dist/docs/decisions/DDR-0003-bounded-logo-slot.html +423 -0
- package/dist/docs/decisions/DDR-0004-slides-rendering-model.html +399 -0
- package/dist/docs/decisions/DDR-0005-deterministic-layout-boundary.html +422 -0
- package/dist/docs/decisions/index.html +11 -0
- package/dist/docs/userguide/cli/build.html +408 -0
- package/dist/docs/userguide/cli/bundle.html +419 -0
- package/dist/docs/userguide/cli/dev.html +428 -0
- package/dist/docs/userguide/cli/index.html +11 -0
- package/dist/docs/userguide/cli/init.html +436 -0
- package/dist/docs/userguide/cli/servers.html +393 -0
- package/dist/docs/userguide/cli/stop.html +408 -0
- package/dist/docs/userguide/cli/update.html +406 -0
- package/dist/docs/userguide/cli/version.html +406 -0
- package/dist/docs/userguide/cli.html +386 -0
- package/dist/docs/userguide/index.html +11 -0
- package/dist/docs/userguide/sharing.html +465 -0
- package/dist/index.html +387 -0
- package/dist/llms.txt +18 -0
- package/dist/provenance.json +7 -0
- package/dist/schemas/index.html +11 -0
- package/dist/schemas/plugin-registry.schema.html +327 -0
- package/dist/schemas/plugin-schemas-notes.html +364 -0
- package/dist/schemas/plugin.schema.html +327 -0
- package/dist/schemas/plugins.schema.html +327 -0
- package/dist/schemas/v0/common.schema.html +386 -0
- package/dist/schemas/v0/index.html +11 -0
- package/dist/schemas/v0/plugin-registry.schema.html +547 -0
- package/dist/schemas/v0/plugin.schema.html +497 -0
- package/dist/schemas/v0/plugins.schema.html +406 -0
- package/dist/schemas/v0/site.schema.html +541 -0
- package/dist/schemas/v0/theme.schema.html +615 -0
- package/dist/schemas.html +351 -0
- package/dist/styles.css +1262 -0
- package/package.json +4 -2
- package/plugins-dist/callouts.css +32 -0
- package/plugins-dist/callouts.js +46 -0
- package/plugins-dist/slides-visuals.css +224 -0
- package/plugins-dist/slides-visuals.js +598 -0
- package/registry/plugins.yaml +35 -0
- package/schemas/README.md +10 -0
- package/schemas/plugin-registry.schema.json +5 -0
- package/schemas/plugin-schemas-notes.md +71 -0
- package/schemas/plugin.schema.json +5 -0
- package/schemas/plugins.schema.json +5 -0
- package/schemas/v0/common.schema.json +64 -0
- package/schemas/v0/plugin-registry.schema.json +225 -0
- package/schemas/v0/plugin.schema.json +175 -0
- package/schemas/v0/plugins.schema.json +84 -0
- package/schemas/v0/site.schema.json +56 -9
- package/schemas/v0/theme.schema.json +105 -22
- package/scripts/build.ts +155 -3
- package/scripts/bundle.ts +258 -95
- package/scripts/dev.ts +203 -1
- package/src/__tests__/build.test.ts +158 -1
- package/src/__tests__/bundle.test.ts +31 -0
- package/src/__tests__/cli.test.ts +14 -3
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/bad-list-indent.md +5 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/blank-line.md +5 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/compare-object-items.md +9 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/indented-fence.md +4 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/stat-grid-missing-fields.md +5 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/unknown-type.md +3 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/compare.md +10 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/comparison-table.md +14 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/funnel.md +7 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/kpi.md +5 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/layer-cake.md +6 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/pyramid.md +6 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/quadrant-grid.md +8 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/scorecard.md +13 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/stat-grid.md +8 -0
- package/src/__tests__/init.test.ts +35 -0
- package/src/__tests__/plugin-loader.test.ts +221 -0
- package/src/__tests__/shared.test.ts +428 -0
- package/src/__tests__/slides-visuals-fence-contract.test.ts +28 -0
- package/src/__tests__/slides-visuals-runtime-regressions.bun.test.ts +114 -0
- package/src/__tests__/styles.test.ts +35 -0
- package/src/cli.ts +9 -4
- package/src/plugin-loader.ts +245 -0
- package/src/shared.ts +614 -7
- package/src/site/styles.css +331 -0
- package/src/site/template.html +66 -5
- package/src/templates/deck.ts +186 -0
- package/src/templates/driver.ts +11 -1
- package/src/templates/minimal.ts +1 -0
package/scripts/bundle.ts
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
* Usage: bun run bundle [folder] [options]
|
|
5
5
|
*
|
|
6
6
|
* Options:
|
|
7
|
-
* -o, --out <dir> Output directory [env:
|
|
7
|
+
* -o, --out <dir> Output directory [env: KITFLY_BUNDLE_OUT] [default: bundles]
|
|
8
8
|
* -n, --name <file> Bundle filename [env: KITFLY_BUNDLE_NAME] [default: bundle.html]
|
|
9
|
-
* --raw Include raw markdown in bundle [env:
|
|
9
|
+
* --raw Include raw markdown in bundle [env: KITFLY_BUNDLE_RAW] [default: true]
|
|
10
10
|
* --no-raw Don't include raw markdown
|
|
11
11
|
* --help Show help message
|
|
12
12
|
*
|
|
13
|
-
* Creates
|
|
13
|
+
* Creates bundles/bundle.html - a single file containing all content,
|
|
14
14
|
* styles, and scripts for offline viewing.
|
|
15
15
|
*/
|
|
16
16
|
|
|
@@ -18,13 +18,16 @@ import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
|
18
18
|
import { basename, extname, join, resolve } from "node:path";
|
|
19
19
|
import { marked, Renderer } from "marked";
|
|
20
20
|
import { ENGINE_ASSETS_DIR } from "../src/engine.ts";
|
|
21
|
+
import { loadPluginInjections } from "../src/plugin-loader.ts";
|
|
21
22
|
import {
|
|
22
23
|
buildBundleFooter,
|
|
23
|
-
// Navigation/template building
|
|
24
24
|
buildSectionNav,
|
|
25
|
+
// Navigation/template building
|
|
26
|
+
buildSlideNav,
|
|
25
27
|
// Types
|
|
26
28
|
type ContentFile,
|
|
27
29
|
collectFiles,
|
|
30
|
+
collectSlides,
|
|
28
31
|
envBool,
|
|
29
32
|
// Config helpers
|
|
30
33
|
envString,
|
|
@@ -34,16 +37,18 @@ import {
|
|
|
34
37
|
loadSiteConfig,
|
|
35
38
|
// Markdown utilities
|
|
36
39
|
parseFrontmatter,
|
|
40
|
+
parseYaml,
|
|
37
41
|
resolveSiteVersion,
|
|
38
42
|
resolveStylesPath,
|
|
39
43
|
type SiteConfig,
|
|
40
44
|
slugify,
|
|
41
45
|
validatePath,
|
|
46
|
+
validateSlidesVisualsFences,
|
|
42
47
|
} from "../src/shared.ts";
|
|
43
48
|
import { generateThemeCSS, getPrismUrls, loadTheme } from "../src/theme.ts";
|
|
44
49
|
|
|
45
50
|
// Defaults
|
|
46
|
-
const DEFAULT_OUT = "
|
|
51
|
+
const DEFAULT_OUT = "bundles";
|
|
47
52
|
const DEFAULT_NAME = "bundle.html";
|
|
48
53
|
|
|
49
54
|
let ROOT = process.cwd();
|
|
@@ -91,11 +96,15 @@ function getConfig(): {
|
|
|
91
96
|
raw: boolean;
|
|
92
97
|
} {
|
|
93
98
|
const args = parseArgs(process.argv.slice(2));
|
|
99
|
+
const legacyOut = envString("KITFLY_BUILD_OUT", DEFAULT_OUT);
|
|
100
|
+
const out = args.out ?? envString("KITFLY_BUNDLE_OUT", legacyOut);
|
|
101
|
+
const legacyRaw = envBool("KITFLY_BUILD_RAW", true);
|
|
102
|
+
const raw = args.raw ?? envBool("KITFLY_BUNDLE_RAW", legacyRaw);
|
|
94
103
|
return {
|
|
95
104
|
folder: args.folder,
|
|
96
|
-
out
|
|
105
|
+
out,
|
|
97
106
|
name: args.name ?? envString("KITFLY_BUNDLE_NAME", DEFAULT_NAME),
|
|
98
|
-
raw
|
|
107
|
+
raw,
|
|
99
108
|
};
|
|
100
109
|
}
|
|
101
110
|
|
|
@@ -295,6 +304,71 @@ function buildBundleNav(files: ContentFile[], config: SiteConfig): string {
|
|
|
295
304
|
return html;
|
|
296
305
|
}
|
|
297
306
|
|
|
307
|
+
async function buildSlidesBundleContent(files: ContentFile[], config: SiteConfig): Promise<string> {
|
|
308
|
+
const slides = await collectSlides(files);
|
|
309
|
+
let validateFences = false;
|
|
310
|
+
try {
|
|
311
|
+
const raw = await readFile(join(ROOT, "kitfly.plugins.yaml"), "utf-8");
|
|
312
|
+
const parsed = parseYaml(raw) as unknown as Record<string, unknown>;
|
|
313
|
+
const enabled = Array.isArray(parsed?.plugins) ? (parsed.plugins as unknown[]) : [];
|
|
314
|
+
validateFences = enabled.some((p) => typeof p === "string" && p.startsWith("slides-visuals@"));
|
|
315
|
+
} catch {
|
|
316
|
+
// no config, skip
|
|
317
|
+
}
|
|
318
|
+
const renderedSlides = await Promise.all(
|
|
319
|
+
slides.map(async (slide, i) => {
|
|
320
|
+
let inner = "";
|
|
321
|
+
if (slide.kind === "markdown") {
|
|
322
|
+
if (validateFences) {
|
|
323
|
+
const diagnostics = validateSlidesVisualsFences(slide.body);
|
|
324
|
+
if (diagnostics.length) {
|
|
325
|
+
const msg = diagnostics
|
|
326
|
+
.slice(0, 12)
|
|
327
|
+
.map((d) => ` - ${slide.sourcePath}:${d.line} ${d.message}`)
|
|
328
|
+
.join("\n");
|
|
329
|
+
throw new Error(`slides-visuals fence contract violations:\n${msg}`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
inner = marked.parse(slide.body) as string;
|
|
333
|
+
} else if (slide.kind === "yaml") {
|
|
334
|
+
inner = `<pre><code class="language-yaml">${escapeHtml(slide.body)}</code></pre>`;
|
|
335
|
+
} else {
|
|
336
|
+
let prettyJson = slide.body;
|
|
337
|
+
try {
|
|
338
|
+
prettyJson = JSON.stringify(JSON.parse(slide.body), null, 2);
|
|
339
|
+
} catch {
|
|
340
|
+
// Keep original text
|
|
341
|
+
}
|
|
342
|
+
inner = `<pre><code class="language-json">${escapeHtml(prettyJson)}</code></pre>`;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
inner = await inlineLocalImages(inner, config);
|
|
346
|
+
inner = rewriteContentLinks(inner, files, slide.sourceUrlPath, config.docroot);
|
|
347
|
+
|
|
348
|
+
const activeClass = i === 0 ? " active" : "";
|
|
349
|
+
const classToken = slide.className ? ` ${slide.className}` : "";
|
|
350
|
+
return `<section id="${slide.id}" class="slide${classToken}${activeClass}" data-slide-index="${i}">${inner}</section>`;
|
|
351
|
+
}),
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
return `
|
|
355
|
+
<div class="slides-shell" style="--slide-aspect: ${config.aspect || "16/9"}">
|
|
356
|
+
<div class="slide-viewport">
|
|
357
|
+
<div class="slide-frame">
|
|
358
|
+
${renderedSlides.join("\n")}
|
|
359
|
+
</div>
|
|
360
|
+
</div>
|
|
361
|
+
<div class="slide-nav" aria-label="Slide navigation">
|
|
362
|
+
<button class="slide-prev" type="button" aria-label="Previous slide">Prev</button>
|
|
363
|
+
<span class="slide-counter">1 / ${slides.length}</span>
|
|
364
|
+
<button class="slide-next" type="button" aria-label="Next slide">Next</button>
|
|
365
|
+
<div class="slide-progress" role="presentation">
|
|
366
|
+
<span class="slide-progress-bar" style="width: ${(1 / slides.length) * 100}%"></span>
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
</div>`;
|
|
370
|
+
}
|
|
371
|
+
|
|
298
372
|
function buildBundleSidebarHeader(
|
|
299
373
|
config: SiteConfig,
|
|
300
374
|
version: string | undefined,
|
|
@@ -304,12 +378,13 @@ function buildBundleSidebarHeader(
|
|
|
304
378
|
const logoClass = config.brand.logoType === "wordmark" ? "logo-wordmark" : "logo-icon";
|
|
305
379
|
const productHref = config.home ? "#home" : "#";
|
|
306
380
|
const versionLabel = version ? `v${version}` : "unversioned";
|
|
381
|
+
const brandInitial = escapeHtml(config.brand.name.trim().charAt(0).toUpperCase() || "K");
|
|
307
382
|
|
|
308
383
|
return `
|
|
309
384
|
<div class="sidebar-header">
|
|
310
385
|
<div class="logo ${logoClass}">
|
|
311
|
-
<a href="${config.brand.url}" class="logo-icon"${brandTarget}>
|
|
312
|
-
<img src="${brandLogo}" alt="${config.brand.name}" class="logo-img">
|
|
386
|
+
<a href="${config.brand.url}" class="logo-icon" data-initial="${brandInitial}"${brandTarget}>
|
|
387
|
+
<img src="${brandLogo}" alt="${config.brand.name}" class="logo-img" onerror="this.onerror=null;this.style.display='none';this.parentElement.classList.add('logo-fallback')">
|
|
313
388
|
</a>
|
|
314
389
|
<span class="logo-text">
|
|
315
390
|
<a href="${config.brand.url}" class="brand"${brandTarget}>${config.brand.name}</a>
|
|
@@ -410,29 +485,6 @@ async function bundle() {
|
|
|
410
485
|
// Resolve site version (site.yaml version, then git tag)
|
|
411
486
|
const version = await resolveSiteVersion(ROOT, config.version);
|
|
412
487
|
|
|
413
|
-
// Build navigation and content sections
|
|
414
|
-
const sections: Map<string, { id: string; title: string; html: string }[]> = new Map();
|
|
415
|
-
|
|
416
|
-
// Add home page as first item if specified
|
|
417
|
-
if (config.home) {
|
|
418
|
-
const homePath = validatePath(ROOT, config.docroot, config.home);
|
|
419
|
-
if (homePath) {
|
|
420
|
-
try {
|
|
421
|
-
await stat(homePath);
|
|
422
|
-
const content = await readFile(homePath, "utf-8");
|
|
423
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
424
|
-
const title = (frontmatter.title as string) || "Home";
|
|
425
|
-
let htmlContent = marked.parse(body) as string;
|
|
426
|
-
htmlContent = await inlineLocalImages(htmlContent, config);
|
|
427
|
-
htmlContent = rewriteContentLinks(htmlContent, files, undefined, config.docroot);
|
|
428
|
-
sections.set("Home", [{ id: "home", title, html: htmlContent }]);
|
|
429
|
-
console.log(` ✓ Added home page: ${config.home}`);
|
|
430
|
-
} catch {
|
|
431
|
-
console.warn(` ⚠ Home page ${config.home} not found`);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
488
|
// Collect page metadata and raw content for AI accessibility
|
|
437
489
|
const pageIndex: {
|
|
438
490
|
path: string;
|
|
@@ -441,77 +493,124 @@ async function bundle() {
|
|
|
441
493
|
description?: string;
|
|
442
494
|
}[] = [];
|
|
443
495
|
const rawMarkdown: { path: string; content: string }[] = [];
|
|
496
|
+
let navHtml = "";
|
|
497
|
+
let contentHtml = "";
|
|
444
498
|
|
|
445
|
-
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
if (file.path.endsWith(".yaml")) {
|
|
452
|
-
htmlContent = `<pre><code class="language-yaml">${escapeHtml(content)}</code></pre>`;
|
|
453
|
-
} else if (file.path.endsWith(".json")) {
|
|
454
|
-
// Render JSON as code block (pretty-printed)
|
|
455
|
-
let prettyJson = content;
|
|
456
|
-
try {
|
|
457
|
-
prettyJson = JSON.stringify(JSON.parse(content), null, 2);
|
|
458
|
-
} catch {
|
|
459
|
-
// Use original if not valid JSON
|
|
460
|
-
}
|
|
461
|
-
htmlContent = `<pre><code class="language-json">${escapeHtml(prettyJson)}</code></pre>`;
|
|
462
|
-
} else {
|
|
463
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
464
|
-
if (frontmatter.title) {
|
|
465
|
-
title = frontmatter.title as string;
|
|
499
|
+
if (config.mode === "slides") {
|
|
500
|
+
const slides = await collectSlides(files);
|
|
501
|
+
for (const file of files) {
|
|
502
|
+
const content = await readFile(file.path, "utf-8");
|
|
503
|
+
if (INCLUDE_RAW && file.path.endsWith(".md")) {
|
|
504
|
+
rawMarkdown.push({ path: file.urlPath, content });
|
|
466
505
|
}
|
|
467
|
-
|
|
468
|
-
|
|
506
|
+
}
|
|
507
|
+
for (const slide of slides) {
|
|
508
|
+
pageIndex.push({
|
|
509
|
+
path: slide.id,
|
|
510
|
+
title: slide.title,
|
|
511
|
+
section: slide.section,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
navHtml = buildSlideNav(slides, config, "slide-1");
|
|
515
|
+
contentHtml = await buildSlidesBundleContent(files, config);
|
|
516
|
+
} else {
|
|
517
|
+
// Build navigation and content sections
|
|
518
|
+
const sections: Map<string, { id: string; title: string; html: string }[]> = new Map();
|
|
519
|
+
|
|
520
|
+
// Add home page as first item if specified
|
|
521
|
+
if (config.home) {
|
|
522
|
+
const homePath = validatePath(ROOT, config.docroot, config.home);
|
|
523
|
+
if (homePath) {
|
|
524
|
+
try {
|
|
525
|
+
await stat(homePath);
|
|
526
|
+
const content = await readFile(homePath, "utf-8");
|
|
527
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
528
|
+
const title = (frontmatter.title as string) || "Home";
|
|
529
|
+
let htmlContent = marked.parse(body) as string;
|
|
530
|
+
htmlContent = await inlineLocalImages(htmlContent, config);
|
|
531
|
+
htmlContent = rewriteContentLinks(htmlContent, files, undefined, config.docroot);
|
|
532
|
+
sections.set("Home", [{ id: "home", title, html: htmlContent }]);
|
|
533
|
+
console.log(` ✓ Added home page: ${config.home}`);
|
|
534
|
+
} catch {
|
|
535
|
+
console.warn(` ⚠ Home page ${config.home} not found`);
|
|
536
|
+
}
|
|
469
537
|
}
|
|
470
|
-
|
|
538
|
+
}
|
|
471
539
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
540
|
+
for (const file of files) {
|
|
541
|
+
const content = await readFile(file.path, "utf-8");
|
|
542
|
+
let title = basename(file.path).replace(/\.(md|yaml|json)$/, "");
|
|
543
|
+
let description: string | undefined;
|
|
544
|
+
let htmlContent: string;
|
|
545
|
+
|
|
546
|
+
if (file.path.endsWith(".yaml")) {
|
|
547
|
+
htmlContent = `<pre><code class="language-yaml">${escapeHtml(content)}</code></pre>`;
|
|
548
|
+
} else if (file.path.endsWith(".json")) {
|
|
549
|
+
// Render JSON as code block (pretty-printed)
|
|
550
|
+
let prettyJson = content;
|
|
551
|
+
try {
|
|
552
|
+
prettyJson = JSON.stringify(JSON.parse(content), null, 2);
|
|
553
|
+
} catch {
|
|
554
|
+
// Use original if not valid JSON
|
|
555
|
+
}
|
|
556
|
+
htmlContent = `<pre><code class="language-json">${escapeHtml(prettyJson)}</code></pre>`;
|
|
557
|
+
} else {
|
|
558
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
559
|
+
if (frontmatter.title) {
|
|
560
|
+
title = frontmatter.title as string;
|
|
561
|
+
}
|
|
562
|
+
if (frontmatter.description) {
|
|
563
|
+
description = frontmatter.description as string;
|
|
564
|
+
}
|
|
565
|
+
htmlContent = marked.parse(body) as string;
|
|
566
|
+
|
|
567
|
+
// Collect raw markdown for AI accessibility
|
|
568
|
+
if (INCLUDE_RAW) {
|
|
569
|
+
rawMarkdown.push({ path: file.urlPath, content });
|
|
570
|
+
}
|
|
475
571
|
}
|
|
476
|
-
}
|
|
477
572
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
573
|
+
// Collect page metadata for content index
|
|
574
|
+
pageIndex.push({
|
|
575
|
+
path: file.urlPath,
|
|
576
|
+
title,
|
|
577
|
+
section: file.section,
|
|
578
|
+
description,
|
|
579
|
+
});
|
|
485
580
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
581
|
+
// Inline any SVG references
|
|
582
|
+
htmlContent = await inlineLocalImages(htmlContent, config);
|
|
583
|
+
htmlContent = rewriteContentLinks(htmlContent, files, file.urlPath, config.docroot);
|
|
489
584
|
|
|
490
|
-
|
|
585
|
+
const sectionId = slugify(file.urlPath);
|
|
491
586
|
|
|
492
|
-
|
|
493
|
-
|
|
587
|
+
if (!sections.has(file.section)) {
|
|
588
|
+
sections.set(file.section, []);
|
|
589
|
+
}
|
|
590
|
+
sections.get(file.section)?.push({ id: sectionId, title, html: htmlContent });
|
|
494
591
|
}
|
|
495
|
-
sections.get(file.section)?.push({ id: sectionId, title, html: htmlContent });
|
|
496
|
-
}
|
|
497
592
|
|
|
498
|
-
|
|
499
|
-
|
|
593
|
+
// Build navigation HTML from shared hierarchical nav tree
|
|
594
|
+
navHtml = buildBundleNav(files, config);
|
|
500
595
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
contentHtml += `
|
|
596
|
+
// Build content HTML
|
|
597
|
+
for (const [, items] of sections) {
|
|
598
|
+
for (const item of items) {
|
|
599
|
+
contentHtml += `
|
|
506
600
|
<section id="${item.id}" class="bundle-section">
|
|
507
601
|
<h1 class="section-title">${item.title}</h1>
|
|
508
602
|
${item.html}
|
|
509
603
|
</section>
|
|
510
604
|
`;
|
|
605
|
+
}
|
|
511
606
|
}
|
|
512
607
|
}
|
|
513
608
|
|
|
514
609
|
const themeCSS = generateThemeCSS(theme);
|
|
610
|
+
const plugins = await loadPluginInjections({
|
|
611
|
+
root: ROOT,
|
|
612
|
+
mode: config.mode === "slides" ? "slides" : "docs",
|
|
613
|
+
});
|
|
515
614
|
|
|
516
615
|
// Inline brand assets for self-contained bundle
|
|
517
616
|
const brandLogo = await inlineBrandAsset(config.brand.logo || "assets/brand/logo.png");
|
|
@@ -551,6 +650,7 @@ ${assets.prismCss}
|
|
|
551
650
|
<style id="prism-dark" disabled>
|
|
552
651
|
${assets.prismCssDark}
|
|
553
652
|
</style>
|
|
653
|
+
${plugins.head}
|
|
554
654
|
<script>
|
|
555
655
|
(function() {
|
|
556
656
|
const saved = localStorage.getItem('theme');
|
|
@@ -566,7 +666,7 @@ ${assets.prismCssDark}
|
|
|
566
666
|
})();
|
|
567
667
|
</script>
|
|
568
668
|
</head>
|
|
569
|
-
<body>
|
|
669
|
+
<body class="${config.mode === "slides" ? "mode-slides" : "mode-docs"}">
|
|
570
670
|
<div class="layout">
|
|
571
671
|
<nav class="sidebar">
|
|
572
672
|
${buildBundleSidebarHeader(config, version, brandLogo)}
|
|
@@ -590,6 +690,7 @@ ${assets.prismAutoloader}
|
|
|
590
690
|
<script>
|
|
591
691
|
${assets.mermaid}
|
|
592
692
|
</script>
|
|
693
|
+
${plugins.bodyEnd}
|
|
593
694
|
<script>
|
|
594
695
|
// Initialize Mermaid
|
|
595
696
|
function getMermaidTheme() {
|
|
@@ -647,17 +748,78 @@ ${assets.mermaid}
|
|
|
647
748
|
}
|
|
648
749
|
}
|
|
649
750
|
|
|
650
|
-
//
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
751
|
+
// Slides mode hash routing
|
|
752
|
+
(function initSlidesMode() {
|
|
753
|
+
const shell = document.querySelector('.slides-shell');
|
|
754
|
+
if (!shell) {
|
|
755
|
+
// Docs mode: retain smooth in-page anchor scrolling.
|
|
756
|
+
document.querySelectorAll('a[href^="#"]').forEach((link) => {
|
|
757
|
+
link.addEventListener('click', (e) => {
|
|
758
|
+
const href = link.getAttribute('href') || '';
|
|
759
|
+
if (href.length <= 1) return;
|
|
760
|
+
const target = document.querySelector(href);
|
|
761
|
+
if (!target) return;
|
|
762
|
+
e.preventDefault();
|
|
763
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
764
|
+
history.replaceState(null, '', href);
|
|
765
|
+
});
|
|
766
|
+
});
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const slides = Array.from(document.querySelectorAll('.slide'));
|
|
771
|
+
if (!slides.length) return;
|
|
772
|
+
|
|
773
|
+
const prevBtn = document.querySelector('.slide-prev');
|
|
774
|
+
const nextBtn = document.querySelector('.slide-next');
|
|
775
|
+
const counter = document.querySelector('.slide-counter');
|
|
776
|
+
const progressBar = document.querySelector('.slide-progress-bar');
|
|
777
|
+
const navLinks = Array.from(document.querySelectorAll('.sidebar-nav a[href^="#slide-"]'));
|
|
778
|
+
let current = 0;
|
|
779
|
+
|
|
780
|
+
function setActive(n) {
|
|
781
|
+
current = Math.max(0, Math.min(n, slides.length - 1));
|
|
782
|
+
slides.forEach((slide, idx) => slide.classList.toggle('active', idx === current));
|
|
783
|
+
navLinks.forEach((link) => {
|
|
784
|
+
const active = link.getAttribute('href') === '#' + slides[current].id;
|
|
785
|
+
link.classList.toggle('active', active);
|
|
786
|
+
});
|
|
787
|
+
if (counter) counter.textContent = (current + 1) + ' / ' + slides.length;
|
|
788
|
+
if (progressBar) progressBar.style.width = (((current + 1) / slides.length) * 100) + '%';
|
|
789
|
+
if (prevBtn) prevBtn.disabled = current === 0;
|
|
790
|
+
if (nextBtn) nextBtn.disabled = current === slides.length - 1;
|
|
791
|
+
history.replaceState(null, '', '#'+slides[current].id);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function setFromHash() {
|
|
795
|
+
const hash = window.location.hash || '';
|
|
796
|
+
const idx = slides.findIndex((s) => '#'+s.id === hash);
|
|
797
|
+
if (idx >= 0) setActive(idx);
|
|
798
|
+
else setActive(0);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
prevBtn?.addEventListener('click', () => setActive(current - 1));
|
|
802
|
+
nextBtn?.addEventListener('click', () => setActive(current + 1));
|
|
803
|
+
|
|
804
|
+
document.addEventListener('keydown', (e) => {
|
|
805
|
+
if (e.key === 'ArrowRight' || e.key === ' ') {
|
|
806
|
+
e.preventDefault();
|
|
807
|
+
setActive(current + 1);
|
|
808
|
+
} else if (e.key === 'ArrowLeft') {
|
|
809
|
+
e.preventDefault();
|
|
810
|
+
setActive(current - 1);
|
|
811
|
+
} else if (e.key === 'Home') {
|
|
812
|
+
e.preventDefault();
|
|
813
|
+
setActive(0);
|
|
814
|
+
} else if (e.key === 'End') {
|
|
815
|
+
e.preventDefault();
|
|
816
|
+
setActive(slides.length - 1);
|
|
658
817
|
}
|
|
659
818
|
});
|
|
660
|
-
|
|
819
|
+
|
|
820
|
+
window.addEventListener('hashchange', setFromHash);
|
|
821
|
+
setFromHash();
|
|
822
|
+
})();
|
|
661
823
|
</script>
|
|
662
824
|
<!-- AI Accessibility: Content Index -->
|
|
663
825
|
<script type="application/json" id="kitfly-content-index">
|
|
@@ -755,9 +917,9 @@ if (import.meta.main) {
|
|
|
755
917
|
Usage: bun run bundle [folder] [options]
|
|
756
918
|
|
|
757
919
|
Options:
|
|
758
|
-
-o, --out <dir> Output directory [env:
|
|
920
|
+
-o, --out <dir> Output directory [env: KITFLY_BUNDLE_OUT] [default: ${DEFAULT_OUT}]
|
|
759
921
|
-n, --name <file> Bundle filename [env: KITFLY_BUNDLE_NAME] [default: ${DEFAULT_NAME}]
|
|
760
|
-
--raw Include raw markdown in bundle [env:
|
|
922
|
+
--raw Include raw markdown in bundle [env: KITFLY_BUNDLE_RAW] [default: true]
|
|
761
923
|
--no-raw Don't include raw markdown
|
|
762
924
|
--help Show this help message
|
|
763
925
|
|
|
@@ -765,8 +927,9 @@ Examples:
|
|
|
765
927
|
bun run bundle
|
|
766
928
|
bun run bundle ./docs
|
|
767
929
|
bun run bundle --name docs.html
|
|
768
|
-
bun run bundle ./docs --out ./
|
|
930
|
+
bun run bundle ./docs --out ./bundles --name handbook.html
|
|
769
931
|
KITFLY_BUNDLE_NAME=docs.html bun run bundle
|
|
932
|
+
KITFLY_BUNDLE_OUT=release bun run bundle
|
|
770
933
|
`);
|
|
771
934
|
process.exit(0);
|
|
772
935
|
}
|