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.
Files changed (194) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +63 -16
  3. package/VERSION +1 -1
  4. package/dist/_raw/content/deployment/preflight.md +134 -0
  5. package/dist/_raw/content/deployment/recipes/aws-s3.md +128 -0
  6. package/dist/_raw/content/deployment/recipes/cloudflare-pages.md +73 -0
  7. package/dist/_raw/content/deployment/recipes/cloudflare-r2.md +156 -0
  8. package/dist/_raw/content/deployment/recipes/fly-io.md +57 -0
  9. package/dist/_raw/content/deployment/recipes/github-pages.md +112 -0
  10. package/dist/_raw/content/deployment/recipes/netlify.md +99 -0
  11. package/dist/_raw/content/deployment/recipes/vercel.md +88 -0
  12. package/dist/_raw/content/deployment/secrets-and-env-vars.md +75 -0
  13. package/dist/_raw/content/deployment.md +128 -0
  14. package/dist/_raw/content/guide/approaches.md +182 -0
  15. package/dist/_raw/content/guide/features.md +121 -0
  16. package/dist/_raw/content/guide/getting-started.md +112 -0
  17. package/dist/_raw/content/guide/kitfly-overview.md +209 -0
  18. package/dist/_raw/content/reference/configuration.md +259 -0
  19. package/dist/_raw/content/reference/design-catalog.md +167 -0
  20. package/dist/_raw/content/reference/environment-variables.md +66 -0
  21. package/dist/_raw/content/reference/glossary.md +92 -0
  22. package/dist/_raw/content/reference/key-concepts.md +118 -0
  23. package/dist/_raw/content/reference/plugins.md +220 -0
  24. package/dist/_raw/content/reference/structure.md +166 -0
  25. package/dist/_raw/content/reference.md +19 -0
  26. package/dist/_raw/content/templates/crucible.md +192 -0
  27. package/dist/_raw/content/templates/handbook.md +83 -0
  28. package/dist/_raw/content/templates/minimal.md +138 -0
  29. package/dist/_raw/content/templates/overview.md +187 -0
  30. package/dist/_raw/content/templates/pipeline.md +151 -0
  31. package/dist/_raw/content/templates/productbook.md +187 -0
  32. package/dist/_raw/content/templates/runbook.md +193 -0
  33. package/dist/_raw/content/templates/servicebook.md +163 -0
  34. package/dist/_raw/docs/decisions/ADR-0001-minimalist-site-code.md +118 -0
  35. package/dist/_raw/docs/decisions/ADR-0002-ai-accessibility.md +153 -0
  36. package/dist/_raw/docs/decisions/ADR-0003-single-file-bundle.md +93 -0
  37. package/dist/_raw/docs/decisions/ADR-0004-bun-runtime.md +98 -0
  38. package/dist/_raw/docs/decisions/ADR-0005-plugin-contract-and-distribution.md +110 -0
  39. package/dist/_raw/docs/decisions/DDR-0001-viewport-locked-layout.md +111 -0
  40. package/dist/_raw/docs/decisions/DDR-0002-theme-system.md +131 -0
  41. package/dist/_raw/docs/decisions/DDR-0003-bounded-logo-slot.md +106 -0
  42. package/dist/_raw/docs/decisions/DDR-0004-slides-rendering-model.md +113 -0
  43. package/dist/_raw/docs/decisions/DDR-0005-deterministic-layout-boundary.md +107 -0
  44. package/dist/_raw/docs/userguide/cli/build.md +85 -0
  45. package/dist/_raw/docs/userguide/cli/bundle.md +81 -0
  46. package/dist/_raw/docs/userguide/cli/dev.md +92 -0
  47. package/dist/_raw/docs/userguide/cli/init.md +116 -0
  48. package/dist/_raw/docs/userguide/cli/servers.md +69 -0
  49. package/dist/_raw/docs/userguide/cli/stop.md +76 -0
  50. package/dist/_raw/docs/userguide/cli/update.md +78 -0
  51. package/dist/_raw/docs/userguide/cli/version.md +65 -0
  52. package/dist/_raw/docs/userguide/cli.md +34 -0
  53. package/dist/_raw/docs/userguide/sharing.md +94 -0
  54. package/dist/_raw/schemas/plugin-schemas-notes.md +71 -0
  55. package/dist/_raw/schemas.md +42 -0
  56. package/dist/assets/brand/kitfly-favicon-32.png +0 -0
  57. package/dist/assets/brand/kitfly-icon-64.png +0 -0
  58. package/dist/assets/brand/kitfly-logo-128.png +0 -0
  59. package/dist/assets/brand/kitfly-logo-512.png +0 -0
  60. package/dist/assets/brand/kitfly-logo.svg +12132 -0
  61. package/dist/assets/brand/kitfly-neon-128.png +0 -0
  62. package/dist/assets/brand/kitfly-neon-192.png +0 -0
  63. package/dist/assets/brand/kitfly-neon-256.png +0 -0
  64. package/dist/assets/brand/kitfly-neon.png +0 -0
  65. package/dist/assets/brand/palette.md +75 -0
  66. package/dist/content/deployment/index.html +11 -0
  67. package/dist/content/deployment/preflight.html +418 -0
  68. package/dist/content/deployment/recipes/aws-s3.html +421 -0
  69. package/dist/content/deployment/recipes/cloudflare-pages.html +372 -0
  70. package/dist/content/deployment/recipes/cloudflare-r2.html +443 -0
  71. package/dist/content/deployment/recipes/fly-io.html +356 -0
  72. package/dist/content/deployment/recipes/github-pages.html +414 -0
  73. package/dist/content/deployment/recipes/index.html +11 -0
  74. package/dist/content/deployment/recipes/netlify.html +394 -0
  75. package/dist/content/deployment/recipes/vercel.html +382 -0
  76. package/dist/content/deployment/secrets-and-env-vars.html +380 -0
  77. package/dist/content/deployment.html +426 -0
  78. package/dist/content/guide/approaches.html +501 -0
  79. package/dist/content/guide/features.html +436 -0
  80. package/dist/content/guide/getting-started.html +403 -0
  81. package/dist/content/guide/index.html +11 -0
  82. package/dist/content/guide/kitfly-overview.html +544 -0
  83. package/dist/content/index.html +11 -0
  84. package/dist/content/reference/configuration.html +580 -0
  85. package/dist/content/reference/design-catalog.html +449 -0
  86. package/dist/content/reference/environment-variables.html +367 -0
  87. package/dist/content/reference/glossary.html +368 -0
  88. package/dist/content/reference/index.html +11 -0
  89. package/dist/content/reference/key-concepts.html +399 -0
  90. package/dist/content/reference/plugins.html +491 -0
  91. package/dist/content/reference/structure.html +463 -0
  92. package/dist/content/reference.html +334 -0
  93. package/dist/content/templates/crucible.html +546 -0
  94. package/dist/content/templates/handbook.html +405 -0
  95. package/dist/content/templates/index.html +11 -0
  96. package/dist/content/templates/minimal.html +447 -0
  97. package/dist/content/templates/overview.html +558 -0
  98. package/dist/content/templates/pipeline.html +494 -0
  99. package/dist/content/templates/productbook.html +540 -0
  100. package/dist/content/templates/runbook.html +543 -0
  101. package/dist/content/templates/servicebook.html +523 -0
  102. package/dist/content-index.json +540 -0
  103. package/dist/docs/decisions/ADR-0001-minimalist-site-code.html +491 -0
  104. package/dist/docs/decisions/ADR-0002-ai-accessibility.html +434 -0
  105. package/dist/docs/decisions/ADR-0003-single-file-bundle.html +412 -0
  106. package/dist/docs/decisions/ADR-0004-bun-runtime.html +409 -0
  107. package/dist/docs/decisions/ADR-0005-plugin-contract-and-distribution.html +402 -0
  108. package/dist/docs/decisions/DDR-0001-viewport-locked-layout.html +459 -0
  109. package/dist/docs/decisions/DDR-0002-theme-system.html +452 -0
  110. package/dist/docs/decisions/DDR-0003-bounded-logo-slot.html +423 -0
  111. package/dist/docs/decisions/DDR-0004-slides-rendering-model.html +399 -0
  112. package/dist/docs/decisions/DDR-0005-deterministic-layout-boundary.html +422 -0
  113. package/dist/docs/decisions/index.html +11 -0
  114. package/dist/docs/userguide/cli/build.html +408 -0
  115. package/dist/docs/userguide/cli/bundle.html +419 -0
  116. package/dist/docs/userguide/cli/dev.html +428 -0
  117. package/dist/docs/userguide/cli/index.html +11 -0
  118. package/dist/docs/userguide/cli/init.html +436 -0
  119. package/dist/docs/userguide/cli/servers.html +393 -0
  120. package/dist/docs/userguide/cli/stop.html +408 -0
  121. package/dist/docs/userguide/cli/update.html +406 -0
  122. package/dist/docs/userguide/cli/version.html +406 -0
  123. package/dist/docs/userguide/cli.html +386 -0
  124. package/dist/docs/userguide/index.html +11 -0
  125. package/dist/docs/userguide/sharing.html +465 -0
  126. package/dist/index.html +387 -0
  127. package/dist/llms.txt +18 -0
  128. package/dist/provenance.json +7 -0
  129. package/dist/schemas/index.html +11 -0
  130. package/dist/schemas/plugin-registry.schema.html +327 -0
  131. package/dist/schemas/plugin-schemas-notes.html +364 -0
  132. package/dist/schemas/plugin.schema.html +327 -0
  133. package/dist/schemas/plugins.schema.html +327 -0
  134. package/dist/schemas/v0/common.schema.html +386 -0
  135. package/dist/schemas/v0/index.html +11 -0
  136. package/dist/schemas/v0/plugin-registry.schema.html +547 -0
  137. package/dist/schemas/v0/plugin.schema.html +497 -0
  138. package/dist/schemas/v0/plugins.schema.html +406 -0
  139. package/dist/schemas/v0/site.schema.html +541 -0
  140. package/dist/schemas/v0/theme.schema.html +615 -0
  141. package/dist/schemas.html +351 -0
  142. package/dist/styles.css +1262 -0
  143. package/package.json +4 -2
  144. package/plugins-dist/callouts.css +32 -0
  145. package/plugins-dist/callouts.js +46 -0
  146. package/plugins-dist/slides-visuals.css +224 -0
  147. package/plugins-dist/slides-visuals.js +598 -0
  148. package/registry/plugins.yaml +35 -0
  149. package/schemas/README.md +10 -0
  150. package/schemas/plugin-registry.schema.json +5 -0
  151. package/schemas/plugin-schemas-notes.md +71 -0
  152. package/schemas/plugin.schema.json +5 -0
  153. package/schemas/plugins.schema.json +5 -0
  154. package/schemas/v0/common.schema.json +64 -0
  155. package/schemas/v0/plugin-registry.schema.json +225 -0
  156. package/schemas/v0/plugin.schema.json +175 -0
  157. package/schemas/v0/plugins.schema.json +84 -0
  158. package/schemas/v0/site.schema.json +56 -9
  159. package/schemas/v0/theme.schema.json +105 -22
  160. package/scripts/build.ts +155 -3
  161. package/scripts/bundle.ts +258 -95
  162. package/scripts/dev.ts +203 -1
  163. package/src/__tests__/build.test.ts +158 -1
  164. package/src/__tests__/bundle.test.ts +31 -0
  165. package/src/__tests__/cli.test.ts +14 -3
  166. package/src/__tests__/fixtures/fences/slides-visuals/invalid/bad-list-indent.md +5 -0
  167. package/src/__tests__/fixtures/fences/slides-visuals/invalid/blank-line.md +5 -0
  168. package/src/__tests__/fixtures/fences/slides-visuals/invalid/compare-object-items.md +9 -0
  169. package/src/__tests__/fixtures/fences/slides-visuals/invalid/indented-fence.md +4 -0
  170. package/src/__tests__/fixtures/fences/slides-visuals/invalid/stat-grid-missing-fields.md +5 -0
  171. package/src/__tests__/fixtures/fences/slides-visuals/invalid/unknown-type.md +3 -0
  172. package/src/__tests__/fixtures/fences/slides-visuals/valid/compare.md +10 -0
  173. package/src/__tests__/fixtures/fences/slides-visuals/valid/comparison-table.md +14 -0
  174. package/src/__tests__/fixtures/fences/slides-visuals/valid/funnel.md +7 -0
  175. package/src/__tests__/fixtures/fences/slides-visuals/valid/kpi.md +5 -0
  176. package/src/__tests__/fixtures/fences/slides-visuals/valid/layer-cake.md +6 -0
  177. package/src/__tests__/fixtures/fences/slides-visuals/valid/pyramid.md +6 -0
  178. package/src/__tests__/fixtures/fences/slides-visuals/valid/quadrant-grid.md +8 -0
  179. package/src/__tests__/fixtures/fences/slides-visuals/valid/scorecard.md +13 -0
  180. package/src/__tests__/fixtures/fences/slides-visuals/valid/stat-grid.md +8 -0
  181. package/src/__tests__/init.test.ts +35 -0
  182. package/src/__tests__/plugin-loader.test.ts +221 -0
  183. package/src/__tests__/shared.test.ts +428 -0
  184. package/src/__tests__/slides-visuals-fence-contract.test.ts +28 -0
  185. package/src/__tests__/slides-visuals-runtime-regressions.bun.test.ts +114 -0
  186. package/src/__tests__/styles.test.ts +35 -0
  187. package/src/cli.ts +9 -4
  188. package/src/plugin-loader.ts +245 -0
  189. package/src/shared.ts +614 -7
  190. package/src/site/styles.css +331 -0
  191. package/src/site/template.html +66 -5
  192. package/src/templates/deck.ts +186 -0
  193. package/src/templates/driver.ts +11 -1
  194. package/src/templates/minimal.ts +1 -0
