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.
- package/CHANGELOG.md +60 -0
- package/LICENSE +21 -0
- package/README.md +136 -0
- package/VERSION +1 -0
- package/package.json +63 -0
- package/schemas/README.md +32 -0
- package/schemas/site.schema.json +5 -0
- package/schemas/theme.schema.json +5 -0
- package/schemas/v0/site.schema.json +172 -0
- package/schemas/v0/theme.schema.json +210 -0
- package/scripts/build-all.ts +121 -0
- package/scripts/build.ts +601 -0
- package/scripts/bundle.ts +781 -0
- package/scripts/dev.ts +777 -0
- package/scripts/generate-checksums.sh +78 -0
- package/scripts/release/export-release-key.sh +28 -0
- package/scripts/release/release-guard-tag-version.sh +79 -0
- package/scripts/release/sign-release-assets.sh +123 -0
- package/scripts/release/upload-release-assets.sh +76 -0
- package/scripts/release/upload-release-provenance.sh +52 -0
- package/scripts/release/verify-public-key.sh +48 -0
- package/scripts/release/verify-signatures.sh +117 -0
- package/scripts/version-sync.ts +82 -0
- package/src/__tests__/build.test.ts +240 -0
- package/src/__tests__/bundle.test.ts +786 -0
- package/src/__tests__/cli.test.ts +706 -0
- package/src/__tests__/crucible.test.ts +1043 -0
- package/src/__tests__/engine.test.ts +157 -0
- package/src/__tests__/init.test.ts +450 -0
- package/src/__tests__/pipeline.test.ts +1087 -0
- package/src/__tests__/productbook.test.ts +1206 -0
- package/src/__tests__/runbook.test.ts +974 -0
- package/src/__tests__/server-registry.test.ts +1251 -0
- package/src/__tests__/servicebook.test.ts +1248 -0
- package/src/__tests__/shared.test.ts +2005 -0
- package/src/__tests__/styles.test.ts +14 -0
- package/src/__tests__/theme-schema.test.ts +47 -0
- package/src/__tests__/theme.test.ts +554 -0
- package/src/cli.ts +582 -0
- package/src/commands/init.ts +92 -0
- package/src/commands/update.ts +444 -0
- package/src/engine.ts +20 -0
- package/src/logger.ts +15 -0
- package/src/migrations/0000_schema_versioning.ts +67 -0
- package/src/migrations/0001_server_port.ts +52 -0
- package/src/migrations/0002_brand_logo.ts +49 -0
- package/src/migrations/index.ts +26 -0
- package/src/migrations/schema.ts +24 -0
- package/src/server-registry.ts +405 -0
- package/src/shared.ts +1239 -0
- package/src/site/styles.css +931 -0
- package/src/site/template.html +193 -0
- package/src/templates/crucible.ts +1163 -0
- package/src/templates/driver.ts +876 -0
- package/src/templates/handbook.ts +339 -0
- package/src/templates/minimal.ts +139 -0
- package/src/templates/pipeline.ts +966 -0
- package/src/templates/productbook.ts +1032 -0
- package/src/templates/runbook.ts +829 -0
- package/src/templates/schema.ts +119 -0
- package/src/templates/servicebook.ts +1242 -0
- 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
|
+
});
|