@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.
Files changed (44) hide show
  1. package/bin/kumiko-build.ts +85 -0
  2. package/bin/kumiko-dev.ts +90 -0
  3. package/package.json +45 -0
  4. package/src/__tests__/build-prod-bundle.integration.ts +265 -0
  5. package/src/__tests__/build-prod-bundle.test.ts +262 -0
  6. package/src/__tests__/cache-headers.test.ts +70 -0
  7. package/src/__tests__/classify-change.test.ts +87 -0
  8. package/src/__tests__/compose-features-wiring.integration.ts +352 -0
  9. package/src/__tests__/compose-features.test.ts +81 -0
  10. package/src/__tests__/crash-tracker.test.ts +89 -0
  11. package/src/__tests__/create-kumiko-server.integration.ts +286 -0
  12. package/src/__tests__/few-shot-corpus.test.ts +311 -0
  13. package/src/__tests__/inject-schema.test.ts +62 -0
  14. package/src/__tests__/resolve-stylesheet.test.ts +90 -0
  15. package/src/__tests__/resolve-tailwind-cli.test.ts +49 -0
  16. package/src/__tests__/run-prod-app-spec.test.ts +57 -0
  17. package/src/__tests__/run-prod-app.integration.ts +535 -0
  18. package/src/__tests__/scaffold-feature.test.ts +143 -0
  19. package/src/__tests__/try-hono-first.test.ts +63 -0
  20. package/src/build-prod-bundle.ts +587 -0
  21. package/src/build-server-bundle.ts +308 -0
  22. package/src/build.ts +28 -0
  23. package/src/codegen/__tests__/run-codegen.test.ts +494 -0
  24. package/src/codegen/__tests__/strict-mode-diagnostics.test.ts +467 -0
  25. package/src/codegen/__tests__/watch.test.ts +186 -0
  26. package/src/codegen/index.ts +17 -0
  27. package/src/codegen/render.ts +225 -0
  28. package/src/codegen/run-codegen.ts +157 -0
  29. package/src/codegen/scan-events.ts +574 -0
  30. package/src/codegen/watch.ts +127 -0
  31. package/src/compose-features.ts +128 -0
  32. package/src/crash-tracker.ts +56 -0
  33. package/src/create-kumiko-server.ts +1010 -0
  34. package/src/drizzle-config.ts +44 -0
  35. package/src/drizzle-tables-auth-mode.ts +32 -0
  36. package/src/drizzle-tables-minimal.ts +22 -0
  37. package/src/few-shot-corpus.ts +369 -0
  38. package/src/index.ts +57 -0
  39. package/src/inject-schema.ts +24 -0
  40. package/src/resolve-tailwind-cli.ts +28 -0
  41. package/src/run-dev-app.ts +290 -0
  42. package/src/run-prod-app.ts +892 -0
  43. package/src/scaffold-feature.ts +226 -0
  44. 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
+ });