@cosmicdrift/kumiko-dev-server 0.307.0 → 0.308.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.307.0",
3
+ "version": "0.308.0",
4
4
  "description": "Dev-tooling for Kumiko apps: local dev-server bootstrap (runDevApp), scaffolding, codegen. Its compose-stacks/env-schema subpaths are consumed at prod boot too — see @cosmicdrift/kumiko-server-runtime for the prod runner itself.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -59,10 +59,10 @@
59
59
  "kumiko-upgrade": "./bin/kumiko-upgrade.ts"
60
60
  },
61
61
  "dependencies": {
62
- "@cosmicdrift/kumiko-bundled-features": "0.307.0",
63
- "@cosmicdrift/kumiko-framework": "0.307.0",
64
- "@cosmicdrift/kumiko-headless": "0.307.0",
65
- "@cosmicdrift/kumiko-server-runtime": "0.307.0",
62
+ "@cosmicdrift/kumiko-bundled-features": "0.308.0",
63
+ "@cosmicdrift/kumiko-framework": "0.308.0",
64
+ "@cosmicdrift/kumiko-headless": "0.308.0",
65
+ "@cosmicdrift/kumiko-server-runtime": "0.308.0",
66
66
  "ts-morph": "^28.0.0"
67
67
  },
68
68
  "publishConfig": {
@@ -17,7 +17,7 @@ import {
17
17
  TestUsers,
18
18
  unsafePushTables,
19
19
  } from "@cosmicdrift/kumiko-framework/stack";
20
- import { z } from "zod";
20
+ import * as z from "zod";
21
21
  import { mergeConfigResolverDefault } from "../run-dev-app";
22
22
 
23
23
  // Pins runDevApp's ENV→config-app-override wiring: mergeConfigResolverDefault
@@ -16,10 +16,11 @@ import {
16
16
  type TenantId,
17
17
  } from "@cosmicdrift/kumiko-framework/engine";
18
18
  import { TestUsers } from "@cosmicdrift/kumiko-framework/stack";
19
- import { z } from "zod";
19
+ import * as z from "zod";
20
20
  import {
21
21
  createKumikoServer,
22
22
  type KumikoServerHandle,
23
+ PROD_BUNDLES_ENV,
23
24
  STYLESHEET_WATCH_ENV,
24
25
  } from "../create-kumiko-server";
25
26
 
@@ -540,6 +541,10 @@ describe("createKumikoServer — real Bun.build (buildClient)", () => {
540
541
  expect(body.length).toBeGreaterThan(0);
541
542
  expect(body).toMatch(/ping/);
542
543
 
544
+ const mapRes = await handle.fetch(new Request("http://localhost/client.js.map"));
545
+ expect(mapRes.status).toBe(200);
546
+ expect(await mapRes.text()).toMatch(/"sources"/);
547
+
543
548
  // Stop the watcher before teardown rmSync deletes clientEntry out from
544
549
  // under it — a bare "client.tsx" delete event classifies as "restart"
545
550
  // (classifyChange only special-cases endsWith("/client.tsx")) and
@@ -550,6 +555,62 @@ describe("createKumikoServer — real Bun.build (buildClient)", () => {
550
555
  rmSync(tmpDir, { recursive: true, force: true });
551
556
  }
552
557
  });
558
+
559
+ test("PROD_BUNDLES_ENV builds a prod-shaped bundle: split chunk served, no sourcemap, NODE_ENV replaced", async () => {
560
+ const tmpDir = mkdtempSync(join(tmpdir(), "kumiko-real-build-prod-"));
561
+ const entry = join(tmpDir, "client.tsx");
562
+ writeFileSync(
563
+ entry,
564
+ `export async function loadLazy() {
565
+ const m = await import("./lazy.ts");
566
+ return m.hello();
567
+ }
568
+ `,
569
+ );
570
+ writeFileSync(
571
+ join(tmpDir, "lazy.ts"),
572
+ `export function hello() {
573
+ return "env=" + process.env.NODE_ENV;
574
+ }
575
+ `,
576
+ );
577
+ const previousProdBundles = process.env[PROD_BUNDLES_ENV];
578
+ process.env[PROD_BUNDLES_ENV] = "1";
579
+ try {
580
+ handle = await createKumikoServer({
581
+ features: [probeFeature],
582
+ port: 0,
583
+ installSignalHandlers: false,
584
+ clientEntry: entry,
585
+ stylesheet: false,
586
+ });
587
+ const res = await handle.fetch(new Request("http://localhost/client.js"));
588
+ expect(res.status).toBe(200);
589
+ const body = await res.text();
590
+
591
+ // Only the dynamic import() call points at the lazy chunk — the
592
+ // entry also statically imports a shared async-loader chunk.
593
+ const chunkMatch = body.match(/import\("(?:\.\/)?(chunk-[\w-]+\.js)"\)/);
594
+ expect(chunkMatch).not.toBeNull();
595
+ const normalizedChunkPath = `/${chunkMatch ? (chunkMatch[1] ?? "") : ""}`;
596
+
597
+ const chunkRes = await handle.fetch(new Request(`http://localhost${normalizedChunkPath}`));
598
+ expect(chunkRes.status).toBe(200);
599
+ expect(chunkRes.headers.get("content-type")).toMatch(/application\/javascript/);
600
+ const chunkBody = await chunkRes.text();
601
+ expect(chunkBody).toContain("env=production");
602
+
603
+ const mapRes = await handle.fetch(new Request("http://localhost/client.js.map"));
604
+ expect(mapRes.status).toBe(404);
605
+
606
+ await handle.stop();
607
+ handle = undefined;
608
+ } finally {
609
+ if (previousProdBundles === undefined) delete process.env[PROD_BUNDLES_ENV];
610
+ else process.env[PROD_BUNDLES_ENV] = previousProdBundles;
611
+ rmSync(tmpDir, { recursive: true, force: true });
612
+ }
613
+ });
553
614
  });
