kitfly 0.2.1 → 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 (108) hide show
  1. package/CHANGELOG.md +56 -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/docs/decisions/ADR-0006-data-driven-content.md +350 -0
  12. package/dist/content/deployment/preflight.html +10 -6
  13. package/dist/content/deployment/recipes/aws-s3.html +10 -6
  14. package/dist/content/deployment/recipes/cloudflare-pages.html +10 -6
  15. package/dist/content/deployment/recipes/cloudflare-r2.html +10 -6
  16. package/dist/content/deployment/recipes/fly-io.html +10 -6
  17. package/dist/content/deployment/recipes/github-pages.html +10 -6
  18. package/dist/content/deployment/recipes/netlify.html +10 -6
  19. package/dist/content/deployment/recipes/vercel.html +10 -6
  20. package/dist/content/deployment/secrets-and-env-vars.html +10 -6
  21. package/dist/content/deployment.html +10 -6
  22. package/dist/content/guide/approaches.html +10 -6
  23. package/dist/content/guide/branding.html +510 -0
  24. package/dist/content/guide/data-driven-content.html +543 -0
  25. package/dist/content/guide/features.html +10 -6
  26. package/dist/content/guide/getting-started.html +10 -6
  27. package/dist/content/guide/kitfly-overview.html +10 -6
  28. package/dist/content/reference/configuration.html +135 -9
  29. package/dist/content/reference/design-catalog.html +10 -6
  30. package/dist/content/reference/environment-variables.html +50 -8
  31. package/dist/content/reference/glossary.html +24 -8
  32. package/dist/content/reference/key-concepts.html +33 -9
  33. package/dist/content/reference/plugins.html +22 -7
  34. package/dist/content/reference/slides-authoring-guidelines.html +10 -6
  35. package/dist/content/reference/structure.html +10 -6
  36. package/dist/content/reference.html +10 -6
  37. package/dist/content/templates/crucible.html +10 -6
  38. package/dist/content/templates/handbook.html +10 -6
  39. package/dist/content/templates/minimal.html +10 -6
  40. package/dist/content/templates/overview.html +10 -6
  41. package/dist/content/templates/pipeline.html +10 -6
  42. package/dist/content/templates/productbook.html +10 -6
  43. package/dist/content/templates/runbook.html +10 -6
  44. package/dist/content/templates/servicebook.html +10 -6
  45. package/dist/content-index.json +29 -2
  46. package/dist/docs/decisions/ADR-0001-minimalist-site-code.html +10 -6
  47. package/dist/docs/decisions/ADR-0002-ai-accessibility.html +10 -6
  48. package/dist/docs/decisions/ADR-0003-single-file-bundle.html +10 -6
  49. package/dist/docs/decisions/ADR-0004-bun-runtime.html +10 -6
  50. package/dist/docs/decisions/ADR-0005-plugin-contract-and-distribution.html +10 -6
  51. package/dist/docs/decisions/ADR-0006-data-driven-content.html +752 -0
  52. package/dist/docs/decisions/DDR-0001-viewport-locked-layout.html +10 -6
  53. package/dist/docs/decisions/DDR-0002-theme-system.html +10 -6
  54. package/dist/docs/decisions/DDR-0003-bounded-logo-slot.html +10 -6
  55. package/dist/docs/decisions/DDR-0004-slides-rendering-model.html +10 -6
  56. package/dist/docs/decisions/DDR-0005-deterministic-layout-boundary.html +10 -6
  57. package/dist/docs/userguide/cli/build.html +10 -6
  58. package/dist/docs/userguide/cli/bundle.html +10 -6
  59. package/dist/docs/userguide/cli/dev.html +10 -6
  60. package/dist/docs/userguide/cli/init.html +10 -6
  61. package/dist/docs/userguide/cli/servers.html +10 -6
  62. package/dist/docs/userguide/cli/stop.html +10 -6
  63. package/dist/docs/userguide/cli/update.html +10 -6
  64. package/dist/docs/userguide/cli/version.html +10 -6
  65. package/dist/docs/userguide/cli.html +10 -6
  66. package/dist/docs/userguide/sharing.html +10 -6
  67. package/dist/index.html +10 -6
  68. package/dist/llms.txt +3 -3
  69. package/dist/provenance.json +4 -4
  70. package/dist/schemas/plugin-registry.schema.html +10 -6
  71. package/dist/schemas/plugin-schemas-notes.html +10 -6
  72. package/dist/schemas/plugin.schema.html +10 -6
  73. package/dist/schemas/plugins.schema.html +10 -6
  74. package/dist/schemas/v0/common.schema.html +14 -10
  75. package/dist/schemas/v0/plugin-registry.schema.html +13 -9
  76. package/dist/schemas/v0/plugin.schema.html +13 -9
  77. package/dist/schemas/v0/plugins.schema.html +13 -9
  78. package/dist/schemas/v0/site.schema.html +67 -7
  79. package/dist/schemas/v0/theme.schema.html +21 -17
  80. package/dist/schemas.html +10 -6
  81. package/dist/styles.css +39 -4
  82. package/package.json +1 -1
  83. package/plugins-dist/latex-runtime.js +140 -0
  84. package/plugins-dist/latex.js +178 -0
  85. package/plugins-dist/slides-charts-lite-runtime.js +179 -0
  86. package/plugins-dist/slides-charts-lite.js +198 -0
  87. package/registry/plugins.yaml +25 -0
  88. package/schemas/v0/site.schema.json +56 -0
  89. package/scripts/build.ts +191 -69
  90. package/scripts/bundle.ts +118 -10
  91. package/scripts/dev.ts +245 -166
  92. package/src/__tests__/brief.test.ts +151 -0
  93. package/src/__tests__/build.test.ts +169 -1
  94. package/src/__tests__/bundle.test.ts +134 -0
  95. package/src/__tests__/init.test.ts +51 -2
  96. package/src/__tests__/latex-runtime.bun.test.ts +35 -0
  97. package/src/__tests__/shared.test.ts +598 -1
  98. package/src/__tests__/slides-charts-lite-runtime.bun.test.ts +45 -0
  99. package/src/cli.ts +11 -4
  100. package/src/commands/init.ts +1 -1
  101. package/src/shared.ts +725 -18
  102. package/src/site/styles.css +39 -4
  103. package/src/site/template.html +5 -2
  104. package/src/templates/brief.ts +486 -0
  105. package/src/templates/deck.ts +59 -0
  106. package/src/templates/driver.ts +46 -13
  107. package/src/templates/handbook.ts +32 -0
  108. 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,14 +35,20 @@ import {
33
35
  envString,
34
36
  // Formatting
35
37
  escapeHtml,
38
+ filterByProfile,
36
39
  filterUnknownSlidesVisualsTypeDiagnostics,
37
40
  // YAML/Config parsing
41
+ loadDataBindings,
38
42
  loadSiteConfig,
43
+ mergeFrontmatterWithBody,
39
44
  // Markdown utilities
45
+ pagePathForData,
40
46
  parseFrontmatter,
41
47
  parseYaml,
48
+ resolveBindings,
42
49
  resolveSiteVersion,
43
50
  resolveStylesPath,
51
+ runPrebuildHooks,
44
52
  type SiteConfig,
45
53
  slugify,
46
54
  validatePath,
@@ -55,6 +63,41 @@ const DEFAULT_NAME = "bundle.html";
55
63
  let ROOT = process.cwd();
56
64
  let OUT_DIR = DEFAULT_OUT;
57
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
+ }
58
101
 