@@ -0,0 +1,8 @@
1
+ :::quadrant-grid
2
+ axis-x: "Effort"
3
+ axis-y: "Impact"
4
+ tl: "Quick Wins"
5
+ tr: "Major Projects"
6
+ bl: "Fill-ins"
7
+ br: "Thankless Tasks"
8
+ :::
@@ -0,0 +1,13 @@
1
+ :::scorecard
2
+ metrics:
3
+ - label: "MRR"
4
+ value: "$240k"
5
+ trend: "+6%"
6
+ - label: "Churn"
7
+ value: "1.2%"
8
+ trend: "-0.3%"
9
+ - label: "NPS"
10
+ value: "48"
11
+ - label: "DAU"
12
+ value: "12,400"
13
+ :::
@@ -0,0 +1,8 @@
1
+ :::stat-grid
2
+ metrics:
3
+ - label: Users
4
+ value: 1,234
5
+ - label: Uptime
6
+ value: 99.95%
7
+ trend: +0.3%
8
+ :::
@@ -51,6 +51,7 @@ describe("template registry", () => {
51
51
  const ids = templates.map((t) => t.id);
52
52
 
53
53
  expect(ids).toContain("minimal");
54
+ expect(ids).toContain("deck");
54
55
  expect(ids).toContain("handbook");
55
56
  expect(templates.length).toBeGreaterThanOrEqual(2);
56
57
  });
