kitfly 0.2.0 → 0.2.3

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 (126) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/README.md +25 -10
  3. package/VERSION +1 -1
  4. package/dist/_raw/content/guide/branding.md +146 -0
  5. package/dist/_raw/content/guide/data-driven-content.md +204 -0
  6. package/dist/_raw/content/reference/configuration.md +145 -7
  7. package/dist/_raw/content/reference/environment-variables.md +26 -1
  8. package/dist/_raw/content/reference/glossary.md +25 -1
  9. package/dist/_raw/content/reference/key-concepts.md +30 -2
  10. package/dist/_raw/content/reference/plugins.md +14 -0
  11. package/dist/_raw/content/reference/slides-authoring-guidelines.md +129 -0
  12. package/dist/_raw/content/reference.md +1 -0
  13. package/dist/_raw/docs/decisions/ADR-0006-data-driven-content.md +350 -0
  14. package/dist/content/deployment/preflight.html +10 -6
  15. package/dist/content/deployment/recipes/aws-s3.html +10 -6
  16. package/dist/content/deployment/recipes/cloudflare-pages.html +10 -6
  17. package/dist/content/deployment/recipes/cloudflare-r2.html +10 -6
  18. package/dist/content/deployment/recipes/fly-io.html +10 -6
  19. package/dist/content/deployment/recipes/github-pages.html +10 -6
  20. package/dist/content/deployment/recipes/netlify.html +10 -6
  21. package/dist/content/deployment/recipes/vercel.html +10 -6
  22. package/dist/content/deployment/secrets-and-env-vars.html +10 -6
  23. package/dist/content/deployment.html +10 -6
  24. package/dist/content/guide/approaches.html +10 -6
  25. package/dist/content/guide/branding.html +510 -0
  26. package/dist/content/guide/data-driven-content.html +543 -0
  27. package/dist/content/guide/features.html +10 -6
  28. package/dist/content/guide/getting-started.html +10 -6
  29. package/dist/content/guide/kitfly-overview.html +10 -6
  30. package/dist/content/reference/configuration.html +135 -9
  31. package/dist/content/reference/design-catalog.html +10 -6
  32. package/dist/content/reference/environment-variables.html +50 -8
  33. package/dist/content/reference/glossary.html +24 -8
  34. package/dist/content/reference/key-concepts.html +33 -9
  35. package/dist/content/reference/plugins.html +22 -7
  36. package/dist/content/reference/slides-authoring-guidelines.html +422 -0
  37. package/dist/content/reference/structure.html +10 -6
  38. package/dist/content/reference.html +11 -6
  39. package/dist/content/templates/crucible.html +10 -6
  40. package/dist/content/templates/handbook.html +10 -6
  41. package/dist/content/templates/minimal.html +10 -6
  42. package/dist/content/templates/overview.html +10 -6
  43. package/dist/content/templates/pipeline.html +10 -6
  44. package/dist/content/templates/productbook.html +10 -6
  45. package/dist/content/templates/runbook.html +10 -6
  46. package/dist/content/templates/servicebook.html +10 -6
  47. package/dist/content-index.json +38 -2
  48. package/dist/docs/decisions/ADR-0001-minimalist-site-code.html +10 -6
  49. package/dist/docs/decisions/ADR-0002-ai-accessibility.html +10 -6
  50. package/dist/docs/decisions/ADR-0003-single-file-bundle.html +10 -6
  51. package/dist/docs/decisions/ADR-0004-bun-runtime.html +10 -6
  52. package/dist/docs/decisions/ADR-0005-plugin-contract-and-distribution.html +10 -6
  53. package/dist/docs/decisions/ADR-0006-data-driven-content.html +752 -0
  54. package/dist/docs/decisions/DDR-0001-viewport-locked-layout.html +10 -6
  55. package/dist/docs/decisions/DDR-0002-theme-system.html +10 -6
  56. package/dist/docs/decisions/DDR-0003-bounded-logo-slot.html +10 -6
  57. package/dist/docs/decisions/DDR-0004-slides-rendering-model.html +10 -6
  58. package/dist/docs/decisions/DDR-0005-deterministic-layout-boundary.html +10 -6
  59. package/dist/docs/userguide/cli/build.html +10 -6
  60. package/dist/docs/userguide/cli/bundle.html +10 -6
  61. package/dist/docs/userguide/cli/dev.html +10 -6
  62. package/dist/docs/userguide/cli/init.html +10 -6
  63. package/dist/docs/userguide/cli/servers.html +10 -6
  64. package/dist/docs/userguide/cli/stop.html +10 -6
  65. package/dist/docs/userguide/cli/update.html +10 -6
  66. package/dist/docs/userguide/cli/version.html +10 -6
  67. package/dist/docs/userguide/cli.html +10 -6
  68. package/dist/docs/userguide/sharing.html +10 -6
  69. package/dist/index.html +10 -6
  70. package/dist/llms.txt +3 -3
  71. package/dist/provenance.json +4 -4
  72. package/dist/schemas/plugin-registry.schema.html +10 -6
  73. package/dist/schemas/plugin-schemas-notes.html +10 -6
  74. package/dist/schemas/plugin.schema.html +10 -6
  75. package/dist/schemas/plugins.schema.html +10 -6
  76. package/dist/schemas/v0/common.schema.html +14 -10
  77. package/dist/schemas/v0/plugin-registry.schema.html +13 -9
  78. package/dist/schemas/v0/plugin.schema.html +13 -9
  79. package/dist/schemas/v0/plugins.schema.html +13 -9
  80. package/dist/schemas/v0/site.schema.html +67 -7
  81. package/dist/schemas/v0/theme.schema.html +21 -17
  82. package/dist/schemas.html +10 -6
  83. package/dist/styles.css +39 -4
  84. package/package.json +1 -1
  85. package/plugins-dist/latex-runtime.js +140 -0
  86. package/plugins-dist/latex.js +178 -0
  87. package/plugins-dist/slides-charts-lite-runtime.js +179 -0
  88. package/plugins-dist/slides-charts-lite.js +198 -0
  89. package/plugins-dist/slides-visuals.css +166 -0
  90. package/plugins-dist/slides-visuals.js +124 -33
  91. package/registry/plugins.yaml +30 -5
  92. package/schemas/v0/site.schema.json +56 -0
  93. package/scripts/build.ts +195 -70
  94. package/scripts/bundle.ts +122 -11
  95. package/scripts/dev.ts +345 -178
  96. package/src/__tests__/brief.test.ts +151 -0
  97. package/src/__tests__/build.test.ts +234 -4
  98. package/src/__tests__/bundle.test.ts +134 -0
  99. package/src/__tests__/dev-plugin-errors.test.ts +20 -0
  100. package/src/__tests__/fixtures/fences/slides-visuals/invalid/flow-branching-no-source.md +5 -0
  101. package/src/__tests__/fixtures/fences/slides-visuals/invalid/flow-converging-no-target.md +6 -0
  102. package/src/__tests__/fixtures/fences/slides-visuals/invalid/staircase-empty-steps.md +3 -0
  103. package/src/__tests__/fixtures/fences/slides-visuals/invalid/timeline-horizontal-no-events.md +2 -0
  104. package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-branching-no-split.md +7 -0
  105. package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-branching.md +8 -0
  106. package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-converging-no-merge.md +7 -0
  107. package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-converging.md +8 -0
  108. package/src/__tests__/fixtures/fences/slides-visuals/valid/staircase-down.md +7 -0
  109. package/src/__tests__/fixtures/fences/slides-visuals/valid/staircase.md +8 -0
  110. package/src/__tests__/fixtures/fences/slides-visuals/valid/timeline-horizontal.md +9 -0
  111. package/src/__tests__/fixtures/fences/slides-visuals/valid/timeline-vertical.md +10 -0
  112. package/src/__tests__/init.test.ts +51 -2
  113. package/src/__tests__/latex-runtime.bun.test.ts +35 -0
  114. package/src/__tests__/shared.test.ts +621 -1
  115. package/src/__tests__/slides-charts-lite-runtime.bun.test.ts +45 -0
  116. package/src/__tests__/slides-visuals-runtime-regressions.bun.test.ts +33 -0
  117. package/src/cli.ts +11 -4
  118. package/src/commands/init.ts +1 -1
  119. package/src/shared.ts +761 -18
  120. package/src/site/styles.css +39 -4
  121. package/src/site/template.html +5 -2
  122. package/src/templates/brief.ts +486 -0
  123. package/src/templates/deck.ts +59 -0
  124. package/src/templates/driver.ts +46 -13
  125. package/src/templates/handbook.ts +32 -0
  126. package/src/templates/runbook.ts +32 -0
