@cosmicdrift/kumiko-dev-server 0.328.1 → 0.330.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 (125) hide show
  1. package/README.md +17 -0
  2. package/bin/kumiko-build.ts +3 -2
  3. package/bin/kumiko-dev.ts +1 -1
  4. package/bin/kumiko-init-deploy.ts +1 -1
  5. package/bin/kumiko-schema-check.ts +4 -1
  6. package/dist/build-server-bundle.d.ts +31 -0
  7. package/dist/build-server-bundle.js +282 -0
  8. package/dist/build.d.ts +2 -0
  9. package/dist/build.js +5 -0
  10. package/dist/cli.d.ts +5 -0
  11. package/dist/cli.js +9 -0
  12. package/dist/codegen/index.d.ts +4 -0
  13. package/dist/codegen/index.js +4 -0
  14. package/dist/codegen/render.d.ts +55 -0
  15. package/dist/codegen/render.js +273 -0
  16. package/dist/codegen/run-codegen.d.ts +20 -0
  17. package/dist/codegen/run-codegen.js +219 -0
  18. package/dist/codegen/scan-events.d.ts +76 -0
  19. package/dist/codegen/scan-events.js +447 -0
  20. package/dist/codegen/watch.d.ts +33 -0
  21. package/dist/codegen/watch.js +112 -0
  22. package/dist/compose-stacks.d.ts +60 -0
  23. package/dist/compose-stacks.js +127 -0
  24. package/dist/crash-tracker.d.ts +17 -0
  25. package/dist/crash-tracker.js +37 -0
  26. package/dist/create-kumiko-server.d.ts +195 -0
  27. package/dist/create-kumiko-server.js +1040 -0
  28. package/dist/env-schema.d.ts +15 -0
  29. package/dist/env-schema.js +60 -0
  30. package/dist/few-shot-corpus.d.ts +72 -0
  31. package/dist/few-shot-corpus.js +262 -0
  32. package/dist/framework-version.d.ts +1 -0
  33. package/dist/framework-version.js +31 -0
  34. package/dist/index.d.ts +22 -0
  35. package/dist/index.js +33 -0
  36. package/dist/init-deploy-cli.d.ts +10 -0
  37. package/dist/init-deploy-cli.js +111 -0
  38. package/dist/kebab.d.ts +1 -0
  39. package/{src/kebab.ts → dist/kebab.js} +2 -3
  40. package/dist/run-dev-app.d.ts +174 -0
  41. package/dist/run-dev-app.js +403 -0
  42. package/dist/scaffold-app-feature.d.ts +16 -0
  43. package/dist/scaffold-app-feature.js +240 -0
  44. package/dist/scaffold-app.d.ts +45 -0
  45. package/dist/scaffold-app.js +852 -0
  46. package/dist/scaffold-demo-tasks.d.ts +8 -0
  47. package/{src/scaffold-demo-tasks.ts → dist/scaffold-demo-tasks.js} +69 -98
  48. package/dist/scaffold-deploy.d.ts +92 -0
  49. package/dist/scaffold-deploy.js +254 -0
  50. package/dist/scaffold-feature.d.ts +26 -0
  51. package/dist/scaffold-feature.js +163 -0
  52. package/dist/schema-apply.d.ts +11 -0
  53. package/dist/schema-apply.js +95 -0
  54. package/dist/schema-check-core.d.ts +2 -0
  55. package/{src/schema-check-core.ts → dist/schema-check-core.js} +8 -10
  56. package/dist/schema-tables-auth-mode.d.ts +2 -0
  57. package/{src/schema-tables-auth-mode.ts → dist/schema-tables-auth-mode.js} +2 -10
  58. package/dist/schema-tables-minimal.d.ts +2 -0
  59. package/{src/schema-tables-minimal.ts → dist/schema-tables-minimal.js} +2 -11
  60. package/dist/setup-test-stack-from-features.d.ts +11 -0
  61. package/dist/setup-test-stack-from-features.js +42 -0
  62. package/dist/welcome-banner.d.ts +15 -0
  63. package/dist/welcome-banner.js +27 -0
  64. package/package.json +71 -24
  65. package/src/changes.json +6 -0
  66. package/src/__tests__/build-prod-bundle.integration.test.ts +0 -282
  67. package/src/__tests__/build-server-bundle.test.ts +0 -228
  68. package/src/__tests__/classify-change.test.ts +0 -87
  69. package/src/__tests__/compose-stacks.test.ts +0 -273
  70. package/src/__tests__/config-resolver-default.integration.test.ts +0 -165
  71. package/src/__tests__/crash-tracker.test.ts +0 -89
  72. package/src/__tests__/create-kumiko-server-errors.test.ts +0 -54
  73. package/src/__tests__/create-kumiko-server-options.test.ts +0 -33
  74. package/src/__tests__/create-kumiko-server.integration.test.ts +0 -1007
  75. package/src/__tests__/discover-format.test.ts +0 -32
  76. package/src/__tests__/env-schema.integration.test.ts +0 -248
  77. package/src/__tests__/env-schema.test.ts +0 -132
  78. package/src/__tests__/few-shot-corpus.test.ts +0 -316
  79. package/src/__tests__/framework-version.test.ts +0 -45
  80. package/src/__tests__/init-deploy-cli.test.ts +0 -113
  81. package/src/__tests__/kumiko-schema-check.test.ts +0 -56
  82. package/src/__tests__/merge-extra-context.test.ts +0 -67
  83. package/src/__tests__/resolve-stylesheet.test.ts +0 -132
  84. package/src/__tests__/run-dev-app-boot-validation.test.ts +0 -38
  85. package/src/__tests__/run-dev-app.integration.test.ts +0 -290
  86. package/src/__tests__/runtime-classification.test.ts +0 -26
  87. package/src/__tests__/saas-identity-wire.integration.test.ts +0 -427
  88. package/src/__tests__/scaffold-app-feature.test.ts +0 -216
  89. package/src/__tests__/scaffold-app.test.ts +0 -630
  90. package/src/__tests__/scaffold-demo-tasks.test.ts +0 -47
  91. package/src/__tests__/scaffold-deploy.test.ts +0 -464
  92. package/src/__tests__/scaffold-dev-cycle.integration.test.ts +0 -109
  93. package/src/__tests__/scaffold-feature.test.ts +0 -143
  94. package/src/__tests__/schema-apply.integration.test.ts +0 -191
  95. package/src/__tests__/setup-test-stack-from-features.integration.test.ts +0 -30
  96. package/src/__tests__/setup-test-stack-job-run-logger.integration.test.ts +0 -72
  97. package/src/__tests__/walkthrough.integration.test.ts +0 -140
  98. package/src/__tests__/welcome-banner.test.ts +0 -42
  99. package/src/build-server-bundle.ts +0 -346
  100. package/src/build.ts +0 -28
  101. package/src/codegen/__tests__/render-codegen.test.ts +0 -100
  102. package/src/codegen/__tests__/run-codegen.test.ts +0 -665
  103. package/src/codegen/__tests__/strict-mode-diagnostics.test.ts +0 -347
  104. package/src/codegen/__tests__/watch.test.ts +0 -222
  105. package/src/codegen/index.ts +0 -19
  106. package/src/codegen/render.ts +0 -305
  107. package/src/codegen/run-codegen.ts +0 -278
  108. package/src/codegen/scan-events.ts +0 -565
  109. package/src/codegen/watch.ts +0 -140
  110. package/src/compose-stacks.ts +0 -193
  111. package/src/crash-tracker.ts +0 -56
  112. package/src/create-kumiko-server.ts +0 -1397
  113. package/src/env-schema.ts +0 -72
  114. package/src/few-shot-corpus.ts +0 -370
  115. package/src/framework-version.ts +0 -39
  116. package/src/index.ts +0 -105
  117. package/src/init-deploy-cli.ts +0 -148
  118. package/src/run-dev-app.ts +0 -694
  119. package/src/scaffold-app-feature.ts +0 -283
  120. package/src/scaffold-app.ts +0 -1033
  121. package/src/scaffold-deploy.ts +0 -393
  122. package/src/scaffold-feature.ts +0 -226
  123. package/src/schema-apply.ts +0 -123
  124. package/src/setup-test-stack-from-features.ts +0 -80
  125. package/src/welcome-banner.ts +0 -43
