@cosmicdrift/kumiko-dev-server 0.16.0 → 0.18.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-dev-server",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "Development server bootstrap for Kumiko apps. Bundles the client, mints dev-JWTs, injects the resolved AppSchema, and seeds an admin. Not for production.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -50,7 +50,7 @@ describe("classifyChange", () => {
50
50
  });
51
51
 
52
52
  test("Integration-Test → ignore (würde sonst Schema-Restart auslösen)", () => {
53
- expect(classifyChange("/abs/samples/foo/src/feature.integration.ts")).toBe("ignore");
53
+ expect(classifyChange("/abs/samples/foo/src/feature.integration.test.ts")).toBe("ignore");
54
54
  });
55
55
 
56
56
  test("E2E-Test → ignore", () => {
@@ -137,12 +137,13 @@ describe("createKumikoServer", () => {
137
137
  expect(res.headers.get("content-type") ?? "").not.toMatch(/text\/event-stream/);
138
138
  });
139
139
 
140
- test("Server-Instanz ist undefined unter Node (vitest)", async () => {
141
- // Sanity check: unter Bun wäre .server gesetzt, unter Node
142
- // (wo dieser Test läuft) muss er undefined sein, sonst hätten
143
- // wir eine falsche Bun-Detection.
140
+ test("Server-Instanz unter Bun gesetzt, unter Node undefined", async () => {
144
141
  const h = await boot();
145
- expect(h.server).toBeUndefined();
142
+ if (typeof Bun !== "undefined") {
143
+ expect(h.server).toBeDefined();
144
+ } else {
145
+ expect(h.server).toBeUndefined();
146
+ }
146
147
  });
147
148
  });
148
149
 
@@ -3,7 +3,7 @@
3
3
  // kein Bun → undefined, Package nicht installiert → undefined.
4
4
 
5
5
  import { describe, expect, test } from "bun:test";
6
- import { resolveTailwindCli } from "../resolve-tailwind-cli";
6
+ import { canResolveTailwindStylesheet, resolveTailwindCli } from "../resolve-tailwind-cli";
7
7
 
8
8
  describe("resolveTailwindCli", () => {
9
9
  test("ohne Bun-Resolver → undefined (silent skip)", () => {
@@ -47,3 +47,35 @@ describe("resolveTailwindCli", () => {
47
47
  expect(calls).toEqual([{ id: "@tailwindcss/cli/package.json", from: "/some/working/dir" }]);
48
48
  });
49
49
  });
50
+
51
+ describe("canResolveTailwindStylesheet", () => {
52
+ test("tailwindcss resolvable from entry dir → true", () => {
53
+ const out = canResolveTailwindStylesheet("/repo/packages/app/src/styles.css", {
54
+ bun: {
55
+ resolveSync: (id, from) => {
56
+ if (id === "tailwindcss" && from === "/repo/packages/app/src") {
57
+ return "/repo/node_modules/tailwindcss/index.css";
58
+ }
59
+ throw new Error("not found");
60
+ },
61
+ },
62
+ cwd: "/repo/packages/app",
63
+ });
64
+ expect(out).toBe(true);
65
+ });
66
+
67
+ test("tailwindcss not resolvable from entry dir → false", () => {
68
+ const out = canResolveTailwindStylesheet(
69
+ "/cache/@cosmicdrift/kumiko-renderer-web/src/styles.css",
70
+ {
71
+ bun: {
72
+ resolveSync: () => {
73
+ throw new Error("not found");
74
+ },
75
+ },
76
+ cwd: "/tmp/minimal-fixture",
77
+ },
78
+ );
79
+ expect(out).toBe(false);
80
+ });
81
+ });
@@ -491,40 +491,21 @@ describe("runProdApp", () => {
491
491
  expect(res.status).toBe(200);
492
492
  });
493
493
 
494
- test("Hard Boot-Gate: pending Migration im Journal → SchemaDriftError, kein Boot", async () => {
495
- // Schreibt ein synthetisches Migration-Dir mit einer Migration die
496
- // nie applied wurde. runProdApp soll mit SchemaDriftError abbrechen
497
- // bevor irgendetwas anderes initialisiert wird.
498
- const { mkdir } = await import("node:fs/promises");
494
+ test("Hard Boot-Gate: pending kumiko-Migration → SchemaDriftError, kein Boot", async () => {
495
+ // Synthetisches kumiko/migrations-Dir mit einer checked-in Migration die
496
+ // nie applied wurde (kein _kumiko_migrations-Eintrag). runProdApp soll mit
497
+ // SchemaDriftError abbrechen bevor irgendetwas anderes initialisiert wird.
499
498
  const driftDir = await mkdtemp(join(tmpdir(), "kumiko-drift-boot-"));
500
499
  tempDirs.push(driftDir);
501
- await mkdir(join(driftDir, "meta"), { recursive: true });
502
500
  await writeFile(
503
- join(driftDir, "meta", "_journal.json"),
504
- JSON.stringify({
505
- version: "7",
506
- dialect: "postgresql",
507
- entries: [
508
- {
509
- idx: 0,
510
- version: "7",
511
- when: 1700000000000,
512
- tag: "0000_pending_migration",
513
- breakpoints: true,
514
- },
515
- ],
516
- }),
501
+ join(driftDir, "0001_pending.sql"),
502
+ `CREATE TABLE "never_created_table" ("id" uuid PRIMARY KEY);`,
517
503
  );
518
504
  await writeFile(
519
- join(driftDir, "meta", "0000_snapshot.json"),
505
+ join(driftDir, ".snapshot.json"),
520
506
  JSON.stringify({
521
- tables: {
522
- "public.never_created_table": {
523
- schema: "",
524
- name: "never_created_table",
525
- columns: { id: { name: "id", type: "uuid", primaryKey: true, notNull: true } },
526
- },
527
- },
507
+ version: 1,
508
+ tables: [{ tableName: "never_created_table", columns: [] }],
528
509
  }),
529
510
  );
530
511
 
@@ -44,6 +44,7 @@ import { existsSync, readdirSync } from "node:fs";
44
44
  import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
45
45
  import { tmpdir } from "node:os";
46
46
  import { join, resolve } from "node:path";
47
+ import { canResolveTailwindStylesheet, resolveTailwindCli } from "./resolve-tailwind-cli";
47
48
 
48
49
  // Bun-Runtime-Check als module-level Konstante: alle Build-Schritte
49
50
  // (Tailwind via Bun.spawn, Client-Bundle via Bun.build, Stylesheet-
@@ -138,7 +139,7 @@ export async function buildProdBundle(options: BuildProdBundleOptions = {}): Pro
138
139
  // der client.tsx ein "import './foo.css'" macht — den Fall lassen
139
140
  // wir hier raus, Tailwind ist die einzige CSS-Quelle).
140
141
  if (stylesheet) {
141
- const css = await runTailwindOnce(stylesheet);
142
+ const css = await runTailwindOnce(stylesheet, cwd);
142
143
  const hash = shortHash(css);
143
144
  const filename = `styles-${hash}.css`;
144
145
  await writeFile(join(assetsDir, filename), css);
@@ -302,9 +303,14 @@ function resolveStylesheetEntry(
302
303
 
303
304
  if (!hasBun) return undefined;
304
305
  try {
305
- return (
306
+ const resolved = (
306
307
  globalThis as { Bun: { resolveSync: (id: string, from: string) => string } }
307
308
  ).Bun.resolveSync("@cosmicdrift/kumiko-renderer-web/styles.css", cwd);
309
+ const bun = (globalThis as { Bun: { resolveSync: (id: string, from: string) => string } }).Bun;
310
+ if (!canResolveTailwindStylesheet(resolved, { bun, cwd })) {
311
+ return undefined;
312
+ }
313
+ return resolved;
308
314
  } catch {
309
315
  return undefined;
310
316
  }
@@ -323,18 +329,30 @@ export function discoverHtmlTemplate(cwd: string): string | undefined {
323
329
  // Build steps
324
330
  // ---------------------------------------------------------------------------
325
331
 
326
- async function runTailwindOnce(entry: string): Promise<string> {
332
+ async function runTailwindOnce(entry: string, cwd: string): Promise<string> {
327
333
  if (!hasBun) {
328
334
  throw new Error(
329
335
  "[kumiko build] Tailwind one-shot requires Bun (Bun.spawn) — run via `bun run …` or `yarn kumiko build`.",
330
336
  );
331
337
  }
338
+ const bunResolver = (globalThis as { Bun: { resolveSync: (id: string, from: string) => string } })
339
+ .Bun;
340
+ const cliPath = resolveTailwindCli({ bun: bunResolver, cwd });
341
+ if (cliPath === undefined) {
342
+ throw new Error(
343
+ "[kumiko build] @tailwindcss/cli nicht auflösbar — `bun install` im App-Root ausführen.",
344
+ );
345
+ }
346
+ if (!canResolveTailwindStylesheet(entry, { bun: bunResolver, cwd })) {
347
+ throw new Error(
348
+ `[kumiko build] tailwindcss nicht auflösbar für ${entry} — peer dependency fehlt am Stylesheet-Ort.`,
349
+ );
350
+ }
332
351
  const tmpDir = await mkdtemp(join(tmpdir(), "kumiko-build-tw-"));
333
352
  const outPath = join(tmpDir, "styles.css");
334
- // --minify: Tailwind-CLI default ist NICHT minified. Symmetric zum
335
- // Bun.build minify-Flag sonst ist das CSS in dist/ ~30 % größer als
336
- // nötig (Whitespace, Kommentare, Newlines).
337
- const proc = Bun.spawn(["bunx", "@tailwindcss/cli", "-i", entry, "-o", outPath, "--minify"], {
353
+ const bunPath = process.argv[0] ?? "bun";
354
+ const proc = Bun.spawn([bunPath, "run", cliPath, "-i", entry, "-o", outPath, "--minify"], {
355
+ cwd,
338
356
  stdout: "inherit",
339
357
  stderr: "inherit",
340
358
  });
@@ -30,7 +30,7 @@ import {
30
30
  TestUsers,
31
31
  } from "@cosmicdrift/kumiko-framework/stack";
32
32
  import { injectSchema } from "./inject-schema";
33
- import { resolveTailwindCli } from "./resolve-tailwind-cli";
33
+ import { canResolveTailwindStylesheet, resolveTailwindCli } from "./resolve-tailwind-cli";
34
34
  import { buildBunServeOptions } from "./run-prod-app";
35
35
  import { tryHonoFirst } from "./try-hono-first";
36
36
 
@@ -362,7 +362,13 @@ export function classifyChange(filename: string): "restart" | "hot-reload" | "ig
362
362
  if (!filename.endsWith(".ts") && !filename.endsWith(".tsx")) return "ignore";
363
363
  if (filename.includes("__tests__")) return "ignore";
364
364
  if (filename.endsWith(".test.ts") || filename.endsWith(".test.tsx")) return "ignore";
365
- if (filename.endsWith(".integration.ts") || filename.endsWith(".e2e.ts")) return "ignore";
365
+ if (
366
+ filename.endsWith(".integration.ts") ||
367
+ filename.endsWith(".integration.test.ts") ||
368
+ filename.endsWith(".e2e.ts")
369
+ ) {
370
+ return "ignore";
371
+ }
366
372
  // Plattformpfad-agnostisch: prüfen auf POSIX und Windows-Trenner.
367
373
  // Wir matchen sowohl `<sep><dir><sep>` als auch trailing-`<sep><dir>`
368
374
  // (für Watcher-Filenames die als relativer Pfad ankommen).
@@ -465,9 +471,14 @@ export function resolveStylesheet(options: CreateKumikoServerOptions): string |
465
471
  return undefined;
466
472
  }
467
473
  try {
468
- return (
474
+ const resolved = (
469
475
  globalThis as { Bun: { resolveSync: (id: string, from: string) => string } }
470
476
  ).Bun.resolveSync("@cosmicdrift/kumiko-renderer-web/styles.css", process.cwd());
477
+ const bun = (globalThis as { Bun: { resolveSync: (id: string, from: string) => string } }).Bun;
478
+ if (!canResolveTailwindStylesheet(resolved, { bun, cwd: process.cwd() })) {
479
+ return undefined;
480
+ }
481
+ return resolved;
471
482
  } catch (err) {
472
483
  logError(
473
484
  "[kumiko-server] couldn't auto-resolve @cosmicdrift/kumiko-renderer-web/styles.css — " +
@@ -7,7 +7,7 @@
7
7
  // testbar (Bun-Branch + Resolve-Fail-Branch) ohne den Server zu
8
8
  // booten.
9
9
 
10
- import { resolve } from "node:path";
10
+ import { dirname, resolve } from "node:path";
11
11
 
12
12
  type BunResolver = { resolveSync: (id: string, from: string) => string };
13
13
 
@@ -26,3 +26,20 @@ export function resolveTailwindCli(deps: ResolveTailwindCliDeps): string | undef
26
26
  return undefined;
27
27
  }
28
28
  }
29
+
30
+ /** Tailwind v4 resolves `@import "tailwindcss"` relative to the entry
31
+ * CSS file. Skip renderer-web fallback (or fail build) when peer deps
32
+ * aren't resolvable from that directory — e.g. Bun cache copies of
33
+ * `@cosmicdrift/kumiko-renderer-web` outside a hoisted node_modules tree. */
34
+ export function canResolveTailwindStylesheet(
35
+ entryCss: string,
36
+ deps: ResolveTailwindCliDeps,
37
+ ): boolean {
38
+ if (deps.bun === undefined) return false;
39
+ try {
40
+ deps.bun.resolveSync("tailwindcss", dirname(entryCss));
41
+ return true;
42
+ } catch {
43
+ return false;
44
+ }
45
+ }
@@ -69,7 +69,10 @@ import {
69
69
  createSeedMigrationContext,
70
70
  runPendingSeedMigrations,
71
71
  } from "@cosmicdrift/kumiko-framework/es-ops";
72
- import { assertSchemaCurrent, SchemaDriftError } from "@cosmicdrift/kumiko-framework/migrations";
72
+ import {
73
+ assertKumikoSchemaCurrent,
74
+ SchemaDriftError,
75
+ } from "@cosmicdrift/kumiko-framework/migrations";
73
76
  import {
74
77
  createDispatcher,
75
78
  createEntityCache,
@@ -369,7 +372,7 @@ export type RunProdAppOptions = {
369
372
  * werden. CSP-Header pro Host können zusätzlich Asset-Pfade
370
373
  * einschränken. */
371
374
  readonly hostDispatch?: HostDispatchFn;
372
- /** Pfad zu drizzle/migrations für den Boot-Gate. Default "./drizzle/
375
+ /** Pfad zu kumiko/migrations für den Boot-Gate. Default "./kumiko/
373
376
  * migrations" relativ zum process-cwd (wo die App gestartet wird —
374
377
  * bei Container-Deploys typischerweise der App-Workspace-Root, weil
375
378
  * WORKDIR im Dockerfile dorthin zeigt). Boot wirft SchemaDriftError
@@ -592,20 +595,20 @@ export async function runProdApp(options: RunProdAppOptions): Promise<ProdAppHan
592
595
  }
593
596
  }
594
597
 
595
- // 5. Schema-Drift-Gate. Drizzle-kit migrate (yarn kumiko migrate apply)
596
- // läuft als CI-Step VOR dem Container-Rollout. Boot prüft hier nur:
597
- // (a) Alle Migrations aus drizzle/migrations/meta/_journal.json
598
- // sind in __drizzle_migrations applied
599
- // (b) Alle erwarteten Tabellen existieren physisch
598
+ // 5. Schema-Drift-Gate (drizzle-frei, kumiko/migrations). `kumiko schema
599
+ // apply` läuft als Deploy-Step VOR dem Container-Rollout. Boot prüft nur:
600
+ // (a) Alle Migrations aus kumiko/migrations/*.sql sind in
601
+ // _kumiko_migrations applied (+ checksum unverändert)
602
+ // (b) Alle Tabellen aus kumiko/migrations/.snapshot.json existieren
600
603
  // Drift = Boot-Error mit klarer Meldung (kein Auto-Heal — mehrere
601
- // Container-Replicas würden sonst race-conditionen beim ALTER TABLE
602
- // fahren). Opt-out via `migrations: false` für custom Schema-Setups.
604
+ // Container-Replicas würden sonst race-conditionen fahren). Opt-out via
605
+ // `migrations: false` für custom Schema-Setups.
603
606
  if (options.migrations !== false) {
604
- const migrationsDir = options.migrations?.dir ?? "./drizzle/migrations";
607
+ const migrationsDir = options.migrations?.dir ?? "./kumiko/migrations";
605
608
  // biome-ignore lint/suspicious/noConsole: boot-time progress hint
606
609
  console.log(`[runProdApp] checking schema drift (${migrationsDir})…`);
607
610
  try {
608
- await assertSchemaCurrent(db, migrationsDir);
611
+ await assertKumikoSchemaCurrent(db, migrationsDir);
609
612
  } catch (err) {
610
613
  if (err instanceof SchemaDriftError) {
611
614
  // biome-ignore lint/suspicious/noConsole: terminal error message