@cosmicdrift/kumiko-dev-server 0.329.0 → 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,347 +0,0 @@
|
|
|
1
|
-
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { dirname, join } from "node:path";
|
|
4
|
-
// TS7's package.json exports only version.cjs at ".", so the classic
|
|
5
|
-
// createProgram/ScriptTarget Compiler API is unreachable via `import *
|
|
6
|
-
// as ts from "typescript"` anymore; ts-morph's vendored (older) compiler
|
|
7
|
-
// build is the only way to get that surface, so diagnostics here run
|
|
8
|
-
// against a different compiler than the repo's own tsc.
|
|
9
|
-
import { ts } from "ts-morph";
|
|
10
|
-
import { runCodegen } from "../run-codegen";
|
|
11
|
-
|
|
12
|
-
const REPO_ROOT = join(__dirname, "../../../../..");
|
|
13
|
-
const FRAMEWORK_SRC = join(REPO_ROOT, "packages/framework/src");
|
|
14
|
-
|
|
15
|
-
const TEST_FIXTURE_DIR = join(__dirname, ".tmp-fixtures");
|
|
16
|
-
const createdDirs: string[] = [];
|
|
17
|
-
|
|
18
|
-
function makeAppDir(): string {
|
|
19
|
-
mkdirSync(TEST_FIXTURE_DIR, { recursive: true });
|
|
20
|
-
const dir = mkdtempSync(join(TEST_FIXTURE_DIR, "app-"));
|
|
21
|
-
createdDirs.push(dir);
|
|
22
|
-
return dir;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
afterAll(() => {
|
|
26
|
-
for (const d of createdDirs) {
|
|
27
|
-
try {
|
|
28
|
-
rmSync(d, { recursive: true, force: true });
|
|
29
|
-
} catch {}
|
|
30
|
-
}
|
|
31
|
-
try {
|
|
32
|
-
rmSync(TEST_FIXTURE_DIR, { recursive: true, force: true });
|
|
33
|
-
} catch {}
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
function write(dir: string, relPath: string, content: string): string {
|
|
37
|
-
const full = join(dir, relPath);
|
|
38
|
-
mkdirSync(dirname(full), { recursive: true });
|
|
39
|
-
writeFileSync(full, content, "utf-8");
|
|
40
|
-
return full;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function compileApp(appRoot: string): readonly ts.Diagnostic[] {
|
|
44
|
-
const compilerOptions: ts.CompilerOptions = {
|
|
45
|
-
target: ts.ScriptTarget.ESNext,
|
|
46
|
-
module: ts.ModuleKind.ESNext,
|
|
47
|
-
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
48
|
-
strict: true,
|
|
49
|
-
skipLibCheck: true,
|
|
50
|
-
esModuleInterop: true,
|
|
51
|
-
noEmit: true,
|
|
52
|
-
baseUrl: REPO_ROOT,
|
|
53
|
-
paths: {
|
|
54
|
-
"@cosmicdrift/kumiko-framework/*": [join(FRAMEWORK_SRC, "*/index.ts")],
|
|
55
|
-
},
|
|
56
|
-
types: [],
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const program = ts.createProgram({
|
|
60
|
-
rootNames: collectFiles(appRoot),
|
|
61
|
-
options: compilerOptions,
|
|
62
|
-
});
|
|
63
|
-
return ts.getPreEmitDiagnostics(program);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function collectFiles(dir: string): string[] {
|
|
67
|
-
const out: string[] = [];
|
|
68
|
-
const fs = require("node:fs");
|
|
69
|
-
const walk = (d: string) => {
|
|
70
|
-
let entries: string[];
|
|
71
|
-
try {
|
|
72
|
-
entries = fs.readdirSync(d);
|
|
73
|
-
} catch {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
for (const e of entries) {
|
|
77
|
-
if (e.startsWith(".") && e !== ".kumiko") continue;
|
|
78
|
-
if (e === "node_modules") continue;
|
|
79
|
-
const full = join(d, e);
|
|
80
|
-
const stat = fs.statSync(full);
|
|
81
|
-
if (stat.isDirectory()) walk(full);
|
|
82
|
-
else if (stat.isFile() && (e.endsWith(".ts") || e.endsWith(".tsx")) && !e.endsWith(".d.ts"))
|
|
83
|
-
out.push(full);
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
walk(join(dir, "src"));
|
|
87
|
-
walk(join(dir, ".kumiko"));
|
|
88
|
-
return out;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function writeOrderPlacedSchema(appRoot: string): void {
|
|
92
|
-
write(
|
|
93
|
-
appRoot,
|
|
94
|
-
"src/feature/events.ts",
|
|
95
|
-
[
|
|
96
|
-
'import { z } from "zod";',
|
|
97
|
-
"export const orderPlacedSchema = z.object({",
|
|
98
|
-
" orderId: z.string(),",
|
|
99
|
-
" customerId: z.string(),",
|
|
100
|
-
" amount: z.number(),",
|
|
101
|
-
"});",
|
|
102
|
-
"",
|
|
103
|
-
].join("\n"),
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
describe("strict-mode diagnostics -- the actual contract of the codegen", () => {
|
|
108
|
-
let appRoot: string;
|
|
109
|
-
let allDiagnostics: readonly ts.Diagnostic[];
|
|
110
|
-
|
|
111
|
-
beforeAll(() => {
|
|
112
|
-
appRoot = makeAppDir();
|
|
113
|
-
writeOrderPlacedSchema(appRoot);
|
|
114
|
-
write(
|
|
115
|
-
appRoot,
|
|
116
|
-
"src/feature/feature.ts",
|
|
117
|
-
[
|
|
118
|
-
'import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";',
|
|
119
|
-
'import { orderPlacedSchema } from "./events";',
|
|
120
|
-
"",
|
|
121
|
-
'export const ordersFeature = defineFeature("orders", (r) => ({',
|
|
122
|
-
' placed: r.defineEvent("placed", orderPlacedSchema),',
|
|
123
|
-
"}));",
|
|
124
|
-
"",
|
|
125
|
-
].join("\n"),
|
|
126
|
-
);
|
|
127
|
-
runCodegen({ appRoot });
|
|
128
|
-
|
|
129
|
-
write(
|
|
130
|
-
appRoot,
|
|
131
|
-
"src/feature/handler-good.ts",
|
|
132
|
-
[
|
|
133
|
-
'import { defineWriteHandler } from "../../.kumiko/define";',
|
|
134
|
-
'import { z } from "zod";',
|
|
135
|
-
"",
|
|
136
|
-
"export const placeOrder = defineWriteHandler({",
|
|
137
|
-
' name: "orders.placeOrder",',
|
|
138
|
-
" schema: z.object({}),",
|
|
139
|
-
' access: { roles: ["Admin"] },',
|
|
140
|
-
" handler: async (_event, ctx) => {",
|
|
141
|
-
" await ctx.appendEvent({",
|
|
142
|
-
' aggregateId: "x",',
|
|
143
|
-
' aggregateType: "order",',
|
|
144
|
-
' type: "orders:event:placed",',
|
|
145
|
-
' payload: { orderId: "o1", customerId: "c1", amount: 99 },',
|
|
146
|
-
" });",
|
|
147
|
-
' return { isSuccess: true as const, data: { id: "o1" } };',
|
|
148
|
-
" },",
|
|
149
|
-
"});",
|
|
150
|
-
"",
|
|
151
|
-
].join("\n"),
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
write(
|
|
155
|
-
appRoot,
|
|
156
|
-
"src/feature/handler-unknown-type.ts",
|
|
157
|
-
[
|
|
158
|
-
'import { defineWriteHandler } from "../../.kumiko/define";',
|
|
159
|
-
'import { z } from "zod";',
|
|
160
|
-
"",
|
|
161
|
-
"export const placeOrder = defineWriteHandler({",
|
|
162
|
-
' name: "orders.placeOrder",',
|
|
163
|
-
" schema: z.object({}),",
|
|
164
|
-
' access: { roles: ["Admin"] },',
|
|
165
|
-
" handler: async (_event, ctx) => {",
|
|
166
|
-
" await ctx.appendEvent({",
|
|
167
|
-
' aggregateId: "x",',
|
|
168
|
-
' aggregateType: "order",',
|
|
169
|
-
' type: "totally:made:up",',
|
|
170
|
-
" payload: { whatever: 1 },",
|
|
171
|
-
" });",
|
|
172
|
-
' return { isSuccess: true as const, data: { id: "x" } };',
|
|
173
|
-
" },",
|
|
174
|
-
"});",
|
|
175
|
-
"",
|
|
176
|
-
].join("\n"),
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
write(
|
|
180
|
-
appRoot,
|
|
181
|
-
"src/feature/handler-payload-mismatch.ts",
|
|
182
|
-
[
|
|
183
|
-
'import { defineWriteHandler } from "../../.kumiko/define";',
|
|
184
|
-
'import { z } from "zod";',
|
|
185
|
-
"",
|
|
186
|
-
"export const placeOrder = defineWriteHandler({",
|
|
187
|
-
' name: "orders.placeOrder",',
|
|
188
|
-
" schema: z.object({}),",
|
|
189
|
-
' access: { roles: ["Admin"] },',
|
|
190
|
-
" handler: async (_event, ctx) => {",
|
|
191
|
-
" await ctx.appendEvent({",
|
|
192
|
-
' aggregateId: "x",',
|
|
193
|
-
' aggregateType: "order",',
|
|
194
|
-
' type: "orders:event:placed",',
|
|
195
|
-
' payload: { orderId: "o1", customerId: "c1", amount: 99, bogus: "extra" },',
|
|
196
|
-
" });",
|
|
197
|
-
' return { isSuccess: true as const, data: { id: "o1" } };',
|
|
198
|
-
" },",
|
|
199
|
-
"});",
|
|
200
|
-
"",
|
|
201
|
-
].join("\n"),
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
write(
|
|
205
|
-
appRoot,
|
|
206
|
-
"src/feature/handler-direct.ts",
|
|
207
|
-
[
|
|
208
|
-
'import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";',
|
|
209
|
-
'import { z } from "zod";',
|
|
210
|
-
"",
|
|
211
|
-
"export const placeOrder = defineWriteHandler({",
|
|
212
|
-
' name: "orders.placeOrder",',
|
|
213
|
-
" schema: z.object({}),",
|
|
214
|
-
' access: { roles: ["Admin"] },',
|
|
215
|
-
" handler: async (_event, ctx) => {",
|
|
216
|
-
" await ctx.appendEvent({",
|
|
217
|
-
' aggregateId: "x",',
|
|
218
|
-
' aggregateType: "order",',
|
|
219
|
-
' type: "orders:event:placed",',
|
|
220
|
-
' payload: { orderId: "o1", customerId: "c1", amount: 99 },',
|
|
221
|
-
" });",
|
|
222
|
-
' return { isSuccess: true as const, data: { id: "o1" } };',
|
|
223
|
-
" },",
|
|
224
|
-
"});",
|
|
225
|
-
"",
|
|
226
|
-
].join("\n"),
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
write(
|
|
230
|
-
appRoot,
|
|
231
|
-
"src/feature/handler-byname-good.ts",
|
|
232
|
-
[
|
|
233
|
-
'import { defineWriteHandler } from "../../.kumiko/define";',
|
|
234
|
-
'import { z } from "zod";',
|
|
235
|
-
'import { ordersFeature } from "./feature";',
|
|
236
|
-
"",
|
|
237
|
-
"const { placed } = ordersFeature.exports;",
|
|
238
|
-
"",
|
|
239
|
-
"export const placeOrder = defineWriteHandler({",
|
|
240
|
-
' name: "orders.placeOrder",',
|
|
241
|
-
" schema: z.object({}),",
|
|
242
|
-
' access: { roles: ["Admin"] },',
|
|
243
|
-
" handler: async (_event, ctx) => {",
|
|
244
|
-
" await ctx.appendEvent({",
|
|
245
|
-
' aggregateId: "x",',
|
|
246
|
-
' aggregateType: "order",',
|
|
247
|
-
" type: placed.name,",
|
|
248
|
-
' payload: { orderId: "o1", customerId: "c1", amount: 99 },',
|
|
249
|
-
" });",
|
|
250
|
-
' return { isSuccess: true as const, data: { id: "o1" } };',
|
|
251
|
-
" },",
|
|
252
|
-
"});",
|
|
253
|
-
"",
|
|
254
|
-
].join("\n"),
|
|
255
|
-
);
|
|
256
|
-
|
|
257
|
-
write(
|
|
258
|
-
appRoot,
|
|
259
|
-
"src/feature/handler-byname-bad.ts",
|
|
260
|
-
[
|
|
261
|
-
'import { defineWriteHandler } from "../../.kumiko/define";',
|
|
262
|
-
'import { z } from "zod";',
|
|
263
|
-
'import { ordersFeature } from "./feature";',
|
|
264
|
-
"",
|
|
265
|
-
"const { placed } = ordersFeature.exports;",
|
|
266
|
-
"",
|
|
267
|
-
"export const placeOrder = defineWriteHandler({",
|
|
268
|
-
' name: "orders.placeOrder",',
|
|
269
|
-
" schema: z.object({}),",
|
|
270
|
-
' access: { roles: ["Admin"] },',
|
|
271
|
-
" handler: async (_event, ctx) => {",
|
|
272
|
-
" await ctx.appendEvent({",
|
|
273
|
-
' aggregateId: "x",',
|
|
274
|
-
' aggregateType: "order",',
|
|
275
|
-
" type: placed.name,",
|
|
276
|
-
' payload: { orderId: "o1", customerId: "c1", amount: 99, bogus: "extra" },',
|
|
277
|
-
" });",
|
|
278
|
-
' return { isSuccess: true as const, data: { id: "o1" } };',
|
|
279
|
-
" },",
|
|
280
|
-
"});",
|
|
281
|
-
"",
|
|
282
|
-
].join("\n"),
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
allDiagnostics = compileApp(appRoot);
|
|
286
|
-
}, 120_000);
|
|
287
|
-
|
|
288
|
-
test("good ctx.appendEvent compiles cleanly", () => {
|
|
289
|
-
const handlerErrors = allDiagnostics.filter((d) =>
|
|
290
|
-
d.file?.fileName.endsWith("/feature/handler-good.ts"),
|
|
291
|
-
);
|
|
292
|
-
expect(handlerErrors).toHaveLength(0);
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
test("unknown event-type triggers TS2322 with augmented map in error message", () => {
|
|
296
|
-
const handlerErrors = allDiagnostics.filter((d) =>
|
|
297
|
-
d.file?.fileName.endsWith("/feature/handler-unknown-type.ts"),
|
|
298
|
-
);
|
|
299
|
-
const ts2322 = handlerErrors.filter((d) => d.code === 2322);
|
|
300
|
-
expect(ts2322.length).toBeGreaterThan(0);
|
|
301
|
-
const flattened = ts2322
|
|
302
|
-
.map((d) => ts.flattenDiagnosticMessageText(d.messageText, "\n"))
|
|
303
|
-
.join("\n");
|
|
304
|
-
expect(flattened).toMatch(/keyof KumikoEventTypeMap|"orders:event:placed"/);
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
test("payload-shape mismatch triggers a property-error", () => {
|
|
308
|
-
const handlerErrors = allDiagnostics.filter((d) =>
|
|
309
|
-
d.file?.fileName.endsWith("/feature/handler-payload-mismatch.ts"),
|
|
310
|
-
);
|
|
311
|
-
const propErrors = handlerErrors.filter((d) => d.code === 2353);
|
|
312
|
-
expect(propErrors.length).toBeGreaterThan(0);
|
|
313
|
-
const flattened = propErrors
|
|
314
|
-
.map((d) => ts.flattenDiagnosticMessageText(d.messageText, "\n"))
|
|
315
|
-
.join("\n");
|
|
316
|
-
expect(flattened).toMatch(/'bogus'/);
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
test("direct framework-import + augmentation-included compiles strict too", () => {
|
|
320
|
-
const handlerErrors = allDiagnostics.filter((d) =>
|
|
321
|
-
d.file?.fileName.endsWith("/feature/handler-direct.ts"),
|
|
322
|
-
);
|
|
323
|
-
expect(handlerErrors).toHaveLength(0);
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
test("eventDef.name pattern: literal-typed name resolves to correct payload-shape", () => {
|
|
327
|
-
const goodErrors = allDiagnostics.filter((d) =>
|
|
328
|
-
d.file?.fileName.endsWith("/feature/handler-byname-good.ts"),
|
|
329
|
-
);
|
|
330
|
-
if (goodErrors.length > 0) {
|
|
331
|
-
const msgs = goodErrors
|
|
332
|
-
.map((d) => ` TS${d.code}: ${ts.flattenDiagnosticMessageText(d.messageText, "\n")}`)
|
|
333
|
-
.join("\n");
|
|
334
|
-
throw new Error(`expected handler-byname-good.ts to compile cleanly, got:\n${msgs}`);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const badErrors = allDiagnostics.filter((d) =>
|
|
338
|
-
d.file?.fileName.endsWith("/feature/handler-byname-bad.ts"),
|
|
339
|
-
);
|
|
340
|
-
const propErrors = badErrors.filter((d) => d.code === 2353);
|
|
341
|
-
expect(propErrors.length).toBeGreaterThan(0);
|
|
342
|
-
const flattened = propErrors
|
|
343
|
-
.map((d) => ts.flattenDiagnosticMessageText(d.messageText, "\n"))
|
|
344
|
-
.join("\n");
|
|
345
|
-
expect(flattened).toMatch(/'bogus'/);
|
|
346
|
-
});
|
|
347
|
-
});
|
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
// watchAndRegenerate — file-watcher-Tests. Verifiziert: initial-Pass
|
|
2
|
-
// läuft synchron, file-changes triggern einen erneuten Pass mit Debounce,
|
|
3
|
-
// close() ist idempotent.
|
|
4
|
-
//
|
|
5
|
-
// Fixtures liegen wie bei strict-mode-diagnostics.test.ts unter
|
|
6
|
-
// `__tests__/.tmp-fixtures/` (gitignored), damit Node's natürliches
|
|
7
|
-
// `node_modules`-Hochsuchen 'zod' findet — auch wenn watch-Tests
|
|
8
|
-
// 'zod' nicht direct nutzen, runCodegen scant feature-files die
|
|
9
|
-
// `import { z } from "zod"` haben.
|
|
10
|
-
|
|
11
|
-
import { afterAll, describe, expect, test } from "bun:test";
|
|
12
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
13
|
-
import { dirname, join } from "node:path";
|
|
14
|
-
import type { CodegenResult } from "../run-codegen";
|
|
15
|
-
import { watchAndRegenerate } from "../watch";
|
|
16
|
-
|
|
17
|
-
const TEST_FIXTURE_DIR = join(__dirname, ".tmp-fixtures");
|
|
18
|
-
const createdDirs: string[] = [];
|
|
19
|
-
|
|
20
|
-
function makeAppDir(): string {
|
|
21
|
-
mkdirSync(TEST_FIXTURE_DIR, { recursive: true });
|
|
22
|
-
const dir = mkdtempSync(join(TEST_FIXTURE_DIR, "watch-"));
|
|
23
|
-
createdDirs.push(dir);
|
|
24
|
-
return dir;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function writeFile(dir: string, relPath: string, content: string): string {
|
|
28
|
-
const full = join(dir, relPath);
|
|
29
|
-
mkdirSync(dirname(full), { recursive: true });
|
|
30
|
-
writeFileSync(full, content, "utf-8");
|
|
31
|
-
return full;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
afterAll(() => {
|
|
35
|
-
for (const d of createdDirs) {
|
|
36
|
-
try {
|
|
37
|
-
rmSync(d, { recursive: true, force: true });
|
|
38
|
-
} catch {
|
|
39
|
-
/* best-effort */
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Polls a predicate at `interval` ms until it returns true, or rejects
|
|
46
|
-
* after `timeout`. Replaces fixed `setTimeout(...)` waits — those
|
|
47
|
-
* implicitly assume "this many ms is enough", which is brittle on
|
|
48
|
-
* loaded CI runners. The polling form converges as fast as the system
|
|
49
|
-
* allows AND fails loudly with a useful message if the event never lands.
|
|
50
|
-
*
|
|
51
|
-
* `retry`/`retryIntervalMs`: for predicates gated on a native `fs.watch`
|
|
52
|
-
* event, a single write only gets one chance at delivery — under macOS
|
|
53
|
-
* FSEvents backlog (hundreds of concurrent recursive watches in a full
|
|
54
|
-
* `bun test` run), events aren't just delayed, they're sometimes dropped
|
|
55
|
-
* entirely, and Node doesn't surface a rescan signal. `retry` re-fires the
|
|
56
|
-
* triggering action on a cadence so a dropped event costs one interval,
|
|
57
|
-
* not the whole timeout. `retryIntervalMs` must stay well above the
|
|
58
|
-
* watcher's `debounceMs` — a retry that lands mid-debounce just resets
|
|
59
|
-
* the timer and can starve `fire()` forever.
|
|
60
|
-
*/
|
|
61
|
-
async function waitFor(
|
|
62
|
-
predicate: () => boolean,
|
|
63
|
-
opts: {
|
|
64
|
-
timeout?: number;
|
|
65
|
-
interval?: number;
|
|
66
|
-
label?: string;
|
|
67
|
-
retry?: () => void;
|
|
68
|
-
retryIntervalMs?: number;
|
|
69
|
-
} = {},
|
|
70
|
-
): Promise<void> {
|
|
71
|
-
const timeout = opts.timeout ?? 5000;
|
|
72
|
-
const interval = opts.interval ?? 25;
|
|
73
|
-
const retryIntervalMs = opts.retryIntervalMs ?? 250;
|
|
74
|
-
const deadline = Date.now() + timeout;
|
|
75
|
-
let nextRetryAt = Date.now() + retryIntervalMs;
|
|
76
|
-
// @timeout-exception: #3118 fs.watch can drop the trigger; retry re-fires it, unlike the shared waitFor
|
|
77
|
-
while (!predicate()) {
|
|
78
|
-
if (Date.now() > deadline) {
|
|
79
|
-
throw new Error(`waitFor: ${opts.label ?? "predicate"} not satisfied within ${timeout}ms`);
|
|
80
|
-
}
|
|
81
|
-
if (opts.retry && Date.now() >= nextRetryAt) {
|
|
82
|
-
opts.retry();
|
|
83
|
-
nextRetryAt = Date.now() + retryIntervalMs;
|
|
84
|
-
}
|
|
85
|
-
await new Promise((r) => setTimeout(r, interval));
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const FEATURE_TEMPLATE = (featureName: string, eventName: string) => `
|
|
90
|
-
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
91
|
-
import { z } from "zod";
|
|
92
|
-
|
|
93
|
-
export default defineFeature("${featureName}", (r) => {
|
|
94
|
-
r.defineEvent("${eventName}", z.object({ id: z.string() }));
|
|
95
|
-
});
|
|
96
|
-
`;
|
|
97
|
-
|
|
98
|
-
describe("watchAndRegenerate", () => {
|
|
99
|
-
test("initial run produces output synchronously", () => {
|
|
100
|
-
const appRoot = makeAppDir();
|
|
101
|
-
writeFile(appRoot, "src/feature.ts", FEATURE_TEMPLATE("billing", "first-event"));
|
|
102
|
-
|
|
103
|
-
const results: CodegenResult[] = [];
|
|
104
|
-
const handle = watchAndRegenerate({
|
|
105
|
-
appRoot,
|
|
106
|
-
onResult: (r) => results.push(r),
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
expect(results).toHaveLength(1);
|
|
110
|
-
expect(results[0]?.eventCount).toBe(1);
|
|
111
|
-
handle.close();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test("file change triggers a re-run after debounce", async () => {
|
|
115
|
-
const appRoot = makeAppDir();
|
|
116
|
-
writeFile(appRoot, "src/feature.ts", FEATURE_TEMPLATE("orders", "first"));
|
|
117
|
-
|
|
118
|
-
const results: CodegenResult[] = [];
|
|
119
|
-
const handle = watchAndRegenerate({
|
|
120
|
-
appRoot,
|
|
121
|
-
debounceMs: 30,
|
|
122
|
-
onResult: (r) => results.push(r),
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
expect(results).toHaveLength(1);
|
|
126
|
-
expect(results[0]?.eventCount).toBe(1);
|
|
127
|
-
|
|
128
|
-
const rewrite = () =>
|
|
129
|
-
writeFile(
|
|
130
|
-
appRoot,
|
|
131
|
-
"src/feature.ts",
|
|
132
|
-
`
|
|
133
|
-
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
134
|
-
import { z } from "zod";
|
|
135
|
-
|
|
136
|
-
export default defineFeature("orders", (r) => {
|
|
137
|
-
r.defineEvent("first", z.object({ id: z.string() }));
|
|
138
|
-
r.defineEvent("second", z.object({ tag: z.string() }));
|
|
139
|
-
});
|
|
140
|
-
`,
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
// Add a second event-definition by rewriting the feature.
|
|
144
|
-
rewrite();
|
|
145
|
-
|
|
146
|
-
// Poll until the watcher's debounced re-run has landed, re-touching
|
|
147
|
-
// the file every 250ms in case the triggering fs.watch event was
|
|
148
|
-
// dropped rather than merely delayed (see waitFor's `retry` doc).
|
|
149
|
-
// Waits on the expected *state* (eventCount 2), not just a length
|
|
150
|
-
// bump — writeFileSync truncates then writes, so an event fired
|
|
151
|
-
// mid-write would satisfy a length-only predicate with a stale
|
|
152
|
-
// (0 or 1) eventCount, especially with retries widening that window.
|
|
153
|
-
try {
|
|
154
|
-
await waitFor(() => results.some((r) => r.eventCount === 2), {
|
|
155
|
-
timeout: 12000,
|
|
156
|
-
label: "second codegen result",
|
|
157
|
-
retry: rewrite,
|
|
158
|
-
});
|
|
159
|
-
expect(results.at(-1)?.eventCount).toBe(2);
|
|
160
|
-
} finally {
|
|
161
|
-
handle.close();
|
|
162
|
-
}
|
|
163
|
-
}, 15000);
|
|
164
|
-
|
|
165
|
-
test("close() is idempotent", () => {
|
|
166
|
-
const appRoot = makeAppDir();
|
|
167
|
-
writeFile(appRoot, "src/feature.ts", FEATURE_TEMPLATE("nope", "evt"));
|
|
168
|
-
const handle = watchAndRegenerate({ appRoot, onResult: () => {} });
|
|
169
|
-
handle.close();
|
|
170
|
-
expect(() => handle.close()).not.toThrow();
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test("non-ts file changes do not trigger codegen", async () => {
|
|
174
|
-
// Negative-assertion shape: prove that .css/.md changes do NOT add
|
|
175
|
-
// a codegen result. Naïve "sleep N ms then assert length stayed"
|
|
176
|
-
// is racy on macOS, where fs.watch can deliver stale events from
|
|
177
|
-
// pre-watcher writes after the watcher is attached. We sidestep
|
|
178
|
-
// that by anchoring on a POSITIVE control: a known-triggering .ts
|
|
179
|
-
// change at the end. waitFor proves the watcher is alive — so the
|
|
180
|
-
// pre-trigger count is trustworthy.
|
|
181
|
-
const appRoot = makeAppDir();
|
|
182
|
-
writeFile(appRoot, "src/feature.ts", FEATURE_TEMPLATE("ignore-css", "evt"));
|
|
183
|
-
|
|
184
|
-
const results: CodegenResult[] = [];
|
|
185
|
-
const handle = watchAndRegenerate({
|
|
186
|
-
appRoot,
|
|
187
|
-
debounceMs: 30,
|
|
188
|
-
onResult: (r) => results.push(r),
|
|
189
|
-
});
|
|
190
|
-
expect(results).toHaveLength(1);
|
|
191
|
-
|
|
192
|
-
// Drain any stale events from the pre-watcher feature.ts write —
|
|
193
|
-
// some platforms deliver these to a watcher attached after the
|
|
194
|
-
// write. Long enough to outlast debounce + scheduler jitter.
|
|
195
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
196
|
-
const baseline = results.length;
|
|
197
|
-
|
|
198
|
-
// Non-ts writes — the regression we want to catch.
|
|
199
|
-
writeFile(appRoot, "src/styles.css", `body { color: red; }`);
|
|
200
|
-
writeFile(appRoot, "src/README.md", `# hi`);
|
|
201
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
202
|
-
const afterNonTs = results.length;
|
|
203
|
-
|
|
204
|
-
// Positive control: a .ts change MUST trigger. waitFor exits as
|
|
205
|
-
// soon as the new result lands, confirming the watcher is alive.
|
|
206
|
-
// Re-touched on retry in case the triggering event is dropped
|
|
207
|
-
// rather than merely delayed under a loaded fs.watch backlog.
|
|
208
|
-
const triggerRewrite = () =>
|
|
209
|
-
writeFile(appRoot, "src/feature.ts", FEATURE_TEMPLATE("ignore-css", "after"));
|
|
210
|
-
triggerRewrite();
|
|
211
|
-
await waitFor(() => results.length > afterNonTs, {
|
|
212
|
-
timeout: 12000,
|
|
213
|
-
label: "ts-change result after non-ts noise",
|
|
214
|
-
retry: triggerRewrite,
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
// The non-ts writes should not have advanced the count past the
|
|
218
|
-
// baseline. If they did, the watcher's filter is broken.
|
|
219
|
-
expect(afterNonTs).toBe(baseline);
|
|
220
|
-
handle.close();
|
|
221
|
-
}, 15000);
|
|
222
|
-
});
|
package/src/codegen/index.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
renderDefineFile,
|
|
3
|
-
renderInlineSchemasFile,
|
|
4
|
-
renderTypesAugmentation,
|
|
5
|
-
renderWriteHandlerTypes,
|
|
6
|
-
} from "./render";
|
|
7
|
-
export { type CodegenOptions, type CodegenResult, runCodegen } from "./run-codegen";
|
|
8
|
-
export {
|
|
9
|
-
formatScanWarning,
|
|
10
|
-
qualifiedNameToConstName,
|
|
11
|
-
rewriteImportPath,
|
|
12
|
-
type ScannedEvent,
|
|
13
|
-
type ScanOptions,
|
|
14
|
-
type ScanResult,
|
|
15
|
-
type ScanWarning,
|
|
16
|
-
type SchemaSource,
|
|
17
|
-
scanEvents,
|
|
18
|
-
} from "./scan-events";
|
|
19
|
-
export { type WatchHandle, type WatchOptions, watchAndRegenerate } from "./watch";
|