kitfly 0.1.2

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 (62) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/LICENSE +21 -0
  3. package/README.md +136 -0
  4. package/VERSION +1 -0
  5. package/package.json +63 -0
  6. package/schemas/README.md +32 -0
  7. package/schemas/site.schema.json +5 -0
  8. package/schemas/theme.schema.json +5 -0
  9. package/schemas/v0/site.schema.json +172 -0
  10. package/schemas/v0/theme.schema.json +210 -0
  11. package/scripts/build-all.ts +121 -0
  12. package/scripts/build.ts +601 -0
  13. package/scripts/bundle.ts +781 -0
  14. package/scripts/dev.ts +777 -0
  15. package/scripts/generate-checksums.sh +78 -0
  16. package/scripts/release/export-release-key.sh +28 -0
  17. package/scripts/release/release-guard-tag-version.sh +79 -0
  18. package/scripts/release/sign-release-assets.sh +123 -0
  19. package/scripts/release/upload-release-assets.sh +76 -0
  20. package/scripts/release/upload-release-provenance.sh +52 -0
  21. package/scripts/release/verify-public-key.sh +48 -0
  22. package/scripts/release/verify-signatures.sh +117 -0
  23. package/scripts/version-sync.ts +82 -0
  24. package/src/__tests__/build.test.ts +240 -0
  25. package/src/__tests__/bundle.test.ts +786 -0
  26. package/src/__tests__/cli.test.ts +706 -0
  27. package/src/__tests__/crucible.test.ts +1043 -0
  28. package/src/__tests__/engine.test.ts +157 -0
  29. package/src/__tests__/init.test.ts +450 -0
  30. package/src/__tests__/pipeline.test.ts +1087 -0
  31. package/src/__tests__/productbook.test.ts +1206 -0
  32. package/src/__tests__/runbook.test.ts +974 -0
  33. package/src/__tests__/server-registry.test.ts +1251 -0
  34. package/src/__tests__/servicebook.test.ts +1248 -0
  35. package/src/__tests__/shared.test.ts +2005 -0
  36. package/src/__tests__/styles.test.ts +14 -0
  37. package/src/__tests__/theme-schema.test.ts +47 -0
  38. package/src/__tests__/theme.test.ts +554 -0
  39. package/src/cli.ts +582 -0
  40. package/src/commands/init.ts +92 -0
  41. package/src/commands/update.ts +444 -0
  42. package/src/engine.ts +20 -0
  43. package/src/logger.ts +15 -0
  44. package/src/migrations/0000_schema_versioning.ts +67 -0
  45. package/src/migrations/0001_server_port.ts +52 -0
  46. package/src/migrations/0002_brand_logo.ts +49 -0
  47. package/src/migrations/index.ts +26 -0
  48. package/src/migrations/schema.ts +24 -0
  49. package/src/server-registry.ts +405 -0
  50. package/src/shared.ts +1239 -0
  51. package/src/site/styles.css +931 -0
  52. package/src/site/template.html +193 -0
  53. package/src/templates/crucible.ts +1163 -0
  54. package/src/templates/driver.ts +876 -0
  55. package/src/templates/handbook.ts +339 -0
  56. package/src/templates/minimal.ts +139 -0
  57. package/src/templates/pipeline.ts +966 -0
  58. package/src/templates/productbook.ts +1032 -0
  59. package/src/templates/runbook.ts +829 -0
  60. package/src/templates/schema.ts +119 -0
  61. package/src/templates/servicebook.ts +1242 -0
  62. package/src/theme.ts +245 -0
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Integration tests for the static site builder (scripts/build.ts)
3
+ *
4
+ * These tests exercise the build() function end-to-end:
5
+ * create a temp site directory -> run build() -> verify output files
6
+ */
7
+
8
+ import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises";
9
+ import { tmpdir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { afterEach, describe, expect, it } from "vitest";
12
+ import { build } from "../../scripts/build.ts";
13
+ import { exists } from "../shared.ts";
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Helpers
17
+ // ---------------------------------------------------------------------------
18
+
19
+ /** Create a temp directory that will be cleaned up after each test. */
20
+ const tempDirs: string[] = [];
21
+
22
+ async function makeTempDir(): Promise<string> {
23
+ const dir = await mkdtemp(join(tmpdir(), "kitfly-build-test-"));
24
+ tempDirs.push(dir);
25
+ return dir;
26
+ }
27
+
28
+ /** Write a minimal site.yaml into the given directory. */
29
+ async function writeSiteYaml(dir: string, extra: Record<string, unknown> = {}): Promise<void> {
30
+ const brand = extra.brand ?? " name: Test\n url: /";
31
+ const sections = extra.sections ?? " - name: Docs\n path: docs";
32
+ const title = extra.title ?? "Test Site";
33
+ const version = extra.version ? `version: ${extra.version}\n` : "";
34
+ const home = extra.home ? `home: ${extra.home}\n` : "";
35
+ const yaml = `title: ${title}\n${version}brand:\n${brand}\n${home}sections:\n${sections}\n`;
36
+ await writeFile(join(dir, "site.yaml"), yaml);
37
+ }
38
+
39
+ /** Write a markdown file, creating parent directories as needed. */
40
+ async function writeMd(dir: string, relPath: string, content: string): Promise<void> {
41
+ const fullPath = join(dir, relPath);
42
+ await mkdir(join(fullPath, ".."), { recursive: true });
43
+ await writeFile(fullPath, content);
44
+ }
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Cleanup
48
+ // ---------------------------------------------------------------------------
49
+
50
+ afterEach(async () => {
51
+ for (const d of tempDirs) {
52
+ await rm(d, { recursive: true, force: true }).catch(() => {});
53
+ }
54
+ tempDirs.length = 0;
55
+ });
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // Tests
59
+ // ---------------------------------------------------------------------------
60
+
61
+ describe("build", () => {
62
+ it("produces index.html with rendered markdown content", async () => {
63
+ const siteDir = await makeTempDir();
64
+ const outDir = "out";
65
+ await writeSiteYaml(siteDir);
66
+ await writeMd(siteDir, "docs/hello.md", "# Hello\n\nWorld");
67
+
68
+ await build({ folder: siteDir, out: outDir });
69
+
70
+ const indexPath = join(siteDir, outDir, "index.html");
71
+ expect(await exists(indexPath)).toBe(true);
72
+
73
+ const html = await readFile(indexPath, "utf-8");
74
+ // The first file is used as index; its content should include the rendered markdown
75
+ expect(html).toContain("<h1");
76
+ expect(html).toContain("Hello");
77
+ expect(html).toContain("<p>World</p>");
78
+ // Should contain site title somewhere in the page
79
+ expect(html).toContain("Test Site");
80
+ });
81
+
82
+ it("creates correct directory structure for multi-section site", async () => {
83
+ const siteDir = await makeTempDir();
84
+ const outDir = "out";
85
+ await writeSiteYaml(siteDir, {
86
+ sections: " - name: Guides\n path: guides\n - name: API\n path: api",
87
+ });
88
+ await writeMd(siteDir, "guides/intro.md", "# Intro\n\nGuide content");
89
+ await writeMd(siteDir, "api/ref.md", "# Reference\n\nAPI content");
90
+
91
+ await build({ folder: siteDir, out: outDir });
92
+
93
+ const dist = join(siteDir, outDir);
94
+
95
+ // Each section's file should produce a .html under its path
96
+ expect(await exists(join(dist, "guides", "intro.html"))).toBe(true);
97
+ expect(await exists(join(dist, "api", "ref.html"))).toBe(true);
98
+
99
+ // Verify content ended up in the right files
100
+ const guidesHtml = await readFile(join(dist, "guides", "intro.html"), "utf-8");
101
+ expect(guidesHtml).toContain("Guide content");
102
+
103
+ const apiHtml = await readFile(join(dist, "api", "ref.html"), "utf-8");
104
+ expect(apiHtml).toContain("API content");
105
+ });
106
+
107
+ it("copies styles.css to output", async () => {
108
+ const siteDir = await makeTempDir();
109
+ const outDir = "out";
110
+ await writeSiteYaml(siteDir);
111
+ await writeMd(siteDir, "docs/page.md", "# Page");
112
+
113
+ await build({ folder: siteDir, out: outDir });
114
+
115
+ const cssPath = join(siteDir, outDir, "styles.css");
116
+ expect(await exists(cssPath)).toBe(true);
117
+
118
+ const css = await readFile(cssPath, "utf-8");
119
+ // The engine styles.css should be non-trivial
120
+ expect(css.length).toBeGreaterThan(100);
121
+ });
122
+
123
+ it("includes nav links to all pages in generated HTML", async () => {
124
+ const siteDir = await makeTempDir();
125
+ const outDir = "out";
126
+ await writeSiteYaml(siteDir);
127
+ await writeMd(siteDir, "docs/alpha.md", "# Alpha");
128
+ await writeMd(siteDir, "docs/beta.md", "# Beta");
129
+
130
+ await build({ folder: siteDir, out: outDir });
131
+
132
+ // Check the index page has nav links to both files
133
+ const indexHtml = await readFile(join(siteDir, outDir, "index.html"), "utf-8");
134
+ expect(indexHtml).toContain("alpha.html");
135
+ expect(indexHtml).toContain("beta.html");
136
+ });
137
+
138
+ it("produces getting-started page when no content files exist", async () => {
139
+ const siteDir = await makeTempDir();
140
+ const outDir = "out";
141
+ // Point section at an empty directory
142
+ await mkdir(join(siteDir, "docs"), { recursive: true });
143
+ await writeSiteYaml(siteDir);
144
+
145
+ await build({ folder: siteDir, out: outDir });
146
+
147
+ const indexPath = join(siteDir, outDir, "index.html");
148
+ expect(await exists(indexPath)).toBe(true);
149
+
150
+ const html = await readFile(indexPath, "utf-8");
151
+ expect(html).toContain("Getting Started");
152
+ });
153
+
154
+ it("respects custom output directory path", async () => {
155
+ const siteDir = await makeTempDir();
156
+ const customOut = "my-custom-output";
157
+ await writeSiteYaml(siteDir);
158
+ await writeMd(siteDir, "docs/page.md", "# Page");
159
+
160
+ await build({ folder: siteDir, out: customOut });
161
+
162
+ // Output should be at siteDir/my-custom-output, NOT siteDir/dist
163
+ expect(await exists(join(siteDir, customOut, "index.html"))).toBe(true);
164
+ expect(await exists(join(siteDir, "dist", "index.html"))).toBe(false);
165
+ });
166
+
167
+ it("generates provenance.json in output", async () => {
168
+ const siteDir = await makeTempDir();
169
+ const outDir = "out";
170
+ await writeSiteYaml(siteDir);
171
+ await writeMd(siteDir, "docs/page.md", "# Page");
172
+
173
+ await build({ folder: siteDir, out: outDir });
174
+
175
+ const provPath = join(siteDir, outDir, "provenance.json");
176
+ expect(await exists(provPath)).toBe(true);
177
+
178
+ const prov = JSON.parse(await readFile(provPath, "utf-8"));
179
+ // version is omitted when no site.yaml version or git tag exists
180
+ expect(prov).toHaveProperty("buildDate");
181
+ expect(prov).toHaveProperty("gitCommit");
182
+ expect(prov).toHaveProperty("gitBranch");
183
+ });
184
+
185
+ it("uses configured site version in rendered sidebar and provenance", async () => {
186
+ const siteDir = await makeTempDir();
187
+ const outDir = "out";
188
+ await writeSiteYaml(siteDir, { version: '"2.4.1"' });
189
+ await writeMd(siteDir, "docs/page.md", "# Page");
190
+
191
+ await build({ folder: siteDir, out: outDir });
192
+
193
+ const html = await readFile(join(siteDir, outDir, "index.html"), "utf-8");
194
+ expect(html).toContain("v2.4.1");
195
+
196
+ const prov = JSON.parse(await readFile(join(siteDir, outDir, "provenance.json"), "utf-8"));
197
+ expect(prov.version).toBe("2.4.1");
198
+ });
199
+
200
+ it("shows unversioned in sidebar when no site version or tag is available", async () => {
201
+ const siteDir = await makeTempDir();
202
+ const outDir = "out";
203
+ await writeSiteYaml(siteDir);
204
+ await writeMd(siteDir, "docs/page.md", "# Page");
205
+
206
+ await build({ folder: siteDir, out: outDir });
207
+
208
+ const html = await readFile(join(siteDir, outDir, "index.html"), "utf-8");
209
+ expect(html).toContain("unversioned");
210
+ });
211
+
212
+ it("generates AI accessibility files (content-index.json, llms.txt, _raw/)", async () => {
213
+ const siteDir = await makeTempDir();
214
+ const outDir = "out";
215
+ await writeSiteYaml(siteDir);
216
+ await writeMd(siteDir, "docs/page.md", "---\ntitle: My Page\n---\n\n# My Page\n\nBody text");
217
+
218
+ await build({ folder: siteDir, out: outDir, raw: true });
219
+
220
+ const dist = join(siteDir, outDir);
221
+
222
+ // content-index.json
223
+ const ciPath = join(dist, "content-index.json");
224
+ expect(await exists(ciPath)).toBe(true);
225
+ const ci = JSON.parse(await readFile(ciPath, "utf-8"));
226
+ expect(ci.title).toBe("Test Site");
227
+ expect(ci.pages).toHaveLength(1);
228
+ expect(ci.pages[0].title).toBe("My Page");
229
+
230
+ // llms.txt
231
+ const llmsPath = join(dist, "llms.txt");
232
+ expect(await exists(llmsPath)).toBe(true);
233
+ const llms = await readFile(llmsPath, "utf-8");
234
+ expect(llms).toContain("Test Site");
235
+
236
+ // _raw/ directory with markdown copy
237
+ const rawEntries = await readdir(join(dist, "_raw"), { recursive: true });
238
+ expect(rawEntries.length).toBeGreaterThan(0);
239
+ });
240
+ });