@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.
- package/README.md +17 -0
- package/bin/kumiko-build.ts +3 -2
- package/bin/kumiko-dev.ts +1 -1
- package/bin/kumiko-init-deploy.ts +1 -1
- package/bin/kumiko-schema-check.ts +4 -1
- package/dist/build-server-bundle.d.ts +31 -0
- package/dist/build-server-bundle.js +282 -0
- package/dist/build.d.ts +2 -0
- package/dist/build.js +5 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +9 -0
- package/dist/codegen/index.d.ts +4 -0
- package/dist/codegen/index.js +4 -0
- package/dist/codegen/render.d.ts +55 -0
- package/dist/codegen/render.js +273 -0
- package/dist/codegen/run-codegen.d.ts +20 -0
- package/dist/codegen/run-codegen.js +219 -0
- package/dist/codegen/scan-events.d.ts +76 -0
- package/dist/codegen/scan-events.js +447 -0
- package/dist/codegen/watch.d.ts +33 -0
- package/dist/codegen/watch.js +112 -0
- package/dist/compose-stacks.d.ts +60 -0
- package/dist/compose-stacks.js +127 -0
- package/dist/crash-tracker.d.ts +17 -0
- package/dist/crash-tracker.js +37 -0
- package/dist/create-kumiko-server.d.ts +195 -0
- package/dist/create-kumiko-server.js +1040 -0
- package/dist/env-schema.d.ts +15 -0
- package/dist/env-schema.js +60 -0
- package/dist/few-shot-corpus.d.ts +72 -0
- package/dist/few-shot-corpus.js +262 -0
- package/dist/framework-version.d.ts +1 -0
- package/dist/framework-version.js +31 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +33 -0
- package/dist/init-deploy-cli.d.ts +10 -0
- package/dist/init-deploy-cli.js +111 -0
- package/dist/kebab.d.ts +1 -0
- package/{src/kebab.ts → dist/kebab.js} +2 -3
- package/dist/run-dev-app.d.ts +174 -0
- package/dist/run-dev-app.js +403 -0
- package/dist/scaffold-app-feature.d.ts +16 -0
- package/dist/scaffold-app-feature.js +240 -0
- package/dist/scaffold-app.d.ts +45 -0
- package/dist/scaffold-app.js +852 -0
- package/dist/scaffold-demo-tasks.d.ts +8 -0
- package/{src/scaffold-demo-tasks.ts → dist/scaffold-demo-tasks.js} +69 -98
- package/dist/scaffold-deploy.d.ts +92 -0
- package/dist/scaffold-deploy.js +254 -0
- package/dist/scaffold-feature.d.ts +26 -0
- package/dist/scaffold-feature.js +163 -0
- package/dist/schema-apply.d.ts +11 -0
- package/dist/schema-apply.js +95 -0
- package/dist/schema-check-core.d.ts +2 -0
- package/{src/schema-check-core.ts → dist/schema-check-core.js} +8 -10
- package/dist/schema-tables-auth-mode.d.ts +2 -0
- package/{src/schema-tables-auth-mode.ts → dist/schema-tables-auth-mode.js} +2 -10
- package/dist/schema-tables-minimal.d.ts +2 -0
- package/{src/schema-tables-minimal.ts → dist/schema-tables-minimal.js} +2 -11
- package/dist/setup-test-stack-from-features.d.ts +11 -0
- package/dist/setup-test-stack-from-features.js +42 -0
- package/dist/welcome-banner.d.ts +15 -0
- package/dist/welcome-banner.js +27 -0
- package/package.json +71 -24
- package/src/changes.json +6 -0
- package/src/__tests__/build-prod-bundle.integration.test.ts +0 -282
- package/src/__tests__/build-server-bundle.test.ts +0 -228
- package/src/__tests__/classify-change.test.ts +0 -87
- package/src/__tests__/compose-stacks.test.ts +0 -273
- package/src/__tests__/config-resolver-default.integration.test.ts +0 -165
- package/src/__tests__/crash-tracker.test.ts +0 -89
- package/src/__tests__/create-kumiko-server-errors.test.ts +0 -54
- package/src/__tests__/create-kumiko-server-options.test.ts +0 -33
- package/src/__tests__/create-kumiko-server.integration.test.ts +0 -1007
- package/src/__tests__/discover-format.test.ts +0 -32
- package/src/__tests__/env-schema.integration.test.ts +0 -248
- package/src/__tests__/env-schema.test.ts +0 -132
- package/src/__tests__/few-shot-corpus.test.ts +0 -316
- package/src/__tests__/framework-version.test.ts +0 -45
- package/src/__tests__/init-deploy-cli.test.ts +0 -113
- package/src/__tests__/kumiko-schema-check.test.ts +0 -56
- package/src/__tests__/merge-extra-context.test.ts +0 -67
- package/src/__tests__/resolve-stylesheet.test.ts +0 -132
- package/src/__tests__/run-dev-app-boot-validation.test.ts +0 -38
- package/src/__tests__/run-dev-app.integration.test.ts +0 -290
- package/src/__tests__/runtime-classification.test.ts +0 -26
- package/src/__tests__/saas-identity-wire.integration.test.ts +0 -427
- package/src/__tests__/scaffold-app-feature.test.ts +0 -216
- package/src/__tests__/scaffold-app.test.ts +0 -630
- package/src/__tests__/scaffold-demo-tasks.test.ts +0 -47
- package/src/__tests__/scaffold-deploy.test.ts +0 -464
- package/src/__tests__/scaffold-dev-cycle.integration.test.ts +0 -109
- package/src/__tests__/scaffold-feature.test.ts +0 -143
- package/src/__tests__/schema-apply.integration.test.ts +0 -191
- package/src/__tests__/setup-test-stack-from-features.integration.test.ts +0 -30
- package/src/__tests__/setup-test-stack-job-run-logger.integration.test.ts +0 -72
- package/src/__tests__/walkthrough.integration.test.ts +0 -140
- package/src/__tests__/welcome-banner.test.ts +0 -42
- package/src/build-server-bundle.ts +0 -346
- package/src/build.ts +0 -28
- package/src/codegen/__tests__/render-codegen.test.ts +0 -100
- package/src/codegen/__tests__/run-codegen.test.ts +0 -665
- package/src/codegen/__tests__/strict-mode-diagnostics.test.ts +0 -347
- package/src/codegen/__tests__/watch.test.ts +0 -222
- package/src/codegen/index.ts +0 -19
- package/src/codegen/render.ts +0 -305
- package/src/codegen/run-codegen.ts +0 -278
- package/src/codegen/scan-events.ts +0 -565
- package/src/codegen/watch.ts +0 -140
- package/src/compose-stacks.ts +0 -193
- package/src/crash-tracker.ts +0 -56
- package/src/create-kumiko-server.ts +0 -1397
- package/src/env-schema.ts +0 -72
- package/src/few-shot-corpus.ts +0 -370
- package/src/framework-version.ts +0 -39
- package/src/index.ts +0 -105
- package/src/init-deploy-cli.ts +0 -148
- package/src/run-dev-app.ts +0 -694
- package/src/scaffold-app-feature.ts +0 -283
- package/src/scaffold-app.ts +0 -1033
- package/src/scaffold-deploy.ts +0 -393
- package/src/scaffold-feature.ts +0 -226
- package/src/schema-apply.ts +0 -123
- package/src/setup-test-stack-from-features.ts +0 -80
- 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
|
-
});
|