kitfly 0.2.1 → 0.2.4

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 (132) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/README.md +38 -21
  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/gantt-widget.md +468 -0
  9. package/dist/_raw/content/reference/glossary.md +25 -1
  10. package/dist/_raw/content/reference/key-concepts.md +30 -2
  11. package/dist/_raw/content/reference/plugins.md +170 -1
  12. package/dist/_raw/docs/decisions/ADR-0006-data-driven-content.md +350 -0
  13. package/dist/content/deployment/preflight.html +11 -8
  14. package/dist/content/deployment/recipes/aws-s3.html +11 -8
  15. package/dist/content/deployment/recipes/cloudflare-pages.html +11 -8
  16. package/dist/content/deployment/recipes/cloudflare-r2.html +11 -8
  17. package/dist/content/deployment/recipes/fly-io.html +11 -8
  18. package/dist/content/deployment/recipes/github-pages.html +11 -8
  19. package/dist/content/deployment/recipes/netlify.html +11 -8
  20. package/dist/content/deployment/recipes/vercel.html +11 -8
  21. package/dist/content/deployment/secrets-and-env-vars.html +11 -8
  22. package/dist/content/deployment.html +11 -8
  23. package/dist/content/guide/approaches.html +11 -8
  24. package/dist/content/guide/branding.html +509 -0
  25. package/dist/content/guide/data-driven-content.html +542 -0
  26. package/dist/content/guide/features.html +11 -8
  27. package/dist/content/guide/getting-started.html +11 -8
  28. package/dist/content/guide/kitfly-overview.html +11 -8
  29. package/dist/content/reference/configuration.html +136 -11
  30. package/dist/content/reference/design-catalog.html +11 -8
  31. package/dist/content/reference/environment-variables.html +51 -10
  32. package/dist/content/reference/gantt-widget.html +899 -0
  33. package/dist/content/reference/glossary.html +25 -10
  34. package/dist/content/reference/key-concepts.html +34 -11
  35. package/dist/content/reference/plugins.html +261 -10
  36. package/dist/content/reference/slides-authoring-guidelines.html +11 -8
  37. package/dist/content/reference/structure.html +11 -8
  38. package/dist/content/reference.html +11 -8
  39. package/dist/content/templates/crucible.html +11 -8
  40. package/dist/content/templates/handbook.html +11 -8
  41. package/dist/content/templates/minimal.html +11 -8
  42. package/dist/content/templates/overview.html +11 -8
  43. package/dist/content/templates/pipeline.html +11 -8
  44. package/dist/content/templates/productbook.html +11 -8
  45. package/dist/content/templates/runbook.html +11 -8
  46. package/dist/content/templates/servicebook.html +11 -8
  47. package/dist/content-index.json +37 -2
  48. package/dist/docs/decisions/ADR-0001-minimalist-site-code.html +11 -8
  49. package/dist/docs/decisions/ADR-0002-ai-accessibility.html +11 -8
  50. package/dist/docs/decisions/ADR-0003-single-file-bundle.html +11 -8
  51. package/dist/docs/decisions/ADR-0004-bun-runtime.html +11 -8
  52. package/dist/docs/decisions/ADR-0005-plugin-contract-and-distribution.html +11 -8
  53. package/dist/docs/decisions/ADR-0006-data-driven-content.html +751 -0
  54. package/dist/docs/decisions/DDR-0001-viewport-locked-layout.html +11 -8
  55. package/dist/docs/decisions/DDR-0002-theme-system.html +11 -8
  56. package/dist/docs/decisions/DDR-0003-bounded-logo-slot.html +11 -8
  57. package/dist/docs/decisions/DDR-0004-slides-rendering-model.html +11 -8
  58. package/dist/docs/decisions/DDR-0005-deterministic-layout-boundary.html +11 -8
  59. package/dist/docs/userguide/cli/build.html +11 -8
  60. package/dist/docs/userguide/cli/bundle.html +11 -8
  61. package/dist/docs/userguide/cli/dev.html +11 -8
  62. package/dist/docs/userguide/cli/init.html +11 -8
  63. package/dist/docs/userguide/cli/servers.html +11 -8
  64. package/dist/docs/userguide/cli/stop.html +11 -8
  65. package/dist/docs/userguide/cli/update.html +11 -8
  66. package/dist/docs/userguide/cli/version.html +11 -8
  67. package/dist/docs/userguide/cli.html +11 -8
  68. package/dist/docs/userguide/sharing.html +11 -8
  69. package/dist/index.html +11 -8
  70. package/dist/llms.txt +3 -3
  71. package/dist/provenance.json +4 -5
  72. package/dist/reports/license-inventory.csv +199 -0
  73. package/dist/schemas/plugin-registry.schema.html +11 -8
  74. package/dist/schemas/plugin-schemas-notes.html +11 -8
  75. package/dist/schemas/plugin.schema.html +11 -8
  76. package/dist/schemas/plugins.schema.html +11 -8
  77. package/dist/schemas/v0/common.schema.html +15 -12
  78. package/dist/schemas/v0/plugin-registry.schema.html +14 -11
  79. package/dist/schemas/v0/plugin.schema.html +14 -11
  80. package/dist/schemas/v0/plugins.schema.html +14 -11
  81. package/dist/schemas/v0/site.schema.html +68 -9
  82. package/dist/schemas/v0/theme.schema.html +22 -19
  83. package/dist/schemas.html +11 -8
  84. package/dist/styles.css +39 -4
  85. package/package.json +1 -1
  86. package/plugins-dist/latex-runtime.js +140 -0
  87. package/plugins-dist/latex.js +178 -0
  88. package/plugins-dist/planning-visuals.css +261 -0
  89. package/plugins-dist/planning-visuals.js +669 -0
  90. package/plugins-dist/slides-charts-lite-runtime.js +179 -0
  91. package/plugins-dist/slides-charts-lite.js +198 -0
  92. package/registry/plugins.yaml +40 -1
  93. package/schemas/v0/site.schema.json +56 -0
  94. package/scripts/build-all.ts +5 -0
  95. package/scripts/build.ts +264 -80
  96. package/scripts/bundle.ts +188 -17
  97. package/scripts/dev.ts +294 -171
  98. package/scripts/embed-docs.ts +119 -0
  99. package/src/__tests__/brief.test.ts +151 -0
  100. package/src/__tests__/build.test.ts +293 -1
  101. package/src/__tests__/bundle.test.ts +195 -0
  102. package/src/__tests__/docs.test.ts +117 -0
  103. package/src/__tests__/fixtures/fences/planning-visuals/invalid/bad-month-format.md +10 -0
  104. package/src/__tests__/fixtures/fences/planning-visuals/invalid/marker-format-mismatch.md +13 -0
  105. package/src/__tests__/fixtures/fences/planning-visuals/invalid/milestone-format-mismatch.md +13 -0
  106. package/src/__tests__/fixtures/fences/planning-visuals/invalid/missing-tracks.md +5 -0
  107. package/src/__tests__/fixtures/fences/planning-visuals/invalid/track-reversed.md +10 -0
  108. package/src/__tests__/fixtures/fences/planning-visuals/valid/markers-basic.md +15 -0
  109. package/src/__tests__/fixtures/fences/planning-visuals/valid/markers-no-milestones.md +13 -0
  110. package/src/__tests__/fixtures/fences/planning-visuals/valid/month-basic.md +16 -0
  111. package/src/__tests__/fixtures/fences/planning-visuals/valid/no-milestones.md +10 -0
  112. package/src/__tests__/fixtures/fences/planning-visuals/valid/week-basic.md +20 -0
  113. package/src/__tests__/init.test.ts +51 -2
  114. package/src/__tests__/latex-runtime.bun.test.ts +35 -0
  115. package/src/__tests__/planning-visuals-fence-contract.test.ts +28 -0
  116. package/src/__tests__/planning-visuals-runtime-regressions.bun.test.ts +68 -0
  117. package/src/__tests__/planning-visuals-runtime.bun.test.ts +192 -0
  118. package/src/__tests__/shared.test.ts +719 -1
  119. package/src/__tests__/slides-charts-lite-runtime.bun.test.ts +45 -0
  120. package/src/cli.ts +124 -22
  121. package/src/commands/docs.ts +71 -0
  122. package/src/commands/init.ts +1 -1
  123. package/src/generated/embedded-docs.ts +2384 -0
  124. package/src/server-registry.ts +50 -10
  125. package/src/shared.ts +1174 -43
  126. package/src/site/styles.css +39 -4
  127. package/src/site/template.html +5 -2
  128. package/src/templates/brief.ts +486 -0
  129. package/src/templates/deck.ts +59 -0
  130. package/src/templates/driver.ts +46 -13
  131. package/src/templates/handbook.ts +32 -0
  132. package/src/templates/runbook.ts +32 -0
