kitfly 0.1.2 → 0.2.0
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 +34 -0
- package/README.md +63 -16
- package/VERSION +1 -1
- package/dist/_raw/content/deployment/preflight.md +134 -0
- package/dist/_raw/content/deployment/recipes/aws-s3.md +128 -0
- package/dist/_raw/content/deployment/recipes/cloudflare-pages.md +73 -0
- package/dist/_raw/content/deployment/recipes/cloudflare-r2.md +156 -0
- package/dist/_raw/content/deployment/recipes/fly-io.md +57 -0
- package/dist/_raw/content/deployment/recipes/github-pages.md +112 -0
- package/dist/_raw/content/deployment/recipes/netlify.md +99 -0
- package/dist/_raw/content/deployment/recipes/vercel.md +88 -0
- package/dist/_raw/content/deployment/secrets-and-env-vars.md +75 -0
- package/dist/_raw/content/deployment.md +128 -0
- package/dist/_raw/content/guide/approaches.md +182 -0
- package/dist/_raw/content/guide/features.md +121 -0
- package/dist/_raw/content/guide/getting-started.md +112 -0
- package/dist/_raw/content/guide/kitfly-overview.md +209 -0
- package/dist/_raw/content/reference/configuration.md +259 -0
- package/dist/_raw/content/reference/design-catalog.md +167 -0
- package/dist/_raw/content/reference/environment-variables.md +66 -0
- package/dist/_raw/content/reference/glossary.md +92 -0
- package/dist/_raw/content/reference/key-concepts.md +118 -0
- package/dist/_raw/content/reference/plugins.md +220 -0
- package/dist/_raw/content/reference/structure.md +166 -0
- package/dist/_raw/content/reference.md +19 -0
- package/dist/_raw/content/templates/crucible.md +192 -0
- package/dist/_raw/content/templates/handbook.md +83 -0
- package/dist/_raw/content/templates/minimal.md +138 -0
- package/dist/_raw/content/templates/overview.md +187 -0
- package/dist/_raw/content/templates/pipeline.md +151 -0
- package/dist/_raw/content/templates/productbook.md +187 -0
- package/dist/_raw/content/templates/runbook.md +193 -0
- package/dist/_raw/content/templates/servicebook.md +163 -0
- package/dist/_raw/docs/decisions/ADR-0001-minimalist-site-code.md +118 -0
- package/dist/_raw/docs/decisions/ADR-0002-ai-accessibility.md +153 -0
- package/dist/_raw/docs/decisions/ADR-0003-single-file-bundle.md +93 -0
- package/dist/_raw/docs/decisions/ADR-0004-bun-runtime.md +98 -0
- package/dist/_raw/docs/decisions/ADR-0005-plugin-contract-and-distribution.md +110 -0
- package/dist/_raw/docs/decisions/DDR-0001-viewport-locked-layout.md +111 -0
- package/dist/_raw/docs/decisions/DDR-0002-theme-system.md +131 -0
- package/dist/_raw/docs/decisions/DDR-0003-bounded-logo-slot.md +106 -0
- package/dist/_raw/docs/decisions/DDR-0004-slides-rendering-model.md +113 -0
- package/dist/_raw/docs/decisions/DDR-0005-deterministic-layout-boundary.md +107 -0
- package/dist/_raw/docs/userguide/cli/build.md +85 -0
- package/dist/_raw/docs/userguide/cli/bundle.md +81 -0
- package/dist/_raw/docs/userguide/cli/dev.md +92 -0
- package/dist/_raw/docs/userguide/cli/init.md +116 -0
- package/dist/_raw/docs/userguide/cli/servers.md +69 -0
- package/dist/_raw/docs/userguide/cli/stop.md +76 -0
- package/dist/_raw/docs/userguide/cli/update.md +78 -0
- package/dist/_raw/docs/userguide/cli/version.md +65 -0
- package/dist/_raw/docs/userguide/cli.md +34 -0
- package/dist/_raw/docs/userguide/sharing.md +94 -0
- package/dist/_raw/schemas/plugin-schemas-notes.md +71 -0
- package/dist/_raw/schemas.md +42 -0
- package/dist/assets/brand/kitfly-favicon-32.png +0 -0
- package/dist/assets/brand/kitfly-icon-64.png +0 -0
- package/dist/assets/brand/kitfly-logo-128.png +0 -0
- package/dist/assets/brand/kitfly-logo-512.png +0 -0
- package/dist/assets/brand/kitfly-logo.svg +12132 -0
- package/dist/assets/brand/kitfly-neon-128.png +0 -0
- package/dist/assets/brand/kitfly-neon-192.png +0 -0
- package/dist/assets/brand/kitfly-neon-256.png +0 -0
- package/dist/assets/brand/kitfly-neon.png +0 -0
- package/dist/assets/brand/palette.md +75 -0
- package/dist/content/deployment/index.html +11 -0
- package/dist/content/deployment/preflight.html +418 -0
- package/dist/content/deployment/recipes/aws-s3.html +421 -0
- package/dist/content/deployment/recipes/cloudflare-pages.html +372 -0
- package/dist/content/deployment/recipes/cloudflare-r2.html +443 -0
- package/dist/content/deployment/recipes/fly-io.html +356 -0
- package/dist/content/deployment/recipes/github-pages.html +414 -0
- package/dist/content/deployment/recipes/index.html +11 -0
- package/dist/content/deployment/recipes/netlify.html +394 -0
- package/dist/content/deployment/recipes/vercel.html +382 -0
- package/dist/content/deployment/secrets-and-env-vars.html +380 -0
- package/dist/content/deployment.html +426 -0
- package/dist/content/guide/approaches.html +501 -0
- package/dist/content/guide/features.html +436 -0
- package/dist/content/guide/getting-started.html +403 -0
- package/dist/content/guide/index.html +11 -0
- package/dist/content/guide/kitfly-overview.html +544 -0
- package/dist/content/index.html +11 -0
- package/dist/content/reference/configuration.html +580 -0
- package/dist/content/reference/design-catalog.html +449 -0
- package/dist/content/reference/environment-variables.html +367 -0
- package/dist/content/reference/glossary.html +368 -0
- package/dist/content/reference/index.html +11 -0
- package/dist/content/reference/key-concepts.html +399 -0
- package/dist/content/reference/plugins.html +491 -0
- package/dist/content/reference/structure.html +463 -0
- package/dist/content/reference.html +334 -0
- package/dist/content/templates/crucible.html +546 -0
- package/dist/content/templates/handbook.html +405 -0
- package/dist/content/templates/index.html +11 -0
- package/dist/content/templates/minimal.html +447 -0
- package/dist/content/templates/overview.html +558 -0
- package/dist/content/templates/pipeline.html +494 -0
- package/dist/content/templates/productbook.html +540 -0
- package/dist/content/templates/runbook.html +543 -0
- package/dist/content/templates/servicebook.html +523 -0
- package/dist/content-index.json +540 -0
- package/dist/docs/decisions/ADR-0001-minimalist-site-code.html +491 -0
- package/dist/docs/decisions/ADR-0002-ai-accessibility.html +434 -0
- package/dist/docs/decisions/ADR-0003-single-file-bundle.html +412 -0
- package/dist/docs/decisions/ADR-0004-bun-runtime.html +409 -0
- package/dist/docs/decisions/ADR-0005-plugin-contract-and-distribution.html +402 -0
- package/dist/docs/decisions/DDR-0001-viewport-locked-layout.html +459 -0
- package/dist/docs/decisions/DDR-0002-theme-system.html +452 -0
- package/dist/docs/decisions/DDR-0003-bounded-logo-slot.html +423 -0
- package/dist/docs/decisions/DDR-0004-slides-rendering-model.html +399 -0
- package/dist/docs/decisions/DDR-0005-deterministic-layout-boundary.html +422 -0
- package/dist/docs/decisions/index.html +11 -0
- package/dist/docs/userguide/cli/build.html +408 -0
- package/dist/docs/userguide/cli/bundle.html +419 -0
- package/dist/docs/userguide/cli/dev.html +428 -0
- package/dist/docs/userguide/cli/index.html +11 -0
- package/dist/docs/userguide/cli/init.html +436 -0
- package/dist/docs/userguide/cli/servers.html +393 -0
- package/dist/docs/userguide/cli/stop.html +408 -0
- package/dist/docs/userguide/cli/update.html +406 -0
- package/dist/docs/userguide/cli/version.html +406 -0
- package/dist/docs/userguide/cli.html +386 -0
- package/dist/docs/userguide/index.html +11 -0
- package/dist/docs/userguide/sharing.html +465 -0
- package/dist/index.html +387 -0
- package/dist/llms.txt +18 -0
- package/dist/provenance.json +7 -0
- package/dist/schemas/index.html +11 -0
- package/dist/schemas/plugin-registry.schema.html +327 -0
- package/dist/schemas/plugin-schemas-notes.html +364 -0
- package/dist/schemas/plugin.schema.html +327 -0
- package/dist/schemas/plugins.schema.html +327 -0
- package/dist/schemas/v0/common.schema.html +386 -0
- package/dist/schemas/v0/index.html +11 -0
- package/dist/schemas/v0/plugin-registry.schema.html +547 -0
- package/dist/schemas/v0/plugin.schema.html +497 -0
- package/dist/schemas/v0/plugins.schema.html +406 -0
- package/dist/schemas/v0/site.schema.html +541 -0
- package/dist/schemas/v0/theme.schema.html +615 -0
- package/dist/schemas.html +351 -0
- package/dist/styles.css +1262 -0
- package/package.json +4 -2
- package/plugins-dist/callouts.css +32 -0
- package/plugins-dist/callouts.js +46 -0
- package/plugins-dist/slides-visuals.css +224 -0
- package/plugins-dist/slides-visuals.js +598 -0
- package/registry/plugins.yaml +35 -0
- package/schemas/README.md +10 -0
- package/schemas/plugin-registry.schema.json +5 -0
- package/schemas/plugin-schemas-notes.md +71 -0
- package/schemas/plugin.schema.json +5 -0
- package/schemas/plugins.schema.json +5 -0
- package/schemas/v0/common.schema.json +64 -0
- package/schemas/v0/plugin-registry.schema.json +225 -0
- package/schemas/v0/plugin.schema.json +175 -0
- package/schemas/v0/plugins.schema.json +84 -0
- package/schemas/v0/site.schema.json +56 -9
- package/schemas/v0/theme.schema.json +105 -22
- package/scripts/build.ts +155 -3
- package/scripts/bundle.ts +258 -95
- package/scripts/dev.ts +203 -1
- package/src/__tests__/build.test.ts +158 -1
- package/src/__tests__/bundle.test.ts +31 -0
- package/src/__tests__/cli.test.ts +14 -3
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/bad-list-indent.md +5 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/blank-line.md +5 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/compare-object-items.md +9 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/indented-fence.md +4 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/stat-grid-missing-fields.md +5 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/unknown-type.md +3 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/compare.md +10 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/comparison-table.md +14 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/funnel.md +7 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/kpi.md +5 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/layer-cake.md +6 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/pyramid.md +6 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/quadrant-grid.md +8 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/scorecard.md +13 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/stat-grid.md +8 -0
- package/src/__tests__/init.test.ts +35 -0
- package/src/__tests__/plugin-loader.test.ts +221 -0
- package/src/__tests__/shared.test.ts +428 -0
- package/src/__tests__/slides-visuals-fence-contract.test.ts +28 -0
- package/src/__tests__/slides-visuals-runtime-regressions.bun.test.ts +114 -0
- package/src/__tests__/styles.test.ts +35 -0
- package/src/cli.ts +9 -4
- package/src/plugin-loader.ts +245 -0
- package/src/shared.ts +614 -7
- package/src/site/styles.css +331 -0
- package/src/site/template.html +66 -5
- package/src/templates/deck.ts +186 -0
- package/src/templates/driver.ts +11 -1
- package/src/templates/minimal.ts +1 -0
|
@@ -14,9 +14,11 @@ import {
|
|
|
14
14
|
buildNavSimple,
|
|
15
15
|
buildNavStatic,
|
|
16
16
|
buildPageMeta,
|
|
17
|
+
buildSlideNav,
|
|
17
18
|
buildToc,
|
|
18
19
|
type ContentFile,
|
|
19
20
|
collectFiles,
|
|
21
|
+
collectSlides,
|
|
20
22
|
envBool,
|
|
21
23
|
envInt,
|
|
22
24
|
envString,
|
|
@@ -32,8 +34,11 @@ import {
|
|
|
32
34
|
parseValue,
|
|
33
35
|
parseYaml,
|
|
34
36
|
resolveSiteVersion,
|
|
37
|
+
rewriteRelativeAssetUrls,
|
|
35
38
|
type SiteConfig,
|
|
39
|
+
segmentSlides,
|
|
36
40
|
slugify,
|
|
41
|
+
splitSlides,
|
|
37
42
|
stripQuotes,
|
|
38
43
|
toUrlPath,
|
|
39
44
|
validatePath,
|
|
@@ -88,6 +93,266 @@ description: A test page
|
|
|
88
93
|
expect(Object.keys(frontmatter)).toHaveLength(0);
|
|
89
94
|
expect(body).toBe(content);
|
|
90
95
|
});
|
|
96
|
+
|
|
97
|
+
it("extracts frontmatter with leading whitespace (delimiter-split slides)", () => {
|
|
98
|
+
const content = `\n\n ---\n title: Slide Two\n ---\n\n# Two`;
|
|
99
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
100
|
+
expect(frontmatter.title).toBe("Slide Two");
|
|
101
|
+
expect(body.trim()).toBe("# Two");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("splitSlides", () => {
|
|
106
|
+
it("splits markdown on explicit slide delimiter", () => {
|
|
107
|
+
const input = `# One
|
|
108
|
+
|
|
109
|
+
--- slide ---
|
|
110
|
+
|
|
111
|
+
# Two`;
|
|
112
|
+
const slides = splitSlides(input);
|
|
113
|
+
expect(slides).toHaveLength(2);
|
|
114
|
+
expect(slides[0]).toContain("# One");
|
|
115
|
+
expect(slides[1]).toContain("# Two");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("does not split on plain horizontal rules", () => {
|
|
119
|
+
const input = `# One
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
Still one slide`;
|
|
124
|
+
const slides = splitSlides(input);
|
|
125
|
+
expect(slides).toHaveLength(1);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("ignores delimiter text inside fenced code blocks", () => {
|
|
129
|
+
const input = `# One
|
|
130
|
+
|
|
131
|
+
\`\`\`md
|
|
132
|
+
--- slide ---
|
|
133
|
+
\`\`\`
|
|
134
|
+
|
|
135
|
+
--- slide ---
|
|
136
|
+
|
|
137
|
+
# Two`;
|
|
138
|
+
const slides = splitSlides(input);
|
|
139
|
+
expect(slides).toHaveLength(2);
|
|
140
|
+
expect(slides[0]).toContain("```md");
|
|
141
|
+
expect(slides[0]).toContain("--- slide ---");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("does not break on 4-backtick fences containing 3-backtick lines", () => {
|
|
145
|
+
const input = `\`\`\`\`md
|
|
146
|
+
\`\`\` still code
|
|
147
|
+
--- slide ---
|
|
148
|
+
\`\`\`\`
|
|
149
|
+
--- slide ---
|
|
150
|
+
# Real slide`;
|
|
151
|
+
const slides = splitSlides(input);
|
|
152
|
+
expect(slides).toHaveLength(2);
|
|
153
|
+
expect(slides[0]).toContain("--- slide ---");
|
|
154
|
+
expect(slides[1].trim()).toBe("# Real slide");
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("segmentSlides", () => {
|
|
159
|
+
it("uses frontmatter title and class when present", () => {
|
|
160
|
+
const input = `---
|
|
161
|
+
title: Intro Slide
|
|
162
|
+
class: two-column
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
# Welcome`;
|
|
166
|
+
const segments = segmentSlides(input, "Deck");
|
|
167
|
+
expect(segments).toHaveLength(1);
|
|
168
|
+
expect(segments[0].title).toBe("Intro Slide");
|
|
169
|
+
expect(segments[0].className).toBe("two-column");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("falls back to first heading when frontmatter title is missing", () => {
|
|
173
|
+
const input = `# Architecture Overview
|
|
174
|
+
|
|
175
|
+
Body`;
|
|
176
|
+
const segments = segmentSlides(input, "Deck");
|
|
177
|
+
expect(segments[0].title).toBe("Architecture Overview");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("ignores headings inside fenced code blocks when deriving title", () => {
|
|
181
|
+
const input = `\`\`\`md
|
|
182
|
+
# Not a real heading
|
|
183
|
+
\`\`\`
|
|
184
|
+
|
|
185
|
+
# Real Heading`;
|
|
186
|
+
const segments = segmentSlides(input, "Deck");
|
|
187
|
+
expect(segments[0].title).toBe("Real Heading");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("falls back to indexed title when no frontmatter title or heading exists", () => {
|
|
191
|
+
const input = `Just text
|
|
192
|
+
--- slide ---
|
|
193
|
+
More text`;
|
|
194
|
+
const segments = segmentSlides(input, "Runbook Deck");
|
|
195
|
+
expect(segments).toHaveLength(2);
|
|
196
|
+
expect(segments[0].title).toBe("Runbook Deck (1)");
|
|
197
|
+
expect(segments[1].title).toBe("Runbook Deck (2)");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("parses frontmatter per slide segment", () => {
|
|
201
|
+
const input = `---
|
|
202
|
+
title: First
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
# One
|
|
206
|
+
--- slide ---
|
|
207
|
+
---
|
|
208
|
+
title: Second
|
|
209
|
+
class: centered
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
# Two`;
|
|
213
|
+
const segments = segmentSlides(input, "Deck");
|
|
214
|
+
expect(segments).toHaveLength(2);
|
|
215
|
+
expect(segments[0].title).toBe("First");
|
|
216
|
+
expect(segments[1].title).toBe("Second");
|
|
217
|
+
expect(segments[1].className).toBe("centered");
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("sanitizes frontmatter class to safe class tokens", () => {
|
|
221
|
+
const input = `---
|
|
222
|
+
class: centered two-column "><img src=x onerror=alert(1)>
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
# Safe classes only`;
|
|
226
|
+
const segments = segmentSlides(input, "Deck");
|
|
227
|
+
expect(segments[0].className).toBe("centered two-column");
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe("collectSlides and buildSlideNav", () => {
|
|
232
|
+
const tempDirs: string[] = [];
|
|
233
|
+
|
|
234
|
+
afterEach(async () => {
|
|
235
|
+
for (const dir of tempDirs) {
|
|
236
|
+
await rm(dir, { recursive: true, force: true });
|
|
237
|
+
}
|
|
238
|
+
tempDirs.length = 0;
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("collects segmented markdown slides and assigns sequential ids", async () => {
|
|
242
|
+
const dir = await mkdtemp(join(tmpdir(), "kitfly-slides-collect-"));
|
|
243
|
+
tempDirs.push(dir);
|
|
244
|
+
const mdPath = join(dir, "deck.md");
|
|
245
|
+
await writeFile(
|
|
246
|
+
mdPath,
|
|
247
|
+
`---
|
|
248
|
+
title: Intro
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
# Intro
|
|
252
|
+
--- slide ---
|
|
253
|
+
# Next`,
|
|
254
|
+
"utf-8",
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const files: ContentFile[] = [
|
|
258
|
+
{ path: mdPath, urlPath: "slides/deck", section: "Slides", sectionBase: "slides" },
|
|
259
|
+
];
|
|
260
|
+
|
|
261
|
+
const slides = await collectSlides(files);
|
|
262
|
+
expect(slides).toHaveLength(2);
|
|
263
|
+
expect(slides[0].id).toBe("slide-1");
|
|
264
|
+
expect(slides[1].id).toBe("slide-2");
|
|
265
|
+
expect(slides[0].title).toBe("Intro");
|
|
266
|
+
expect(slides[1].title).toBe("Next");
|
|
267
|
+
expect(slides[0].kind).toBe("markdown");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("collects yaml/json files as single non-markdown slides", async () => {
|
|
271
|
+
const dir = await mkdtemp(join(tmpdir(), "kitfly-slides-kinds-"));
|
|
272
|
+
tempDirs.push(dir);
|
|
273
|
+
const yamlPath = join(dir, "config.yaml");
|
|
274
|
+
const jsonPath = join(dir, "data.json");
|
|
275
|
+
await writeFile(yamlPath, "name: test\n", "utf-8");
|
|
276
|
+
await writeFile(jsonPath, '{"ok":true}\n', "utf-8");
|
|
277
|
+
|
|
278
|
+
const files: ContentFile[] = [
|
|
279
|
+
{ path: yamlPath, urlPath: "ref/config", section: "Reference", sectionBase: "ref" },
|
|
280
|
+
{ path: jsonPath, urlPath: "ref/data", section: "Reference", sectionBase: "ref" },
|
|
281
|
+
];
|
|
282
|
+
const slides = await collectSlides(files);
|
|
283
|
+
expect(slides).toHaveLength(2);
|
|
284
|
+
expect(slides[0].kind).toBe("yaml");
|
|
285
|
+
expect(slides[1].kind).toBe("json");
|
|
286
|
+
expect(slides[0].title).toBe("config");
|
|
287
|
+
expect(slides[1].title).toBe("data");
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("builds slide nav grouped by section using slide ids", () => {
|
|
291
|
+
const nav = buildSlideNav(
|
|
292
|
+
[
|
|
293
|
+
{
|
|
294
|
+
index: 0,
|
|
295
|
+
frontmatter: {},
|
|
296
|
+
body: "# A",
|
|
297
|
+
title: "Slide A",
|
|
298
|
+
id: "slide-1",
|
|
299
|
+
section: "Slides",
|
|
300
|
+
sourcePath: "/tmp/a.md",
|
|
301
|
+
sourceUrlPath: "slides/a",
|
|
302
|
+
kind: "markdown",
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
index: 1,
|
|
306
|
+
frontmatter: {},
|
|
307
|
+
body: "# B",
|
|
308
|
+
title: "Slide B",
|
|
309
|
+
id: "slide-2",
|
|
310
|
+
section: "Slides",
|
|
311
|
+
sourcePath: "/tmp/b.md",
|
|
312
|
+
sourceUrlPath: "slides/b",
|
|
313
|
+
kind: "markdown",
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
{
|
|
317
|
+
docroot: ".",
|
|
318
|
+
title: "Deck",
|
|
319
|
+
brand: { name: "Test", url: "/" },
|
|
320
|
+
sections: [{ name: "Slides", path: "slides" }],
|
|
321
|
+
},
|
|
322
|
+
"slide-2",
|
|
323
|
+
);
|
|
324
|
+
expect(nav).toContain('<a href="#slide-1">Slide A</a>');
|
|
325
|
+
expect(nav).toContain('<a href="#slide-2" class="active">Slide B</a>');
|
|
326
|
+
expect(nav).toContain('<span class="nav-section">Slides</span>');
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe("rewriteRelativeAssetUrls", () => {
|
|
331
|
+
it("rewrites image sources relative to the source markdown path", () => {
|
|
332
|
+
const html = '<p><img src="./img/diagram.png" alt="diagram"></p>';
|
|
333
|
+
const rewritten = rewriteRelativeAssetUrls(html, "slides/deck", "/");
|
|
334
|
+
expect(rewritten).toContain('src="/slides/img/diagram.png"');
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("preserves external and anchor refs", () => {
|
|
338
|
+
const html = '<a href="https://example.com">ext</a> <a href="#slide-2">hash</a>';
|
|
339
|
+
const rewritten = rewriteRelativeAssetUrls(html, "slides/deck", "./");
|
|
340
|
+
expect(rewritten).toContain('href="https://example.com"');
|
|
341
|
+
expect(rewritten).toContain('href="#slide-2"');
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it("keeps query/hash suffix when rewriting", () => {
|
|
345
|
+
const html = '<a href="../files/report.pdf?dl=1#v2">report</a>';
|
|
346
|
+
const rewritten = rewriteRelativeAssetUrls(html, "slides/deck", "./");
|
|
347
|
+
expect(rewritten).toContain('href="./files/report.pdf?dl=1#v2"');
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it("does not rewrite non-asset href links", () => {
|
|
351
|
+
const html = '<a href="other.md">doc</a> <a href="./other.md">doc2</a>';
|
|
352
|
+
const rewritten = rewriteRelativeAssetUrls(html, "slides/deck", "./");
|
|
353
|
+
expect(rewritten).toContain('href="other.md"');
|
|
354
|
+
expect(rewritten).toContain('href="./other.md"');
|
|
355
|
+
});
|
|
91
356
|
});
|
|
92
357
|
|
|
93
358
|
// ---------------------------------------------------------------------------
|
|
@@ -818,6 +1083,8 @@ describe("loadSiteConfig", () => {
|
|
|
818
1083
|
const result = await loadSiteConfig("/nonexistent/path", "Default Title");
|
|
819
1084
|
expect(result.docroot).toBe(".");
|
|
820
1085
|
expect(result.title).toBe("Default Title");
|
|
1086
|
+
expect(result.mode).toBe("docs");
|
|
1087
|
+
expect(result.aspect).toBe("16/9");
|
|
821
1088
|
expect(result.brand.name).toBe("Handbook");
|
|
822
1089
|
expect(result.sections).toEqual([]);
|
|
823
1090
|
});
|
|
@@ -887,6 +1154,58 @@ ${links}
|
|
|
887
1154
|
await rm(dir, { recursive: true, force: true });
|
|
888
1155
|
}
|
|
889
1156
|
});
|
|
1157
|
+
|
|
1158
|
+
it("parses slides mode and aspect from site.yaml", async () => {
|
|
1159
|
+
const dir = await mkdtemp(join(tmpdir(), "kitfly-slides-config-"));
|
|
1160
|
+
try {
|
|
1161
|
+
await writeFile(
|
|
1162
|
+
join(dir, "site.yaml"),
|
|
1163
|
+
`title: Slides
|
|
1164
|
+
mode: slides
|
|
1165
|
+
aspect: "4/3"
|
|
1166
|
+
brand:
|
|
1167
|
+
name: Test
|
|
1168
|
+
url: /
|
|
1169
|
+
sections:
|
|
1170
|
+
- name: Deck
|
|
1171
|
+
path: slides
|
|
1172
|
+
`,
|
|
1173
|
+
"utf-8",
|
|
1174
|
+
);
|
|
1175
|
+
|
|
1176
|
+
const config = await loadSiteConfig(dir);
|
|
1177
|
+
expect(config.mode).toBe("slides");
|
|
1178
|
+
expect(config.aspect).toBe("4/3");
|
|
1179
|
+
} finally {
|
|
1180
|
+
await rm(dir, { recursive: true, force: true });
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
it("falls back to default docs mode and aspect when invalid", async () => {
|
|
1185
|
+
const dir = await mkdtemp(join(tmpdir(), "kitfly-slides-defaults-"));
|
|
1186
|
+
try {
|
|
1187
|
+
await writeFile(
|
|
1188
|
+
join(dir, "site.yaml"),
|
|
1189
|
+
`title: Defaults
|
|
1190
|
+
mode: invalid
|
|
1191
|
+
aspect: "21/9"
|
|
1192
|
+
brand:
|
|
1193
|
+
name: Test
|
|
1194
|
+
url: /
|
|
1195
|
+
sections:
|
|
1196
|
+
- name: Deck
|
|
1197
|
+
path: slides
|
|
1198
|
+
`,
|
|
1199
|
+
"utf-8",
|
|
1200
|
+
);
|
|
1201
|
+
|
|
1202
|
+
const config = await loadSiteConfig(dir);
|
|
1203
|
+
expect(config.mode).toBe("docs");
|
|
1204
|
+
expect(config.aspect).toBe("16/9");
|
|
1205
|
+
} finally {
|
|
1206
|
+
await rm(dir, { recursive: true, force: true });
|
|
1207
|
+
}
|
|
1208
|
+
});
|
|
890
1209
|
});
|
|
891
1210
|
|
|
892
1211
|
// ---------------------------------------------------------------------------
|
|
@@ -1017,6 +1336,115 @@ describe("resolveSiteVersion", () => {
|
|
|
1017
1336
|
const version = await resolveSiteVersion("/nonexistent/path", "9.9.9");
|
|
1018
1337
|
expect(version).toBe("9.9.9");
|
|
1019
1338
|
});
|
|
1339
|
+
|
|
1340
|
+
it("resolves auto from VERSION first non-empty line", async () => {
|
|
1341
|
+
const dir = await mkdtemp(join(tmpdir(), "kitfly-version-auto-"));
|
|
1342
|
+
try {
|
|
1343
|
+
await writeFile(join(dir, "VERSION"), "\n \n 1.2.3 \n2.0.0\n", "utf-8");
|
|
1344
|
+
const version = await resolveSiteVersion(dir, "auto");
|
|
1345
|
+
expect(version).toBe("1.2.3");
|
|
1346
|
+
} finally {
|
|
1347
|
+
await rm(dir, { recursive: true, force: true });
|
|
1348
|
+
}
|
|
1349
|
+
});
|
|
1350
|
+
|
|
1351
|
+
it("resolves version from file path with spaces", async () => {
|
|
1352
|
+
const dir = await mkdtemp(join(tmpdir(), "kitfly-version-file-"));
|
|
1353
|
+
try {
|
|
1354
|
+
await mkdir(join(dir, "meta"), { recursive: true });
|
|
1355
|
+
await writeFile(join(dir, "meta", "site version.txt"), "2026.02.12\n", "utf-8");
|
|
1356
|
+
const version = await resolveSiteVersion(dir, "file:./meta/site version.txt");
|
|
1357
|
+
expect(version).toBe("2026.02.12");
|
|
1358
|
+
} finally {
|
|
1359
|
+
await rm(dir, { recursive: true, force: true });
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
it("rejects absolute file path and falls back", async () => {
|
|
1364
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
1365
|
+
try {
|
|
1366
|
+
const version = await resolveSiteVersion("/nonexistent/path", "file:/etc/hostname");
|
|
1367
|
+
expect(version).toBeUndefined();
|
|
1368
|
+
expect(warn).toHaveBeenCalledWith(
|
|
1369
|
+
expect.stringContaining("version file: absolute paths are not allowed"),
|
|
1370
|
+
);
|
|
1371
|
+
} finally {
|
|
1372
|
+
warn.mockRestore();
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
|
|
1376
|
+
it("rejects windows absolute file path and falls back", async () => {
|
|
1377
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
1378
|
+
try {
|
|
1379
|
+
const version = await resolveSiteVersion("/nonexistent/path", "file:C:\\temp\\VERSION");
|
|
1380
|
+
expect(version).toBeUndefined();
|
|
1381
|
+
expect(warn).toHaveBeenCalledWith(
|
|
1382
|
+
expect.stringContaining("version file: absolute paths are not allowed"),
|
|
1383
|
+
);
|
|
1384
|
+
} finally {
|
|
1385
|
+
warn.mockRestore();
|
|
1386
|
+
}
|
|
1387
|
+
});
|
|
1388
|
+
|
|
1389
|
+
it("rejects windows drive-relative file path and falls back", async () => {
|
|
1390
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
1391
|
+
try {
|
|
1392
|
+
const version = await resolveSiteVersion("/nonexistent/path", "file:C:VERSION");
|
|
1393
|
+
expect(version).toBeUndefined();
|
|
1394
|
+
expect(warn).toHaveBeenCalledWith(
|
|
1395
|
+
expect.stringContaining("version file: absolute paths are not allowed"),
|
|
1396
|
+
);
|
|
1397
|
+
} finally {
|
|
1398
|
+
warn.mockRestore();
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
|
|
1402
|
+
it("rejects path that escapes site root and falls back", async () => {
|
|
1403
|
+
const dir = await mkdtemp(join(tmpdir(), "kitfly-version-escape-"));
|
|
1404
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
1405
|
+
try {
|
|
1406
|
+
const version = await resolveSiteVersion(dir, "file:../../etc/passwd");
|
|
1407
|
+
expect(version).toBeUndefined();
|
|
1408
|
+
expect(warn).toHaveBeenCalledWith(
|
|
1409
|
+
expect.stringContaining("version file: path escapes site root"),
|
|
1410
|
+
);
|
|
1411
|
+
} finally {
|
|
1412
|
+
warn.mockRestore();
|
|
1413
|
+
await rm(dir, { recursive: true, force: true });
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
|
|
1417
|
+
it("warns for empty file path and falls back", async () => {
|
|
1418
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined);
|
|
1419
|
+
try {
|
|
1420
|
+
const version = await resolveSiteVersion("/nonexistent/path", "file:");
|
|
1421
|
+
expect(version).toBeUndefined();
|
|
1422
|
+
expect(warn).toHaveBeenCalledWith("version file: path is empty");
|
|
1423
|
+
} finally {
|
|
1424
|
+
warn.mockRestore();
|
|
1425
|
+
}
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
it("falls back when auto VERSION file is missing", async () => {
|
|
1429
|
+
const dir = await mkdtemp(join(tmpdir(), "kitfly-version-auto-missing-"));
|
|
1430
|
+
try {
|
|
1431
|
+
const version = await resolveSiteVersion(dir, "auto");
|
|
1432
|
+
expect(version).toBeUndefined();
|
|
1433
|
+
} finally {
|
|
1434
|
+
await rm(dir, { recursive: true, force: true });
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
it("falls back when auto VERSION file is empty", async () => {
|
|
1439
|
+
const dir = await mkdtemp(join(tmpdir(), "kitfly-version-auto-empty-"));
|
|
1440
|
+
try {
|
|
1441
|
+
await writeFile(join(dir, "VERSION"), "\n \n\t\n", "utf-8");
|
|
1442
|
+
const version = await resolveSiteVersion(dir, "auto");
|
|
1443
|
+
expect(version).toBeUndefined();
|
|
1444
|
+
} finally {
|
|
1445
|
+
await rm(dir, { recursive: true, force: true });
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1020
1448
|
});
|
|
1021
1449
|
|
|
1022
1450
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { validateSlidesVisualsFences } from "../shared.ts";
|
|
5
|
+
|
|
6
|
+
const FIXTURES = join(__dirname, "fixtures", "fences", "slides-visuals");
|
|
7
|
+
|
|
8
|
+
describe("slides-visuals fence contract", () => {
|
|
9
|
+
it("accepts valid fixtures", async () => {
|
|
10
|
+
const dir = join(FIXTURES, "valid");
|
|
11
|
+
const files = await readdir(dir);
|
|
12
|
+
for (const f of files) {
|
|
13
|
+
const md = await readFile(join(dir, f), "utf-8");
|
|
14
|
+
const diags = validateSlidesVisualsFences(md);
|
|
15
|
+
expect(diags, `${f} should be valid`).toHaveLength(0);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("rejects invalid fixtures", async () => {
|
|
20
|
+
const dir = join(FIXTURES, "invalid");
|
|
21
|
+
const files = await readdir(dir);
|
|
22
|
+
for (const f of files) {
|
|
23
|
+
const md = await readFile(join(dir, f), "utf-8");
|
|
24
|
+
const diags = validateSlidesVisualsFences(md);
|
|
25
|
+
expect(diags.length, `${f} should be invalid`).toBeGreaterThan(0);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
type SlidesVisualsTestHooks = {
|
|
4
|
+
parseBodyNodesWithFirstLines: (
|
|
5
|
+
firstLines: string[],
|
|
6
|
+
between: any[],
|
|
7
|
+
end: any,
|
|
8
|
+
type: string,
|
|
9
|
+
) => Record<string, unknown>;
|
|
10
|
+
rowCells: (row: unknown) => string[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
class FakeElement {
|
|
14
|
+
tagName: string;
|
|
15
|
+
textContent: string;
|
|
16
|
+
#children: FakeElement[];
|
|
17
|
+
|
|
18
|
+
constructor(tagName: string, textContent = "", children: FakeElement[] = []) {
|
|
19
|
+
this.tagName = tagName;
|
|
20
|
+
this.textContent = textContent;
|
|
21
|
+
this.#children = children;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
querySelectorAll(selector: string): FakeElement[] {
|
|
25
|
+
if (selector === ":scope > li") return this.#children;
|
|
26
|
+
if (selector === ":scope > p") return [];
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function loadHooks(): Promise<SlidesVisualsTestHooks> {
|
|
32
|
+
// Executes the plugin file and registers hooks on globalThis (in non-DOM test env).
|
|
33
|
+
// @ts-expect-error — JS plugin file, no declaration needed
|
|
34
|
+
await import("../../plugins-dist/slides-visuals.js");
|
|
35
|
+
const hooks = (globalThis as any).__kitflySlidesVisualsTest as SlidesVisualsTestHooks | undefined;
|
|
36
|
+
if (!hooks) throw new Error("slides-visuals test hooks not found on globalThis");
|
|
37
|
+
return hooks;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
test("slides-visuals: absorbed scalar marker preserves preceding item (compare)", async () => {
|
|
41
|
+
const { parseBodyNodesWithFirstLines } = await loadHooks();
|
|
42
|
+
|
|
43
|
+
const out = parseBodyNodesWithFirstLines(
|
|
44
|
+
['left-title: "Build In-House"', "left:"],
|
|
45
|
+
[],
|
|
46
|
+
new FakeElement("UL", "", [
|
|
47
|
+
new FakeElement("LI", "Ongoing maintenance burden\nright-title: Auth0 / Clerk"),
|
|
48
|
+
new FakeElement("LI", "Production-ready in 2 weeks"),
|
|
49
|
+
]),
|
|
50
|
+
"compare",
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(out["left-title"]).toBe("Build In-House");
|
|
54
|
+
expect(out["right-title"]).toBe("Auth0 / Clerk");
|
|
55
|
+
expect(out.left).toEqual(["Ongoing maintenance burden"]);
|
|
56
|
+
expect(out.right).toEqual(["Production-ready in 2 weeks"]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("slides-visuals: absorbed list marker preserves preceding item (comparison-table)", async () => {
|
|
60
|
+
const { parseBodyNodesWithFirstLines } = await loadHooks();
|
|
61
|
+
|
|
62
|
+
const out = parseBodyNodesWithFirstLines(
|
|
63
|
+
["headers:"],
|
|
64
|
+
[],
|
|
65
|
+
new FakeElement("UL", "", [
|
|
66
|
+
new FakeElement("LI", "Feature"),
|
|
67
|
+
new FakeElement("LI", "Us"),
|
|
68
|
+
new FakeElement("LI", "Competitor A"),
|
|
69
|
+
new FakeElement("LI", "Competitor B\nrows:"),
|
|
70
|
+
new FakeElement("LI", '["Real-time sync", "Yes", "Yes", "No"]'),
|
|
71
|
+
]),
|
|
72
|
+
"comparison-table",
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(out.headers).toEqual(["Feature", "Us", "Competitor A", "Competitor B"]);
|
|
76
|
+
expect(out.rows).toEqual(['["Real-time sync", "Yes", "Yes", "No"]']);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("slides-visuals: strips trailing ::: from string list items (layer-cake)", async () => {
|
|
80
|
+
const { parseBodyNodesWithFirstLines } = await loadHooks();
|
|
81
|
+
|
|
82
|
+
const out = parseBodyNodesWithFirstLines(
|
|
83
|
+
["layers:"],
|
|
84
|
+
[],
|
|
85
|
+
new FakeElement("UL", "", [new FakeElement("LI", "Infrastructure :::")]),
|
|
86
|
+
"layer-cake",
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
expect(out.layers).toEqual(["Infrastructure"]);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("slides-visuals: parses object list items for stat-grid metrics", async () => {
|
|
93
|
+
const { parseBodyNodesWithFirstLines } = await loadHooks();
|
|
94
|
+
|
|
95
|
+
const out = parseBodyNodesWithFirstLines(
|
|
96
|
+
["metrics:"],
|
|
97
|
+
[],
|
|
98
|
+
new FakeElement("UL", "", [
|
|
99
|
+
new FakeElement("LI", "label: Users\nvalue: 1.2M\ntrend: +6%"),
|
|
100
|
+
new FakeElement("LI", "label: MRR\nvalue: $240k"),
|
|
101
|
+
]),
|
|
102
|
+
"stat-grid",
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
expect(out.metrics).toEqual([
|
|
106
|
+
{ label: "Users", value: "1.2M", trend: "+6%" },
|
|
107
|
+
{ label: "MRR", value: "$240k" },
|
|
108
|
+
]);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("slides-visuals: rowCells parses JSON array strings", async () => {
|
|
112
|
+
const hooks = await loadHooks();
|
|
113
|
+
expect(hooks.rowCells('["A", "B", "C"]')).toEqual(["A", "B", "C"]);
|
|
114
|
+
});
|
|
@@ -12,3 +12,38 @@ describe("sidebar folder indicator CSS", () => {
|
|
|
12
12
|
expect(css).toContain("transform: rotate(90deg)");
|
|
13
13
|
});
|
|
14
14
|
});
|
|
15
|
+
|
|
16
|
+
describe("slide layout primitives CSS", () => {
|
|
17
|
+
it("includes core block-flow/grid/stack primitives", async () => {
|
|
18
|
+
const css = await readFile(join(process.cwd(), "src/site/styles.css"), "utf-8");
|
|
19
|
+
expect(css).toContain(".block-flow");
|
|
20
|
+
expect(css).toContain(".block-grid");
|
|
21
|
+
expect(css).toContain(".block-stack");
|
|
22
|
+
expect(css).toContain(".block-label");
|
|
23
|
+
expect(css).toContain(".block-flow:not(.vertical) .block:not(:last-child)::after");
|
|
24
|
+
expect(css).toContain(".block-flow.vertical .block:not(:last-child)::after");
|
|
25
|
+
expect(css).toContain(".block-grid.cols-3");
|
|
26
|
+
expect(css).toContain(".block-grid.cols-4");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("includes shape modifiers for block visuals", async () => {
|
|
30
|
+
const css = await readFile(join(process.cwd(), "src/site/styles.css"), "utf-8");
|
|
31
|
+
expect(css).toContain(".block.circle");
|
|
32
|
+
expect(css).toContain(".block.pill");
|
|
33
|
+
expect(css).toContain(".block.diamond");
|
|
34
|
+
expect(css).toContain(".block.chevron");
|
|
35
|
+
expect(css).toContain(".block.hexagon");
|
|
36
|
+
expect(css).toContain(".block.triangle");
|
|
37
|
+
expect(css).toContain(".block.block-arrow");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("keeps non-active layout-class slides hidden", async () => {
|
|
41
|
+
const css = await readFile(join(process.cwd(), "src/site/styles.css"), "utf-8");
|
|
42
|
+
expect(css).toContain(".slide {");
|
|
43
|
+
expect(css).toContain("display: none;");
|
|
44
|
+
expect(css).toContain(".slide.active.centered");
|
|
45
|
+
expect(css).toContain(".slide.active.two-column");
|
|
46
|
+
expect(css).not.toContain(".slide.centered {\n min-height: 100%;\n display: flex;");
|
|
47
|
+
expect(css).not.toContain(".slide.two-column {\n display: grid;");
|
|
48
|
+
});
|
|
49
|
+
});
|
package/src/cli.ts
CHANGED
|
@@ -70,7 +70,7 @@ kitfly v${VERSION} - Turn your writing into a website
|
|
|
70
70
|
Usage:
|
|
71
71
|
kitfly dev [folder] Start dev server with hot reload
|
|
72
72
|
kitfly build [folder] Build static site to dist/
|
|
73
|
-
kitfly bundle [folder] Build single-file HTML bundle
|
|
73
|
+
kitfly bundle [folder] Build single-file HTML bundle to bundles/
|
|
74
74
|
kitfly init [name] Create new project from template
|
|
75
75
|
kitfly update [version] Update standalone site code
|
|
76
76
|
kitfly servers List running dev servers
|
|
@@ -86,11 +86,15 @@ Dev options:
|
|
|
86
86
|
--json Output JSON (implies --daemon)
|
|
87
87
|
--no-open Don't open browser
|
|
88
88
|
|
|
89
|
-
Build
|
|
89
|
+
Build options:
|
|
90
90
|
--out <dir> Output directory [env: KITFLY_BUILD_OUT] (default: dist)
|
|
91
|
-
--name <file> Bundle filename (default: bundle.html)
|
|
92
91
|
--no-raw Don't include raw markdown
|
|
93
92
|
|
|
93
|
+
Bundle options:
|
|
94
|
+
--out <dir> Output directory [env: KITFLY_BUNDLE_OUT] (default: bundles)
|
|
95
|
+
--name <file> Bundle filename (default: bundle.html)
|
|
96
|
+
--no-raw Don't include raw markdown [env: KITFLY_BUNDLE_RAW]
|
|
97
|
+
|
|
94
98
|
Stop options:
|
|
95
99
|
--force Skip graceful shutdown, kill immediately
|
|
96
100
|
|
|
@@ -117,6 +121,7 @@ Examples:
|
|
|
117
121
|
kitfly logs 3340 --follow
|
|
118
122
|
kitfly logs --clean
|
|
119
123
|
kitfly build ./docs --out ./public
|
|
124
|
+
kitfly bundle ./docs --out ./bundles --name docs.html
|
|
120
125
|
kitfly init my-handbook
|
|
121
126
|
kitfly update --check
|
|
122
127
|
|
|
@@ -359,7 +364,7 @@ async function main() {
|
|
|
359
364
|
|
|
360
365
|
case "bundle": {
|
|
361
366
|
const folder = positional[0] || ".";
|
|
362
|
-
const out = (flags.out as string) || "
|
|
367
|
+
const out = (flags.out as string) || "bundles";
|
|
363
368
|
const name = (flags.name as string) || "bundle.html";
|
|
364
369
|
const raw = flags.raw !== false; // --no-raw disables raw markdown
|
|
365
370
|
const { bundleSite } = await import("../scripts/bundle.ts");
|