59
102
  // ---------------------------------------------------------------------------
60
103
  // CLI argument parsing
@@ -65,6 +108,7 @@ interface ParsedArgs {
65
108
  out?: string;
66
109
  name?: string;
67
110
  raw?: boolean;
111
+ profile?: string;
68
112
  }
69
113
 
70
114
  function parseArgs(argv: string[]): ParsedArgs {
@@ -79,6 +123,9 @@ function parseArgs(argv: string[]): ParsedArgs {
79
123
  } else if ((arg === "--name" || arg === "-n") && next && !next.startsWith("-")) {
80
124
  result.name = next;
81
125
  i++;
126
+ } else if (arg === "--profile" && next && !next.startsWith("-")) {
127
+ result.profile = next;
128
+ i++;
82
129
  } else if (arg === "--raw") {
83
130
  result.raw = true;
84
131
  } else if (arg === "--no-raw") {
@@ -95,6 +142,7 @@ function getConfig(): {
95
142
  out: string;
96
143
  name: string;
97
144
  raw: boolean;
145
+ profile?: string;
98
146
  } {
99
147
  const args = parseArgs(process.argv.slice(2));
100
148
  const legacyOut = envString("KITFLY_BUILD_OUT", DEFAULT_OUT);
@@ -106,6 +154,7 @@ function getConfig(): {
106
154
  out,
107
155
  name: args.name ?? envString("KITFLY_BUNDLE_NAME", DEFAULT_NAME),
108
156
  raw,
157
+ profile: args.profile ?? process.env.KITFLY_PROFILE,
109
158
  };
110
159
  }
111
160
 
@@ -306,7 +355,9 @@ function buildBundleNav(files: ContentFile[], config: SiteConfig): string {
306
355
  }
307
356
 
308
357
  async function buildSlidesBundleContent(files: ContentFile[], config: SiteConfig): Promise<string> {
309
- const slides = await collectSlides(files);
358
+ const slides = await collectSlides(files, {
359
+ markdownTransform: (raw, file) => applyDataBindingsForSlides(raw, file.path, config),
360
+ });
310
361
  let validateFences = false;
311
362
  try {
312
363
  const raw = await readFile(join(ROOT, "kitfly.plugins.yaml"), "utf-8");
@@ -376,18 +427,26 @@ function buildBundleSidebarHeader(
376
427
  config: SiteConfig,
377
428
  version: string | undefined,
378
429
  brandLogo: string,
430
+ brandLogoDark?: string,
379
431
  ): string {
380
432
  const brandTarget = config.brand.external ? ' target="_blank" rel="noopener"' : "";
381
433
  const logoClass = config.brand.logoType === "wordmark" ? "logo-wordmark" : "logo-icon";
382
434
  const productHref = config.home ? "#home" : "#";
383
435
  const versionLabel = version ? `v${version}` : "unversioned";
384
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
+ });
385
444
 
386
445
  return `
387
446
  <div class="sidebar-header">
388
447
  <div class="logo ${logoClass}">
389
448
  <a href="${config.brand.url}" class="logo-icon" data-initial="${brandInitial}"${brandTarget}>
390
- <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}
391
450
  </a>
392
451
  <span class="logo-text">
393
452
  <a href="${config.brand.url}" class="brand"${brandTarget}>${config.brand.name}</a>
@@ -427,6 +486,18 @@ async function inlineBrandAsset(assetPath: string): Promise<string> {
427
486
  /* continue */
428
487
  }
429
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
+ }
430
501
  return assetPath;
431
502
  }
432
503
 
@@ -466,12 +537,26 @@ async function bundle() {
466
537
 
467
538
  const config = await loadSiteConfig(ROOT, "Documentation");
468
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
+ }
469
550
 
470
551
  const theme = await loadTheme(ROOT);
471
552
  console.log(` ✓ Loaded theme: "${theme.name || "default"}"`);
472
553
  const prismUrls = getPrismUrls(theme);
473
554
 
474
- const files = await collectFiles(ROOT, config);
555
+ const files = await filterByProfile(
556
+ await collectFiles(ROOT, config),
557
+ ACTIVE_PROFILE,
558
+ config.profiles,
559
+ );
475
560
  if (files.length === 0) {
476
561
  console.error("No content files found. Cannot create bundle.");
477
562
  process.exit(1);
@@ -514,7 +599,7 @@ async function bundle() {
514
599
  section: slide.section,
515
600
  });
516
601
  }
517
- navHtml = buildSlideNav(slides, config, "slide-1");
602
+ navHtml = buildSlideNavHierarchical(slides, config, "slide-1");
518
603
  contentHtml = await buildSlidesBundleContent(files, config);
519
604
  } else {
520
605
  // Build navigation and content sections
@@ -527,7 +612,11 @@ async function bundle() {
527
612
  try {
528
613
  await stat(homePath);
529
614
  const content = await readFile(homePath, "utf-8");
530
- const { frontmatter, body } = parseFrontmatter(content);
615
+ const { frontmatter, body } = await applyDataBindingsToMarkdown(
616
+ content,
617
+ homePath,
618
+ config,
619
+ );
531
620
  const title = (frontmatter.title as string) || "Home";
532
621
  let htmlContent = marked.parse(body) as string;
533
622
  htmlContent = await inlineLocalImages(htmlContent, config);
@@ -558,7 +647,7 @@ async function bundle() {
558
647
  }
559
648
  htmlContent = `<pre><code class="language-json">${escapeHtml(prettyJson)}</code></pre>`;
560
649
  } else {
561
- const { frontmatter, body } = parseFrontmatter(content);
650
+ const { frontmatter, body } = await applyDataBindingsToMarkdown(content, file.path, config);
562
651
  if (frontmatter.title) {
563
652
  title = frontmatter.title as string;
564
653
  }
@@ -617,7 +706,19 @@ async function bundle() {
617
706
 
618
707
  // Inline brand assets for self-contained bundle
619
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;
620
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;
621
722
 
622
723
  // Build the complete HTML document
623
724
  const html = `<!DOCTYPE html>
@@ -672,7 +773,7 @@ ${assets.prismCssDark}
672
773
  <body class="${config.mode === "slides" ? "mode-slides" : "mode-docs"}">
673
774
  <div class="layout">
674
775
  <nav class="sidebar">
675
- ${buildBundleSidebarHeader(config, version, brandLogo)}
776
+ ${buildBundleSidebarHeader(config, version, brandLogo, brandLogoDark)}
676
777
  <div class="sidebar-nav">
677
778
  ${navHtml}
678
779
  </div>
@@ -683,7 +784,7 @@ ${buildBundleSidebarHeader(config, version, brandLogo)}
683
784
  </article>
684
785
  </main>
685
786
  </div>
686
- ${buildBundleFooter(version, config)}
787
+ ${buildBundleFooter(version, config, footerLogo, footerLogoDark)}
687
788
  <script>
688
789
  ${assets.prismCore}
689
790
  </script>
@@ -749,6 +850,9 @@ ${assets.mermaid}
749
850
  if (window.reinitMermaid) {
750
851
  window.reinitMermaid();
751
852
  }
853
+ if (window.reinitCharts) {
854
+ window.reinitCharts();
855
+ }
752
856
  }
753
857
 
754
858
  // Slides mode hash routing
@@ -864,7 +968,7 @@ ${JSON.stringify(
864
968
  </html>`;
865
969
 
866
970
  // Write the bundle
867
- const outDir = join(ROOT, OUT_DIR);
971
+ const outDir = resolve(ROOT, normalizeMsysPath(OUT_DIR));
868
972
  await mkdir(outDir, { recursive: true });
869
973
  const bundlePath = join(outDir, BUNDLE_NAME);
870
974
  await writeFile(bundlePath, html);
@@ -881,6 +985,7 @@ export interface BundleOptions {
881
985
  out?: string;
882
986
  name?: string;
883
987
  raw?: boolean; // Include raw markdown in bundle (default: true)
988
+ profile?: string;
884
989
  }
885
990
 
886
991
  let INCLUDE_RAW = true;
@@ -910,6 +1015,7 @@ export async function bundleSite(options: BundleOptions = {}) {
910
1015
  if (options.raw === false) {
911
1016
  INCLUDE_RAW = false;
912
1017
  }
1018
+ ACTIVE_PROFILE = options.profile;
913
1019
  await bundle();
914
1020
  }
915
1021
 
@@ -922,6 +1028,7 @@ Usage: bun run bundle [folder] [options]
922
1028
  Options:
923
1029
  -o, --out <dir> Output directory [env: KITFLY_BUNDLE_OUT] [default: ${DEFAULT_OUT}]
924
1030
  -n, --name <file> Bundle filename [env: KITFLY_BUNDLE_NAME] [default: ${DEFAULT_NAME}]
1031
+ --profile <name> Active content profile [env: KITFLY_PROFILE]
925
1032
  --raw Include raw markdown in bundle [env: KITFLY_BUNDLE_RAW] [default: true]
926
1033
  --no-raw Don't include raw markdown
927
1034
  --help Show this help message
@@ -943,5 +1050,6 @@ Examples:
943
1050
  out: cfg.out,
944
1051
  name: cfg.name,
945
1052
  raw: cfg.raw,
1053
+ profile: cfg.profile,
946
1054
  }).catch(console.error);
947
1055
  }