@@ -0,0 +1,45 @@
1
+ import { expect, test } from "bun:test";
2
+
3
+ type ChartsLiteHooks = {
4
+ parseSpec: (text: string) => {
5
+ kind: string;
6
+ labels: string[];
7
+ data: number[];
8
+ title: string;
9
+ color: string;
10
+ height: number;
11
+ } | null;
12
+ };
13
+
14
+ async function loadHooks(): Promise<ChartsLiteHooks> {
15
+ // @ts-expect-error JS plugin helper file
16
+ await import("../../plugins-dist/slides-charts-lite-runtime.js");
17
+ const hooks = (globalThis as any).__kitflyChartsLiteTest as ChartsLiteHooks | undefined;
18
+ if (!hooks) throw new Error("slides-charts-lite test hooks not found on globalThis");
19
+ return hooks;
20
+ }
21
+
22
+ test("slides-charts-lite: parses valid chart block", async () => {
23
+ const hooks = await loadHooks();
24
+ const spec = hooks.parseSpec(`kind: bar\nlabels: ["Q1","Q2"]\ndata: [10, 20]\nheight: 400`);
25
+ expect(spec).toEqual({
26
+ kind: "bar",
27
+ labels: ["Q1", "Q2"],
28
+ data: [10, 20],
29
+ title: "",
30
+ color: "primary",
31
+ height: 400,
32
+ });
33
+ });
34
+
35
+ test("slides-charts-lite: rejects mismatched labels/data", async () => {
36
+ const hooks = await loadHooks();
37
+ const spec = hooks.parseSpec(`kind: line\nlabels: ["A","B"]\ndata: [1]`);
38
+ expect(spec).toBeNull();
39
+ });
40
+
41
+ test("slides-charts-lite: rejects unknown kind", async () => {
42
+ const hooks = await loadHooks();
43
+ const spec = hooks.parseSpec(`kind: scatter\nlabels: ["A"]\ndata: [1]`);
44
+ expect(spec).toBeNull();
45
+ });
package/src/cli.ts CHANGED
@@ -12,6 +12,9 @@ import { dirname, join, resolve } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { loadSiteConfig } from "./shared.ts";
14
14
 