package/scripts/bundle.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  * Options:
7
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
+ * --profile <name> Active content profile [env: KITFLY_PROFILE]
9
10
  * --raw Include raw markdown in bundle [env: KITFLY_BUNDLE_RAW] [default: true]
10
11
  * --no-raw Don't include raw markdown
11
12
  * --help Show help message
@@ -21,9 +22,10 @@ import { ENGINE_ASSETS_DIR } from "../src/engine.ts";
21
22
  import { loadPluginInjections } from "../src/plugin-loader.ts";
22
23
  import {
23
24
  buildBundleFooter,
25
+ buildLogoImgHtml,
24
26
  buildSectionNav,
25
27
  // Navigation/template building
26
- buildSlideNav,
28
+ buildSlideNavHierarchical,
27
29
  // Types
28
30
  type ContentFile,
29
31
  collectFiles,
@@ -33,13 +35,20 @@ import {
33
35
  envString,
34
36
  // Formatting
35
37
  escapeHtml,
38
+ filterByProfile,
39
+ filterUnknownSlidesVisualsTypeDiagnostics,
36
40
  // YAML/Config parsing
41
+ loadDataBindings,
37
42
  loadSiteConfig,
43
+ mergeFrontmatterWithBody,
38
44
  // Markdown utilities
45
+ pagePathForData,
39
46
  parseFrontmatter,
40
47
  parseYaml,
48
+ resolveBindings,
41
49
  resolveSiteVersion,
42
50
  resolveStylesPath,
51
+ runPrebuildHooks,
43
52
  type SiteConfig,
44
53
  slugify,
45
54
  validatePath,
@@ -54,6 +63,41 @@ const DEFAULT_NAME = "bundle.html";
54
63
  let ROOT = process.cwd();
55
64
  let OUT_DIR = DEFAULT_OUT;
56
65
  let BUNDLE_NAME = DEFAULT_NAME;
66
+ let ACTIVE_PROFILE: string | undefined;
67
+
68
+ async function applyDataBindingsToMarkdown(
69
+ rawMarkdown: string,
70
+ filePath: string,
71
+ config: SiteConfig,
72
+ ): Promise<{ frontmatter: Record<string, unknown>; body: string }> {
73
+ const parsed = parseFrontmatter(rawMarkdown);
74
+ const dataRef = typeof parsed.frontmatter.data === "string" ? parsed.frontmatter.data.trim() : "";
75
+ if (!dataRef) return parsed;
76
+
77
+ const pagePath = pagePathForData(ROOT, config.docroot, filePath);
78
+ const bindings = await loadDataBindings(dataRef, pagePath, ROOT, config.docroot, config.dataroot);
79
+ return {
80
+ frontmatter: parsed.frontmatter,
81
+ body: resolveBindings(parsed.body, bindings, pagePath),
82
+ };
83
+ }
84
+
85
+ async function applyDataBindingsForSlides(
86
+ rawMarkdown: string,
87
+ filePath: string,
88
+ config: SiteConfig,
89
+ ): Promise<string> {
90
+ const resolved = await applyDataBindingsToMarkdown(rawMarkdown, filePath, config);
91
+ return mergeFrontmatterWithBody(rawMarkdown, resolved.body);
92
+ }
93
+
94
+ function normalizeMsysPath(p: string): string {
95
+ // Git Bash / MSYS-style paths: /c/Users/... -> C:\Users\...
96
+ if (process.platform !== "win32") return p;
97
+ const m = p.match(/^\/([a-zA-Z])\/(.*)$/);
98
+ if (!m) return p;
99
+ return `${m[1].toUpperCase()}:\\${m[2].replaceAll("/", "\\")}`;
100
+ }
57
101
 
58
102
  // ---------------------------------------------------------------------------
59
103
  // CLI argument parsing
@@ -64,6 +108,7 @@ interface ParsedArgs {
64
108
  out?: string;
65
109
  name?: string;
66
110
  raw?: boolean;
111
+ profile?: string;
67
112
  }
68
113
 
69
114
  function parseArgs(argv: string[]): ParsedArgs {
@@ -78,6 +123,9 @@ function parseArgs(argv: string[]): ParsedArgs {
78
123
  } else if ((arg === "--name" || arg === "-n") && next && !next.startsWith("-")) {
79
124
  result.name = next;
80
125
  i++;
126
+ } else if (arg === "--profile" && next && !next.startsWith("-")) {
127
+ result.profile = next;
128
+ i++;
81
129
  } else if (arg === "--raw") {
82
130
  result.raw = true;
83
131
  } else if (arg === "--no-raw") {
@@ -94,6 +142,7 @@ function getConfig(): {
94
142
  out: string;
95
143
  name: string;
96
144
  raw: boolean;
145
+ profile?: string;
97
146
  } {
98
147
  const args = parseArgs(process.argv.slice(2));
99
148
  const legacyOut = envString("KITFLY_BUILD_OUT", DEFAULT_OUT);
@@ -105,6 +154,7 @@ function getConfig(): {
105
154
  out,
106
155
  name: args.name ?? envString("KITFLY_BUNDLE_NAME", DEFAULT_NAME),
107
156
  raw,
157
+ profile: args.profile ?? process.env.KITFLY_PROFILE,
108
158
  };
109
159
  }
110
160
 
@@ -305,7 +355,9 @@ function buildBundleNav(files: ContentFile[], config: SiteConfig): string {
305
355
  }
306
356
 
307
357
  async function buildSlidesBundleContent(files: ContentFile[], config: SiteConfig): Promise<string> {
308
- const slides = await collectSlides(files);
358
+ const slides = await collectSlides(files, {
359
+ markdownTransform: (raw, file) => applyDataBindingsForSlides(raw, file.path, config),
360
+ });
309
361
  let validateFences = false;
310
362
  try {
311
363
  const raw = await readFile(join(ROOT, "kitfly.plugins.yaml"), "utf-8");
@@ -320,7 +372,9 @@ async function buildSlidesBundleContent(files: ContentFile[], config: SiteConfig
320
372
  let inner = "";
321
373
  if (slide.kind === "markdown") {
322
374
  if (validateFences) {
323
- const diagnostics = validateSlidesVisualsFences(slide.body);
375
+ const diagnostics = filterUnknownSlidesVisualsTypeDiagnostics(
376
+ validateSlidesVisualsFences(slide.body),
377
+ );
324
378
  if (diagnostics.length) {
325
379
  const msg = diagnostics
326
380
  .slice(0, 12)
@@ -373,18 +427,26 @@ function buildBundleSidebarHeader(
373
427
  config: SiteConfig,
374
428
  version: string | undefined,
375
429
  brandLogo: string,
430
+ brandLogoDark?: string,
376
431
  ): string {
377
432
  const brandTarget = config.brand.external ? ' target="_blank" rel="noopener"' : "";
378
433
  const logoClass = config.brand.logoType === "wordmark" ? "logo-wordmark" : "logo-icon";
379
434
  const productHref = config.home ? "#home" : "#";
380
435
  const versionLabel = version ? `v${version}` : "unversioned";
381
436
  const brandInitial = escapeHtml(config.brand.name.trim().charAt(0).toUpperCase() || "K");
437
+ const brandLogoHtml = buildLogoImgHtml({
438
+ logo: brandLogo,
439
+ logoDark: brandLogoDark,
440
+ alt: config.brand.name,
441
+ className: "logo-img",
442
+ onerrorFallback: true,
443
+ });
382
444
 
383
445
  return `
384
446
  <div class="sidebar-header">
385
447
  <div class="logo ${logoClass}">
386
448
  <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')">
449
+ ${brandLogoHtml}
388
450
  </a>
389
451
  <span class="logo-text">
390
452
  <a href="${config.brand.url}" class="brand"${brandTarget}>${config.brand.name}</a>
@@ -424,6 +486,18 @@ async function inlineBrandAsset(assetPath: string): Promise<string> {
424
486
  /* continue */
425
487
  }
426
488
  }
489
+
490
+ // Support any safe site-root-relative path (e.g., logos/footer.png).
491
+ const siteRootPath = validatePath(ROOT, ".", clean, false);
492
+ if (siteRootPath) {
493
+ try {
494
+ await stat(siteRootPath);
495
+ const uri = await fileToDataUri(siteRootPath);
496
+ if (uri) return uri;
497
+ } catch {
498
+ /* continue */
499
+ }
500
+ }
427
501
  return assetPath;
428
502
  }
429
503
 
@@ -463,12 +537,26 @@ async function bundle() {
463
537
 
464
538
  const config = await loadSiteConfig(ROOT, "Documentation");
465
539
  console.log(` ✓ Loaded config: "${config.title}" (${config.sections.length} sections)`);
540
+ if (config.prebuild?.length) {
541
+ await runPrebuildHooks(
542
+ config.prebuild,
543
+ ROOT,
544
+ "bundle",
545
+ ACTIVE_PROFILE,
546
+ config.dataroot || "data",
547
+ );
548
+ console.log(` ✓ prebuild hooks (${config.prebuild.length})`);
549
+ }
466
550
 
467
551
  const theme = await loadTheme(ROOT);
468
552
  console.log(` ✓ Loaded theme: "${theme.name || "default"}"`);
469
553
  const prismUrls = getPrismUrls(theme);
470
554
 
471
- const files = await collectFiles(ROOT, config);
555
+ const files = await filterByProfile(
556
+ await collectFiles(ROOT, config),
557
+ ACTIVE_PROFILE,
558
+ config.profiles,
559
+ );
472
560
  if (files.length === 0) {
473
561
  console.error("No content files found. Cannot create bundle.");
474
562
  process.exit(1);
@@ -511,7 +599,7 @@ async function bundle() {
511
599
  section: slide.section,
512
600
  });
513
601
  }
514
- navHtml = buildSlideNav(slides, config, "slide-1");
602
+ navHtml = buildSlideNavHierarchical(slides, config, "slide-1");
515
603
  contentHtml = await buildSlidesBundleContent(files, config);
516
604
  } else {
517
605
  // Build navigation and content sections
@@ -524,7 +612,11 @@ async function bundle() {
524
612
  try {
525
613
  await stat(homePath);
526
614
  const content = await readFile(homePath, "utf-8");
527
- const { frontmatter, body } = parseFrontmatter(content);
615
+ const { frontmatter, body } = await applyDataBindingsToMarkdown(
616
+ content,
617
+ homePath,
618
+ config,
619
+ );
528
620
  const title = (frontmatter.title as string) || "Home";
529
621
  let htmlContent = marked.parse(body) as string;
530
622
  htmlContent = await inlineLocalImages(htmlContent, config);
@@ -555,7 +647,7 @@ async function bundle() {
555
647
  }
556
648
  htmlContent = `<pre><code class="language-json">${escapeHtml(prettyJson)}</code></pre>`;
557
649
  } else {
558
- const { frontmatter, body } = parseFrontmatter(content);
650
+ const { frontmatter, body } = await applyDataBindingsToMarkdown(content, file.path, config);
559
651
  if (frontmatter.title) {
560
652
  title = frontmatter.title as string;
561
653
  }
@@ -614,7 +706,19 @@ async function bundle() {
614
706
 
615
707
  // Inline brand assets for self-contained bundle
616
708
  const brandLogo = await inlineBrandAsset(config.brand.logo || "assets/brand/logo.png");
709
+ const brandLogoDark =
710
+ typeof config.brand.logoDark === "string"
711
+ ? await inlineBrandAsset(config.brand.logoDark)
712
+ : undefined;
617
713
  const brandFavicon = await inlineBrandAsset(config.brand.favicon || "assets/brand/favicon.png");
714
+ const footerLogo =
715
+ typeof config.footer?.logo === "string"
716
+ ? await inlineBrandAsset(config.footer.logo)
717
+ : undefined;
718
+ const footerLogoDark =
719
+ typeof config.footer?.logoDark === "string"
720
+ ? await inlineBrandAsset(config.footer.logoDark)
721
+ : undefined;
618
722
 
619
723
  // Build the complete HTML document
620
724
  const html = `<!DOCTYPE html>
@@ -669,7 +773,7 @@ ${assets.prismCssDark}
669
773
  <body class="${config.mode === "slides" ? "mode-slides" : "mode-docs"}">
670
774
  <div class="layout">
671
775
  <nav class="sidebar">
672
- ${buildBundleSidebarHeader(config, version, brandLogo)}
776
+ ${buildBundleSidebarHeader(config, version, brandLogo, brandLogoDark)}
673
777
  <div class="sidebar-nav">
674
778
  ${navHtml}
675
779
  </div>
@@ -680,7 +784,7 @@ ${buildBundleSidebarHeader(config, version, brandLogo)}
680
784
  </article>
681
785
  </main>
682
786
  </div>
683
- ${buildBundleFooter(version, config)}
787
+ ${buildBundleFooter(version, config, footerLogo, footerLogoDark)}
684
788
  <script>
685
789
  ${assets.prismCore}
686
790
  </script>
@@ -746,6 +850,9 @@ ${assets.mermaid}
746
850
  if (window.reinitMermaid) {
747
851
  window.reinitMermaid();
748
852
  }
853
+ if (window.reinitCharts) {
854
+ window.reinitCharts();
855
+ }
749
856
  }
750
857
 
751
858
  // Slides mode hash routing
@@ -861,7 +968,7 @@ ${JSON.stringify(
861
968
  </html>`;
862
969
 
863
970
  // Write the bundle
864
- const outDir = join(ROOT, OUT_DIR);
971
+ const outDir = resolve(ROOT, normalizeMsysPath(OUT_DIR));
865
972
  await mkdir(outDir, { recursive: true });
866
973
  const bundlePath = join(outDir, BUNDLE_NAME);
867
974
  await writeFile(bundlePath, html);
@@ -878,6 +985,7 @@ export interface BundleOptions {
878
985
  out?: string;
879
986
  name?: string;
880
987
  raw?: boolean; // Include raw markdown in bundle (default: true)
988
+ profile?: string;
881
989
  }
882
990
 
883
991
  let INCLUDE_RAW = true;
@@ -907,6 +1015,7 @@ export async function bundleSite(options: BundleOptions = {}) {
907
1015
  if (options.raw === false) {
908
1016
  INCLUDE_RAW = false;
909
1017
  }
1018
+ ACTIVE_PROFILE = options.profile;
910
1019
  await bundle();
911
1020
  }
912
1021
 
@@ -919,6 +1028,7 @@ Usage: bun run bundle [folder] [options]
919
1028
  Options:
920
1029
  -o, --out <dir> Output directory [env: KITFLY_BUNDLE_OUT] [default: ${DEFAULT_OUT}]
921
1030
  -n, --name <file> Bundle filename [env: KITFLY_BUNDLE_NAME] [default: ${DEFAULT_NAME}]
1031
+ --profile <name> Active content profile [env: KITFLY_PROFILE]
922
1032
  --raw Include raw markdown in bundle [env: KITFLY_BUNDLE_RAW] [default: true]
923
1033
  --no-raw Don't include raw markdown
924
1034
  --help Show this help message
@@ -940,5 +1050,6 @@ Examples:
940
1050
  out: cfg.out,
941
1051
  name: cfg.name,
942
1052
  raw: cfg.raw,
1053
+ profile: cfg.profile,
943
1054
  }).catch(console.error);
944
1055
  }