@cosmicdrift/kumiko-dev-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/kumiko-build.ts +85 -0
- package/bin/kumiko-dev.ts +90 -0
- package/package.json +45 -0
- package/src/__tests__/build-prod-bundle.integration.ts +265 -0
- package/src/__tests__/build-prod-bundle.test.ts +262 -0
- package/src/__tests__/cache-headers.test.ts +70 -0
- package/src/__tests__/classify-change.test.ts +87 -0
- package/src/__tests__/compose-features-wiring.integration.ts +352 -0
- package/src/__tests__/compose-features.test.ts +81 -0
- package/src/__tests__/crash-tracker.test.ts +89 -0
- package/src/__tests__/create-kumiko-server.integration.ts +286 -0
- package/src/__tests__/few-shot-corpus.test.ts +311 -0
- package/src/__tests__/inject-schema.test.ts +62 -0
- package/src/__tests__/resolve-stylesheet.test.ts +90 -0
- package/src/__tests__/resolve-tailwind-cli.test.ts +49 -0
- package/src/__tests__/run-prod-app-spec.test.ts +57 -0
- package/src/__tests__/run-prod-app.integration.ts +535 -0
- package/src/__tests__/scaffold-feature.test.ts +143 -0
- package/src/__tests__/try-hono-first.test.ts +63 -0
- package/src/build-prod-bundle.ts +587 -0
- package/src/build-server-bundle.ts +308 -0
- package/src/build.ts +28 -0
- package/src/codegen/__tests__/run-codegen.test.ts +494 -0
- package/src/codegen/__tests__/strict-mode-diagnostics.test.ts +467 -0
- package/src/codegen/__tests__/watch.test.ts +186 -0
- package/src/codegen/index.ts +17 -0
- package/src/codegen/render.ts +225 -0
- package/src/codegen/run-codegen.ts +157 -0
- package/src/codegen/scan-events.ts +574 -0
- package/src/codegen/watch.ts +127 -0
- package/src/compose-features.ts +128 -0
- package/src/crash-tracker.ts +56 -0
- package/src/create-kumiko-server.ts +1010 -0
- package/src/drizzle-config.ts +44 -0
- package/src/drizzle-tables-auth-mode.ts +32 -0
- package/src/drizzle-tables-minimal.ts +22 -0
- package/src/few-shot-corpus.ts +369 -0
- package/src/index.ts +57 -0
- package/src/inject-schema.ts +24 -0
- package/src/resolve-tailwind-cli.ts +28 -0
- package/src/run-dev-app.ts +290 -0
- package/src/run-prod-app.ts +892 -0
- package/src/scaffold-feature.ts +226 -0
- package/src/try-hono-first.ts +46 -0
|
@@ -0,0 +1,494 @@
|
|
|
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 { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { tmpdir } from "node:os";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { describe, expect, test } from "vitest";
|
|
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
|
+
const result = runCodegen({ appRoot });
|
|
61
|
+
|
|
62
|
+
expect(result.eventCount).toBe(1);
|
|
63
|
+
expect(result.warnings).toEqual([]);
|
|
64
|
+
expect(result.didWriteTypes).toBe(true);
|
|
65
|
+
expect(result.didWriteDefine).toBe(true);
|
|
66
|
+
expect(result.didWriteSchemas).toBe(false);
|
|
67
|
+
|
|
68
|
+
const types = readFileSync(join(appRoot, ".kumiko", "types.generated.d.ts"), "utf-8");
|
|
69
|
+
expect(types).toContain(`"my-feat:event:foo-happened": z.infer<typeof fooSchema>;`);
|
|
70
|
+
expect(types).toContain(`import type { fooSchema }`);
|
|
71
|
+
expect(types).toContain(`export {};`);
|
|
72
|
+
|
|
73
|
+
const define = readFileSync(join(appRoot, ".kumiko", "define.ts"), "utf-8");
|
|
74
|
+
expect(define).toContain(`/// <reference path="./types.generated.d.ts" />`);
|
|
75
|
+
expect(define).toContain(`export function defineWriteHandler<`);
|
|
76
|
+
expect(define).toContain(
|
|
77
|
+
`fwDefineWriteHandler<TName, TSchema, TData, KumikoEventTypeMap>(def)`,
|
|
78
|
+
);
|
|
79
|
+
expect(define).toContain(`export function defineQueryHandler<`);
|
|
80
|
+
|
|
81
|
+
expect(existsSync(join(appRoot, ".kumiko", "schemas.generated.ts"))).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("scans object-form r.defineEvent", () => {
|
|
85
|
+
const appRoot = makeAppDir();
|
|
86
|
+
write(
|
|
87
|
+
appRoot,
|
|
88
|
+
"src/feature/events.ts",
|
|
89
|
+
`import { z } from "zod";
|
|
90
|
+
export const barSchema = z.object({ count: z.number() });
|
|
91
|
+
`,
|
|
92
|
+
);
|
|
93
|
+
write(
|
|
94
|
+
appRoot,
|
|
95
|
+
"src/feature/feature.ts",
|
|
96
|
+
`import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
97
|
+
import { barSchema } from "./events";
|
|
98
|
+
|
|
99
|
+
export const myFeature = defineFeature("objForm", (r) => {
|
|
100
|
+
r.defineEvent({ name: "bar-occurred", schema: barSchema });
|
|
101
|
+
});
|
|
102
|
+
`,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const result = runCodegen({ appRoot });
|
|
106
|
+
expect(result.eventCount).toBe(1);
|
|
107
|
+
const types = readFileSync(join(appRoot, ".kumiko", "types.generated.d.ts"), "utf-8");
|
|
108
|
+
expect(types).toContain(`"obj-form:event:bar-occurred": z.infer<typeof barSchema>;`);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("inline-schema becomes a generated const in schemas.generated.ts", () => {
|
|
112
|
+
const appRoot = makeAppDir();
|
|
113
|
+
write(
|
|
114
|
+
appRoot,
|
|
115
|
+
"src/feature/feature.ts",
|
|
116
|
+
`import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
117
|
+
import { z } from "zod";
|
|
118
|
+
|
|
119
|
+
export const myFeature = defineFeature("inlineFeat", (r) => {
|
|
120
|
+
r.defineEvent("inline-evt", z.object({ id: z.string(), count: z.number() }));
|
|
121
|
+
});
|
|
122
|
+
`,
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const result = runCodegen({ appRoot });
|
|
126
|
+
expect(result.eventCount).toBe(1);
|
|
127
|
+
expect(result.warnings).toEqual([]);
|
|
128
|
+
expect(result.didWriteSchemas).toBe(true);
|
|
129
|
+
|
|
130
|
+
const schemas = readFileSync(join(appRoot, ".kumiko", "schemas.generated.ts"), "utf-8");
|
|
131
|
+
expect(schemas).toContain(`import { z } from "zod";`);
|
|
132
|
+
// Generated const-name is stable + qualifiedName-derived; the exact
|
|
133
|
+
// string is part of the contract because types.generated.d.ts
|
|
134
|
+
// imports it under that name.
|
|
135
|
+
expect(schemas).toMatch(/export const _kg_inlineFeat__inlineEvt = z\.object/);
|
|
136
|
+
expect(schemas).toContain(`z.object({ id: z.string(), count: z.number() })`);
|
|
137
|
+
|
|
138
|
+
const types = readFileSync(join(appRoot, ".kumiko", "types.generated.d.ts"), "utf-8");
|
|
139
|
+
expect(types).toContain(
|
|
140
|
+
`"inline-feat:event:inline-evt": z.infer<typeof _kg_inlineFeat__inlineEvt>;`,
|
|
141
|
+
);
|
|
142
|
+
expect(types).toContain(`from "./schemas.generated"`);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("computed name via const-member resolves to string literal", () => {
|
|
146
|
+
const appRoot = makeAppDir();
|
|
147
|
+
write(
|
|
148
|
+
appRoot,
|
|
149
|
+
"src/feature/events.ts",
|
|
150
|
+
`import { z } from "zod";
|
|
151
|
+
export const EVT = {
|
|
152
|
+
sent: "invoice-sent",
|
|
153
|
+
paid: "invoice-paid",
|
|
154
|
+
} as const;
|
|
155
|
+
export const sentSchema = z.object({});
|
|
156
|
+
export const paidSchema = z.object({ amount: z.number() });
|
|
157
|
+
`,
|
|
158
|
+
);
|
|
159
|
+
write(
|
|
160
|
+
appRoot,
|
|
161
|
+
"src/feature/feature.ts",
|
|
162
|
+
`import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
163
|
+
import { EVT, sentSchema, paidSchema } from "./events";
|
|
164
|
+
|
|
165
|
+
export const myFeature = defineFeature("billing", (r) => {
|
|
166
|
+
r.defineEvent(EVT.sent, sentSchema);
|
|
167
|
+
r.defineEvent(EVT.paid, paidSchema);
|
|
168
|
+
});
|
|
169
|
+
`,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const result = runCodegen({ appRoot });
|
|
173
|
+
expect(result.eventCount).toBe(2);
|
|
174
|
+
expect(result.warnings).toEqual([]);
|
|
175
|
+
|
|
176
|
+
const types = readFileSync(join(appRoot, ".kumiko", "types.generated.d.ts"), "utf-8");
|
|
177
|
+
expect(types).toContain(`"billing:event:invoice-sent": z.infer<typeof sentSchema>;`);
|
|
178
|
+
expect(types).toContain(`"billing:event:invoice-paid": z.infer<typeof paidSchema>;`);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("computed name + inline schema (recipes pattern)", () => {
|
|
182
|
+
const appRoot = makeAppDir();
|
|
183
|
+
write(
|
|
184
|
+
appRoot,
|
|
185
|
+
"src/feature/events.ts",
|
|
186
|
+
`export const EVT = {
|
|
187
|
+
forced: "force-applied",
|
|
188
|
+
} as const;
|
|
189
|
+
`,
|
|
190
|
+
);
|
|
191
|
+
write(
|
|
192
|
+
appRoot,
|
|
193
|
+
"src/feature/feature.ts",
|
|
194
|
+
`import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
195
|
+
import { z } from "zod";
|
|
196
|
+
import { EVT } from "./events";
|
|
197
|
+
|
|
198
|
+
export const myFeature = defineFeature("billing", (r) => {
|
|
199
|
+
r.defineEvent(EVT.forced, z.object({ reason: z.string() }));
|
|
200
|
+
});
|
|
201
|
+
`,
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const result = runCodegen({ appRoot });
|
|
205
|
+
expect(result.eventCount).toBe(1);
|
|
206
|
+
expect(result.warnings).toEqual([]);
|
|
207
|
+
expect(result.didWriteSchemas).toBe(true);
|
|
208
|
+
|
|
209
|
+
const types = readFileSync(join(appRoot, ".kumiko", "types.generated.d.ts"), "utf-8");
|
|
210
|
+
expect(types).toContain(
|
|
211
|
+
`"billing:event:force-applied": z.infer<typeof _kg_billing__forceApplied>;`,
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("merges events from multiple feature files", () => {
|
|
216
|
+
const appRoot = makeAppDir();
|
|
217
|
+
write(
|
|
218
|
+
appRoot,
|
|
219
|
+
"src/a/events.ts",
|
|
220
|
+
`import { z } from "zod";\nexport const aSchema = z.object({ a: z.string() });\n`,
|
|
221
|
+
);
|
|
222
|
+
write(
|
|
223
|
+
appRoot,
|
|
224
|
+
"src/a/feature.ts",
|
|
225
|
+
`import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
226
|
+
import { aSchema } from "./events";
|
|
227
|
+
export default defineFeature("featA", (r) => {
|
|
228
|
+
r.defineEvent("evt", aSchema);
|
|
229
|
+
});
|
|
230
|
+
`,
|
|
231
|
+
);
|
|
232
|
+
write(
|
|
233
|
+
appRoot,
|
|
234
|
+
"src/b/events.ts",
|
|
235
|
+
`import { z } from "zod";\nexport const bSchema = z.object({ b: z.number() });\n`,
|
|
236
|
+
);
|
|
237
|
+
write(
|
|
238
|
+
appRoot,
|
|
239
|
+
"src/b/feature.ts",
|
|
240
|
+
`import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
241
|
+
import { bSchema } from "./events";
|
|
242
|
+
export default defineFeature("featB", (r) => {
|
|
243
|
+
r.defineEvent("evt", bSchema);
|
|
244
|
+
});
|
|
245
|
+
`,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const result = runCodegen({ appRoot });
|
|
249
|
+
expect(result.eventCount).toBe(2);
|
|
250
|
+
const types = readFileSync(join(appRoot, ".kumiko", "types.generated.d.ts"), "utf-8");
|
|
251
|
+
expect(types).toContain(`"feat-a:event:evt"`);
|
|
252
|
+
expect(types).toContain(`"feat-b:event:evt"`);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("warns + dedupes on duplicate qualifiedName", () => {
|
|
256
|
+
const appRoot = makeAppDir();
|
|
257
|
+
write(
|
|
258
|
+
appRoot,
|
|
259
|
+
"src/x/events.ts",
|
|
260
|
+
`import { z } from "zod";\nexport const xSchema = z.object({ id: z.string() });\nexport const ySchema = z.object({ name: z.string() });\n`,
|
|
261
|
+
);
|
|
262
|
+
write(
|
|
263
|
+
appRoot,
|
|
264
|
+
"src/x/feature1.ts",
|
|
265
|
+
`import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
266
|
+
import { xSchema } from "./events";
|
|
267
|
+
export default defineFeature("dup", (r) => {
|
|
268
|
+
r.defineEvent("collide", xSchema);
|
|
269
|
+
});
|
|
270
|
+
`,
|
|
271
|
+
);
|
|
272
|
+
write(
|
|
273
|
+
appRoot,
|
|
274
|
+
"src/x/feature2.ts",
|
|
275
|
+
`import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
276
|
+
import { ySchema } from "./events";
|
|
277
|
+
export default defineFeature("dup", (r) => {
|
|
278
|
+
r.defineEvent("collide", ySchema);
|
|
279
|
+
});
|
|
280
|
+
`,
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
const result = runCodegen({ appRoot });
|
|
284
|
+
expect(result.eventCount).toBe(1);
|
|
285
|
+
expect(result.warnings.length).toBeGreaterThanOrEqual(1);
|
|
286
|
+
const w = result.warnings.find((w) => w.reason.includes("duplicate"));
|
|
287
|
+
expect(w).toBeDefined();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("idempotent — second run does not re-write files", () => {
|
|
291
|
+
const appRoot = makeAppDir();
|
|
292
|
+
write(
|
|
293
|
+
appRoot,
|
|
294
|
+
"src/feature/events.ts",
|
|
295
|
+
`import { z } from "zod";\nexport const sSchema = z.object({ id: z.string() });\n`,
|
|
296
|
+
);
|
|
297
|
+
write(
|
|
298
|
+
appRoot,
|
|
299
|
+
"src/feature/feature.ts",
|
|
300
|
+
`import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
301
|
+
import { sSchema } from "./events";
|
|
302
|
+
export default defineFeature("idem", (r) => {
|
|
303
|
+
r.defineEvent("only", sSchema);
|
|
304
|
+
});
|
|
305
|
+
`,
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const first = runCodegen({ appRoot });
|
|
309
|
+
expect(first.didWriteTypes).toBe(true);
|
|
310
|
+
expect(first.didWriteDefine).toBe(true);
|
|
311
|
+
|
|
312
|
+
const second = runCodegen({ appRoot });
|
|
313
|
+
expect(second.didWriteTypes).toBe(false);
|
|
314
|
+
expect(second.didWriteDefine).toBe(false);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("warns when schema is locally declared (not imported, not inline z.*)", () => {
|
|
318
|
+
const appRoot = makeAppDir();
|
|
319
|
+
// Schema is declared as a local const but referenced by a name that's
|
|
320
|
+
// neither an imported identifier nor an inline z.* call (it's an
|
|
321
|
+
// identifier-reference to the local const). Scanner can't reach it
|
|
322
|
+
// without a separate scan of the file's local symbols → warn + skip.
|
|
323
|
+
write(
|
|
324
|
+
appRoot,
|
|
325
|
+
"src/inline/feature.ts",
|
|
326
|
+
`import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
327
|
+
import { z } from "zod";
|
|
328
|
+
|
|
329
|
+
const localSchema = z.object({ x: z.string() });
|
|
330
|
+
|
|
331
|
+
export default defineFeature("inline", (r) => {
|
|
332
|
+
r.defineEvent("local", localSchema);
|
|
333
|
+
});
|
|
334
|
+
`,
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
const result = runCodegen({ appRoot });
|
|
338
|
+
expect(result.eventCount).toBe(0);
|
|
339
|
+
expect(result.warnings.length).toBe(1);
|
|
340
|
+
expect(result.warnings[0]?.reason).toMatch(/not a named import nor an inline z\.\* call/);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("skips test files, node_modules, .kumiko, dist", () => {
|
|
344
|
+
const appRoot = makeAppDir();
|
|
345
|
+
write(
|
|
346
|
+
appRoot,
|
|
347
|
+
"src/feature/events.ts",
|
|
348
|
+
`import { z } from "zod";\nexport const realSchema = z.object({});\n`,
|
|
349
|
+
);
|
|
350
|
+
write(
|
|
351
|
+
appRoot,
|
|
352
|
+
"src/feature/feature.ts",
|
|
353
|
+
`import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
354
|
+
import { realSchema } from "./events";
|
|
355
|
+
export default defineFeature("real", (r) => {
|
|
356
|
+
r.defineEvent("ok", realSchema);
|
|
357
|
+
});
|
|
358
|
+
`,
|
|
359
|
+
);
|
|
360
|
+
// Should be ignored:
|
|
361
|
+
write(
|
|
362
|
+
appRoot,
|
|
363
|
+
"src/feature/__tests__/feature.test.ts",
|
|
364
|
+
`import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
365
|
+
import { z } from "zod";
|
|
366
|
+
const fakeSchema = z.object({});
|
|
367
|
+
defineFeature("fake-from-test", (r) => {
|
|
368
|
+
r.defineEvent("nope", fakeSchema);
|
|
369
|
+
});
|
|
370
|
+
`,
|
|
371
|
+
);
|
|
372
|
+
write(
|
|
373
|
+
appRoot,
|
|
374
|
+
".kumiko/old.ts",
|
|
375
|
+
`import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
376
|
+
import { z } from "zod";
|
|
377
|
+
const oldSchema = z.object({});
|
|
378
|
+
defineFeature("old-codegen-output", (r) => {
|
|
379
|
+
r.defineEvent("nope", oldSchema);
|
|
380
|
+
});
|
|
381
|
+
`,
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
const result = runCodegen({ appRoot });
|
|
385
|
+
expect(result.eventCount).toBe(1);
|
|
386
|
+
const types = readFileSync(join(appRoot, ".kumiko", "types.generated.d.ts"), "utf-8");
|
|
387
|
+
expect(types).toContain(`"real:event:ok"`);
|
|
388
|
+
expect(types).not.toContain(`"fake-from-test`);
|
|
389
|
+
expect(types).not.toContain(`"old-codegen-output`);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test("0 events + no existing .kumiko/ → bails with skipped=true", () => {
|
|
393
|
+
const appRoot = makeAppDir();
|
|
394
|
+
// App with NO r.defineEvent at all. Codegen should not create an
|
|
395
|
+
// empty `.kumiko/` directory.
|
|
396
|
+
write(
|
|
397
|
+
appRoot,
|
|
398
|
+
"src/feature/feature.ts",
|
|
399
|
+
`import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
400
|
+
export default defineFeature("nothing", (r) => {
|
|
401
|
+
r.requires("auth");
|
|
402
|
+
});
|
|
403
|
+
`,
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
const result = runCodegen({ appRoot });
|
|
407
|
+
expect(result.eventCount).toBe(0);
|
|
408
|
+
expect(result.skipped).toBe(true);
|
|
409
|
+
expect(result.didWriteTypes).toBe(false);
|
|
410
|
+
expect(result.didWriteDefine).toBe(false);
|
|
411
|
+
expect(existsSync(join(appRoot, ".kumiko"))).toBe(false);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test("schemas.generated.ts gets removed when last inline-schema disappears", () => {
|
|
415
|
+
const appRoot = makeAppDir();
|
|
416
|
+
write(
|
|
417
|
+
appRoot,
|
|
418
|
+
"src/feature/feature.ts",
|
|
419
|
+
`import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
420
|
+
import { z } from "zod";
|
|
421
|
+
export default defineFeature("evolve", (r) => {
|
|
422
|
+
r.defineEvent("inline-once", z.object({ x: z.string() }));
|
|
423
|
+
});
|
|
424
|
+
`,
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
const first = runCodegen({ appRoot });
|
|
428
|
+
expect(first.didWriteSchemas).toBe(true);
|
|
429
|
+
expect(existsSync(join(appRoot, ".kumiko", "schemas.generated.ts"))).toBe(true);
|
|
430
|
+
|
|
431
|
+
// Refactor: schema moves out of the call-site into a named export.
|
|
432
|
+
write(
|
|
433
|
+
appRoot,
|
|
434
|
+
"src/feature/events.ts",
|
|
435
|
+
`import { z } from "zod";\nexport const onceSchema = z.object({ x: z.string() });\n`,
|
|
436
|
+
);
|
|
437
|
+
write(
|
|
438
|
+
appRoot,
|
|
439
|
+
"src/feature/feature.ts",
|
|
440
|
+
`import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
441
|
+
import { onceSchema } from "./events";
|
|
442
|
+
export default defineFeature("evolve", (r) => {
|
|
443
|
+
r.defineEvent("inline-once", onceSchema);
|
|
444
|
+
});
|
|
445
|
+
`,
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
const second = runCodegen({ appRoot });
|
|
449
|
+
expect(second.didWriteSchemas).toBe(true); // means: removed
|
|
450
|
+
expect(existsSync(join(appRoot, ".kumiko", "schemas.generated.ts"))).toBe(false);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
test(".kumiko/package.json has the shape that yarn 4 link: needs", () => {
|
|
454
|
+
// The generated package.json turns `.kumiko/` into an installable
|
|
455
|
+
// file-link package (`"@app/define": "link:./.kumiko"` in app's
|
|
456
|
+
// package.json). The shape here is load-bearing:
|
|
457
|
+
// - name=@app/define matches what handlers import
|
|
458
|
+
// - exports."."=./define.ts gives the wrapper as the default
|
|
459
|
+
// - exports."./*"=./* lets apps reach types.generated etc.
|
|
460
|
+
// - license=BUSL-1.1 keeps License-Check happy (UNLICENSED would deny)
|
|
461
|
+
// - main+types pin TypeScript + Node resolution targets
|
|
462
|
+
// Regressions in any field break either runtime resolution or the
|
|
463
|
+
// kumiko-check License-Check gate — the test fails fast.
|
|
464
|
+
const appRoot = makeAppDir();
|
|
465
|
+
write(
|
|
466
|
+
appRoot,
|
|
467
|
+
"src/feature.ts",
|
|
468
|
+
`import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
469
|
+
import { z } from "zod";
|
|
470
|
+
export default defineFeature("pkgjson", (r) => {
|
|
471
|
+
r.defineEvent("evt", z.object({ id: z.string() }));
|
|
472
|
+
});
|
|
473
|
+
`,
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
runCodegen({ appRoot });
|
|
477
|
+
|
|
478
|
+
const pkgPath = join(appRoot, ".kumiko", "package.json");
|
|
479
|
+
expect(existsSync(pkgPath)).toBe(true);
|
|
480
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as Record<string, unknown>;
|
|
481
|
+
expect(pkg).toMatchObject({
|
|
482
|
+
name: "@app/define",
|
|
483
|
+
private: true,
|
|
484
|
+
license: "BUSL-1.1",
|
|
485
|
+
type: "module",
|
|
486
|
+
main: "./define.ts",
|
|
487
|
+
types: "./define.ts",
|
|
488
|
+
exports: {
|
|
489
|
+
".": "./define.ts",
|
|
490
|
+
"./*": "./*",
|
|
491
|
+
},
|
|
492
|
+
});
|
|
493
|
+
});
|
|
494
|
+
});
|