15
+ // Exit cleanly when piped output is closed early (e.g., `kitfly docs show x | less` then quit)
16
+ process.on("SIGPIPE", () => process.exit(0));
17
+
15
18
  // Resolve paths relative to CLI location (works in binary too)
16
19
  const __dirname = dirname(fileURLToPath(import.meta.url));
17
20
  const ROOT = join(__dirname, "..");
@@ -76,23 +79,27 @@ Usage:
76
79
  kitfly servers List running dev servers
77
80
  kitfly stop <port|all> Stop dev server(s)
78
81
  kitfly logs <port> View daemon server logs
82
+ kitfly docs [list|show] Browse embedded documentation
79
83
  kitfly version Show version (use 'version extended' for details)
80
84
  kitfly help Show this help
81
85
 
82
86
  Dev options:
83
87
  --port <n> Server port [env: KITFLY_DEV_PORT] (default: 3333)
84
88
  --host <h> Server host [env: KITFLY_DEV_HOST] (default: localhost)
89
+ --profile <p> Active content profile [env: KITFLY_PROFILE]
85
90
  --daemon, -d Run in background, return immediately
86
91
  --json Output JSON (implies --daemon)
87
92
  --no-open Don't open browser
88
93
 
89
94
  Build options:
90
95
  --out <dir> Output directory [env: KITFLY_BUILD_OUT] (default: dist)
