@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,90 @@
|
|
|
1
|
+
// Unit-Tests für resolveStylesheet — die Verzweigungen aus
|
|
2
|
+
// createKumikoServer's CSS-Setup. Läuft unter vitest auf Node, also
|
|
3
|
+
// ohne Bun.serve und ohne Bun.resolveSync; der Default-Resolution-
|
|
4
|
+
// Pfad (Bun-only) ist hier explizit als "skip silent" abgedeckt.
|
|
5
|
+
//
|
|
6
|
+
// Warum als eigener File statt im create-kumiko-server.integration.ts:
|
|
7
|
+
// die anderen Tests dort booten ein TestStack (DB + Redis), das brauchen
|
|
8
|
+
// wir für reine Resolver-Logik nicht.
|
|
9
|
+
|
|
10
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { describe, expect, test } from "vitest";
|
|
14
|
+
import { resolveStylesheet } from "../create-kumiko-server";
|
|
15
|
+
|
|
16
|
+
describe("resolveStylesheet", () => {
|
|
17
|
+
test("string → resolved absolute path", () => {
|
|
18
|
+
// Relativ zum CWD aufgelöst — das ist Node's path.resolve-Default.
|
|
19
|
+
const out = resolveStylesheet({
|
|
20
|
+
features: [],
|
|
21
|
+
stylesheet: "./some/stylesheet.css",
|
|
22
|
+
});
|
|
23
|
+
expect(out).toMatch(/\/some\/stylesheet\.css$/);
|
|
24
|
+
expect(out?.startsWith("/")).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("absolute string bleibt unverändert (path.resolve idempotent)", () => {
|
|
28
|
+
const out = resolveStylesheet({
|
|
29
|
+
features: [],
|
|
30
|
+
stylesheet: "/abs/path/styles.css",
|
|
31
|
+
});
|
|
32
|
+
expect(out).toBe("/abs/path/styles.css");
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("false → undefined (Pipeline explizit aus)", () => {
|
|
36
|
+
const out = resolveStylesheet({
|
|
37
|
+
features: [],
|
|
38
|
+
stylesheet: false,
|
|
39
|
+
clientEntry: "./entry.tsx",
|
|
40
|
+
});
|
|
41
|
+
expect(out).toBeUndefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("undefined ohne clientEntry → undefined (kein Browser-Bundle, keine CSS)", () => {
|
|
45
|
+
const out = resolveStylesheet({
|
|
46
|
+
features: [],
|
|
47
|
+
});
|
|
48
|
+
expect(out).toBeUndefined();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("undefined + clientEntry unter Node (ohne Bun): undefined (silent skip)", () => {
|
|
52
|
+
// hasBun ist false in vitest-Node — Default-Resolution wird übersprungen,
|
|
53
|
+
// ohne Throw. Wer im Test eine echte CSS will, übergibt explicit string.
|
|
54
|
+
// Unter Bun (production) würde Bun.resolveSync den Path liefern; das
|
|
55
|
+
// testet der bestehende create-kumiko-server.integration.ts.
|
|
56
|
+
const out = resolveStylesheet({
|
|
57
|
+
features: [],
|
|
58
|
+
clientEntry: "./entry.tsx",
|
|
59
|
+
});
|
|
60
|
+
expect(out).toBeUndefined();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("undefined + clientEntry + src/styles.css existiert → returns App-Theme-Override", () => {
|
|
64
|
+
// Auto-Detection greift VOR dem renderer-web-Fallback: Wenn die App
|
|
65
|
+
// ein eigenes src/styles.css hat (App-Theme-Pattern), wird das
|
|
66
|
+
// automatisch als Tailwind-Entry genommen — symmetrisch zum prod-
|
|
67
|
+
// Build (kumiko-build:resolveStylesheetEntry). Ohne diesen Check
|
|
68
|
+
// müsste jede App `stylesheet:` explizit setzen.
|
|
69
|
+
// realpath aufgelöst, weil macOS' /var → /private/var Symlink sonst
|
|
70
|
+
// einen anderen Pfad rauswirft als process.cwd() nach chdir.
|
|
71
|
+
const tmpDir = realpathSync(mkdtempSync(join(tmpdir(), "kumiko-resolve-styles-")));
|
|
72
|
+
const srcDir = join(tmpDir, "src");
|
|
73
|
+
mkdirSync(srcDir);
|
|
74
|
+
const stylesheet = join(srcDir, "styles.css");
|
|
75
|
+
writeFileSync(stylesheet, "/* app theme */");
|
|
76
|
+
|
|
77
|
+
const cwdBefore = process.cwd();
|
|
78
|
+
process.chdir(tmpDir);
|
|
79
|
+
try {
|
|
80
|
+
const out = resolveStylesheet({
|
|
81
|
+
features: [],
|
|
82
|
+
clientEntry: "./entry.tsx",
|
|
83
|
+
});
|
|
84
|
+
expect(out).toBe(stylesheet);
|
|
85
|
+
} finally {
|
|
86
|
+
process.chdir(cwdBefore);
|
|
87
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Unit-Tests für resolveTailwindCli — die zwei Failure-Branches sind
|
|
2
|
+
// genau das, was den dev-server-Crash bei flakigem Netz verhindert:
|
|
3
|
+
// kein Bun → undefined, Package nicht installiert → undefined.
|
|
4
|
+
|
|
5
|
+
import { describe, expect, test } from "vitest";
|
|
6
|
+
import { resolveTailwindCli } from "../resolve-tailwind-cli";
|
|
7
|
+
|
|
8
|
+
describe("resolveTailwindCli", () => {
|
|
9
|
+
test("ohne Bun-Resolver → undefined (silent skip)", () => {
|
|
10
|
+
const out = resolveTailwindCli({ bun: undefined, cwd: "/somewhere" });
|
|
11
|
+
expect(out).toBeUndefined();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("Bun.resolveSync wirft → undefined", () => {
|
|
15
|
+
const out = resolveTailwindCli({
|
|
16
|
+
bun: {
|
|
17
|
+
resolveSync: () => {
|
|
18
|
+
throw new Error("module not found");
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
cwd: "/somewhere",
|
|
22
|
+
});
|
|
23
|
+
expect(out).toBeUndefined();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("Bun.resolveSync liefert package.json → absoluter Bin-Pfad", () => {
|
|
27
|
+
const out = resolveTailwindCli({
|
|
28
|
+
bun: {
|
|
29
|
+
resolveSync: () => "/repo/node_modules/@tailwindcss/cli/package.json",
|
|
30
|
+
},
|
|
31
|
+
cwd: "/repo",
|
|
32
|
+
});
|
|
33
|
+
expect(out).toBe("/repo/node_modules/@tailwindcss/cli/dist/index.mjs");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("Bun-Resolver wird mit korrektem id und cwd aufgerufen", () => {
|
|
37
|
+
const calls: Array<{ id: string; from: string }> = [];
|
|
38
|
+
resolveTailwindCli({
|
|
39
|
+
bun: {
|
|
40
|
+
resolveSync: (id, from) => {
|
|
41
|
+
calls.push({ id, from });
|
|
42
|
+
return "/x/node_modules/@tailwindcss/cli/package.json";
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
cwd: "/some/working/dir",
|
|
46
|
+
});
|
|
47
|
+
expect(calls).toEqual([{ id: "@tailwindcss/cli/package.json", from: "/some/working/dir" }]);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Spec-Tests für die Bun.serve-Options + Heartbeat-Cadence. Diese
|
|
2
|
+
// Konstanten/Defaults haben Live-Bugs verursacht und sind 1-Zeile-
|
|
3
|
+
// revertierbar — die Tests pinsen Intent + akzeptablen Range gegen
|
|
4
|
+
// "looks like a leak"-Reverts oder "bisschen rauf, sollte reichen"-
|
|
5
|
+
// Tweaks.
|
|
6
|
+
|
|
7
|
+
import { SSE_HEARTBEAT_INTERVAL_MS } from "@cosmicdrift/kumiko-framework/api";
|
|
8
|
+
import { describe, expect, test } from "vitest";
|
|
9
|
+
import { buildBunServeOptions } from "../run-prod-app";
|
|
10
|
+
|
|
11
|
+
describe("Bun.serve options for production", () => {
|
|
12
|
+
test("idleTimeout is 0 (disabled) — required for SSE long-lived connections", () => {
|
|
13
|
+
// Bun.serve default ist 10s — ohne Override killt das SSE-Streams
|
|
14
|
+
// mit halbem HTTP/2-RST_STREAM. Spec ist "disabled"; jeder andere
|
|
15
|
+
// Wert (auch ein vermeintlich "großzügiges" 60) bricht SSE sobald
|
|
16
|
+
// ein Client den Tab im Hintergrund hat (kein Heartbeat-Read auf
|
|
17
|
+
// Browser-Seite, Idle-Timer feuert).
|
|
18
|
+
const opts = buildBunServeOptions(0, () => new Response("ok"));
|
|
19
|
+
expect(opts.idleTimeout).toBe(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("port + fetch werden 1:1 durchgereicht", () => {
|
|
23
|
+
const fetchHandler = (_req: Request) => new Response("test");
|
|
24
|
+
const opts = buildBunServeOptions(3000, fetchHandler);
|
|
25
|
+
expect(opts.port).toBe(3000);
|
|
26
|
+
expect(opts.fetch).toBe(fetchHandler);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("SSE heartbeat cadence", () => {
|
|
31
|
+
test("liegt unter dem strengsten Edge-Idle-Timeout (Cloudflare 100s)", () => {
|
|
32
|
+
// Bun.serve idleTimeout ist disabled (siehe buildBunServeOptions),
|
|
33
|
+
// damit fällt der Bun-default-10s-Layer raus. Die strengsten
|
|
34
|
+
// verbleibenden Layer sind CDN/LB-Edges:
|
|
35
|
+
// - Cloudflare Edge: 100 s ← strengster realistischer Layer
|
|
36
|
+
// - AWS ALB: 60 s
|
|
37
|
+
// - Nginx default: keep-alive 60 s, aber nicht für Streams
|
|
38
|
+
// Heartbeat muss DEUTLICH darunter liegen damit auch ein verschluckter
|
|
39
|
+
// Frame nicht zur Connection-Death führt — Faktor 5 ist konservativ.
|
|
40
|
+
expect(SSE_HEARTBEAT_INTERVAL_MS).toBeLessThan(60_000);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("liegt nicht zu hoch — DefenseInDepth gegen Bun-Default-Drift", () => {
|
|
44
|
+
// Wenn jemand idleTimeout: 0 wieder rausnimmt (Audit, Linter,
|
|
45
|
+
// Misverständnis), darf der Heartbeat nicht der einzige
|
|
46
|
+
// Schutzwall sein der unter dem 10s-Bun-default liegt. ≤30s
|
|
47
|
+
// gibt Cushion + bleibt deutlich unter Bun-default-10s × 3.
|
|
48
|
+
expect(SSE_HEARTBEAT_INTERVAL_MS).toBeLessThanOrEqual(30_000);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("ist >= 5 s — Frequency-Wall gegen versehentliches Network-Spam", () => {
|
|
52
|
+
// 1000 anonyme Viewer × Heartbeat-Frequency darf den Server nicht
|
|
53
|
+
// mit Frames fluten. 5s = 200 frames/s pro 1000 Clients ist OK.
|
|
54
|
+
// Unter 5s wäre eher ein "tippfehler" als bewusste Wahl.
|
|
55
|
+
expect(SSE_HEARTBEAT_INTERVAL_MS).toBeGreaterThanOrEqual(5_000);
|
|
56
|
+
});
|
|
57
|
+
});
|