@@ -1,665 +0,0 @@
1
- // Codegen — End-to-End-Test mit realistischen Feature-Files.
2
- //
3
- // Schreibt Features + events.ts in ein tmp-Verzeichnis, ruft runCodegen
4
- // auf, und prüft die generierten Files.
5
- //
6
- // Was hier verifiziert wird:
7
- // - Position-Form `r.defineEvent("name", schema)` → "imported"
8
- // - Object-Form `r.defineEvent({ name, schema })` → "imported"
9
- // - Inline-Form `r.defineEvent("name", z.object({...}))` → "inline"
10
- // - Computed-Name `r.defineEvent(NAME_CONST.member, schema)` → "imported"
11
- // mit string-resolved Name aus dem `as const`-Object
12
- // - Mehrere Features in unterschiedlichen Files werden zusammengeführt
13
- // - Doppelte qualifiedName erzeugen Warning + werden dedupliziert
14
- // - Idempotent: 2× run schreibt beim 2. Mal nicht (didWrite=false)
15
- // - Locally-declared schema (kein import, kein inline-z.*) → Warning, skip
16
- // - skipped-Flag wenn 0 Events UND kein .kumiko/ schon existiert
17
- // - schemas.generated.ts wird erzeugt wenn inline-Schemas, sonst nicht
18
-
19
- import { describe, expect, test } from "bun:test";
20
- import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
21
- import { tmpdir } from "node:os";
22
- import { join } from "node:path";
23
- import { runCodegen } from "../run-codegen";
24
-
25
- function makeAppDir(): string {
26
- return mkdtempSync(join(tmpdir(), "kumiko-codegen-"));
27
- }
28
-
29
- function write(dir: string, relPath: string, content: string): string {
30
- const full = join(dir, relPath);
31
- const lastSep = full.lastIndexOf("/");
32
- mkdirSync(full.substring(0, lastSep), { recursive: true });
33
- writeFileSync(full, content, "utf-8");
34
- return full;
35
- }
36
-
37
- describe("runCodegen", () => {
38
- test("scans position-form r.defineEvent + writes augmentation", () => {
39
- const appRoot = makeAppDir();
40
-
41
- write(
42
- appRoot,
43
- "src/feature/events.ts",
44
- `import { z } from "zod";
45
- export const fooSchema = z.object({ id: z.string() });
46
- `,
47
- );
48
- write(
49
- appRoot,
50
- "src/feature/feature.ts",
51
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
52
- import { fooSchema } from "./events";
53
-
54
- export const myFeature = defineFeature("myFeat", (r) => {
55
- r.defineEvent("foo-happened", fooSchema);
56
- });
57
- `,
58
- );
59
-
60
- // handlerQns: [] opts out of the #2757 manifest-fallback warning — out of scope here.
61
- const result = runCodegen({ appRoot, handlerQns: [] });
62
-
63
- expect(result.eventCount).toBe(1);
64
- expect(result.warnings).toEqual([]);
65
- expect(result.didWriteTypes).toBe(true);
66
- expect(result.didWriteDefine).toBe(true);
67
- expect(result.didWriteSchemas).toBe(false);
68
-
69
- const types = readFileSync(join(appRoot, ".kumiko", "types.generated.d.ts"), "utf-8");
70
- expect(types).toContain(`"my-feat:event:foo-happened": z.infer<typeof fooSchema>;`);
71
- expect(types).toContain(`import type { fooSchema }`);
72
- expect(types).toContain(`export {};`);
73
-
74
- const define = readFileSync(join(appRoot, ".kumiko", "define.ts"), "utf-8");
75
- expect(define).toContain(`/// <reference path="./types.generated.d.ts" />`);
76
- expect(define).toContain(`export function defineWriteHandler<`);
77
- expect(define).toContain(
78
- `fwDefineWriteHandler<TName, TSchema, TData, KumikoEventTypeMap>(def)`,
79
- );
80
- expect(define).toContain(`export function defineQueryHandler<`);
81
-
82
- expect(existsSync(join(appRoot, ".kumiko", "schemas.generated.ts"))).toBe(false);
83
- });
84
-
85
- test("scans object-form r.defineEvent", () => {
86
- const appRoot = makeAppDir();
87
- write(
88
- appRoot,
89
- "src/feature/events.ts",
90
- `import { z } from "zod";
91
- export const barSchema = z.object({ count: z.number() });
92
- `,
93
- );
94
- write(
95
- appRoot,
96
- "src/feature/feature.ts",
97
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
98
- import { barSchema } from "./events";
99
-
100
- export const myFeature = defineFeature("objForm", (r) => {
101
- r.defineEvent({ name: "bar-occurred", schema: barSchema });
102
- });
103
- `,
104
- );
105
-
106
- const result = runCodegen({ appRoot });
107
- expect(result.eventCount).toBe(1);
108
- const types = readFileSync(join(appRoot, ".kumiko", "types.generated.d.ts"), "utf-8");
109
- expect(types).toContain(`"obj-form:event:bar-occurred": z.infer<typeof barSchema>;`);
110
- });
111
-
112
- test("inline-schema becomes a generated const in schemas.generated.ts", () => {
113
- const appRoot = makeAppDir();
114
- write(
115
- appRoot,
116
- "src/feature/feature.ts",
117
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
118
- import { z } from "zod";
119
-
120
- export const myFeature = defineFeature("inlineFeat", (r) => {
121
- r.defineEvent("inline-evt", z.object({ id: z.string(), count: z.number() }));
122
- });
123
- `,
124
- );
125
-
126
- const result = runCodegen({ appRoot, handlerQns: [] });
127
- expect(result.eventCount).toBe(1);
128
- expect(result.warnings).toEqual([]);
129
- expect(result.didWriteSchemas).toBe(true);
130
-
131
- const schemas = readFileSync(join(appRoot, ".kumiko", "schemas.generated.ts"), "utf-8");
132
- expect(schemas).toContain(`import { z } from "zod";`);
133
- // Generated const-name is stable + qualifiedName-derived; the exact
134
- // string is part of the contract because types.generated.d.ts
135
- // imports it under that name.
136
- expect(schemas).toMatch(/export const _kg_inlineFeat__inlineEvt = z\.object/);
137
- expect(schemas).toContain(`z.object({ id: z.string(), count: z.number() })`);
138
-
139
- const types = readFileSync(join(appRoot, ".kumiko", "types.generated.d.ts"), "utf-8");
140
- expect(types).toContain(
141
- `"inline-feat:event:inline-evt": z.infer<typeof _kg_inlineFeat__inlineEvt>;`,
142
- );
143
- expect(types).toContain(`from "./schemas.generated"`);
144
- });
145
-
146
- test("computed name via const-member resolves to string literal", () => {
147
- const appRoot = makeAppDir();
148
- write(
149
- appRoot,
150
- "src/feature/events.ts",
151
- `import { z } from "zod";
152
- export const EVT = {
153
- sent: "invoice-sent",
154
- paid: "invoice-paid",
155
- } as const;
156
- export const sentSchema = z.object({});
157
- export const paidSchema = z.object({ amount: z.number() });
158
- `,
159
- );
160
- write(
161
- appRoot,
162
- "src/feature/feature.ts",
163
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
164
- import { EVT, sentSchema, paidSchema } from "./events";
165
-
166
- export const myFeature = defineFeature("billing", (r) => {
167
- r.defineEvent(EVT.sent, sentSchema);
168
- r.defineEvent(EVT.paid, paidSchema);
169
- });
170
- `,
171
- );
172
-
173
- const result = runCodegen({ appRoot, handlerQns: [] });
174
- expect(result.eventCount).toBe(2);
175
- expect(result.warnings).toEqual([]);
176
-
177
- const types = readFileSync(join(appRoot, ".kumiko", "types.generated.d.ts"), "utf-8");
178
- expect(types).toContain(`"billing:event:invoice-sent": z.infer<typeof sentSchema>;`);
179
- expect(types).toContain(`"billing:event:invoice-paid": z.infer<typeof paidSchema>;`);
180
- });
181
-
182
- test("computed name + inline schema (recipes pattern)", () => {
183
- const appRoot = makeAppDir();
184
- write(
185
- appRoot,
186
- "src/feature/events.ts",
187
- `export const EVT = {
188
- forced: "force-applied",
189
- } as const;
190
- `,
191
- );
192
- write(
193
- appRoot,
194
- "src/feature/feature.ts",
195
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
196
- import { z } from "zod";
197
- import { EVT } from "./events";
198
-
199
- export const myFeature = defineFeature("billing", (r) => {
200
- r.defineEvent(EVT.forced, z.object({ reason: z.string() }));
201
- });
202
- `,
203
- );
204
-
205
- const result = runCodegen({ appRoot, handlerQns: [] });
206
- expect(result.eventCount).toBe(1);
207
- expect(result.warnings).toEqual([]);
208
- expect(result.didWriteSchemas).toBe(true);
209
-
210
- const types = readFileSync(join(appRoot, ".kumiko", "types.generated.d.ts"), "utf-8");
211
- expect(types).toContain(
212
- `"billing:event:force-applied": z.infer<typeof _kg_billing__forceApplied>;`,
213
- );
214
- });
215
-
216
- test("merges events from multiple feature files", () => {
217
- const appRoot = makeAppDir();
218
- write(
219
- appRoot,
220
- "src/a/events.ts",
221
- `import { z } from "zod";\nexport const aSchema = z.object({ a: z.string() });\n`,
222
- );
223
- write(
224
- appRoot,
225
- "src/a/feature.ts",
226
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
227
- import { aSchema } from "./events";
228
- export default defineFeature("featA", (r) => {
229
- r.defineEvent("evt", aSchema);
230
- });
231
- `,
232
- );
233
- write(
234
- appRoot,
235
- "src/b/events.ts",
236
- `import { z } from "zod";\nexport const bSchema = z.object({ b: z.number() });\n`,
237
- );
238
- write(
239
- appRoot,
240
- "src/b/feature.ts",
241
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
242
- import { bSchema } from "./events";
243
- export default defineFeature("featB", (r) => {
244
- r.defineEvent("evt", bSchema);
245
- });
246
- `,
247
- );
248
-
249
- const result = runCodegen({ appRoot });
250
- expect(result.eventCount).toBe(2);
251
- const types = readFileSync(join(appRoot, ".kumiko", "types.generated.d.ts"), "utf-8");
252
- expect(types).toContain(`"feat-a:event:evt"`);
253
- expect(types).toContain(`"feat-b:event:evt"`);
254
- });
255
-
256
- test("warns + dedupes on duplicate qualifiedName", () => {
257
- const appRoot = makeAppDir();
258
- write(
259
- appRoot,
260
- "src/x/events.ts",
261
- `import { z } from "zod";\nexport const xSchema = z.object({ id: z.string() });\nexport const ySchema = z.object({ name: z.string() });\n`,
262
- );
263
- write(
264
- appRoot,
265
- "src/x/feature1.ts",
266
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
267
- import { xSchema } from "./events";
268
- export default defineFeature("dup", (r) => {
269
- r.defineEvent("collide", xSchema);
270
- });
271
- `,
272
- );
273
- write(
274
- appRoot,
275
- "src/x/feature2.ts",
276
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
277
- import { ySchema } from "./events";
278
- export default defineFeature("dup", (r) => {
279
- r.defineEvent("collide", ySchema);
280
- });
281
- `,
282
- );
283
-
284
- const result = runCodegen({ appRoot });
285
- expect(result.eventCount).toBe(1);
286
- expect(result.warnings.length).toBeGreaterThanOrEqual(1);
287
- const w = result.warnings.find((w) => w.message.includes("duplicate"));
288
- expect(w).toBeDefined();
289
- });
290
-
291
- test("emits WriteHandlerQn union and TypedDispatcher when handlerQns passed", () => {
292
- const appRoot = makeAppDir();
293
- write(
294
- appRoot,
295
- "src/feature/events.ts",
296
- `import { z } from "zod";\nexport const sSchema = z.object({ id: z.string() });\n`,
297
- );
298
- write(
299
- appRoot,
300
- "src/feature/feature.ts",
301
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
302
- import { sSchema } from "./events";
303
- export default defineFeature("typed", (r) => {
304
- r.defineEvent("only", sSchema);
305
- });
306
- `,
307
- );
308
-
309
- const result = runCodegen({
310
- appRoot,
311
- handlerQns: ["typed:write:create"],
312
- });
313
- expect(result.skipped).toBe(false);
314
-
315
- const types = readFileSync(join(appRoot, ".kumiko", "types.generated.d.ts"), "utf-8");
316
- expect(types).toContain('| "typed:write:create"');
317
-
318
- const define = readFileSync(join(appRoot, ".kumiko", "define.ts"), "utf-8");
319
- expect(define).toContain("createTypedDispatcher");
320
- });
321
-
322
- test("idempotent — second run does not re-write files", () => {
323
- const appRoot = makeAppDir();
324
- write(
325
- appRoot,
326
- "src/feature/events.ts",
327
- `import { z } from "zod";\nexport const sSchema = z.object({ id: z.string() });\n`,
328
- );
329
- write(
330
- appRoot,
331
- "src/feature/feature.ts",
332
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
333
- import { sSchema } from "./events";
334
- export default defineFeature("idem", (r) => {
335
- r.defineEvent("only", sSchema);
336
- });
337
- `,
338
- );
339
-
340
- const first = runCodegen({ appRoot });
341
- expect(first.didWriteTypes).toBe(true);
342
- expect(first.didWriteDefine).toBe(true);
343
-
344
- const second = runCodegen({ appRoot });
345
- expect(second.didWriteTypes).toBe(false);
346
- expect(second.didWriteDefine).toBe(false);
347
- });
348
-
349
- test("warns when schema is locally declared (not imported, not inline z.*)", () => {
350
- const appRoot = makeAppDir();
351
- // Schema is declared as a local const but referenced by a name that's
352
- // neither an imported identifier nor an inline z.* call (it's an
353
- // identifier-reference to the local const). Scanner can't reach it
354
- // without a separate scan of the file's local symbols → warn + skip.
355
- write(
356
- appRoot,
357
- "src/inline/feature.ts",
358
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
359
- import { z } from "zod";
360
-
361
- const localSchema = z.object({ x: z.string() });
362
-
363
- export default defineFeature("inline", (r) => {
364
- r.defineEvent("local", localSchema);
365
- });
366
- `,
367
- );
368
-
369
- const result = runCodegen({ appRoot });
370
- expect(result.eventCount).toBe(0);
371
- expect(result.warnings.length).toBe(1);
372
- expect(result.warnings[0]?.message).toMatch(/not a named import nor an inline z\.\* call/);
373
- });
374
-
375
- test("skips test files, node_modules, .kumiko, dist", () => {
376
- const appRoot = makeAppDir();
377
- write(
378
- appRoot,
379
- "src/feature/events.ts",
380
- `import { z } from "zod";\nexport const realSchema = z.object({});\n`,
381
- );
382
- write(
383
- appRoot,
384
- "src/feature/feature.ts",
385
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
386
- import { realSchema } from "./events";
387
- export default defineFeature("real", (r) => {
388
- r.defineEvent("ok", realSchema);
389
- });
390
- `,
391
- );
392
- // Should be ignored:
393
- write(
394
- appRoot,
395
- "src/feature/__tests__/feature.test.ts",
396
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
397
- import { z } from "zod";
398
- const fakeSchema = z.object({});
399
- defineFeature("fake-from-test", (r) => {
400
- r.defineEvent("nope", fakeSchema);
401
- });
402
- `,
403
- );
404
- write(
405
- appRoot,
406
- ".kumiko/old.ts",
407
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
408
- import { z } from "zod";
409
- const oldSchema = z.object({});
410
- defineFeature("old-codegen-output", (r) => {
411
- r.defineEvent("nope", oldSchema);
412
- });
413
- `,
414
- );
415
-
416
- const result = runCodegen({ appRoot });
417
- expect(result.eventCount).toBe(1);
418
- const types = readFileSync(join(appRoot, ".kumiko", "types.generated.d.ts"), "utf-8");
419
- expect(types).toContain(`"real:event:ok"`);
420
- expect(types).not.toContain(`"fake-from-test`);
421
- expect(types).not.toContain(`"old-codegen-output`);
422
- });
423
-
424
- test("0 events + no existing .kumiko/ → bails with skipped=true", () => {
425
- const appRoot = makeAppDir();
426
- // App with NO r.defineEvent at all. Codegen should not create an
427
- // empty `.kumiko/` directory.
428
- write(
429
- appRoot,
430
- "src/feature/feature.ts",
431
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
432
- export default defineFeature("nothing", (r) => {
433
- r.requires("auth");
434
- });
435
- `,
436
- );
437
-
438
- const result = runCodegen({ appRoot });
439
- expect(result.eventCount).toBe(0);
440
- expect(result.skipped).toBe(true);
441
- expect(result.didWriteTypes).toBe(false);
442
- expect(result.didWriteDefine).toBe(false);
443
- expect(existsSync(join(appRoot, ".kumiko"))).toBe(false);
444
- });
445
-
446
- test("schemas.generated.ts gets removed when last inline-schema disappears", () => {
447
- const appRoot = makeAppDir();
448
- write(
449
- appRoot,
450
- "src/feature/feature.ts",
451
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
452
- import { z } from "zod";
453
- export default defineFeature("evolve", (r) => {
454
- r.defineEvent("inline-once", z.object({ x: z.string() }));
455
- });
456
- `,
457
- );
458
-
459
- const first = runCodegen({ appRoot });
460
- expect(first.didWriteSchemas).toBe(true);
461
- expect(existsSync(join(appRoot, ".kumiko", "schemas.generated.ts"))).toBe(true);
462
-
463
- // Refactor: schema moves out of the call-site into a named export.
464
- write(
465
- appRoot,
466
- "src/feature/events.ts",
467
- `import { z } from "zod";\nexport const onceSchema = z.object({ x: z.string() });\n`,
468
- );
469
- write(
470
- appRoot,
471
- "src/feature/feature.ts",
472
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
473
- import { onceSchema } from "./events";
474
- export default defineFeature("evolve", (r) => {
475
- r.defineEvent("inline-once", onceSchema);
476
- });
477
- `,
478
- );
479
-
480
- const second = runCodegen({ appRoot });
481
- expect(second.didWriteSchemas).toBe(true); // means: removed
482
- expect(existsSync(join(appRoot, ".kumiko", "schemas.generated.ts"))).toBe(false);
483
- });
484
-
485
- test(".kumiko/package.json has the shape that yarn 4 link: needs", () => {
486
- // The generated package.json turns `.kumiko/` into an installable
487
- // file-link package (`"@app/define": "link:./.kumiko"` in app's
488
- // package.json). The shape here is load-bearing:
489
- // - name=@app/define matches what handlers import
490
- // - exports."."=./define.ts gives the wrapper as the default
491
- // - exports."./*"=./* lets apps reach types.generated etc.
492
- // - license=BUSL-1.1 keeps License-Check happy (UNLICENSED would deny)
493
- // - main+types pin TypeScript + Node resolution targets
494
- // Regressions in any field break either runtime resolution or the
495
- // kumiko-check License-Check gate — the test fails fast.
496
- const appRoot = makeAppDir();
497
- write(
498
- appRoot,
499
- "src/feature.ts",
500
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
501
- import { z } from "zod";
502
- export default defineFeature("pkgjson", (r) => {
503
- r.defineEvent("evt", z.object({ id: z.string() }));
504
- });
505
- `,
506
- );
507
-
508
- runCodegen({ appRoot });
509
-
510
- const pkgPath = join(appRoot, ".kumiko", "package.json");
511
- expect(existsSync(pkgPath)).toBe(true);
512
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as Record<string, unknown>;
513
- expect(pkg).toMatchObject({
514
- name: "@app/define",
515
- private: true,
516
- license: "BUSL-1.1",
517
- type: "module",
518
- main: "./define.ts",
519
- types: "./define.ts",
520
- exports: {
521
- ".": "./define.ts",
522
- "./*": "./*",
523
- },
524
- });
525
- });
526
-
527
- // #2757 — `kumiko-build` runs `runCodegen({ appRoot })` with no
528
- // `handlerQns`. Without a `feature-manifest.json` that falls back to
529
- // an empty list, which used to mean "render types.generated.d.ts and
530
- // define.ts as if there were no write handlers at all" — silently
531
- // deleting an already-checked-in WriteHandlerQn union / TypedDispatcher
532
- // that a previous dev-server run had generated from the real registry.
533
- describe("preserving the handler-derived block when handlerQns is omitted (#2757)", () => {
534
- function writeSingleEventFeature(appRoot: string): void {
535
- write(
536
- appRoot,
537
- "src/feature/events.ts",
538
- `import { z } from "zod";\nexport const sSchema = z.object({ id: z.string() });\n`,
539
- );
540
- write(
541
- appRoot,
542
- "src/feature/feature.ts",
543
- `import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
544
- import { sSchema } from "./events";
545
- export default defineFeature("app", (r) => {
546
- r.defineEvent("only", sSchema);
547
- });
548
- `,
549
- );
550
- }
551
-
552
- test("a block generated with handlerQns survives a later run without handlerQns", () => {
553
- const appRoot = makeAppDir();
554
- writeSingleEventFeature(appRoot);
555
-
556
- runCodegen({ appRoot, handlerQns: ["app:write:create"] });
557
- const typesPath = join(appRoot, ".kumiko", "types.generated.d.ts");
558
- const definePath = join(appRoot, ".kumiko", "define.ts");
559
- expect(readFileSync(typesPath, "utf-8")).toContain('| "app:write:create"');
560
- expect(readFileSync(definePath, "utf-8")).toContain("createTypedDispatcher");
561
-
562
- // No feature-manifest.json in appRoot — this is the kumiko-build path.
563
- const result = runCodegen({ appRoot });
564
-
565
- expect(readFileSync(typesPath, "utf-8")).toContain('| "app:write:create"');
566
- expect(readFileSync(definePath, "utf-8")).toContain("createTypedDispatcher");
567
- expect(result.warnings.some((w) => w.message.includes("feature-manifest.json"))).toBe(true);
568
- // Round-trip is byte-identical, not just "still contains the QN" — writeIfChanged
569
- // must see no diff once the preserved block is reattached.
570
- expect(result.didWriteTypes).toBe(false);
571
- expect(result.didWriteDefine).toBe(false);
572
- });
573
-
574
- test("a block from a pre-#2757 file (no marker) survives a run without handlerQns", () => {
575
- const appRoot = makeAppDir();
576
- writeSingleEventFeature(appRoot);
577
- // Legacy shape: what the released renderer wrote before markers existed.
578
- write(
579
- appRoot,
580
- ".kumiko/types.generated.d.ts",
581
- `export {};\n\nexport type WriteHandlerQn =\n | "app:write:legacy"\n;\n`,
582
- );
583
- write(
584
- appRoot,
585
- ".kumiko/define.ts",
586
- `export function defineQueryHandler() {}\n\nimport type { Dispatcher, WriteOpts, WriteResult } from "@cosmicdrift/kumiko-headless";\nexport function createTypedDispatcher() {}\n`,
587
- );
588
-
589
- const result = runCodegen({ appRoot });
590
-
591
- expect(readFileSync(join(appRoot, ".kumiko", "types.generated.d.ts"), "utf-8")).toContain(
592
- '| "app:write:legacy"',
593
- );
594
- expect(readFileSync(join(appRoot, ".kumiko", "define.ts"), "utf-8")).toContain(
595
- "createTypedDispatcher",
596
- );
597
- expect(result.warnings.some((w) => w.message.includes("feature-manifest.json"))).toBe(true);
598
- });
599
-
600
- test("no handlerQns and no prior generated file → no block, no crash", () => {
601
- const appRoot = makeAppDir();
602
- writeSingleEventFeature(appRoot);
603
-
604
- const result = runCodegen({ appRoot });
605
-
606
- const typesPath = join(appRoot, ".kumiko", "types.generated.d.ts");
607
- const definePath = join(appRoot, ".kumiko", "define.ts");
608
- expect(readFileSync(typesPath, "utf-8")).not.toContain("WriteHandlerQn");
609
- expect(readFileSync(definePath, "utf-8")).not.toContain("TypedDispatcher");
610
- expect(result.skipped).toBe(false);
611
- });
612
-
613
- test("a preserved block is not duplicated across repeated runs without handlerQns", () => {
614
- const appRoot = makeAppDir();
615
- writeSingleEventFeature(appRoot);
616
-
617
- runCodegen({ appRoot, handlerQns: ["app:write:create"] });
618
- runCodegen({ appRoot });
619
- runCodegen({ appRoot });
620
-
621
- const typesPath = join(appRoot, ".kumiko", "types.generated.d.ts");
622
- const definePath = join(appRoot, ".kumiko", "define.ts");
623
- const typesOccurrences =
624
- readFileSync(typesPath, "utf-8").split("app:write:create").length - 1;
625
- const defineOccurrences =
626
- readFileSync(definePath, "utf-8").split("createTypedDispatcher").length - 1;
627
- expect(typesOccurrences).toBe(1);
628
- expect(defineOccurrences).toBe(1);
629
- });
630
-
631
- test("a later run with handlerQns overwrites a preserved stale block", () => {
632
- const appRoot = makeAppDir();
633
- writeSingleEventFeature(appRoot);
634
-
635
- runCodegen({ appRoot, handlerQns: ["app:write:create"] });
636
- runCodegen({ appRoot }); // preserves app:write:create
637
- runCodegen({ appRoot, handlerQns: ["app:write:update"] });
638
-
639
- const typesPath = join(appRoot, ".kumiko", "types.generated.d.ts");
640
- const typesContent = readFileSync(typesPath, "utf-8");
641
- expect(typesContent).toContain('| "app:write:update"');
642
- expect(typesContent).not.toContain("app:write:create");
643
- });
644
-
645
- test("the missing-manifest warning appears only when handlerQns is omitted", () => {
646
- const appRoot = makeAppDir();
647
- writeSingleEventFeature(appRoot);
648
-
649
- const withoutOption = runCodegen({ appRoot });
650
- expect(withoutOption.warnings.some((w) => w.message.includes("feature-manifest.json"))).toBe(
651
- true,
652
- );
653
-
654
- const explicitEmpty = runCodegen({ appRoot, handlerQns: [] });
655
- expect(explicitEmpty.warnings.some((w) => w.message.includes("feature-manifest.json"))).toBe(
656
- false,
657
- );
658
-
659
- const explicitList = runCodegen({ appRoot, handlerQns: ["app:write:create"] });
660
- expect(explicitList.warnings.some((w) => w.message.includes("feature-manifest.json"))).toBe(
661
- false,
662
- );
663
- });
664
- });
665
- });