96
+ --profile <p> Active content profile [env: KITFLY_PROFILE]
91
97
  --no-raw Don't include raw markdown
92
98
 
93
99
  Bundle options:
94
100
  --out <dir> Output directory [env: KITFLY_BUNDLE_OUT] (default: bundles)
95
101
  --name <file> Bundle filename (default: bundle.html)
102
+ --profile <p> Active content profile [env: KITFLY_PROFILE]
96
103
  --no-raw Don't include raw markdown [env: KITFLY_BUNDLE_RAW]
97
104
 
98
105
  Stop options:
@@ -204,6 +211,7 @@ async function main() {
204
211
  }
205
212
 
206
213
  const host = (flags.host as string) || "localhost";
214
+ const profile = (flags.profile as string | undefined) ?? process.env.KITFLY_PROFILE;
207
215
 
208
216
  // Warn if binding to all interfaces
209
217
  if (host === "0.0.0.0" || host === "::") {
@@ -271,7 +279,7 @@ async function main() {
271
279
 
272
280
  if (daemon) {
273
281
  // Daemon mode: spawn detached process using shell redirection
274
- const { mkdir, writeFile } = await import("node:fs/promises");
282
+ const { mkdir, writeFile, open: fsOpen } = await import("node:fs/promises");
275
283
  const logsDir = join(getKitflyHome(), "logs");
276
284
  await mkdir(logsDir, { recursive: true });
277
285
 
@@ -283,18 +291,53 @@ async function main() {
283
291
 
284
292
  // Build command with shell redirection for logging
285
293
  // Pass --log-format structured so dev.ts enables structured request logging
286
- // Use nohup to prevent SIGHUP on terminal close
287
- const shellCmd = `nohup bun run "${devScript}" "${folder}" --port ${port} --host "${host}" --no-open --log-format structured > "${logPath}" 2>&1 &`;
288
-
289
- const proc = Bun.spawn(["sh", "-c", shellCmd], {
290
- cwd: process.cwd(),
291
- stdout: "ignore",
292
- stderr: "ignore",
293
- stdin: "ignore",
294
- });
295
-
296
- // Wait for shell to spawn the background process
297
- await proc.exited;
294
+ const profileArg = profile ? ` --profile "${profile}"` : "";
295
+
296
+ // Open log file as a write handle to pass as stdout/stderr for the child
297
+ const logFd = await fsOpen(logPath, "a");
298
+
299
+ let proc: ReturnType<typeof Bun.spawn>;
300
+ if (process.platform === "win32") {
301
+ // On Windows, use Bun.spawn with detached:true and stdio redirected to log file.
302
+ // nohup and sh -c are not available; Bun's detached mode achieves the same.
303
+ const args = [
304
+ "bun",
305
+ "run",
306
+ devScript,
307
+ folder,
308
+ "--port",
309
+ String(port),
310
+ "--host",
311
+ host,
312
+ ...(profile ? ["--profile", profile] : []),
313
+ "--no-open",
314
+ "--log-format",
315
+ "structured",
316
+ ];
317
+ proc = Bun.spawn(args, {
318
+ cwd: process.cwd(),
319
+ stdout: logFd.fd,
320
+ stderr: logFd.fd,
321
+ stdin: "ignore",
322
+ detached: true,
323
+ });
324
+ proc.unref();
325
+ await logFd.close();
326
+ // Give Windows a moment to spawn the child before proc.exited resolves
327
+ await new Promise((resolve) => setTimeout(resolve, 200));
328
+ } else {
329
+ // Unix: nohup via sh -c keeps the process alive after terminal close
330
+ await logFd.close();
331
+ const shellCmd = `nohup bun run "${devScript}" "${folder}" --port ${port} --host "${host}"${profileArg} --no-open --log-format structured > "${logPath}" 2>&1 &`;
332
+ proc = Bun.spawn(["sh", "-c", shellCmd], {
333
+ cwd: process.cwd(),
334
+ stdout: "ignore",
335
+ stderr: "ignore",
336
+ stdin: "ignore",
337
+ });
338
+ // Wait for shell to spawn the background process
339
+ await proc.exited;
340
+ }
298
341
 
299
342
  // Give server a moment to start
300
343
  await new Promise((resolve) => setTimeout(resolve, 1000));
@@ -348,7 +391,7 @@ async function main() {
348
391
  } else {
349
392
  // Foreground mode: run directly
350
393
  const { dev } = await import("../scripts/dev.ts");
351
- await dev({ folder, port, host, open });
394
+ await dev({ folder, port, host, open, profile });
352
395
  }
353
396
  break;
354
397
  }
@@ -357,8 +400,9 @@ async function main() {
357
400
  const folder = positional[0] || ".";
358
401
  const out = (flags.out as string) || "dist";
359
402
  const raw = flags.raw !== false; // --no-raw disables raw markdown
403
+ const profile = (flags.profile as string | undefined) ?? process.env.KITFLY_PROFILE;
360
404
  const { build } = await import("../scripts/build.ts");
361
- await build({ folder, out, raw });
405
+ await build({ folder, out, raw, profile });
362
406
  break;
363
407
  }
364
408
 
@@ -367,8 +411,9 @@ async function main() {
367
411
  const out = (flags.out as string) || "bundles";
368
412
  const name = (flags.name as string) || "bundle.html";
369
413
  const raw = flags.raw !== false; // --no-raw disables raw markdown
414
+ const profile = (flags.profile as string | undefined) ?? process.env.KITFLY_PROFILE;
370
415
  const { bundleSite } = await import("../scripts/bundle.ts");
371
- await bundleSite({ folder, out, name, raw });
416
+ await bundleSite({ folder, out, name, raw, profile });
372
417
  break;
373
418
  }
374
419
 
@@ -531,12 +576,47 @@ async function main() {
531
576
  const follow = flags.follow === true || flags.f === true;
532
577
 
533
578
  if (follow) {
534
- // tail -f equivalent using Bun.spawn
535
- const proc = Bun.spawn(["tail", "-f", logFile], {
536
- stdout: "inherit",
537
- stderr: "inherit",
538
- });
539
- await proc.exited;
579
+ if (process.platform !== "win32") {
580
+ // Unix: tail -f is available and efficient
581
+ const proc = Bun.spawn(["tail", "-f", logFile], {
582
+ stdout: "inherit",
583
+ stderr: "inherit",
584
+ });
585
+ await proc.exited;
586
+ } else {
587
+ // Windows: tail -f is not available; poll the file for new content
588
+ const { watch } = await import("node:fs");
589
+ const { open: fsOpen } = await import("node:fs/promises");
590
+ let fd: import("node:fs/promises").FileHandle;
591
+ try {
592
+ fd = await fsOpen(logFile, "r");
593
+ } catch {
594
+ console.error(`No log file found for port ${logPort}`);
595
+ console.error(` Expected: ${logFile}`);
596
+ process.exit(1);
597
+ }
598
+ // Print existing content first
599
+ const existing = await fd.readFile("utf-8");
600
+ if (existing.length > 0) process.stdout.write(existing);
601
+ let offset = Buffer.byteLength(existing, "utf-8");
602
+ // Watch for changes and stream new bytes
603
+ const watcher = watch(logFile, async () => {
604
+ const buf = Buffer.alloc(65536);
605
+ const { bytesRead } = await fd.read(buf, 0, buf.length, offset);
606
+ if (bytesRead > 0) {
607
+ offset += bytesRead;
608
+ process.stdout.write(buf.subarray(0, bytesRead));
609
+ }
610
+ });
611
+ // Keep running until Ctrl+C
612
+ await new Promise<void>((resolve) => {
613
+ process.on("SIGINT", () => {
614
+ watcher.close();
615
+ void fd.close();
616
+ resolve();
617
+ });
618
+ });
619
+ }
540
620
  } else {
541
621
  const { readFile } = await import("node:fs/promises");
542
622
  try {
@@ -555,6 +635,28 @@ async function main() {
555
635
  break;
556
636
  }
557
637
 
638
+ case "docs": {
639
+ const sub = positional[0];
640
+ const { docsList, docsShow } = await import("./commands/docs.ts");
641
+ if (!sub || sub === "list") {
642
+ docsList();
643
+ } else if (sub === "show") {
644
+ const slug = positional[1];
645
+ if (!slug) {
646
+ console.error("Error: Slug required.\n");
647
+ console.error("Usage: kitfly docs show <slug>");
648
+ console.error(" kitfly docs list");
649
+ process.exit(1);
650
+ }
651
+ docsShow(slug);
652
+ } else {
653
+ console.error(`Unknown docs subcommand: "${sub}"\n`);
654
+ console.error("Usage: kitfly docs [list|show <slug>]");
655
+ process.exit(1);
656
+ }
657
+ break;
658
+ }
659
+
558
660
  case "version":
559
661
  case "-v":
560
662
  case "--version": {
@@ -0,0 +1,71 @@
1
+ /**
2
+ * kitfly docs — browse embedded documentation from the CLI.
3
+ *
4
+ * Subcommands:
5
+ * kitfly docs [list] List available doc slugs with titles
6
+ * kitfly docs show <slug> Output raw markdown to stdout
7
+ */
8
+
9
+ import { EMBEDDED_DOCS } from "../generated/embedded-docs.ts";
10
+
11
+ // Build a Map from the generated tuple array for O(1) lookup
12
+ const docsMap = new Map<string, { title: string; content: string }>();
13
+ for (const [slug, title, content] of EMBEDDED_DOCS) {
14
+ docsMap.set(slug, { title, content });
15
+ }
16
+
17
+ export function docsList(): void {
18
+ if (docsMap.size === 0) {
19
+ console.log("No embedded documentation available.");
20
+ return;
21
+ }
22
+
23
+ const slugs = [...docsMap.keys()].sort();
24
+ const maxSlug = Math.max(...slugs.map((s) => s.length));
25
+
26
+ console.log(`Embedded documentation (${slugs.length} topics):\n`);
27
+
28
+ for (const slug of slugs) {
29
+ const entry = docsMap.get(slug);
30
+ if (entry) console.log(` ${slug.padEnd(maxSlug + 2)}${entry.title}`);
31
+ }
32
+
33
+ console.log(`\nUsage: kitfly docs show <slug>`);
34
+ }
35
+
36
+ export function docsShow(slug: string): void {
37
+ const entry = docsMap.get(slug);
38
+
39
+ if (!entry) {
40
+ const suggestions = findSimilar(slug, [...docsMap.keys()], 3);
41
+ console.error(`Not found: "${slug}"`);
42
+ if (suggestions.length > 0) {
43
+ console.error(`Did you mean: ${suggestions.join(", ")}?`);
44
+ }
45
+ console.error(`\nUse 'kitfly docs list' to see available topics.`);
46
+ process.exit(1);
47
+ }
48
+
49
+ console.log(entry.content);
50
+ }
51
+
52
+ /**
53
+ * Find similar slugs by prefix or substring match.
54
+ * Returns up to `max` results, prioritizing prefix matches.
55
+ */
56
+ export function findSimilar(query: string, slugs: string[], max: number): string[] {
57
+ const q = query.toLowerCase();
58
+ const prefixMatches: string[] = [];
59
+ const substringMatches: string[] = [];
60
+
61
+ for (const slug of slugs) {
62
+ const s = slug.toLowerCase();
63
+ if (s.startsWith(q) || q.startsWith(s)) {
64
+ prefixMatches.push(slug);
65
+ } else if (s.includes(q) || q.includes(s)) {
66
+ substringMatches.push(slug);
67
+ }
68
+ }
69
+
70
+ return [...prefixMatches, ...substringMatches].slice(0, max);
71
+ }
@@ -4,7 +4,7 @@
4
4
  * Usage:
5
5
  * kitfly init <name> [--template <type>] [--no-git]
6
6
  *
7
- * Templates: minimal, handbook (more coming)
7
+ * Templates: minimal, handbook, brief (more coming)
8
8
  */
9
9
 
10
10
  import { readdir, stat } from "node:fs/promises";