@@ -257,6 +258,40 @@ describe("handbook template", () => {
257
258
  });
258
259
  });
259
260
 
261
+ // ---------------------------------------------------------------------------
262
+ // Deck Template (slides mode)
263
+ // ---------------------------------------------------------------------------
264
+
265
+ describe("deck template", () => {
266
+ const projectName = "test-deck";
267
+
268
+ it("creates slides-focused starter files", async () => {
269
+ await runTemplate({
270
+ name: projectName,
271
+ template: "deck",
272
+ git: false,
273
+ });
274
+
275
+ expect(generatedExists(projectName, "site.yaml")).toBe(true);
276
+ expect(generatedExists(projectName, "content/slides/briefing.md")).toBe(true);
277
+ expect(generatedExists(projectName, "CUSTOMIZING.md")).toBe(true);
278
+ });
279
+
280
+ it("configures site.yaml for slides mode", async () => {
281
+ await runTemplate({
282
+ name: projectName,
283
+ template: "deck",
284
+ git: false,
285
+ });
286
+
287
+ const siteYaml = await readGenerated(projectName, "site.yaml");
288
+ expect(siteYaml).toContain("mode: slides");
289
+ expect(siteYaml).toContain('aspect: "16/9"');
290
+ expect(siteYaml).toContain('name: "Slides"');
291
+ expect(siteYaml).toContain('path: "content/slides"');
292
+ });
293
+ });
294
+
260
295
  // ---------------------------------------------------------------------------
