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.
- package/CHANGELOG.md +79 -0
- package/README.md +38 -21
- package/VERSION +1 -1
- package/dist/_raw/content/guide/branding.md +146 -0
- package/dist/_raw/content/guide/data-driven-content.md +204 -0
- package/dist/_raw/content/reference/configuration.md +145 -7
- package/dist/_raw/content/reference/environment-variables.md +26 -1
- package/dist/_raw/content/reference/gantt-widget.md +468 -0
- package/dist/_raw/content/reference/glossary.md +25 -1
- package/dist/_raw/content/reference/key-concepts.md +30 -2
- package/dist/_raw/content/reference/plugins.md +170 -1
- package/dist/_raw/docs/decisions/ADR-0006-data-driven-content.md +350 -0
- package/dist/content/deployment/preflight.html +11 -8
- package/dist/content/deployment/recipes/aws-s3.html +11 -8
- package/dist/content/deployment/recipes/cloudflare-pages.html +11 -8
- package/dist/content/deployment/recipes/cloudflare-r2.html +11 -8
- package/dist/content/deployment/recipes/fly-io.html +11 -8
- package/dist/content/deployment/recipes/github-pages.html +11 -8
- package/dist/content/deployment/recipes/netlify.html +11 -8
- package/dist/content/deployment/recipes/vercel.html +11 -8
- package/dist/content/deployment/secrets-and-env-vars.html +11 -8
- package/dist/content/deployment.html +11 -8
- package/dist/content/guide/approaches.html +11 -8
- package/dist/content/guide/branding.html +509 -0
- package/dist/content/guide/data-driven-content.html +542 -0
- package/dist/content/guide/features.html +11 -8
- package/dist/content/guide/getting-started.html +11 -8
- package/dist/content/guide/kitfly-overview.html +11 -8
- package/dist/content/reference/configuration.html +136 -11
- package/dist/content/reference/design-catalog.html +11 -8
- package/dist/content/reference/environment-variables.html +51 -10
- package/dist/content/reference/gantt-widget.html +899 -0
- package/dist/content/reference/glossary.html +25 -10
- package/dist/content/reference/key-concepts.html +34 -11
- package/dist/content/reference/plugins.html +261 -10
- package/dist/content/reference/slides-authoring-guidelines.html +11 -8
- package/dist/content/reference/structure.html +11 -8
- package/dist/content/reference.html +11 -8
- package/dist/content/templates/crucible.html +11 -8
- package/dist/content/templates/handbook.html +11 -8
- package/dist/content/templates/minimal.html +11 -8
- package/dist/content/templates/overview.html +11 -8
- package/dist/content/templates/pipeline.html +11 -8
- package/dist/content/templates/productbook.html +11 -8
- package/dist/content/templates/runbook.html +11 -8
- package/dist/content/templates/servicebook.html +11 -8
- package/dist/content-index.json +37 -2
- package/dist/docs/decisions/ADR-0001-minimalist-site-code.html +11 -8
- package/dist/docs/decisions/ADR-0002-ai-accessibility.html +11 -8
- package/dist/docs/decisions/ADR-0003-single-file-bundle.html +11 -8
- package/dist/docs/decisions/ADR-0004-bun-runtime.html +11 -8
- package/dist/docs/decisions/ADR-0005-plugin-contract-and-distribution.html +11 -8
- package/dist/docs/decisions/ADR-0006-data-driven-content.html +751 -0
- package/dist/docs/decisions/DDR-0001-viewport-locked-layout.html +11 -8
- package/dist/docs/decisions/DDR-0002-theme-system.html +11 -8
- package/dist/docs/decisions/DDR-0003-bounded-logo-slot.html +11 -8
- package/dist/docs/decisions/DDR-0004-slides-rendering-model.html +11 -8
- package/dist/docs/decisions/DDR-0005-deterministic-layout-boundary.html +11 -8
- package/dist/docs/userguide/cli/build.html +11 -8
- package/dist/docs/userguide/cli/bundle.html +11 -8
- package/dist/docs/userguide/cli/dev.html +11 -8
- package/dist/docs/userguide/cli/init.html +11 -8
- package/dist/docs/userguide/cli/servers.html +11 -8
- package/dist/docs/userguide/cli/stop.html +11 -8
- package/dist/docs/userguide/cli/update.html +11 -8
- package/dist/docs/userguide/cli/version.html +11 -8
- package/dist/docs/userguide/cli.html +11 -8
- package/dist/docs/userguide/sharing.html +11 -8
- package/dist/index.html +11 -8
- package/dist/llms.txt +3 -3
- package/dist/provenance.json +4 -5
- package/dist/reports/license-inventory.csv +199 -0
- package/dist/schemas/plugin-registry.schema.html +11 -8
- package/dist/schemas/plugin-schemas-notes.html +11 -8
- package/dist/schemas/plugin.schema.html +11 -8
- package/dist/schemas/plugins.schema.html +11 -8
- package/dist/schemas/v0/common.schema.html +15 -12
- package/dist/schemas/v0/plugin-registry.schema.html +14 -11
- package/dist/schemas/v0/plugin.schema.html +14 -11
- package/dist/schemas/v0/plugins.schema.html +14 -11
- package/dist/schemas/v0/site.schema.html +68 -9
- package/dist/schemas/v0/theme.schema.html +22 -19
- package/dist/schemas.html +11 -8
- package/dist/styles.css +39 -4
- package/package.json +1 -1
- package/plugins-dist/latex-runtime.js +140 -0
- package/plugins-dist/latex.js +178 -0
- package/plugins-dist/planning-visuals.css +261 -0
- package/plugins-dist/planning-visuals.js +669 -0
- package/plugins-dist/slides-charts-lite-runtime.js +179 -0
- package/plugins-dist/slides-charts-lite.js +198 -0
- package/registry/plugins.yaml +40 -1
- package/schemas/v0/site.schema.json +56 -0
- package/scripts/build-all.ts +5 -0
- package/scripts/build.ts +264 -80
- package/scripts/bundle.ts +188 -17
- package/scripts/dev.ts +294 -171
- package/scripts/embed-docs.ts +119 -0
- package/src/__tests__/brief.test.ts +151 -0
- package/src/__tests__/build.test.ts +293 -1
- package/src/__tests__/bundle.test.ts +195 -0
- package/src/__tests__/docs.test.ts +117 -0
- package/src/__tests__/fixtures/fences/planning-visuals/invalid/bad-month-format.md +10 -0
- package/src/__tests__/fixtures/fences/planning-visuals/invalid/marker-format-mismatch.md +13 -0
- package/src/__tests__/fixtures/fences/planning-visuals/invalid/milestone-format-mismatch.md +13 -0
- package/src/__tests__/fixtures/fences/planning-visuals/invalid/missing-tracks.md +5 -0
- package/src/__tests__/fixtures/fences/planning-visuals/invalid/track-reversed.md +10 -0
- package/src/__tests__/fixtures/fences/planning-visuals/valid/markers-basic.md +15 -0
- package/src/__tests__/fixtures/fences/planning-visuals/valid/markers-no-milestones.md +13 -0
- package/src/__tests__/fixtures/fences/planning-visuals/valid/month-basic.md +16 -0
- package/src/__tests__/fixtures/fences/planning-visuals/valid/no-milestones.md +10 -0
- package/src/__tests__/fixtures/fences/planning-visuals/valid/week-basic.md +20 -0
- package/src/__tests__/init.test.ts +51 -2
- package/src/__tests__/latex-runtime.bun.test.ts +35 -0
- package/src/__tests__/planning-visuals-fence-contract.test.ts +28 -0
- package/src/__tests__/planning-visuals-runtime-regressions.bun.test.ts +68 -0
- package/src/__tests__/planning-visuals-runtime.bun.test.ts +192 -0
- package/src/__tests__/shared.test.ts +719 -1
- package/src/__tests__/slides-charts-lite-runtime.bun.test.ts +45 -0
- package/src/cli.ts +124 -22
- package/src/commands/docs.ts +71 -0
- package/src/commands/init.ts +1 -1
- package/src/generated/embedded-docs.ts +2384 -0
- package/src/server-registry.ts +50 -10
- package/src/shared.ts +1174 -43
- package/src/site/styles.css +39 -4
- package/src/site/template.html +5 -2
- package/src/templates/brief.ts +486 -0
- package/src/templates/deck.ts +59 -0
- package/src/templates/driver.ts +46 -13
- package/src/templates/handbook.ts +32 -0
- package/src/templates/runbook.ts +32 -0
package/scripts/build.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Options:
|
|
7
7
|
* -o, --out <dir> Output directory [env: KITFLY_BUILD_OUT] [default: dist]
|
|
8
|
+
* --profile <name> Active content profile [env: KITFLY_PROFILE]
|
|
8
9
|
* --raw Include raw markdown files [env: KITFLY_BUILD_RAW] [default: true]
|
|
9
10
|
* --no-raw Don't include raw markdown files
|
|
10
11
|
* --help Show help message
|
|
@@ -20,13 +21,15 @@ import { loadPluginInjections, type PluginInjections } from "../src/plugin-loade
|
|
|
20
21
|
import {
|
|
21
22
|
buildBreadcrumbsStatic,
|
|
22
23
|
buildFooter,
|
|
24
|
+
buildLogoImgHtml,
|
|
23
25
|
buildNavStatic,
|
|
24
26
|
buildPageMeta,
|
|
25
|
-
|
|
27
|
+
buildSlideNavHierarchical,
|
|
26
28
|
buildToc,
|
|
27
29
|
type ContentFile,
|
|
28
30
|
collectFiles,
|
|
29
31
|
// Navigation/template building
|
|
32
|
+
collectPlanningVisualsContainmentWarnings,
|
|
30
33
|
collectSlides,
|
|
31
34
|
envBool,
|
|
32
35
|
// Config helpers
|
|
@@ -35,22 +38,30 @@ import {
|
|
|
35
38
|
escapeHtml,
|
|
36
39
|
// File utilities
|
|
37
40
|
exists,
|
|
41
|
+
filterByProfile,
|
|
42
|
+
filterUnknownPlanningVisualsTypeDiagnostics,
|
|
38
43
|
filterUnknownSlidesVisualsTypeDiagnostics,
|
|
39
44
|
// Provenance
|
|
40
45
|
generateProvenance,
|
|
46
|
+
loadDataBindings,
|
|
41
47
|
// YAML/Config parsing
|
|
42
48
|
loadSiteConfig,
|
|
49
|
+
mergeFrontmatterWithBody,
|
|
43
50
|
type Provenance,
|
|
51
|
+
pagePathForData,
|
|
44
52
|
// Markdown utilities
|
|
45
53
|
parseFrontmatter,
|
|
46
54
|
parseYaml,
|
|
55
|
+
resolveBindings,
|
|
47
56
|
resolveStylesPath,
|
|
48
57
|
resolveTemplatePath,
|
|
49
58
|
rewriteRelativeAssetUrls,
|
|
59
|
+
runPrebuildHooks,
|
|
50
60
|
// Types
|
|
51
61
|
type SiteConfig,
|
|
52
62
|
slugify,
|
|
53
63
|
validatePath,
|
|
64
|
+
validatePlanningVisualsFences,
|
|
54
65
|
validateSlidesVisualsFences,
|
|
55
66
|
} from "../src/shared.ts";
|
|
56
67
|
import { generateThemeCSS, getPrismUrls, loadTheme, type Theme } from "../src/theme.ts";
|
|
@@ -60,6 +71,41 @@ const DEFAULT_OUT = "dist";
|
|
|
60
71
|
|
|
61
72
|
let ROOT = process.cwd();
|
|
62
73
|
let OUT_DIR = DEFAULT_OUT;
|
|
74
|
+
let ACTIVE_PROFILE: string | undefined;
|
|
75
|
+
|
|
76
|
+
async function applyDataBindingsToMarkdown(
|
|
77
|
+
rawMarkdown: string,
|
|
78
|
+
filePath: string,
|
|
79
|
+
config: SiteConfig,
|
|
80
|
+
): Promise<{ frontmatter: Record<string, unknown>; body: string }> {
|
|
81
|
+
const parsed = parseFrontmatter(rawMarkdown);
|
|
82
|
+
const dataRef = typeof parsed.frontmatter.data === "string" ? parsed.frontmatter.data.trim() : "";
|
|
83
|
+
if (!dataRef) return parsed;
|
|
84
|
+
|
|
85
|
+
const pagePath = pagePathForData(ROOT, config.docroot, filePath);
|
|
86
|
+
const bindings = await loadDataBindings(dataRef, pagePath, ROOT, config.docroot, config.dataroot);
|
|
87
|
+
return {
|
|
88
|
+
frontmatter: parsed.frontmatter,
|
|
89
|
+
body: resolveBindings(parsed.body, bindings, pagePath),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function applyDataBindingsForSlides(
|
|
94
|
+
rawMarkdown: string,
|
|
95
|
+
filePath: string,
|
|
96
|
+
config: SiteConfig,
|
|
97
|
+
): Promise<string> {
|
|
98
|
+
const resolved = await applyDataBindingsToMarkdown(rawMarkdown, filePath, config);
|
|
99
|
+
return mergeFrontmatterWithBody(rawMarkdown, resolved.body);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function normalizeMsysPath(p: string): string {
|
|
103
|
+
// Git Bash / MSYS-style paths: /c/Users/... -> C:\Users\...
|
|
104
|
+
if (process.platform !== "win32") return p;
|
|
105
|
+
const m = p.match(/^\/([a-zA-Z])\/(.*)$/);
|
|
106
|
+
if (!m) return p;
|
|
107
|
+
return `${m[1].toUpperCase()}:\\${m[2].replaceAll("/", "\\")}`;
|
|
108
|
+
}
|
|
63
109
|
|
|
64
110
|
// ---------------------------------------------------------------------------
|
|
65
111
|
// CLI argument parsing
|
|
@@ -69,6 +115,7 @@ interface ParsedArgs {
|
|
|
69
115
|
folder?: string;
|
|
70
116
|
out?: string;
|
|
71
117
|
raw?: boolean;
|
|
118
|
+
profile?: string;
|
|
72
119
|
}
|
|
73
120
|
|
|
74
121
|
function parseArgs(argv: string[]): ParsedArgs {
|
|
@@ -80,6 +127,9 @@ function parseArgs(argv: string[]): ParsedArgs {
|
|
|
80
127
|
if ((arg === "--out" || arg === "-o") && next && !next.startsWith("-")) {
|
|
81
128
|
result.out = next;
|
|
82
129
|
i++;
|
|
130
|
+
} else if (arg === "--profile" && next && !next.startsWith("-")) {
|
|
131
|
+
result.profile = next;
|
|
132
|
+
i++;
|
|
83
133
|
} else if (arg === "--raw") {
|
|
84
134
|
result.raw = true;
|
|
85
135
|
} else if (arg === "--no-raw") {
|
|
@@ -91,12 +141,13 @@ function parseArgs(argv: string[]): ParsedArgs {
|
|
|
91
141
|
return result;
|
|
92
142
|
}
|
|
93
143
|
|
|
94
|
-
function getConfig(): { folder?: string; out: string; raw: boolean } {
|
|
144
|
+
function getConfig(): { folder?: string; out: string; raw: boolean; profile?: string } {
|
|
95
145
|
const args = parseArgs(process.argv.slice(2));
|
|
96
146
|
return {
|
|
97
147
|
folder: args.folder,
|
|
98
148
|
out: args.out ?? envString("KITFLY_BUILD_OUT", DEFAULT_OUT),
|
|
99
149
|
raw: args.raw ?? envBool("KITFLY_BUILD_RAW", true),
|
|
150
|
+
profile: args.profile ?? process.env.KITFLY_PROFILE,
|
|
100
151
|
};
|
|
101
152
|
}
|
|
102
153
|
|
|
@@ -106,6 +157,27 @@ async function resolveSiteAssetsDir(siteRoot: string): Promise<string | null> {
|
|
|
106
157
|
return null;
|
|
107
158
|
}
|
|
108
159
|
|
|
160
|
+
type FenceValidationFlags = {
|
|
161
|
+
slidesVisuals: boolean;
|
|
162
|
+
planningVisuals: boolean;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
async function getFenceValidationFlags(root: string): Promise<FenceValidationFlags> {
|
|
166
|
+
try {
|
|
167
|
+
const raw = await readFile(join(root, "kitfly.plugins.yaml"), "utf-8");
|
|
168
|
+
const parsed = parseYaml(raw) as unknown as Record<string, unknown>;
|
|
169
|
+
const enabled = Array.isArray(parsed?.plugins) ? (parsed.plugins as unknown[]) : [];
|
|
170
|
+
return {
|
|
171
|
+
slidesVisuals: enabled.some((p) => typeof p === "string" && p.startsWith("slides-visuals@")),
|
|
172
|
+
planningVisuals: enabled.some(
|
|
173
|
+
(p) => typeof p === "string" && p.startsWith("planning-visuals@"),
|
|
174
|
+
),
|
|
175
|
+
};
|
|
176
|
+
} catch {
|
|
177
|
+
return { slidesVisuals: false, planningVisuals: false };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
109
181
|
function computePathPrefix(urlKey: string): string {
|
|
110
182
|
const clean = urlKey.replace(/^\/+/, "").replace(/\.html$/, "");
|
|
111
183
|
if (!clean) return "./";
|
|
@@ -169,6 +241,7 @@ async function renderFile(
|
|
|
169
241
|
config: SiteConfig,
|
|
170
242
|
theme: Theme,
|
|
171
243
|
plugins: PluginInjections,
|
|
244
|
+
fenceValidation: FenceValidationFlags,
|
|
172
245
|
): Promise<string> {
|
|
173
246
|
const uiVersion = provenance.version ? `v${provenance.version}` : "unversioned";
|
|
174
247
|
const content = await readFile(filePath, "utf-8");
|
|
@@ -189,17 +262,33 @@ async function renderFile(
|
|
|
189
262
|
}
|
|
190
263
|
htmlContent = `<h1>${title}</h1>\n<pre><code class="language-json">${escapeHtml(prettyJson)}</code></pre>`;
|
|
191
264
|
} else {
|
|
192
|
-
const { frontmatter, body } =
|
|
265
|
+
const { frontmatter, body } = await applyDataBindingsToMarkdown(content, filePath, config);
|
|
193
266
|
if (frontmatter.title) {
|
|
194
267
|
title = frontmatter.title as string;
|
|
195
268
|
}
|
|
196
269
|
pageMeta = buildPageMeta(frontmatter);
|
|
270
|
+
if (fenceValidation.planningVisuals) {
|
|
271
|
+
const diagnostics = filterUnknownPlanningVisualsTypeDiagnostics(
|
|
272
|
+
validatePlanningVisualsFences(body),
|
|
273
|
+
);
|
|
274
|
+
if (diagnostics.length) {
|
|
275
|
+
const msg = diagnostics
|
|
276
|
+
.slice(0, 12)
|
|
277
|
+
.map((d) => ` - ${filePath}:${d.line} ${d.message}`)
|
|
278
|
+
.join("\n");
|
|
279
|
+
throw new Error(`planning-visuals fence contract violations:\n${msg}`);
|
|
280
|
+
}
|
|
281
|
+
const warnings = collectPlanningVisualsContainmentWarnings(body);
|
|
282
|
+
for (const warning of warnings.slice(0, 12)) {
|
|
283
|
+
console.warn(` ⚠ ${filePath}:${warning.line} ${warning.message}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
197
286
|
htmlContent = marked.parse(body) as string;
|
|
198
287
|
}
|
|
199
288
|
|
|
200
289
|
const pathPrefix = computePathPrefix(urlKey);
|
|
201
290
|
const nav = buildNavStatic(files, urlKey, config, pathPrefix);
|
|
202
|
-
const footer = buildFooter(provenance, config);
|
|
291
|
+
const footer = buildFooter(provenance, config, pathPrefix);
|
|
203
292
|
const breadcrumbs = buildBreadcrumbsStatic(urlKey, pathPrefix, files, config);
|
|
204
293
|
const toc = buildToc(htmlContent);
|
|
205
294
|
const brandTarget = config.brand.external ? ' target="_blank" rel="noopener"' : "";
|
|
@@ -207,32 +296,50 @@ async function renderFile(
|
|
|
207
296
|
const prismUrls = getPrismUrls(theme);
|
|
208
297
|
const logoClass = config.brand.logoType === "wordmark" ? "logo-wordmark" : "logo-icon";
|
|
209
298
|
const brandInitial = escapeHtml(config.brand.name.trim().charAt(0).toUpperCase() || "K");
|
|
299
|
+
const mobileLogoHtml = buildLogoImgHtml({
|
|
300
|
+
logo: config.brand.logo || "assets/brand/logo.png",
|
|
301
|
+
logoDark: config.brand.logoDark,
|
|
302
|
+
alt: config.brand.name,
|
|
303
|
+
className: `logo-img ${logoClass}`,
|
|
304
|
+
pathPrefix,
|
|
305
|
+
onerrorFallback: true,
|
|
306
|
+
});
|
|
307
|
+
const sidebarLogoHtml = buildLogoImgHtml({
|
|
308
|
+
logo: config.brand.logo || "assets/brand/logo.png",
|
|
309
|
+
logoDark: config.brand.logoDark,
|
|
310
|
+
alt: config.brand.name,
|
|
311
|
+
className: "logo-img",
|
|
312
|
+
pathPrefix,
|
|
313
|
+
onerrorFallback: true,
|
|
314
|
+
});
|
|
210
315
|
|
|
211
316
|
return template
|
|
212
317
|
.replace("{{BODY_CLASS}}", "mode-docs")
|
|
213
|
-
.replace(/\{\{PATH_PREFIX\}\}/g, pathPrefix)
|
|
214
|
-
.replace(/\{\{BRAND_URL\}\}/g, config.brand.url)
|
|
215
|
-
.replace(/\{\{BRAND_TARGET\}\}/g, brandTarget)
|
|
216
|
-
.replace(/\{\{BRAND_NAME\}\}/g, config.brand.name)
|
|
217
|
-
.replace(/\{\{BRAND_INITIAL\}\}/g, brandInitial)
|
|
218
|
-
.replace(
|
|
219
|
-
.replace(
|
|
220
|
-
.replace(/\{\{
|
|
221
|
-
.replace(/\{\{
|
|
222
|
-
.replace(
|
|
223
|
-
.replace(
|
|
224
|
-
.replace("{{
|
|
225
|
-
.replace("{{
|
|
226
|
-
.replace("{{
|
|
227
|
-
.replace("{{
|
|
228
|
-
.replace("{{
|
|
229
|
-
.replace("{{
|
|
230
|
-
.replace("{{
|
|
231
|
-
.replace("{{
|
|
232
|
-
.replace("{{
|
|
233
|
-
.replace("{{
|
|
234
|
-
.replace("{{
|
|
235
|
-
.replace("{{
|
|
318
|
+
.replace(/\{\{PATH_PREFIX\}\}/g, () => pathPrefix)
|
|
319
|
+
.replace(/\{\{BRAND_URL\}\}/g, () => config.brand.url)
|
|
320
|
+
.replace(/\{\{BRAND_TARGET\}\}/g, () => brandTarget)
|
|
321
|
+
.replace(/\{\{BRAND_NAME\}\}/g, () => config.brand.name)
|
|
322
|
+
.replace(/\{\{BRAND_INITIAL\}\}/g, () => brandInitial)
|
|
323
|
+
.replace("{{MOBILE_BRAND_LOGO_IMG}}", () => mobileLogoHtml)
|
|
324
|
+
.replace("{{SIDEBAR_BRAND_LOGO_IMG}}", () => sidebarLogoHtml)
|
|
325
|
+
.replace(/\{\{BRAND_LOGO\}\}/g, () => config.brand.logo || "assets/brand/logo.png")
|
|
326
|
+
.replace(/\{\{BRAND_FAVICON\}\}/g, () => config.brand.favicon || "assets/brand/favicon.png")
|
|
327
|
+
.replace(/\{\{BRAND_LOGO_CLASS\}\}/g, () => logoClass)
|
|
328
|
+
.replace(/\{\{SITE_TITLE\}\}/g, () => config.title)
|
|
329
|
+
.replace("{{TITLE}}", () => title)
|
|
330
|
+
.replace("{{VERSION}}", () => uiVersion)
|
|
331
|
+
.replace("{{BRANCH}}", () => provenance.gitBranch)
|
|
332
|
+
.replace("{{BREADCRUMBS}}", () => breadcrumbs)
|
|
333
|
+
.replace("{{PAGE_META}}", () => pageMeta)
|
|
334
|
+
.replace("{{NAV}}", () => nav)
|
|
335
|
+
.replace("{{CONTENT}}", () => htmlContent)
|
|
336
|
+
.replace("{{TOC}}", () => toc)
|
|
337
|
+
.replace("{{FOOTER}}", () => footer)
|
|
338
|
+
.replace("{{THEME_CSS}}", () => themeCSS)
|
|
339
|
+
.replace("{{PLUGIN_HEAD}}", () => plugins.head)
|
|
340
|
+
.replace("{{PLUGIN_BODY_END}}", () => plugins.bodyEnd)
|
|
341
|
+
.replace("{{PRISM_LIGHT_URL}}", () => prismUrls.light)
|
|
342
|
+
.replace("{{PRISM_DARK_URL}}", () => prismUrls.dark)
|
|
236
343
|
.replace("{{HOT_RELOAD_SCRIPT}}", "");
|
|
237
344
|
}
|
|
238
345
|
|
|
@@ -274,32 +381,50 @@ sections:
|
|
|
274
381
|
const pathPrefix = "./";
|
|
275
382
|
const logoClass = config.brand.logoType === "wordmark" ? "logo-wordmark" : "logo-icon";
|
|
276
383
|
const brandInitial = escapeHtml(config.brand.name.trim().charAt(0).toUpperCase() || "K");
|
|
384
|
+
const mobileLogoHtml = buildLogoImgHtml({
|
|
385
|
+
logo: config.brand.logo || "assets/brand/logo.png",
|
|
386
|
+
logoDark: config.brand.logoDark,
|
|
387
|
+
alt: config.brand.name,
|
|
388
|
+
className: `logo-img ${logoClass}`,
|
|
389
|
+
pathPrefix,
|
|
390
|
+
onerrorFallback: true,
|
|
391
|
+
});
|
|
392
|
+
const sidebarLogoHtml = buildLogoImgHtml({
|
|
393
|
+
logo: config.brand.logo || "assets/brand/logo.png",
|
|
394
|
+
logoDark: config.brand.logoDark,
|
|
395
|
+
alt: config.brand.name,
|
|
396
|
+
className: "logo-img",
|
|
397
|
+
pathPrefix,
|
|
398
|
+
onerrorFallback: true,
|
|
399
|
+
});
|
|
277
400
|
|
|
278
401
|
return template
|
|
279
402
|
.replace("{{BODY_CLASS}}", "mode-docs")
|
|
280
|
-
.replace(/\{\{PATH_PREFIX\}\}/g, pathPrefix)
|
|
281
|
-
.replace(/\{\{BRAND_URL\}\}/g, config.brand.url)
|
|
282
|
-
.replace(/\{\{BRAND_TARGET\}\}/g, brandTarget)
|
|
283
|
-
.replace(/\{\{BRAND_NAME\}\}/g, config.brand.name)
|
|
284
|
-
.replace(/\{\{BRAND_INITIAL\}\}/g, brandInitial)
|
|
285
|
-
.replace(
|
|
286
|
-
.replace(
|
|
287
|
-
.replace(/\{\{
|
|
288
|
-
.replace(/\{\{
|
|
403
|
+
.replace(/\{\{PATH_PREFIX\}\}/g, () => pathPrefix)
|
|
404
|
+
.replace(/\{\{BRAND_URL\}\}/g, () => config.brand.url)
|
|
405
|
+
.replace(/\{\{BRAND_TARGET\}\}/g, () => brandTarget)
|
|
406
|
+
.replace(/\{\{BRAND_NAME\}\}/g, () => config.brand.name)
|
|
407
|
+
.replace(/\{\{BRAND_INITIAL\}\}/g, () => brandInitial)
|
|
408
|
+
.replace("{{MOBILE_BRAND_LOGO_IMG}}", () => mobileLogoHtml)
|
|
409
|
+
.replace("{{SIDEBAR_BRAND_LOGO_IMG}}", () => sidebarLogoHtml)
|
|
410
|
+
.replace(/\{\{BRAND_LOGO\}\}/g, () => config.brand.logo || "assets/brand/logo.png")
|
|
411
|
+
.replace(/\{\{BRAND_FAVICON\}\}/g, () => config.brand.favicon || "assets/brand/favicon.png")
|
|
412
|
+
.replace(/\{\{BRAND_LOGO_CLASS\}\}/g, () => logoClass)
|
|
413
|
+
.replace(/\{\{SITE_TITLE\}\}/g, () => config.title)
|
|
289
414
|
.replace("{{TITLE}}", "Getting Started")
|
|
290
|
-
.replace("{{VERSION}}", uiVersion)
|
|
291
|
-
.replace("{{BRANCH}}", provenance.gitBranch)
|
|
415
|
+
.replace("{{VERSION}}", () => uiVersion)
|
|
416
|
+
.replace("{{BRANCH}}", () => provenance.gitBranch)
|
|
292
417
|
.replace("{{BREADCRUMBS}}", "")
|
|
293
418
|
.replace("{{PAGE_META}}", "")
|
|
294
419
|
.replace("{{NAV}}", "<ul></ul>")
|
|
295
|
-
.replace("{{CONTENT}}", htmlContent)
|
|
420
|
+
.replace("{{CONTENT}}", () => htmlContent)
|
|
296
421
|
.replace("{{TOC}}", "")
|
|
297
|
-
.replace("{{FOOTER}}", buildFooter(provenance, config))
|
|
298
|
-
.replace("{{THEME_CSS}}", themeCSS)
|
|
299
|
-
.replace("{{PLUGIN_HEAD}}", plugins.head)
|
|
300
|
-
.replace("{{PLUGIN_BODY_END}}", plugins.bodyEnd)
|
|
301
|
-
.replace("{{PRISM_LIGHT_URL}}", prismUrls.light)
|
|
302
|
-
.replace("{{PRISM_DARK_URL}}", prismUrls.dark)
|
|
422
|
+
.replace("{{FOOTER}}", () => buildFooter(provenance, config, pathPrefix))
|
|
423
|
+
.replace("{{THEME_CSS}}", () => themeCSS)
|
|
424
|
+
.replace("{{PLUGIN_HEAD}}", () => plugins.head)
|
|
425
|
+
.replace("{{PLUGIN_BODY_END}}", () => plugins.bodyEnd)
|
|
426
|
+
.replace("{{PRISM_LIGHT_URL}}", () => prismUrls.light)
|
|
427
|
+
.replace("{{PRISM_DARK_URL}}", () => prismUrls.dark)
|
|
303
428
|
.replace("{{HOT_RELOAD_SCRIPT}}", "");
|
|
304
429
|
}
|
|
305
430
|
|
|
@@ -310,24 +435,18 @@ async function renderSlidesIndex(
|
|
|
310
435
|
config: SiteConfig,
|
|
311
436
|
theme: Theme,
|
|
312
437
|
plugins: PluginInjections,
|
|
438
|
+
fenceValidation: FenceValidationFlags,
|
|
313
439
|
): Promise<string> {
|
|
314
440
|
const uiVersion = provenance.version ? `v${provenance.version}` : "unversioned";
|
|
315
441
|
const pathPrefix = "./";
|
|
316
|
-
const slides = await collectSlides(files
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const raw = await readFile(join(ROOT, "kitfly.plugins.yaml"), "utf-8");
|
|
320
|
-
const parsed = parseYaml(raw) as unknown as Record<string, unknown>;
|
|
321
|
-
const enabled = Array.isArray(parsed?.plugins) ? (parsed.plugins as unknown[]) : [];
|
|
322
|
-
validateFences = enabled.some((p) => typeof p === "string" && p.startsWith("slides-visuals@"));
|
|
323
|
-
} catch {
|
|
324
|
-
// no config, skip
|
|
325
|
-
}
|
|
442
|
+
const slides = await collectSlides(files, {
|
|
443
|
+
markdownTransform: (raw, file) => applyDataBindingsForSlides(raw, file.path, config),
|
|
444
|
+
});
|
|
326
445
|
const renderedSlides = await Promise.all(
|
|
327
446
|
slides.map(async (slide, i) => {
|
|
328
447
|
let inner = "";
|
|
329
448
|
if (slide.kind === "markdown") {
|
|
330
|
-
if (
|
|
449
|
+
if (fenceValidation.slidesVisuals) {
|
|
331
450
|
const diagnostics = filterUnknownSlidesVisualsTypeDiagnostics(
|
|
332
451
|
validateSlidesVisualsFences(slide.body),
|
|
333
452
|
);
|
|
@@ -339,6 +458,22 @@ async function renderSlidesIndex(
|
|
|
339
458
|
throw new Error(`slides-visuals fence contract violations:\n${msg}`);
|
|
340
459
|
}
|
|
341
460
|
}
|
|
461
|
+
if (fenceValidation.planningVisuals) {
|
|
462
|
+
const diagnostics = filterUnknownPlanningVisualsTypeDiagnostics(
|
|
463
|
+
validatePlanningVisualsFences(slide.body),
|
|
464
|
+
);
|
|
465
|
+
if (diagnostics.length) {
|
|
466
|
+
const msg = diagnostics
|
|
467
|
+
.slice(0, 12)
|
|
468
|
+
.map((d) => ` - ${slide.sourcePath}:${d.line} ${d.message}`)
|
|
469
|
+
.join("\n");
|
|
470
|
+
throw new Error(`planning-visuals fence contract violations:\n${msg}`);
|
|
471
|
+
}
|
|
472
|
+
const warnings = collectPlanningVisualsContainmentWarnings(slide.body);
|
|
473
|
+
for (const warning of warnings.slice(0, 12)) {
|
|
474
|
+
console.warn(` ⚠ ${slide.sourcePath}:${warning.line} ${warning.message}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
342
477
|
inner = marked.parse(slide.body) as string;
|
|
343
478
|
} else if (slide.kind === "yaml") {
|
|
344
479
|
inner = `<pre><code class="language-yaml">${escapeHtml(slide.body)}</code></pre>`;
|
|
@@ -376,38 +511,56 @@ async function renderSlidesIndex(
|
|
|
376
511
|
</div>
|
|
377
512
|
</div>`;
|
|
378
513
|
|
|
379
|
-
const nav =
|
|
514
|
+
const nav = buildSlideNavHierarchical(slides, config, "slide-1");
|
|
380
515
|
const brandTarget = config.brand.external ? ' target="_blank" rel="noopener"' : "";
|
|
381
516
|
const logoClass = config.brand.logoType === "wordmark" ? "logo-wordmark" : "logo-icon";
|
|
382
517
|
const themeCSS = generateThemeCSS(theme);
|
|
383
518
|
const prismUrls = getPrismUrls(theme);
|
|
384
519
|
const brandInitial = escapeHtml(config.brand.name.trim().charAt(0).toUpperCase() || "K");
|
|
520
|
+
const mobileLogoHtml = buildLogoImgHtml({
|
|
521
|
+
logo: config.brand.logo || "assets/brand/logo.png",
|
|
522
|
+
logoDark: config.brand.logoDark,
|
|
523
|
+
alt: config.brand.name,
|
|
524
|
+
className: `logo-img ${logoClass}`,
|
|
525
|
+
pathPrefix,
|
|
526
|
+
onerrorFallback: true,
|
|
527
|
+
});
|
|
528
|
+
const sidebarLogoHtml = buildLogoImgHtml({
|
|
529
|
+
logo: config.brand.logo || "assets/brand/logo.png",
|
|
530
|
+
logoDark: config.brand.logoDark,
|
|
531
|
+
alt: config.brand.name,
|
|
532
|
+
className: "logo-img",
|
|
533
|
+
pathPrefix,
|
|
534
|
+
onerrorFallback: true,
|
|
535
|
+
});
|
|
385
536
|
|
|
386
537
|
return template
|
|
387
538
|
.replace("{{BODY_CLASS}}", "mode-slides")
|
|
388
|
-
.replace(/\{\{PATH_PREFIX\}\}/g, pathPrefix)
|
|
389
|
-
.replace(/\{\{BRAND_URL\}\}/g, config.brand.url)
|
|
390
|
-
.replace(/\{\{BRAND_TARGET\}\}/g, brandTarget)
|
|
391
|
-
.replace(/\{\{BRAND_NAME\}\}/g, config.brand.name)
|
|
392
|
-
.replace(/\{\{BRAND_INITIAL\}\}/g, brandInitial)
|
|
393
|
-
.replace(
|
|
394
|
-
.replace(
|
|
395
|
-
.replace(/\{\{
|
|
396
|
-
.replace(/\{\{
|
|
397
|
-
.replace(
|
|
398
|
-
.replace(
|
|
399
|
-
.replace("{{
|
|
539
|
+
.replace(/\{\{PATH_PREFIX\}\}/g, () => pathPrefix)
|
|
540
|
+
.replace(/\{\{BRAND_URL\}\}/g, () => config.brand.url)
|
|
541
|
+
.replace(/\{\{BRAND_TARGET\}\}/g, () => brandTarget)
|
|
542
|
+
.replace(/\{\{BRAND_NAME\}\}/g, () => config.brand.name)
|
|
543
|
+
.replace(/\{\{BRAND_INITIAL\}\}/g, () => brandInitial)
|
|
544
|
+
.replace("{{MOBILE_BRAND_LOGO_IMG}}", () => mobileLogoHtml)
|
|
545
|
+
.replace("{{SIDEBAR_BRAND_LOGO_IMG}}", () => sidebarLogoHtml)
|
|
546
|
+
.replace(/\{\{BRAND_LOGO\}\}/g, () => config.brand.logo || "assets/brand/logo.png")
|
|
547
|
+
.replace(/\{\{BRAND_FAVICON\}\}/g, () => config.brand.favicon || "assets/brand/favicon.png")
|
|
548
|
+
.replace(/\{\{BRAND_LOGO_CLASS\}\}/g, () => logoClass)
|
|
549
|
+
.replace(/\{\{SITE_TITLE\}\}/g, () => config.title)
|
|
550
|
+
.replace("{{TITLE}}", () => config.title)
|
|
551
|
+
.replace("{{VERSION}}", () => uiVersion)
|
|
552
|
+
.replace("{{BRANCH}}", () => provenance.gitBranch)
|
|
400
553
|
.replace("{{BREADCRUMBS}}", "")
|
|
401
554
|
.replace("{{PAGE_META}}", "")
|
|
402
|
-
.replace("{{NAV}}", nav)
|
|
403
|
-
.replace("{{CONTENT}}", htmlContent)
|
|
555
|
+
.replace("{{NAV}}", () => nav)
|
|
556
|
+
.replace("{{CONTENT}}", () => htmlContent)
|
|
404
557
|
.replace("{{TOC}}", "")
|
|
405
|
-
.replace("{{FOOTER}}", buildFooter(provenance, config))
|
|
406
|
-
.replace("{{THEME_CSS}}", themeCSS)
|
|
407
|
-
.replace("{{PLUGIN_HEAD}}", plugins.head)
|
|
408
|
-
.replace("{{PLUGIN_BODY_END}}", plugins.bodyEnd)
|
|
409
|
-
.replace("{{PRISM_LIGHT_URL}}", prismUrls.light)
|
|
410
|
-
.replace("{{PRISM_DARK_URL}}", prismUrls.dark)
|
|
558
|
+
.replace("{{FOOTER}}", () => buildFooter(provenance, config, pathPrefix))
|
|
559
|
+
.replace("{{THEME_CSS}}", () => themeCSS)
|
|
560
|
+
.replace("{{PLUGIN_HEAD}}", () => plugins.head)
|
|
561
|
+
.replace("{{PLUGIN_BODY_END}}", () => plugins.bodyEnd)
|
|
562
|
+
.replace("{{PRISM_LIGHT_URL}}", () => prismUrls.light)
|
|
563
|
+
.replace("{{PRISM_DARK_URL}}", () => prismUrls.dark)
|
|
411
564
|
.replace("{{HOT_RELOAD_SCRIPT}}", "");
|
|
412
565
|
}
|
|
413
566
|
|
|
@@ -416,6 +569,7 @@ export interface BuildOptions {
|
|
|
416
569
|
folder?: string;
|
|
417
570
|
out?: string;
|
|
418
571
|
raw?: boolean; // Include raw markdown files (default: true)
|
|
572
|
+
profile?: string;
|
|
419
573
|
}
|
|
420
574
|
|
|
421
575
|
let INCLUDE_RAW = true;
|
|
@@ -430,18 +584,29 @@ export async function build(options: BuildOptions = {}) {
|
|
|
430
584
|
if (options.raw === false) {
|
|
431
585
|
INCLUDE_RAW = false;
|
|
432
586
|
}
|
|
587
|
+
ACTIVE_PROFILE = options.profile;
|
|
433
588
|
await buildSite();
|
|
434
589
|
}
|
|
435
590
|
|
|
436
591
|
// Rename internal function
|
|
437
592
|
async function buildSite() {
|
|
438
|
-
const DIST =
|
|
593
|
+
const DIST = resolve(ROOT, normalizeMsysPath(OUT_DIR));
|
|
439
594
|
|
|
440
595
|
console.log("Building site...\n");
|
|
441
596
|
|
|
442
597
|
// Load configuration
|
|
443
598
|
const config = await loadSiteConfig(ROOT);
|
|
444
599
|
console.log(` ✓ Loaded config: "${config.title}" (${config.sections.length} sections)`);
|
|
600
|
+
if (config.prebuild?.length) {
|
|
601
|
+
await runPrebuildHooks(
|
|
602
|
+
config.prebuild,
|
|
603
|
+
ROOT,
|
|
604
|
+
"build",
|
|
605
|
+
ACTIVE_PROFILE,
|
|
606
|
+
config.dataroot || "data",
|
|
607
|
+
);
|
|
608
|
+
console.log(` ✓ prebuild hooks (${config.prebuild.length})`);
|
|
609
|
+
}
|
|
445
610
|
|
|
446
611
|
// Load theme
|
|
447
612
|
const theme = await loadTheme(ROOT);
|
|
@@ -465,6 +630,7 @@ async function buildSite() {
|
|
|
465
630
|
root: ROOT,
|
|
466
631
|
mode: config.mode === "slides" ? "slides" : "docs",
|
|
467
632
|
});
|
|
633
|
+
const fenceValidation = await getFenceValidationFlags(ROOT);
|
|
468
634
|
|
|
469
635
|
// Copy CSS
|
|
470
636
|
const css = await readFile(await resolveStylesPath(ROOT), "utf-8");
|
|
@@ -499,7 +665,11 @@ async function buildSite() {
|
|
|
499
665
|
}
|
|
500
666
|
|
|
501
667
|
// Collect and render all files
|
|
502
|
-
const files = await
|
|
668
|
+
const files = await filterByProfile(
|
|
669
|
+
await collectFiles(ROOT, config),
|
|
670
|
+
ACTIVE_PROFILE,
|
|
671
|
+
config.profiles,
|
|
672
|
+
);
|
|
503
673
|
|
|
504
674
|
if (files.length === 0) {
|
|
505
675
|
// No content - render Getting Started page
|
|
@@ -511,7 +681,15 @@ async function buildSite() {
|
|
|
511
681
|
}
|
|
512
682
|
|
|
513
683
|
if (config.mode === "slides") {
|
|
514
|
-
const html = await renderSlidesIndex(
|
|
684
|
+
const html = await renderSlidesIndex(
|
|
685
|
+
template,
|
|
686
|
+
files,
|
|
687
|
+
provenance,
|
|
688
|
+
config,
|
|
689
|
+
theme,
|
|
690
|
+
plugins,
|
|
691
|
+
fenceValidation,
|
|
692
|
+
);
|
|
515
693
|
await writeFile(join(DIST, "index.html"), html);
|
|
516
694
|
console.log(` ✓ index.html (slides mode, ${files.length} source files)`);
|
|
517
695
|
await generateAIAccessibility(DIST, files, config, provenance);
|
|
@@ -530,6 +708,7 @@ async function buildSite() {
|
|
|
530
708
|
config,
|
|
531
709
|
theme,
|
|
532
710
|
plugins,
|
|
711
|
+
fenceValidation,
|
|
533
712
|
);
|
|
534
713
|
|
|
535
714
|
// Create output path
|
|
@@ -556,6 +735,7 @@ async function buildSite() {
|
|
|
556
735
|
config,
|
|
557
736
|
theme,
|
|
558
737
|
plugins,
|
|
738
|
+
fenceValidation,
|
|
559
739
|
);
|
|
560
740
|
await writeFile(join(DIST, "index.html"), homeHtml);
|
|
561
741
|
console.log(` ✓ index.html (from ${config.home})`);
|
|
@@ -571,6 +751,7 @@ async function buildSite() {
|
|
|
571
751
|
config,
|
|
572
752
|
theme,
|
|
573
753
|
plugins,
|
|
754
|
+
fenceValidation,
|
|
574
755
|
);
|
|
575
756
|
await writeFile(join(DIST, "index.html"), indexHtml);
|
|
576
757
|
console.log(" ✓ index.html");
|
|
@@ -588,6 +769,7 @@ async function buildSite() {
|
|
|
588
769
|
config,
|
|
589
770
|
theme,
|
|
590
771
|
plugins,
|
|
772
|
+
fenceValidation,
|
|
591
773
|
);
|
|
592
774
|
await writeFile(join(DIST, "index.html"), indexHtml);
|
|
593
775
|
console.log(" ✓ index.html");
|
|
@@ -733,6 +915,7 @@ Usage: bun run build [folder] [options]
|
|
|
733
915
|
|
|
734
916
|
Options:
|
|
735
917
|
-o, --out <dir> Output directory [env: KITFLY_BUILD_OUT] [default: ${DEFAULT_OUT}]
|
|
918
|
+
--profile <name> Active content profile [env: KITFLY_PROFILE]
|
|
736
919
|
--raw Include raw markdown files [env: KITFLY_BUILD_RAW] [default: true]
|
|
737
920
|
--no-raw Don't include raw markdown files
|
|
738
921
|
--help Show this help message
|
|
@@ -752,5 +935,6 @@ Examples:
|
|
|
752
935
|
folder: cfg.folder,
|
|
753
936
|
out: cfg.out,
|
|
754
937
|
raw: cfg.raw,
|
|
938
|
+
profile: cfg.profile,
|
|
755
939
|
}).catch(console.error);
|
|
756
940
|
}
|