kitfly 0.1.2 → 0.2.1
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 +46 -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/slides-authoring-guidelines.md +129 -0
- package/dist/_raw/content/reference/structure.md +166 -0
- package/dist/_raw/content/reference.md +20 -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/slides-authoring-guidelines.html +418 -0
- package/dist/content/reference/structure.html +463 -0
- package/dist/content/reference.html +335 -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 +549 -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 +390 -0
- package/plugins-dist/slides-visuals.js +689 -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 +158 -3
- package/scripts/bundle.ts +261 -95
- package/scripts/dev.ts +301 -11
- package/src/__tests__/build.test.ts +220 -1
- package/src/__tests__/bundle.test.ts +31 -0
- package/src/__tests__/cli.test.ts +14 -3
- package/src/__tests__/dev-plugin-errors.test.ts +20 -0
- 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/flow-branching-no-source.md +5 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/flow-converging-no-target.md +6 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/indented-fence.md +4 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/staircase-empty-steps.md +3 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/stat-grid-missing-fields.md +5 -0
- package/src/__tests__/fixtures/fences/slides-visuals/invalid/timeline-horizontal-no-events.md +2 -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/flow-branching-no-split.md +7 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-branching.md +8 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-converging-no-merge.md +7 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/flow-converging.md +8 -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/staircase-down.md +7 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/staircase.md +8 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/stat-grid.md +8 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/timeline-horizontal.md +9 -0
- package/src/__tests__/fixtures/fences/slides-visuals/valid/timeline-vertical.md +10 -0
- package/src/__tests__/init.test.ts +35 -0
- package/src/__tests__/plugin-loader.test.ts +221 -0
- package/src/__tests__/shared.test.ts +451 -0
- package/src/__tests__/slides-visuals-fence-contract.test.ts +28 -0
- package/src/__tests__/slides-visuals-runtime-regressions.bun.test.ts +147 -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 +650 -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
package/scripts/dev.ts
CHANGED
|
@@ -14,26 +14,36 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { watch } from "node:fs";
|
|
17
|
-
import { readFile } from "node:fs/promises";
|
|
17
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
18
18
|
import { basename, extname, join, resolve } from "node:path";
|
|
19
19
|
import { marked, Renderer } from "marked";
|
|
20
20
|
import { ENGINE_ASSETS_DIR, ENGINE_SITE_DIR } from "../src/engine.ts";
|
|
21
|
+
import {
|
|
22
|
+
loadPluginInjections,
|
|
23
|
+
PluginConfigError,
|
|
24
|
+
PluginIntegrityError,
|
|
25
|
+
PluginNetworkError,
|
|
26
|
+
PluginPolicyError,
|
|
27
|
+
} from "../src/plugin-loader.ts";
|
|
21
28
|
import {
|
|
22
29
|
buildBreadcrumbsSimple,
|
|
23
30
|
buildFooter,
|
|
24
31
|
buildNavSimple,
|
|
25
32
|
buildPageMeta,
|
|
33
|
+
buildSlideNav,
|
|
26
34
|
buildToc,
|
|
27
35
|
// Network utilities
|
|
28
36
|
checkPortOrExit,
|
|
29
37
|
// Navigation/template building
|
|
30
38
|
collectFiles,
|
|
39
|
+
collectSlides,
|
|
31
40
|
envBool,
|
|
32
41
|
envInt,
|
|
33
42
|
// Config helpers
|
|
34
43
|
envString,
|
|
35
44
|
// Formatting
|
|
36
45
|
escapeHtml,
|
|
46
|
+
filterUnknownSlidesVisualsTypeDiagnostics,
|
|
37
47
|
// Provenance
|
|
38
48
|
generateProvenance,
|
|
39
49
|
// YAML/Config parsing
|
|
@@ -41,13 +51,16 @@ import {
|
|
|
41
51
|
type Provenance,
|
|
42
52
|
// Markdown utilities
|
|
43
53
|
parseFrontmatter,
|
|
54
|
+
parseYaml,
|
|
44
55
|
resolveStylesPath,
|
|
45
56
|
resolveTemplatePath,
|
|
57
|
+
rewriteRelativeAssetUrls,
|
|
46
58
|
// Types
|
|
47
59
|
type SiteConfig,
|
|
48
60
|
slugify,
|
|
49
61
|
toUrlPath,
|
|
50
62
|
validatePath,
|
|
63
|
+
validateSlidesVisualsFences,
|
|
51
64
|
} from "../src/shared.ts";
|
|
52
65
|
import { generateThemeCSS, getPrismUrls, loadTheme, type Theme } from "../src/theme.ts";
|
|
53
66
|
|
|
@@ -69,6 +82,60 @@ let daemonLog: {
|
|
|
69
82
|
error: (msg: string) => void;
|
|
70
83
|
} | null = null;
|
|
71
84
|
|
|
85
|
+
function isPluginLoaderError(error: unknown): error is Error {
|
|
86
|
+
return (
|
|
87
|
+
error instanceof PluginConfigError ||
|
|
88
|
+
error instanceof PluginIntegrityError ||
|
|
89
|
+
error instanceof PluginPolicyError ||
|
|
90
|
+
error instanceof PluginNetworkError
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function pluginVersionMismatchHint(message: string): string {
|
|
95
|
+
const m = message.match(/^Plugin ([a-z0-9-]+) version mismatch: ([^ ]+) != ([^ ]+)$/i);
|
|
96
|
+
if (!m) return "";
|
|
97
|
+
const pluginId = m[1];
|
|
98
|
+
const expected = m[3];
|
|
99
|
+
return `Update <code>kitfly.plugins.yaml</code> to <code>${pluginId}@${expected}</code>, then refresh.`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function buildDevPluginErrorHtml(message: string): string {
|
|
103
|
+
const hint = pluginVersionMismatchHint(message);
|
|
104
|
+
const safeMessage = escapeHtml(message);
|
|
105
|
+
const hintBlock = hint
|
|
106
|
+
? `<p>${hint}</p>`
|
|
107
|
+
: "<p>Check <code>kitfly.plugins.yaml</code> and <code>registry/plugins.yaml</code>, then refresh.</p>";
|
|
108
|
+
return `<!doctype html>
|
|
109
|
+
<html lang="en">
|
|
110
|
+
<head>
|
|
111
|
+
<meta charset="utf-8">
|
|
112
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
113
|
+
<title>Plugin Configuration Error</title>
|
|
114
|
+
<style>
|
|
115
|
+
body { font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif; margin: 0; background: #0b1020; color: #e8ecf3; }
|
|
116
|
+
main { max-width: 820px; margin: 8vh auto; padding: 1.25rem; }
|
|
117
|
+
.card { background: #131a2e; border: 1px solid #2a3557; border-radius: 12px; padding: 1rem 1.1rem; }
|
|
118
|
+
h1 { margin: 0 0 0.75rem; font-size: 1.25rem; }
|
|
119
|
+
p, li { line-height: 1.5; }
|
|
120
|
+
code { background: #0e1528; padding: 0.08rem 0.3rem; border-radius: 6px; border: 1px solid #2a3557; }
|
|
121
|
+
pre { margin: 0.8rem 0 0; padding: 0.75rem; background: #0e1528; border: 1px solid #2a3557; border-radius: 8px; overflow: auto; }
|
|
122
|
+
.muted { color: #b5bfd2; font-size: 0.92rem; }
|
|
123
|
+
</style>
|
|
124
|
+
</head>
|
|
125
|
+
<body>
|
|
126
|
+
<main>
|
|
127
|
+
<div class="card">
|
|
128
|
+
<h1>Plugin setup error</h1>
|
|
129
|
+
<p>Kitfly could not load one or more plugins for dev preview.</p>
|
|
130
|
+
${hintBlock}
|
|
131
|
+
<pre><code>${safeMessage}</code></pre>
|
|
132
|
+
<p class="muted">After updating config, refresh this page. No dev server restart required.</p>
|
|
133
|
+
</div>
|
|
134
|
+
</main>
|
|
135
|
+
</body>
|
|
136
|
+
</html>`;
|
|
137
|
+
}
|
|
138
|
+
|
|
72
139
|
/** Log info — uses structured logger in daemon mode, console.log otherwise */
|
|
73
140
|
function logInfo(msg: string): void {
|
|
74
141
|
if (daemonLog) daemonLog.info(msg);
|
|
@@ -187,6 +254,72 @@ marked.use({ renderer });
|
|
|
187
254
|
// Track connected clients for hot reload
|
|
188
255
|
const clients: Set<ReadableStreamDefaultController> = new Set();
|
|
189
256
|
|
|
257
|
+
let pluginCache: { key: string; head: string; bodyEnd: string } | null = null;
|
|
258
|
+
|
|
259
|
+
async function isSlidesVisualsEnabled(): Promise<boolean> {
|
|
260
|
+
const configPath = join(ROOT, "kitfly.plugins.yaml");
|
|
261
|
+
try {
|
|
262
|
+
const raw = await readFile(configPath, "utf-8");
|
|
263
|
+
const parsed = parseYaml(raw) as unknown as Record<string, unknown>;
|
|
264
|
+
const plugins = Array.isArray(parsed?.plugins) ? (parsed.plugins as unknown[]) : [];
|
|
265
|
+
return plugins.some((p) => typeof p === "string" && p.startsWith("slides-visuals@"));
|
|
266
|
+
} catch {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async function getPluginInjectionsCached(
|
|
272
|
+
mode: "docs" | "slides",
|
|
273
|
+
): Promise<{ head: string; bodyEnd: string }> {
|
|
274
|
+
const configPath = join(ROOT, "kitfly.plugins.yaml");
|
|
275
|
+
let configMtime = "missing";
|
|
276
|
+
try {
|
|
277
|
+
configMtime = String((await stat(configPath)).mtimeMs);
|
|
278
|
+
} catch {
|
|
279
|
+
return { head: "", bodyEnd: "" };
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const siteRegistryPath = join(ROOT, "registry", "plugins.yaml");
|
|
283
|
+
let registryMtime = "none";
|
|
284
|
+
try {
|
|
285
|
+
registryMtime = String((await stat(siteRegistryPath)).mtimeMs);
|
|
286
|
+
} catch {
|
|
287
|
+
// Uses engine registry by default.
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
let pluginAssetsMtime = "none";
|
|
291
|
+
try {
|
|
292
|
+
const mtimes: number[] = [];
|
|
293
|
+
const dirs = [join(ROOT, "plugins-dist"), join(ENGINE_SITE_DIR, "..", "plugins-dist")];
|
|
294
|
+
for (const dir of dirs) {
|
|
295
|
+
try {
|
|
296
|
+
const entries = await readdir(dir);
|
|
297
|
+
for (const name of entries) {
|
|
298
|
+
if (!/\.(js|css)$/i.test(name)) continue;
|
|
299
|
+
try {
|
|
300
|
+
mtimes.push((await stat(join(dir, name))).mtimeMs);
|
|
301
|
+
} catch {
|
|
302
|
+
// ignore
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
} catch {
|
|
306
|
+
// ignore
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
pluginAssetsMtime = mtimes.length ? String(Math.max(...mtimes)) : "none";
|
|
310
|
+
} catch {
|
|
311
|
+
// ignore
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const key = `${mode}:${configMtime}:${registryMtime}:${pluginAssetsMtime}`;
|
|
315
|
+
if (pluginCache && pluginCache.key === key) {
|
|
316
|
+
return { head: pluginCache.head, bodyEnd: pluginCache.bodyEnd };
|
|
317
|
+
}
|
|
318
|
+
const injected = await loadPluginInjections({ root: ROOT, mode });
|
|
319
|
+
pluginCache = { key, head: injected.head, bodyEnd: injected.bodyEnd };
|
|
320
|
+
return injected;
|
|
321
|
+
}
|
|
322
|
+
|
|
190
323
|
// Convert markdown to HTML with template
|
|
191
324
|
async function renderPage(
|
|
192
325
|
filePath: string,
|
|
@@ -234,6 +367,7 @@ async function renderPage(
|
|
|
234
367
|
const themeCSS = generateThemeCSS(theme);
|
|
235
368
|
const prismUrls = getPrismUrls(theme);
|
|
236
369
|
const pathPrefix = "/";
|
|
370
|
+
const plugins = await getPluginInjectionsCached(config.mode === "slides" ? "slides" : "docs");
|
|
237
371
|
|
|
238
372
|
const hotReloadScript = `
|
|
239
373
|
<script>
|
|
@@ -243,12 +377,15 @@ async function renderPage(
|
|
|
243
377
|
</script>`;
|
|
244
378
|
|
|
245
379
|
const logoClass = config.brand.logoType === "wordmark" ? "logo-wordmark" : "logo-icon";
|
|
380
|
+
const brandInitial = escapeHtml(config.brand.name.trim().charAt(0).toUpperCase() || "K");
|
|
246
381
|
|
|
247
382
|
return template
|
|
383
|
+
.replace("{{BODY_CLASS}}", "mode-docs")
|
|
248
384
|
.replace(/\{\{PATH_PREFIX\}\}/g, pathPrefix)
|
|
249
385
|
.replace(/\{\{BRAND_URL\}\}/g, config.brand.url)
|
|
250
386
|
.replace(/\{\{BRAND_TARGET\}\}/g, brandTarget)
|
|
251
387
|
.replace(/\{\{BRAND_NAME\}\}/g, config.brand.name)
|
|
388
|
+
.replace(/\{\{BRAND_INITIAL\}\}/g, brandInitial)
|
|
252
389
|
.replace(/\{\{BRAND_LOGO\}\}/g, config.brand.logo || "assets/brand/logo.png")
|
|
253
390
|
.replace(/\{\{BRAND_FAVICON\}\}/g, config.brand.favicon || "assets/brand/favicon.png")
|
|
254
391
|
.replace(/\{\{BRAND_LOGO_CLASS\}\}/g, logoClass)
|
|
@@ -263,6 +400,120 @@ async function renderPage(
|
|
|
263
400
|
.replace("{{TOC}}", toc)
|
|
264
401
|
.replace("{{FOOTER}}", footer)
|
|
265
402
|
.replace("{{THEME_CSS}}", themeCSS)
|
|
403
|
+
.replace("{{PLUGIN_HEAD}}", plugins.head)
|
|
404
|
+
.replace("{{PLUGIN_BODY_END}}", plugins.bodyEnd)
|
|
405
|
+
.replace("{{PRISM_LIGHT_URL}}", prismUrls.light)
|
|
406
|
+
.replace("{{PRISM_DARK_URL}}", prismUrls.dark)
|
|
407
|
+
.replace("{{HOT_RELOAD_SCRIPT}}", hotReloadScript);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
async function renderSlidesPage(
|
|
411
|
+
provenance: Provenance,
|
|
412
|
+
config: SiteConfig,
|
|
413
|
+
theme: Theme,
|
|
414
|
+
): Promise<string> {
|
|
415
|
+
const uiVersion = provenance.version ? `v${provenance.version}` : "unversioned";
|
|
416
|
+
const template = await readFile(await resolveTemplatePath(ROOT), "utf-8");
|
|
417
|
+
const files = await collectFiles(ROOT, config);
|
|
418
|
+
const slides = await collectSlides(files);
|
|
419
|
+
|
|
420
|
+
if (slides.length === 0) {
|
|
421
|
+
return renderGettingStarted(provenance, config, theme);
|
|
422
|
+
}
|
|
423
|
+
const pathPrefix = "/";
|
|
424
|
+
const validateFences = await isSlidesVisualsEnabled();
|
|
425
|
+
|
|
426
|
+
const sections = await Promise.all(
|
|
427
|
+
slides.map(async (slide, i) => {
|
|
428
|
+
let inner = "";
|
|
429
|
+
if (slide.kind === "markdown") {
|
|
430
|
+
if (validateFences) {
|
|
431
|
+
const diagnostics = filterUnknownSlidesVisualsTypeDiagnostics(
|
|
432
|
+
validateSlidesVisualsFences(slide.body),
|
|
433
|
+
);
|
|
434
|
+
if (diagnostics.length) {
|
|
435
|
+
const msg = diagnostics
|
|
436
|
+
.slice(0, 12)
|
|
437
|
+
.map((d) => ` - ${slide.sourcePath}:${d.line} ${d.message}`)
|
|
438
|
+
.join("\n");
|
|
439
|
+
throw new Error(`slides-visuals fence contract violations:\n${msg}`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
inner = marked.parse(slide.body) as string;
|
|
443
|
+
} else if (slide.kind === "yaml") {
|
|
444
|
+
inner = `<pre><code class="language-yaml">${escapeHtml(slide.body)}</code></pre>`;
|
|
445
|
+
} else {
|
|
446
|
+
let prettyJson = slide.body;
|
|
447
|
+
try {
|
|
448
|
+
prettyJson = JSON.stringify(JSON.parse(slide.body), null, 2);
|
|
449
|
+
} catch {
|
|
450
|
+
// Use original if not valid JSON
|
|
451
|
+
}
|
|
452
|
+
inner = `<pre><code class="language-json">${escapeHtml(prettyJson)}</code></pre>`;
|
|
453
|
+
}
|
|
454
|
+
inner = rewriteRelativeAssetUrls(inner, slide.sourceUrlPath, pathPrefix);
|
|
455
|
+
|
|
456
|
+
const classToken = slide.className ? ` ${slide.className}` : "";
|
|
457
|
+
const activeClass = i === 0 ? " active" : "";
|
|
458
|
+
return `<section id="${slide.id}" class="slide${classToken}${activeClass}" data-slide-index="${i}">${inner}</section>`;
|
|
459
|
+
}),
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
const htmlContent = `
|
|
463
|
+
<div class="slides-shell" style="--slide-aspect: ${config.aspect || "16/9"}">
|
|
464
|
+
<div class="slide-viewport">
|
|
465
|
+
<div class="slide-frame">
|
|
466
|
+
${sections.join("\n")}
|
|
467
|
+
</div>
|
|
468
|
+
</div>
|
|
469
|
+
<div class="slide-nav" aria-label="Slide navigation">
|
|
470
|
+
<button class="slide-prev" type="button" aria-label="Previous slide">Prev</button>
|
|
471
|
+
<span class="slide-counter">1 / ${slides.length}</span>
|
|
472
|
+
<button class="slide-next" type="button" aria-label="Next slide">Next</button>
|
|
473
|
+
<div class="slide-progress" role="presentation">
|
|
474
|
+
<span class="slide-progress-bar" style="width: ${(1 / slides.length) * 100}%"></span>
|
|
475
|
+
</div>
|
|
476
|
+
</div>
|
|
477
|
+
</div>`;
|
|
478
|
+
|
|
479
|
+
const nav = buildSlideNav(slides, config, "slide-1");
|
|
480
|
+
const footer = buildFooter(provenance, config);
|
|
481
|
+
const brandTarget = config.brand.external ? ' target="_blank" rel="noopener"' : "";
|
|
482
|
+
const themeCSS = generateThemeCSS(theme);
|
|
483
|
+
const prismUrls = getPrismUrls(theme);
|
|
484
|
+
const hotReloadScript = `
|
|
485
|
+
<script>
|
|
486
|
+
const es = new EventSource('/__reload');
|
|
487
|
+
es.onmessage = () => location.reload();
|
|
488
|
+
es.onerror = () => setTimeout(() => location.reload(), 1000);
|
|
489
|
+
</script>`;
|
|
490
|
+
const plugins = await getPluginInjectionsCached("slides");
|
|
491
|
+
const logoClass = config.brand.logoType === "wordmark" ? "logo-wordmark" : "logo-icon";
|
|
492
|
+
const brandInitial = escapeHtml(config.brand.name.trim().charAt(0).toUpperCase() || "K");
|
|
493
|
+
|
|
494
|
+
return template
|
|
495
|
+
.replace("{{BODY_CLASS}}", "mode-slides")
|
|
496
|
+
.replace(/\{\{PATH_PREFIX\}\}/g, pathPrefix)
|
|
497
|
+
.replace(/\{\{BRAND_URL\}\}/g, config.brand.url)
|
|
498
|
+
.replace(/\{\{BRAND_TARGET\}\}/g, brandTarget)
|
|
499
|
+
.replace(/\{\{BRAND_NAME\}\}/g, config.brand.name)
|
|
500
|
+
.replace(/\{\{BRAND_INITIAL\}\}/g, brandInitial)
|
|
501
|
+
.replace(/\{\{BRAND_LOGO\}\}/g, config.brand.logo || "assets/brand/logo.png")
|
|
502
|
+
.replace(/\{\{BRAND_FAVICON\}\}/g, config.brand.favicon || "assets/brand/favicon.png")
|
|
503
|
+
.replace(/\{\{BRAND_LOGO_CLASS\}\}/g, logoClass)
|
|
504
|
+
.replace(/\{\{SITE_TITLE\}\}/g, config.title)
|
|
505
|
+
.replace("{{TITLE}}", config.title)
|
|
506
|
+
.replace("{{VERSION}}", uiVersion)
|
|
507
|
+
.replace("{{BRANCH}}", provenance.gitBranch)
|
|
508
|
+
.replace("{{BREADCRUMBS}}", "")
|
|
509
|
+
.replace("{{PAGE_META}}", "")
|
|
510
|
+
.replace("{{NAV}}", nav)
|
|
511
|
+
.replace("{{CONTENT}}", htmlContent)
|
|
512
|
+
.replace("{{TOC}}", "")
|
|
513
|
+
.replace("{{FOOTER}}", footer)
|
|
514
|
+
.replace("{{THEME_CSS}}", themeCSS)
|
|
515
|
+
.replace("{{PLUGIN_HEAD}}", plugins.head)
|
|
516
|
+
.replace("{{PLUGIN_BODY_END}}", plugins.bodyEnd)
|
|
266
517
|
.replace("{{PRISM_LIGHT_URL}}", prismUrls.light)
|
|
267
518
|
.replace("{{PRISM_DARK_URL}}", prismUrls.dark)
|
|
268
519
|
.replace("{{HOT_RELOAD_SCRIPT}}", hotReloadScript);
|
|
@@ -303,6 +554,7 @@ sections:
|
|
|
303
554
|
const themeCSS = generateThemeCSS(theme);
|
|
304
555
|
const prismUrls = getPrismUrls(theme);
|
|
305
556
|
const pathPrefix = "/";
|
|
557
|
+
const plugins = await getPluginInjectionsCached(config.mode === "slides" ? "slides" : "docs");
|
|
306
558
|
|
|
307
559
|
const hotReloadScript = `
|
|
308
560
|
<script>
|
|
@@ -312,12 +564,15 @@ sections:
|
|
|
312
564
|
</script>`;
|
|
313
565
|
|
|
314
566
|
const logoClass = config.brand.logoType === "wordmark" ? "logo-wordmark" : "logo-icon";
|
|
567
|
+
const brandInitial = escapeHtml(config.brand.name.trim().charAt(0).toUpperCase() || "K");
|
|
315
568
|
|
|
316
569
|
return template
|
|
570
|
+
.replace("{{BODY_CLASS}}", "mode-docs")
|
|
317
571
|
.replace(/\{\{PATH_PREFIX\}\}/g, pathPrefix)
|
|
318
572
|
.replace(/\{\{BRAND_URL\}\}/g, config.brand.url)
|
|
319
573
|
.replace(/\{\{BRAND_TARGET\}\}/g, brandTarget)
|
|
320
574
|
.replace(/\{\{BRAND_NAME\}\}/g, config.brand.name)
|
|
575
|
+
.replace(/\{\{BRAND_INITIAL\}\}/g, brandInitial)
|
|
321
576
|
.replace(/\{\{BRAND_LOGO\}\}/g, config.brand.logo || "assets/brand/logo.png")
|
|
322
577
|
.replace(/\{\{BRAND_FAVICON\}\}/g, config.brand.favicon || "assets/brand/favicon.png")
|
|
323
578
|
.replace(/\{\{BRAND_LOGO_CLASS\}\}/g, logoClass)
|
|
@@ -332,6 +587,8 @@ sections:
|
|
|
332
587
|
.replace("{{TOC}}", "")
|
|
333
588
|
.replace("{{FOOTER}}", buildFooter(provenance, config))
|
|
334
589
|
.replace("{{THEME_CSS}}", themeCSS)
|
|
590
|
+
.replace("{{PLUGIN_HEAD}}", plugins.head)
|
|
591
|
+
.replace("{{PLUGIN_BODY_END}}", plugins.bodyEnd)
|
|
335
592
|
.replace("{{PRISM_LIGHT_URL}}", prismUrls.light)
|
|
336
593
|
.replace("{{PRISM_DARK_URL}}", prismUrls.dark)
|
|
337
594
|
.replace("{{HOT_RELOAD_SCRIPT}}", hotReloadScript);
|
|
@@ -610,6 +867,14 @@ async function main() {
|
|
|
610
867
|
});
|
|
611
868
|
}
|
|
612
869
|
|
|
870
|
+
// Slides mode renders as a single-page deck with hash routing
|
|
871
|
+
if (config.mode === "slides") {
|
|
872
|
+
const html = await renderSlidesPage(provenance, config, theme);
|
|
873
|
+
return new Response(html, {
|
|
874
|
+
headers: { "Content-Type": "text/html" },
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
|
|
613
878
|
// Find and render markdown/yaml file
|
|
614
879
|
const filePath = await findFile(url.pathname, config);
|
|
615
880
|
if (filePath) {
|
|
@@ -652,19 +917,44 @@ async function main() {
|
|
|
652
917
|
return new Response("Not found", { status: 404 });
|
|
653
918
|
}
|
|
654
919
|
|
|
655
|
-
// Wrap with request logging
|
|
656
|
-
const fetch =
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
920
|
+
// Wrap with request logging + friendly plugin errors.
|
|
921
|
+
const fetch = async (req: Request) => {
|
|
922
|
+
const start = performance.now();
|
|
923
|
+
const url = new URL(req.url);
|
|
924
|
+
try {
|
|
925
|
+
const response = await handleRequest(req);
|
|
926
|
+
if (daemonLog && url.pathname !== "/__reload") {
|
|
660
927
|
const duration = (performance.now() - start).toFixed(0);
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
928
|
+
daemonLog.info(`${req.method} ${url.pathname} ${response.status} ${duration}ms`);
|
|
929
|
+
}
|
|
930
|
+
return response;
|
|
931
|
+
} catch (error) {
|
|
932
|
+
const duration = (performance.now() - start).toFixed(0);
|
|
933
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
934
|
+
if (isPluginLoaderError(error)) {
|
|
935
|
+
if (daemonLog && url.pathname !== "/__reload") {
|
|
936
|
+
daemonLog.warn(
|
|
937
|
+
`${req.method} ${url.pathname} 500 ${duration}ms plugin error: ${message}`,
|
|
938
|
+
);
|
|
939
|
+
} else if (!daemonLog) {
|
|
940
|
+
logWarn(`Plugin error: ${message}`);
|
|
664
941
|
}
|
|
665
|
-
return
|
|
942
|
+
return new Response(buildDevPluginErrorHtml(message), {
|
|
943
|
+
status: 500,
|
|
944
|
+
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
if (daemonLog && url.pathname !== "/__reload") {
|
|
948
|
+
daemonLog.error(`${req.method} ${url.pathname} 500 ${duration}ms ${message}`);
|
|
949
|
+
} else if (!daemonLog) {
|
|
950
|
+
console.error(error);
|
|
666
951
|
}
|
|
667
|
-
|
|
952
|
+
return new Response("Internal server error", {
|
|
953
|
+
status: 500,
|
|
954
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" },
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
};
|
|
668
958
|
|
|
669
959
|
// Create server
|
|
670
960
|
Bun.serve({
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* create a temp site directory -> run build() -> verify output files
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
8
9
|
import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
9
10
|
import { tmpdir } from "node:os";
|
|
10
11
|
import { join } from "node:path";
|
|
@@ -31,8 +32,10 @@ async function writeSiteYaml(dir: string, extra: Record<string, unknown> = {}):
|
|
|
31
32
|
const sections = extra.sections ?? " - name: Docs\n path: docs";
|
|
32
33
|
const title = extra.title ?? "Test Site";
|
|
33
34
|
const version = extra.version ? `version: ${extra.version}\n` : "";
|
|
35
|
+
const mode = extra.mode ? `mode: ${extra.mode}\n` : "";
|
|
36
|
+
const aspect = extra.aspect ? `aspect: ${extra.aspect}\n` : "";
|
|
34
37
|
const home = extra.home ? `home: ${extra.home}\n` : "";
|
|
35
|
-
const yaml = `title: ${title}\n${version}brand:\n${brand}\n${home}sections:\n${sections}\n`;
|
|
38
|
+
const yaml = `title: ${title}\n${version}${mode}${aspect}brand:\n${brand}\n${home}sections:\n${sections}\n`;
|
|
36
39
|
await writeFile(join(dir, "site.yaml"), yaml);
|
|
37
40
|
}
|
|
38
41
|
|
|
@@ -43,6 +46,10 @@ async function writeMd(dir: string, relPath: string, content: string): Promise<v
|
|
|
43
46
|
await writeFile(fullPath, content);
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
function sha256Hex(text: string): string {
|
|
50
|
+
return createHash("sha256").update(new TextEncoder().encode(text)).digest("hex");
|
|
51
|
+
}
|
|
52
|
+
|
|
46
53
|
// ---------------------------------------------------------------------------
|
|
47
54
|
// Cleanup
|
|
48
55
|
// ---------------------------------------------------------------------------
|
|
@@ -209,6 +216,41 @@ describe("build", () => {
|
|
|
209
216
|
expect(html).toContain("unversioned");
|
|
210
217
|
});
|
|
211
218
|
|
|
219
|
+
it("builds a single-page hash-routed deck when mode is slides", async () => {
|
|
220
|
+
const siteDir = await makeTempDir();
|
|
221
|
+
const outDir = "out";
|
|
222
|
+
await writeSiteYaml(siteDir, {
|
|
223
|
+
mode: "slides",
|
|
224
|
+
aspect: '"4/3"',
|
|
225
|
+
sections: " - name: Slides\n path: slides",
|
|
226
|
+
});
|
|
227
|
+
await writeMd(
|
|
228
|
+
siteDir,
|
|
229
|
+
"slides/deck.md",
|
|
230
|
+
`---
|
|
231
|
+
title: Intro
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
# Intro
|
|
235
|
+

|
|
236
|
+
[Report](../files/report.pdf)
|
|
237
|
+
--- slide ---
|
|
238
|
+
# Next`,
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
await build({ folder: siteDir, out: outDir });
|
|
242
|
+
|
|
243
|
+
const html = await readFile(join(siteDir, outDir, "index.html"), "utf-8");
|
|
244
|
+
expect(html).toContain('class="mode-slides"');
|
|
245
|
+
expect(html).toContain('id="slide-1"');
|
|
246
|
+
expect(html).toContain('id="slide-2"');
|
|
247
|
+
expect(html).toContain('href="#slide-1"');
|
|
248
|
+
expect(html).toContain('href="#slide-2"');
|
|
249
|
+
expect(html).toContain('src="./slides/img/diagram.png"');
|
|
250
|
+
expect(html).toContain('href="./files/report.pdf"');
|
|
251
|
+
expect(await exists(join(siteDir, outDir, "slides", "deck.html"))).toBe(false);
|
|
252
|
+
});
|
|
253
|
+
|
|
212
254
|
it("generates AI accessibility files (content-index.json, llms.txt, _raw/)", async () => {
|
|
213
255
|
const siteDir = await makeTempDir();
|
|
214
256
|
const outDir = "out";
|
|
@@ -237,4 +279,181 @@ describe("build", () => {
|
|
|
237
279
|
const rawEntries = await readdir(join(dist, "_raw"), { recursive: true });
|
|
238
280
|
expect(rawEntries.length).toBeGreaterThan(0);
|
|
239
281
|
});
|
|
282
|
+
|
|
283
|
+
it("injects enabled plugins into generated HTML", async () => {
|
|
284
|
+
const siteDir = await makeTempDir();
|
|
285
|
+
const outDir = "out";
|
|
286
|
+
await writeSiteYaml(siteDir);
|
|
287
|
+
await writeMd(siteDir, "docs/page.md", "> NOTE: Hello\n\nBody");
|
|
288
|
+
|
|
289
|
+
// Local plugin assets + registry
|
|
290
|
+
const js = "console.log('callouts');";
|
|
291
|
+
const css = ".kitfly-callout{border-left:6px solid red;}";
|
|
292
|
+
await mkdir(join(siteDir, "plugins-dist"), { recursive: true });
|
|
293
|
+
await writeFile(join(siteDir, "plugins-dist", "callouts.js"), js, "utf-8");
|
|
294
|
+
await writeFile(join(siteDir, "plugins-dist", "callouts.css"), css, "utf-8");
|
|
295
|
+
|
|
296
|
+
await mkdir(join(siteDir, "registry"), { recursive: true });
|
|
297
|
+
await writeFile(
|
|
298
|
+
join(siteDir, "registry", "plugins.yaml"),
|
|
299
|
+
`version: 1
|
|
300
|
+
updated: "2026-02-12"
|
|
301
|
+
baseUrl: ""
|
|
302
|
+
plugins:
|
|
303
|
+
callouts:
|
|
304
|
+
name: "Callout Boxes"
|
|
305
|
+
description: "Test callouts"
|
|
306
|
+
version: "0.2.0"
|
|
307
|
+
contract: "1"
|
|
308
|
+
kitfly: ">=0.2.0 <1.0.0"
|
|
309
|
+
license: MIT
|
|
310
|
+
verified: true
|
|
311
|
+
assets:
|
|
312
|
+
js: "plugins-dist/callouts.js"
|
|
313
|
+
css: "plugins-dist/callouts.css"
|
|
314
|
+
assetSha256:
|
|
315
|
+
js: "sha256:${sha256Hex(js)}"
|
|
316
|
+
css: "sha256:${sha256Hex(css)}"
|
|
317
|
+
`,
|
|
318
|
+
"utf-8",
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
await writeFile(
|
|
322
|
+
join(siteDir, "kitfly.plugins.yaml"),
|
|
323
|
+
"plugins:\n - callouts@0.2.0\n",
|
|
324
|
+
"utf-8",
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
await build({ folder: siteDir, out: outDir });
|
|
328
|
+
|
|
329
|
+
const html = await readFile(join(siteDir, outDir, "index.html"), "utf-8");
|
|
330
|
+
expect(html).toContain('data-kitfly-plugin="callouts@0.2.0"');
|
|
331
|
+
expect(html).toContain(css);
|
|
332
|
+
expect(html).toContain(js);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("injects slides-only plugins when mode=slides", async () => {
|
|
336
|
+
const siteDir = await makeTempDir();
|
|
337
|
+
const outDir = "out";
|
|
338
|
+
await writeSiteYaml(siteDir, { mode: "slides" });
|
|
339
|
+
await writeMd(
|
|
340
|
+
siteDir,
|
|
341
|
+
"docs/deck.md",
|
|
342
|
+
`# Title
|
|
343
|
+
|
|
344
|
+
:::kpi
|
|
345
|
+
label: Uptime
|
|
346
|
+
value: 99.95%
|
|
347
|
+
trend: +0.3%
|
|
348
|
+
:::
|
|
349
|
+
`,
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
const js = "console.log('slides visuals');";
|
|
353
|
+
const css = ".kitfly-visual{border:1px solid red;}";
|
|
354
|
+
await mkdir(join(siteDir, "plugins-dist"), { recursive: true });
|
|
355
|
+
await writeFile(join(siteDir, "plugins-dist", "slides-visuals.js"), js, "utf-8");
|
|
356
|
+
await writeFile(join(siteDir, "plugins-dist", "slides-visuals.css"), css, "utf-8");
|
|
357
|
+
|
|
358
|
+
await mkdir(join(siteDir, "registry"), { recursive: true });
|
|
359
|
+
await writeFile(
|
|
360
|
+
join(siteDir, "registry", "plugins.yaml"),
|
|
361
|
+
`version: 1
|
|
362
|
+
updated: "2026-02-13"
|
|
363
|
+
baseUrl: ""
|
|
364
|
+
plugins:
|
|
365
|
+
slides-visuals:
|
|
366
|
+
name: "Slides Visuals"
|
|
367
|
+
description: "Test visuals"
|
|
368
|
+
version: "0.2.1"
|
|
369
|
+
contract: "1"
|
|
370
|
+
kitfly: ">=0.2.0 <1.0.0"
|
|
371
|
+
license: MIT
|
|
372
|
+
verified: true
|
|
373
|
+
modes: ["slides"]
|
|
374
|
+
assets:
|
|
375
|
+
js: "plugins-dist/slides-visuals.js"
|
|
376
|
+
css: "plugins-dist/slides-visuals.css"
|
|
377
|
+
assetSha256:
|
|
378
|
+
js: "sha256:${sha256Hex(js)}"
|
|
379
|
+
css: "sha256:${sha256Hex(css)}"
|
|
380
|
+
`,
|
|
381
|
+
"utf-8",
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
await writeFile(
|
|
385
|
+
join(siteDir, "kitfly.plugins.yaml"),
|
|
386
|
+
"plugins:\n - slides-visuals@0.2.1\n",
|
|
387
|
+
"utf-8",
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
await build({ folder: siteDir, out: outDir });
|
|
391
|
+
|
|
392
|
+
const html = await readFile(join(siteDir, outDir, "index.html"), "utf-8");
|
|
393
|
+
expect(html).toContain('data-kitfly-plugin="slides-visuals@0.2.1"');
|
|
394
|
+
expect(html).toContain(css);
|
|
395
|
+
expect(html).toContain(js);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("ignores unknown slides-visuals block types while enforcing known contracts", async () => {
|
|
399
|
+
const siteDir = await makeTempDir();
|
|
400
|
+
const outDir = "out";
|
|
401
|
+
await writeSiteYaml(siteDir, { mode: "slides" });
|
|
402
|
+
await writeMd(
|
|
403
|
+
siteDir,
|
|
404
|
+
"docs/deck.md",
|
|
405
|
+
`# Title
|
|
406
|
+
|
|
407
|
+
:::future-thing
|
|
408
|
+
note: this should pass through
|
|
409
|
+
:::
|
|
410
|
+
|
|
411
|
+
:::kpi
|
|
412
|
+
label: Uptime
|
|
413
|
+
value: 99.95%
|
|
414
|
+
:::
|
|
415
|
+
`,
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
const js = "console.log('slides visuals');";
|
|
419
|
+
const css = ".kitfly-visual{border:1px solid red;}";
|
|
420
|
+
await mkdir(join(siteDir, "plugins-dist"), { recursive: true });
|
|
421
|
+
await writeFile(join(siteDir, "plugins-dist", "slides-visuals.js"), js, "utf-8");
|
|
422
|
+
await writeFile(join(siteDir, "plugins-dist", "slides-visuals.css"), css, "utf-8");
|
|
423
|
+
await mkdir(join(siteDir, "registry"), { recursive: true });
|
|
424
|
+
await writeFile(
|
|
425
|
+
join(siteDir, "registry", "plugins.yaml"),
|
|
426
|
+
`version: 1
|
|
427
|
+
updated: "2026-02-15"
|
|
428
|
+
baseUrl: ""
|
|
429
|
+
plugins:
|
|
430
|
+
slides-visuals:
|
|
431
|
+
name: "Slides Visuals"
|
|
432
|
+
description: "Test visuals"
|
|
433
|
+
version: "0.2.1"
|
|
434
|
+
contract: "1"
|
|
435
|
+
kitfly: ">=0.2.0 <1.0.0"
|
|
436
|
+
license: MIT
|
|
437
|
+
verified: true
|
|
438
|
+
modes: ["slides"]
|
|
439
|
+
assets:
|
|
440
|
+
js: "plugins-dist/slides-visuals.js"
|
|
441
|
+
css: "plugins-dist/slides-visuals.css"
|
|
442
|
+
assetSha256:
|
|
443
|
+
js: "sha256:${sha256Hex(js)}"
|
|
444
|
+
css: "sha256:${sha256Hex(css)}"
|
|
445
|
+
`,
|
|
446
|
+
"utf-8",
|
|
447
|
+
);
|
|
448
|
+
await writeFile(
|
|
449
|
+
join(siteDir, "kitfly.plugins.yaml"),
|
|
450
|
+
"plugins:\n - slides-visuals@0.2.1\n",
|
|
451
|
+
"utf-8",
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
await expect(build({ folder: siteDir, out: outDir })).resolves.toBeUndefined();
|
|
455
|
+
const html = await readFile(join(siteDir, outDir, "index.html"), "utf-8");
|
|
456
|
+
expect(html).toContain('data-kitfly-plugin="slides-visuals@0.2.1"');
|
|
457
|
+
expect(html).toContain("future-thing");
|
|
458
|
+
});
|
|
240
459
|
});
|