261
296
  // Custom Branding
262
297
  // ---------------------------------------------------------------------------
@@ -0,0 +1,221 @@
1
+ import { createHash } from "node:crypto";
2
+ import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { describe, expect, it } from "vitest";
7
+ import {
8
+ loadPluginInjections,
9
+ loadPluginRegistry,
10
+ PluginConfigError,
11
+ PluginIntegrityError,
12
+ } from "../plugin-loader.ts";
13
+
14
+ const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
15
+
16
+ function sha256Hex(text: string): string {
17
+ return createHash("sha256").update(new TextEncoder().encode(text)).digest("hex");
18
+ }
19
+
20
+ describe("plugin loader", () => {
21
+ it("inlines local assets with checksum verification", async () => {
22
+ const root = await mkdtemp(join(tmpdir(), "kitfly-plugins-"));
23
+
24
+ await mkdir(join(root, "registry"), { recursive: true });
25
+ await mkdir(join(root, "plugins-dist"), { recursive: true });
26
+
27
+ const js = "console.log('hello plugin');";
28
+ const css = ".hello{color:red;}";
29
+ await writeFile(join(root, "plugins-dist", "hello.js"), js, "utf-8");
30
+ await writeFile(join(root, "plugins-dist", "hello.css"), css, "utf-8");
31
+
32
+ const registryYaml = `version: 1
33
+ updated: "2026-02-12"
34
+ baseUrl: ""
35
+ plugins:
36
+ hello:
37
+ name: "Hello"
38
+ description: "Test plugin"
39
+ version: "1.0.0"
40
+ contract: "1"
41
+ kitfly: ">=0.2.0 <1.0.0"
42
+ license: MIT
43
+ verified: true
44
+ assets:
45
+ js: "plugins-dist/hello.js"
46
+ css: "plugins-dist/hello.css"
47
+ assetSha256:
48
+ js: "sha256:${sha256Hex(js)}"
49
+ css: "sha256:${sha256Hex(css)}"
50
+ `;
51
+
52
+ await writeFile(join(root, "registry", "plugins.yaml"), registryYaml, "utf-8");
53
+
54
+ const configYaml = `plugins:
55
+ - hello@1.0.0
56
+ `;
57
+ await writeFile(join(root, "kitfly.plugins.yaml"), configYaml, "utf-8");
58
+
59
+ const injections = await loadPluginInjections({ root });
60
+ expect(injections.head).toContain(css);
61
+ expect(injections.bodyEnd).toContain(js);
62
+ });
63
+
64
+ it("respects plugin mode allowlist (modes)", async () => {
65
+ const root = await mkdtemp(join(tmpdir(), "kitfly-plugins-"));
66
+
67
+ await mkdir(join(root, "registry"), { recursive: true });
68
+ await mkdir(join(root, "plugins-dist"), { recursive: true });
69
+
70
+ const js = "console.log('hello plugin');";
71
+ await writeFile(join(root, "plugins-dist", "hello.js"), js, "utf-8");
72
+
73
+ const registryYaml = `version: 1
74
+ updated: "2026-02-12"
75
+ baseUrl: ""
76
+ plugins:
77
+ hello:
78
+ name: "Hello"
79
+ description: "Test plugin"
80
+ version: "1.0.0"
81
+ contract: "1"
82
+ kitfly: ">=0.2.0 <1.0.0"
83
+ license: MIT
84
+ verified: true
85
+ modes: ["slides"]
86
+ assets:
87
+ js: "plugins-dist/hello.js"
88
+ assetSha256:
89
+ js: "sha256:${sha256Hex(js)}"
90
+ `;
91
+
92
+ await writeFile(join(root, "registry", "plugins.yaml"), registryYaml, "utf-8");
93
+ await writeFile(join(root, "kitfly.plugins.yaml"), "plugins:\n - hello@1.0.0\n", "utf-8");
94
+
95
+ const docs = await loadPluginInjections({ root, mode: "docs" });
96
+ expect(docs.head).toBe("");
97
+ expect(docs.bodyEnd).toBe("");
98
+
99
+ const slides = await loadPluginInjections({ root, mode: "slides" });
100
+ expect(slides.bodyEnd).toContain(js);
101
+ });
102
+
103
+ it("treats empty modes as blocked", async () => {
104
+ const root = await mkdtemp(join(tmpdir(), "kitfly-plugins-"));
105
+
106
+ await mkdir(join(root, "registry"), { recursive: true });
107
+ await mkdir(join(root, "plugins-dist"), { recursive: true });
108
+
109
+ const js = "console.log('hello plugin');";
110
+ await writeFile(join(root, "plugins-dist", "hello.js"), js, "utf-8");
111
+
112
+ const registryYaml = `version: 1
113
+ updated: "2026-02-12"
114
+ baseUrl: ""
115
+ plugins:
116
+ hello:
117
+ name: "Hello"
118
+ description: "Test plugin"
119
+ version: "1.0.0"
120
+ contract: "1"
121
+ kitfly: ">=0.2.0 <1.0.0"
122
+ license: MIT
123
+ verified: true
124
+ modes: []
125
+ assets:
126
+ js: "plugins-dist/hello.js"
127
+ assetSha256:
128
+ js: "sha256:${sha256Hex(js)}"
129
+ `;
130
+
131
+ await writeFile(join(root, "registry", "plugins.yaml"), registryYaml, "utf-8");
132
+ await writeFile(join(root, "kitfly.plugins.yaml"), "plugins:\n - hello@1.0.0\n", "utf-8");
133
+
134
+ const injections = await loadPluginInjections({ root, mode: "slides" });
135
+ expect(injections.head).toBe("");
136
+ expect(injections.bodyEnd).toBe("");
137
+ });
138
+
139
+ it("throws on checksum mismatch", async () => {
140
+ const root = await mkdtemp(join(tmpdir(), "kitfly-plugins-"));
141
+
142
+ await mkdir(join(root, "registry"), { recursive: true });
143
+ await mkdir(join(root, "plugins-dist"), { recursive: true });
144
+
145
+ const js = "console.log('hello plugin');";
146
+ await writeFile(join(root, "plugins-dist", "hello.js"), js, "utf-8");
147
+
148
+ const registryYaml = `version: 1
149
+ updated: "2026-02-12"
150
+ baseUrl: ""
151
+ plugins:
152
+ hello:
153
+ name: "Hello"
154
+ description: "Test plugin"
155
+ version: "1.0.0"
156
+ contract: "1"
157
+ kitfly: ">=0.2.0 <1.0.0"
158
+ license: MIT
159
+ verified: true
160
+ assets:
161
+ js: "plugins-dist/hello.js"
162
+ assetSha256:
163
+ js: "sha256:${"0".repeat(64)}"
164
+ `;
165
+
166
+ await writeFile(join(root, "registry", "plugins.yaml"), registryYaml, "utf-8");
167
+ await writeFile(join(root, "kitfly.plugins.yaml"), "plugins:\n - hello@1.0.0\n", "utf-8");
168
+
169
+ await expect(loadPluginInjections({ root })).rejects.toBeInstanceOf(PluginIntegrityError);
170
+ });
171
+
172
+ it("rejects invalid canonical refs (prevents attribute injection)", async () => {
173
+ const root = await mkdtemp(join(tmpdir(), "kitfly-plugins-"));
174
+ await mkdir(join(root, "registry"), { recursive: true });
175
+
176
+ await writeFile(
177
+ join(root, "registry", "plugins.yaml"),
178
+ `version: 1
179
+ updated: "2026-02-12"
180
+ baseUrl: ""
181
+ plugins: {}
182
+ `,
183
+ "utf-8",
184
+ );
185
+
186
+ await writeFile(
187
+ join(root, "kitfly.plugins.yaml"),
188
+ `plugins:
189
+ - bad"@1.0.0
190
+ `,
191
+ "utf-8",
192
+ );
193
+
194
+ await expect(loadPluginInjections({ root })).rejects.toBeInstanceOf(PluginConfigError);
195
+ });
196
+ });
197
+
198
+ describe("registry consistency", () => {
199
+ it("all plugin dist checksums match registry/plugins.yaml", async () => {
200
+ const registryPath = join(REPO_ROOT, "registry", "plugins.yaml");
201
+ const registry = await loadPluginRegistry(registryPath);
202
+
203
+ for (const [id, plugin] of Object.entries(registry.plugins)) {
204
+ const { assets } = plugin;
205
+ for (const kind of ["js", "css"] as const) {
206
+ const relPath = assets[kind];
207
+ if (!relPath) continue;
208
+
209
+ const expectedRaw = assets.assetSha256[kind];
210
+ if (!expectedRaw) throw new Error(`${id}: missing assetSha256.${kind}`);
211
+
212
+ const expectedHex = expectedRaw.replace(/^sha256:/, "").toLowerCase();
213
+ const filePath = join(REPO_ROOT, relPath);
214
+ const content = await readFile(filePath);
215
+ const actualHex = createHash("sha256").update(content).digest("hex");
216
+
217
+ expect(actualHex, `${id} ${kind} (${relPath})`).toBe(expectedHex);
218
+ }
219
+ }
220
+ });
221
+ });