554
615
 
555
616
  describe("createKumikoServer — hot-reload broadcast", () => {
@@ -8,7 +8,7 @@ import { describe, expect, it } from "bun:test";
8
8
  import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
9
9
  import { composeEnvSchema, KumikoBootError } from "@cosmicdrift/kumiko-framework/env";
10
10
  import { runProdApp } from "@cosmicdrift/kumiko-server-runtime/run-prod-app";
11
- import { z } from "zod";
11
+ import * as z from "zod";
12
12
  import { frameworkCoreEnvSchema } from "../env-schema";
13
13
  import * as devServerPublicApi from "../index";
14
14
 
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from "bun:test";
2
2
  import { composeEnvSchema, KumikoBootError, parseEnv } from "@cosmicdrift/kumiko-framework/env";
3
- import { z } from "zod";
3
+ import * as z from "zod";
4
4
  import { type FrameworkCoreEnv, frameworkCoreEnvSchema } from "../env-schema";
5
5
 
6
6
  // Statt Sentinel-throw IM try (ein nicht-werfender parseEnv ließe den
@@ -23,7 +23,7 @@ import {
23
23
  } from "@cosmicdrift/kumiko-framework/engine";
24
24
  import { TestUsers } from "@cosmicdrift/kumiko-framework/stack";
25
25
  import * as jose from "jose";
26
- import { z } from "zod";
26
+ import * as z from "zod";
27
27
  import type { KumikoServerHandle } from "../create-kumiko-server";
28
28
  import { runDevApp } from "../run-dev-app";
29
29
 
package/src/changes.json CHANGED
@@ -1,4 +1,9 @@
1
1
  [
2
+ {
3
+ "version": "0.308.0",
4
+ "type": "improvement",
5
+ "title": "Dev server can build prod-shaped client bundles, E2E uses them"
6
+ },
2
7
  {
3
8
  "version": "0.307.0",
4
9
  "type": "fix",
@@ -269,6 +269,10 @@ const AUTH_COOKIE = "kumiko_auth";
269
269
  // runs under Node and cannot import this module.
270
270
  export const STYLESHEET_WATCH_ENV = "KUMIKO_DEV_STYLESHEET_WATCH";
271
271
 
272
+ // Duplicated in packages/testing/src/e2e/constants.ts: the Playwright config
273
+ // runs under Node and cannot import this module.
274
+ export const PROD_BUNDLES_ENV = "KUMIKO_DEV_PROD_BUNDLES";
275
+
272
276
  // Reload snippet injected into every page-load so the browser
273
277
  // subscribes to /_reload without the HTML needing to hard-code it.
274
278
  //
@@ -325,7 +329,13 @@ const DEFAULT_HTML = `<!doctype html>
325
329
  </html>
326
330
  `;
327
331
 
328
- type ClientBundle = { readonly js: string; readonly map: string };
332
+ type ClientBundle = {
333
+ readonly js: string;
334
+ readonly map: string;
335
+ // Only set when PROD_BUNDLES_ENV builds with splitting. Path -> content,
336
+ // e.g. "/chunk-abc123.js" -> "...".
337
+ readonly chunks?: ReadonlyMap<string, string>;
338
+ };
329
339
 
330
340
  async function buildClient(entry: string): Promise<ClientBundle> {
331
341
  if (!hasBun) {
@@ -334,23 +344,35 @@ async function buildClient(entry: string): Promise<ClientBundle> {
334
344
  );
335
345
  }
336
346
  const unminified = process.env["KUMIKO_DEV_UNMINIFIED"] === "1";
347
+ // Same splitting/sourcemap/NODE_ENV as build-prod-bundle.ts, opt-in via
348
+ // env so `bun dev` stays unchanged.
349
+ const prodShaped = process.env[PROD_BUNDLES_ENV] === "1";
337
350
  const built = await Bun.build({
338
351
  entrypoints: [entry],
339
352
  target: "browser",
340
353
  minify: !unminified,
341
- sourcemap: "linked",
354
+ sourcemap: prodShaped ? "none" : "linked",
355
+ splitting: prodShaped,
356
+ ...(prodShaped ? { define: { "process.env.NODE_ENV": JSON.stringify("production") } } : {}),
342
357
  });
343
358
  if (!built.success) {
344
359
  logError("[kumiko-server] client bundle failed:");
345
360
  for (const log of built.logs) logError(log);
346
361
  throw new Error("client bundle failed");
347
362
  }
348
- const jsOutput = built.outputs.find((o) => o.path.endsWith(".js"));
363
+ const jsOutput = built.outputs.find((o) => o.kind === "entry-point");
349
364
  const mapOutput = built.outputs.find((o) => o.path.endsWith(".js.map"));
350
365
  if (!jsOutput) throw new Error("[kumiko-server] client bundle produced no .js output");
366
+ const chunks = new Map<string, string>();
367
+ for (const output of built.outputs) {
368
+ if (output.kind !== "chunk") continue;
369
+ const chunkPath = `/${output.path.replace(/^\.\//, "")}`;
370
+ chunks.set(chunkPath, await output.text());
371
+ }
351
372
  return {
352
373
  js: await jsOutput.text(),
353
374
  map: mapOutput ? await mapOutput.text() : "",
375
+ chunks,
354
376
  };
355
377
  }
356
378
 
@@ -1055,6 +1077,17 @@ export async function createKumikoServer(
1055
1077
  headers: { "Content-Type": "application/javascript; charset=utf-8" },
1056
1078
  });
1057
1079
  }
1080
+ // Looked up in the live bundles instead of a separate index, which a
1081
+ // hot-reload rebuild would leave stale.
1082
+ for (const bundle of clientBundles.values()) {
1083
+ const chunk = bundle.chunks?.get(url.pathname);
1084
+ if (chunk !== undefined) {
1085
+ return new Response(chunk, {
1086
+ headers: { "Content-Type": "application/javascript; charset=utf-8" },
1087
+ });
1088
+ }
1089
+ }
1090
+
1058
1091
  // .js.map-Variante: gleicher Lookup mit /.map abgeschnitten.
1059
1092
  if (url.pathname.endsWith(".js.map")) {
1060
1093
  const jsPath = url.pathname.slice(0, -".map".length);
package/src/env-schema.ts CHANGED
@@ -15,7 +15,7 @@
15
15
  // Feature-specific vars (JWT_SECRET, KUMIKO_SECRETS_MASTER_KEY_*) live
16
16
  // in their owning feature's envSchema — Phase 2 (bundled-features).
17
17
 
18
- import { z } from "zod";
18
+ import * as z from "zod";
19
19
 
20
20
  /** Env-vars read by framework-core (api/server, db/connection,
21
21
  * dev-server/run-prod-app). NOT including feature-specific vars.
package/src/index.ts CHANGED
@@ -54,6 +54,7 @@ export {
54
54
  type CreateKumikoServerOptions,
55
55
  createKumikoServer,
56
56
  type KumikoServerHandle,
57
+ PROD_BUNDLES_ENV,
57
58
  resolveStylesheet,
58
59
  STYLESHEET_WATCH_ENV,
59
60
  } from "./create-kumiko-server";