@cosmicdrift/kumiko-dev-server 0.307.0 → 0.309.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 +5 -5
- package/src/__tests__/config-resolver-default.integration.test.ts +1 -1
- package/src/__tests__/create-kumiko-server.integration.test.ts +62 -1
- package/src/__tests__/env-schema.integration.test.ts +1 -1
- package/src/__tests__/env-schema.test.ts +1 -1
- package/src/__tests__/run-dev-app.integration.test.ts +1 -1
- package/src/changes.json +5 -0
- package/src/create-kumiko-server.ts +36 -3
- package/src/env-schema.ts +1 -1
- package/src/index.ts +2 -1
- package/src/run-dev-app.ts +8 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-dev-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.309.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.
|
|
63
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
64
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
65
|
-
"@cosmicdrift/kumiko-server-runtime": "0.
|
|
62
|
+
"@cosmicdrift/kumiko-bundled-features": "0.309.0",
|
|
63
|
+
"@cosmicdrift/kumiko-framework": "0.309.0",
|
|
64
|
+
"@cosmicdrift/kumiko-headless": "0.309.0",
|
|
65
|
+
"@cosmicdrift/kumiko-server-runtime": "0.309.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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
@@ -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 = {
|
|
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.
|
|
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
|
|
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
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// - runDevApp (high-level)
|
|
11
11
|
// Mischt die Standard-Features (config/user/tenant/auth-email-
|
|
12
12
|
// password) automatisch dazu wenn `auth` gesetzt ist, wired die
|
|
13
|
-
// Login-Routes + Error-Map, ruft
|
|
13
|
+
// Login-Routes + Error-Map, ruft seedAdminGuarded im onAfterSetup. Default
|
|
14
14
|
// für Sample-Apps und Showcases — 5-10 Zeilen Bootstrap statt 50.
|
|
15
15
|
|
|
16
16
|
export {
|
|
@@ -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";
|
package/src/run-dev-app.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// onAfterSetup. Reduziert den Sample-Bootstrap von 50 Zeilen auf 5-10.
|
|
1
|
+
// High-level dev-server wrapper for sample apps and showcases. In auth mode it
|
|
2
|
+
// mixes in the standard features (config/user/tenant/auth-email-password),
|
|
3
|
+
// wires AuthRoutesConfig plus the login error map and calls seedAdminGuarded()
|
|
4
|
+
// in onAfterSetup, cutting a sample bootstrap from ~50 lines to 5-10.
|
|
6
5
|
//
|
|
7
6
|
// Auto-mix passiert NUR im auth-mode. Ohne `auth`-Block bleibt der
|
|
8
7
|
// Server im Auto-Mint-JWT-Modus (Dev-Default) und mischt nichts dazu —
|
|
@@ -16,7 +15,7 @@
|
|
|
16
15
|
import { AuthErrors, AuthHandlers } from "@cosmicdrift/kumiko-bundled-features/auth-email-password";
|
|
17
16
|
import {
|
|
18
17
|
type SeedAdminOptions,
|
|
19
|
-
|
|
18
|
+
seedAdminGuarded,
|
|
20
19
|
} from "@cosmicdrift/kumiko-bundled-features/auth-email-password/seeding";
|
|
21
20
|
import {
|
|
22
21
|
EXT_SESSION_STORE,
|
|
@@ -209,9 +208,8 @@ export type RunDevAppOptions = {
|
|
|
209
208
|
/** SIGINT/SIGTERM-Handler installieren (Default true; in Tests auf
|
|
210
209
|
* false damit repeated boots keine Listener akkumulieren). */
|
|
211
210
|
readonly installSignalHandlers?: boolean;
|
|
212
|
-
/** Auth
|
|
213
|
-
*
|
|
214
|
-
* JWT-Modus. */
|
|
211
|
+
/** Auth mode: standard features mixed in, auth routes wired, seedAdminGuarded
|
|
212
|
+
* runs in onAfterSetup. Without `auth` the server stays in auto-mint JWT mode. */
|
|
215
213
|
readonly auth?: RunDevAppAuthOptions;
|
|
216
214
|
/** Eigene Seed-Funktionen, laufen nach dem Admin (wenn auth) in
|
|
217
215
|
* Array-Reihenfolge. */
|
|
@@ -598,7 +596,7 @@ export async function runDevApp(options: RunDevAppOptions): Promise<KumikoServer
|
|
|
598
596
|
);
|
|
599
597
|
}
|
|
600
598
|
if (effectiveAuth) {
|
|
601
|
-
await
|
|
599
|
+
await seedAdminGuarded(stack.db, effectiveAuth.admin);
|
|
602
600
|
}
|
|
603
601
|
// Apply r.config({ seeds }) declared by any registered feature.
|
|
604
602
|
// Runs before user-supplied seed callbacks so those can read /
|