@cosmicdrift/kumiko-dev-server 0.297.0 → 0.299.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.297.0",
3
+ "version": "0.299.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.297.0",
63
- "@cosmicdrift/kumiko-framework": "0.297.0",
64
- "@cosmicdrift/kumiko-headless": "0.297.0",
65
- "@cosmicdrift/kumiko-server-runtime": "0.297.0",
62
+ "@cosmicdrift/kumiko-bundled-features": "0.299.0",
63
+ "@cosmicdrift/kumiko-framework": "0.299.0",
64
+ "@cosmicdrift/kumiko-headless": "0.299.0",
65
+ "@cosmicdrift/kumiko-server-runtime": "0.299.0",
66
66
  "ts-morph": "^28.0.0"
67
67
  },
68
68
  "publishConfig": {
@@ -2,18 +2,25 @@ import { afterEach, describe, expect, test } from "bun:test";
2
2
  import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
- import { NO_ROUTE_MATCH_HEADER_NAME } from "@cosmicdrift/kumiko-framework/api";
5
+ import {
6
+ ExtraRouteRejection,
7
+ NO_ROUTE_MATCH_HEADER_NAME,
8
+ signatureRoute,
9
+ } from "@cosmicdrift/kumiko-framework/api";
6
10
  import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
7
11
  import {
8
12
  createBooleanField,
9
13
  createEntity,
10
14
  createTextField,
11
15
  defineFeature,
16
+ type TenantId,
12
17
  } from "@cosmicdrift/kumiko-framework/engine";
13
18
  import { TestUsers } from "@cosmicdrift/kumiko-framework/stack";
14
19
  import { z } from "zod";
15
20
  import { createKumikoServer, type KumikoServerHandle } from "../create-kumiko-server";
16
21
 
22
+ const TENANT_ID = "00000000-0000-4000-8000-000000000001" as TenantId;
23
+
17
24
  // Integration-Test: bootet createKumikoServer mit echtem Postgres,
18
25
  // echtem Redis, echter Kumiko-Pipeline. Treibt den fetch-Handler
19
26
  // direkt an (nicht über Bun.serve + Socket), weil vitest unter Node
@@ -30,8 +37,16 @@ const probeEntity = createEntity({
30
37
 
31
38
  const probeFeature = defineFeature("dev-server-probe", (r) => {
32
39
  r.entity("probe", probeEntity);
33
- // SystemAdmin-gated write — Ziel des extraRoutes.dispatchSystemWrite-
34
- // Tests (252/2): Echo von user.tenantId + roles beweist Dispatch durch
40
+ // Anonymous query for hostDispatch's deps.systemQuery — proves the
41
+ // dev path gets the same anonymous-role dispatch-deps shape as prod.
42
+ r.queryHandler({
43
+ name: "ping",
44
+ schema: z.object({}),
45
+ access: { roles: ["anonymous"] },
46
+ handler: async () => ({ pong: true }),
47
+ });
48
+ // SystemAdmin-gated write — Ziel der entry:"signature" / `wire`-Tests
49
+ // (252/2): Echo von user.tenantId + roles beweist Dispatch durch
35
50
  // den echten Dispatcher (Zod + Access-Check) mit Ziel-Tenant-SystemUser.
36
51
  r.writeHandler({
37
52
  name: "probe-write",
@@ -320,35 +335,77 @@ describe("createKumikoServer (Multi-Entry)", () => {
320
335
  );
321
336
  expect(res.status).toBe(404);
322
337
  });
338
+
339
+ test("hostDispatch: async fn erhält deps.systemQuery, 'not-found' → 404 (kumiko-framework#3050 dev-Pendant)", async () => {
340
+ tmpDir = mkdtempSync(join(tmpdir(), "kumiko-multi-it-"));
341
+ const publicEntry = join(tmpDir, "client-public.tsx");
342
+ writeFileSync(publicEntry, "// public");
343
+ let systemQueryPonged = false;
344
+
345
+ handle = await createKumikoServer({
346
+ features: [probeFeature],
347
+ port: 0,
348
+ installSignalHandlers: false,
349
+ clientEntries: [{ name: "public", sourceFile: publicEntry }],
350
+ _buildBundle: async () => ({ js: "// PUBLIC-BUNDLE", map: "" }),
351
+ hostDispatch: async (_req, deps) => {
352
+ const result = await deps.systemQuery("dev-server-probe:query:ping", {}, TENANT_ID);
353
+ systemQueryPonged = (result as { pong: boolean }).pong === true; // @cast-boundary engine-payload
354
+ return { kind: "not-found" };
355
+ },
356
+ });
357
+
358
+ const res = await handle.fetch(new Request("http://localhost/"));
359
+ expect(res.status).toBe(404);
360
+ expect(systemQueryPonged).toBe(true);
361
+ });
323
362
  });
324
363
 
325
- // 252/2: der Dev-Pfad bekommt dieselbe extraRoutes-deps-Closure wie
326
- // runProdApp (seit der Extraktion nach extra-routes-deps.ts geteilt) —
327
- // hier der analoge Beweis gegen createKumikoServer.
328
- describe("createKumikoServer extraRoutes-deps", () => {
329
- test("dispatchSystemWrite schreibt als SystemAdmin des Ziel-Tenants, registry verfügbar", async () => {
364
+ // 252/2: the dev path shares extra-routes-deps.ts with runProdApp —
365
+ // here the analogous proof against createKumikoServer, with `wire` for the
366
+ // registry/dispatchSystemWrite check and an entry:"signature" route for
367
+ // the webhook dispatch.
368
+ describe("createKumikoServer extraRoutes / wire deps", () => {
369
+ test("wire: dispatchSystemWrite schreibt als SystemAdmin des Ziel-Tenants, registry verfügbar", async () => {
330
370
  const tenantId = "00000000-0000-4000-8000-000000000042";
331
371
  let registryHasProbe = false;
332
372
  handle = await createKumikoServer({
333
373
  features: [probeFeature],
334
374
  port: 0,
335
375
  installSignalHandlers: false,
336
- extraRoutes: (app, deps) => {
376
+ extraRoutes: [
377
+ signatureRoute({
378
+ method: "POST",
379
+ path: "/webhook-probe",
380
+ entry: "signature",
381
+ verify: async (req) => {
382
+ if (req.headers["x-probe-signature"] !== "test-secret") {
383
+ throw new ExtraRouteRejection(401, { error: "bad-signature" });
384
+ }
385
+ },
386
+ handler: async (c, _verified, deps) => {
387
+ const result = await deps.dispatchSystemWrite({
388
+ handlerQn: "dev-server-probe:write:probe-write",
389
+ payload: { note: "from-webhook" },
390
+ tenantId: tenantId as import("@cosmicdrift/kumiko-framework/engine").TenantId,
391
+ });
392
+ return c.json(result);
393
+ },
394
+ }),
395
+ ],
396
+ wire: (deps) => {
337
397
  registryHasProbe = deps.registry.features.has("dev-server-probe");
338
- app.post("/webhook-probe", async (c) => {
339
- const result = await deps.dispatchSystemWrite({
340
- handlerQn: "dev-server-probe:write:probe-write",
341
- payload: { note: "from-webhook" },
342
- tenantId: tenantId as import("@cosmicdrift/kumiko-framework/engine").TenantId,
343
- });
344
- return c.json(result);
345
- });
346
398
  },
347
399
  });
348
400
 
349
401
  expect(registryHasProbe).toBe(true);
350
402
 
351
- const res = await handle.fetch(new Request("http://test/webhook-probe", { method: "POST" }));
403
+ const res = await handle.fetch(
404
+ new Request("http://test/webhook-probe", {
405
+ method: "POST",
406
+ headers: { "x-probe-signature": "test-secret" },
407
+ }),
408
+ );
352
409
  expect(res.status).toBe(200);
353
410
  const body = (await res.json()) as {
354
411
  isSuccess: boolean;
@@ -369,9 +426,14 @@ describe("createKumikoServer — extraRoutes vs. onAfterSetup ordering", () => {
369
426
  features: [probeFeature],
370
427
  port: 0,
371
428
  installSignalHandlers: false,
372
- extraRoutes: (app) => {
373
- app.get("/probe", (c) => c.json({ ok: true }));
374
- },
429
+ extraRoutes: [
430
+ {
431
+ method: "GET",
432
+ path: "/probe",
433
+ entry: "anonymous",
434
+ handler: (c) => c.json({ ok: true }),
435
+ },
436
+ ],
375
437
  onAfterSetup: async (stack) => {
376
438
  await stack.http.writeOk(
377
439
  "dev-server-probe:write:probe-write",
@@ -1,7 +1,7 @@
1
1
  // scaffoldApp unit-tests (DX-1.0 + #352 deploy/schema scaffold).
2
2
 
3
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
4
- import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
3
+ import { afterAll, afterEach, beforeEach, describe, expect, test } from "bun:test";
4
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
5
5
  import { tmpdir } from "node:os";
6
6
  import { join } from "node:path";
7
7
  import { type ScaffoldTestSetup, scaffoldApp } from "../scaffold-app";
@@ -380,12 +380,13 @@ describe("scaffoldApp", () => {
380
380
  expect(main).toContain("...kmsWiring,");
381
381
  });
382
382
 
383
- test("src/run-config.ts mounts secrets + sessions + tasks + HAS_AUTH", async () => {
383
+ test("src/run-config.ts mounts secrets + auth-foundation + sessions + tasks + HAS_AUTH", async () => {
384
384
  const dest = join(tmp, "my-shop");
385
385
  await scaffoldApp({ name: "my-shop", destination: dest });
386
386
 
387
387
  const runConfig = readFileSync(join(dest, "src/run-config.ts"), "utf-8");
388
388
  expect(runConfig).toContain("createSecretsFeature()");
389
+ expect(runConfig).toContain("authFoundationFeature");
389
390
  expect(runConfig).toContain("createSessionsFeature()");
390
391
  expect(runConfig).toContain("tasksFeature");
391
392
  expect(runConfig).toContain('from "./features/tasks"');
@@ -562,3 +563,56 @@ describe("scaffoldApp", () => {
562
563
  expect(uuidA).toBe(uuidB);
563
564
  });
564
565
  });
566
+
567
+ // File-content assertions on run-config.ts can't catch a missing sessionStore
568
+ // provider — only actually booting the generated bin/main.ts can.
569
+ describe("generated default app actually boots (kumiko-framework#3120)", () => {
570
+ const FIXTURE_ROOT = join(import.meta.dir, ".tmp-fixtures");
571
+ const createdDirs: string[] = [];
572
+
573
+ afterAll(() => {
574
+ for (const d of createdDirs) {
575
+ try {
576
+ rmSync(d, { recursive: true, force: true });
577
+ } catch {
578
+ // ignore — best-effort
579
+ }
580
+ }
581
+ });
582
+
583
+ test("KUMIKO_DRY_RUN_ENV=boot bun bin/main.ts exits 0 against the default scaffold", async () => {
584
+ mkdirSync(FIXTURE_ROOT, { recursive: true });
585
+ const cwd = mkdtempSync(join(FIXTURE_ROOT, "boot-"));
586
+ createdDirs.push(cwd);
587
+ const dest = join(cwd, "boot-fixture");
588
+
589
+ await scaffoldApp({ name: "boot-fixture", destination: dest });
590
+
591
+ const proc = Bun.spawn({
592
+ cmd: ["bun", "bin/main.ts"],
593
+ cwd: dest,
594
+ env: {
595
+ ...process.env,
596
+ KUMIKO_DRY_RUN_ENV: "boot",
597
+ JWT_SECRET: "a".repeat(32),
598
+ KUMIKO_SECRETS_MASTER_KEY_V1: Buffer.alloc(32, 7).toString("base64"),
599
+ DATABASE_URL: "postgres://dummy:dummy@127.0.0.1:1/dummy",
600
+ REDIS_URL: "redis://127.0.0.1:1",
601
+ PLATFORM_KEK: "",
602
+ SUBJECT_KEYS_DATABASE_URL: "",
603
+ KUMIKO_BLIND_INDEX_KEY: "",
604
+ },
605
+ stdout: "pipe",
606
+ stderr: "pipe",
607
+ });
608
+ const [stdout, stderr, code] = await Promise.all([
609
+ new Response(proc.stdout).text(),
610
+ new Response(proc.stderr).text(),
611
+ proc.exited,
612
+ ]);
613
+
614
+ expect(code, `stdout:\n${stdout}\nstderr:\n${stderr}`).toBe(0);
615
+ expect(`${stdout}${stderr}`).not.toContain("BOOT ABORTED");
616
+ expect(stdout).toContain("boot validation OK");
617
+ });
618
+ });
package/src/changes.json CHANGED
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.299.0",
4
+ "type": "fix",
5
+ "title": "Scaffolded docker-compose.yml uses the correct Postgres 18 data path; default app mounts auth-foundation so it boots",
6
+ "migration": "No action needed for existing apps; only newly scaffolded apps are affected."
7
+ },
8
+ {
9
+ "version": "0.298.0",
10
+ "type": "breaking",
11
+ "title": "createKumikoServer/runDevApp take ExtraRouteDefinition[]; dev hostDispatch gets systemQuery",
12
+ "migration": "extraRoutes on createKumikoServer/runDevApp changes from (app, deps) => void to readonly ExtraRouteDefinition[] - see the framework core changelog entry for the route-kind/dep breakdown. wire?: (deps: SystemWireDeps) => void | Promise<void> replaces non-route setup previously done inside the old extraRoutes callback. The dev hostDispatch callback now receives a second argument { systemQuery }; a dispatch implementation reading the dev db directly switches to systemQuery."
13
+ },
2
14
  {
3
15
  "version": "0.291.0",
4
16
  "type": "fix",
@@ -23,6 +23,7 @@ import { resolveAnonymousAccessFromRegistry } from "@cosmicdrift/kumiko-bundled-
23
23
  import {
24
24
  type AuthRoutesConfig,
25
25
  buildRequestContextDataFromRequest,
26
+ type ExtraRouteDefinition,
26
27
  generateToken,
27
28
  requestContext,
28
29
  } from "@cosmicdrift/kumiko-framework/api";
@@ -47,8 +48,8 @@ import {
47
48
  import { startDevJobRunners } from "@cosmicdrift/kumiko-server-runtime/boot/job-run-logger";
48
49
  import { buildBunServeOptions } from "@cosmicdrift/kumiko-server-runtime/bun-serve-options";
49
50
  import {
50
- type ExtraRoutesSystemDeps,
51
51
  makeDispatchSystemWrite,
52
+ type SystemWireDeps,
52
53
  } from "@cosmicdrift/kumiko-server-runtime/extra-routes-deps";
53
54
  import { injectSchema } from "@cosmicdrift/kumiko-server-runtime/inject-schema";
54
55
  import {
@@ -127,7 +128,10 @@ export type DevHostDispatchResult =
127
128
 
128
129
  /** Picks an entry by inspecting the incoming request. Wird von
129
130
  * Multi-Entry-Apps gesetzt; im Single-Entry-Mode irrelevant. */
130
- export type DevHostDispatch = (req: Request) => DevHostDispatchResult;
131
+ export type DevHostDispatch = (
132
+ req: Request,
133
+ deps: { readonly systemQuery: PageHeadSystemQuery },
134
+ ) => DevHostDispatchResult | Promise<DevHostDispatchResult>;
131
135
 
132
136
  export type CreateKumikoServerOptions = {
133
137
  /** Features whose entities, handlers, and screens get wired into the
@@ -217,12 +221,15 @@ export type CreateKumikoServerOptions = {
217
221
  * tenant, …). Muss idempotent sein — im persistent-DB-Modus läuft
218
222
  * es bei jedem Boot. */
219
223
  readonly onAfterSetup?: (stack: TestStack) => Promise<void>;
220
- /** Mount-Point für app-eigene HTTP-Routes außerhalb des Dispatcher-
221
- * Systems — symmetrisch zum runProdApp.extraRoutes. Wird VOR der
222
- * Static/HTML-Auslieferung aufgerufen, sodass eigene GETs (/feed.xml,
223
- * /og-image, …) Vorrang vor dem Dev-Asset-Pfad haben. `deps` statt
224
- * `ctx` weil dies kein HandlerContext ist — kein user/tenant. */
225
- readonly extraRoutes?: (app: import("hono").Hono, deps: ExtraRoutesSystemDeps) => void;
224
+ /** Declarative HTTP-routes outside the /api/write|query|batch pipeline —
225
+ * symmetric to runProdApp.extraRoutes (kumiko-framework#3050). Mounted
226
+ * inside buildServer (setupTestStack), before the Static/HTML-fallback,
227
+ * so an own GET (/feed.xml, /og-image, …) wins over the dev-asset path. */
228
+ readonly extraRoutes?: readonly ExtraRouteDefinition[];
229
+ /** Hook for app-wired co-running components that need the system-write
230
+ * dispatcher — runs after buildServer, before onAfterSetup (seeds), with
231
+ * NO `app` (routes are declared via `extraRoutes`, not wired here). */
232
+ readonly wire?: (deps: SystemWireDeps) => void | Promise<void>;
226
233
  /** Per-request head-metadata resolver (Open-Graph/title/description) —
227
234
  * same option, same `PageHeadResolver` signature, and same shared
228
235
  * resolve+inject call (`resolveAndInjectPageHead`) as `runProdApp`'s
@@ -830,6 +837,7 @@ export async function createKumikoServer(
830
837
  ...(options.effectiveFeatures !== undefined && {
831
838
  effectiveFeatures: options.effectiveFeatures,
832
839
  }),
840
+ ...(options.extraRoutes !== undefined && { extraRoutes: options.extraRoutes }),
833
841
  // jobs: {} = enqueuer-only; startDevJobRunners below is the sole
834
842
  // consumer/cron-scheduler per lane, so runOnBoot/cron jobs don't double-fire.
835
843
  jobs: {},
@@ -837,16 +845,12 @@ export async function createKumikoServer(
837
845
  await createEventsTable(stack.db);
838
846
  await pushEntityProjectionTables(stack, stack.registry);
839
847
 
840
- // App-eigene HTTP-Routes ans Hono-app hängen — symmetrisch zur
841
- // gleichnamigen Option in runProdApp. Wird vor dem dev-fallback
842
- // (HTML/JS/CSS-Serving via handleFetch unten) registriert, damit
843
- // explizite Routen wie /feed.xml den Asset-Pfad schlagen.
844
- //
845
- // Muss VOR dem Seed laufen: ein Seed, der über stack.http dispatcht,
846
- // baut Honos Matcher — danach wirft jedes weitere app.get() mit
847
- // "Can not add a route since the matcher is already built".
848
- if (options.extraRoutes !== undefined) {
849
- options.extraRoutes(stack.app, {
848
+ // `extraRoutes` is already mounted — setupTestStack's buildServer() call
849
+ // wired it above, before this function ever runs. `wire` is the
850
+ // remaining escape-hatch for app-wired co-running components that need
851
+ // the system-write dispatcher but don't declare a route.
852
+ if (options.wire !== undefined) {
853
+ await options.wire({
850
854
  db: stack.db,
851
855
  // Der nackte ioredis-Client (nicht der TestRedis-Wrapper) —
852
856
  // Parität mit runProdApp, App-Code soll in dev+prod dasselbe sehen.
@@ -861,8 +865,6 @@ export async function createKumikoServer(
861
865
  // zugreifen kann, und VOR dem Server-Start damit der erste HTTP-Request
862
866
  // bereits gegen einen gefüllten State läuft. Idempotenz ist Caller-
863
867
  // Verantwortung (persistent-DB-Modus läuft es bei jedem Boot).
864
- // Dispatching through stack.http below builds Hono's matcher — no route
865
- // may be registered after this point (see extraRoutes above).
866
868
  if (options.onAfterSetup !== undefined) {
867
869
  await options.onAfterSetup(stack);
868
870
  }
@@ -943,6 +945,15 @@ export async function createKumikoServer(
943
945
  // ist es immer "client" mit Schema-Inject true (Single-Entry-Default
944
946
  // damit der Client TypeScript-Schemas findet).
945
947
  //
948
+ // Anonymous-role systemQuery, shared by resolvePageHead and hostDispatch —
949
+ // mirrors runProdApp's HostDispatchFn deps so dev/prod stay symmetric.
950
+ const buildDevSystemQuery =
951
+ (req: Request): PageHeadSystemQuery =>
952
+ (type, payload, tenantId) =>
953
+ requestContext.run(requestContext.get() ?? buildRequestContextDataFromRequest(req), () =>
954
+ stack.dispatcher.query(type, payload, createAnonymousUser(tenantId)),
955
+ );
956
+
946
957
  // resolvePageHead goes through the same headless resolveAndInjectPageHead
947
958
  // that runProdApp uses, so dev and e2e exercise the one timeout and
948
959
  // fallback path instead of a second copy of it (#3026).
@@ -966,14 +977,10 @@ export async function createKumikoServer(
966
977
  if (options.resolvePageHead !== undefined) {
967
978
  const url = new URL(req.url);
968
979
  const host = req.headers.get("host") ?? url.host;
969
- const systemQuery: PageHeadSystemQuery = (type, payload, tenantId) =>
970
- requestContext.run(requestContext.get() ?? buildRequestContextDataFromRequest(req), () =>
971
- stack.dispatcher.query(type, payload, createAnonymousUser(tenantId)),
972
- );
973
980
  html = await resolveAndInjectPageHead(html, options.resolvePageHead, {
974
981
  path: url.pathname,
975
982
  host,
976
- systemQuery,
983
+ systemQuery: buildDevSystemQuery(req),
977
984
  });
978
985
  }
979
986
  return new Response(html, { headers });
@@ -1085,7 +1092,7 @@ export async function createKumikoServer(
1085
1092
  // Discriminated-Dispatch — symmetric zu prod. Ohne hostDispatch
1086
1093
  // landet das im Single-Entry-Default ("client" + Schema-Inject).
1087
1094
  if (options.hostDispatch !== undefined) {
1088
- const dispatch = options.hostDispatch(req);
1095
+ const dispatch = await options.hostDispatch(req, { systemQuery: buildDevSystemQuery(req) });
1089
1096
  if (dispatch.kind === "redirect") {
1090
1097
  return new Response(null, {
1091
1098
  status: dispatch.status ?? 302,
@@ -251,6 +251,9 @@ export type RunDevAppOptions = {
251
251
  * Hono-app gehängt, läuft VOR dem static-asset-Pfad. Symmetrisch zur
252
252
  * gleichnamigen Option in runProdApp. */
253
253
  readonly extraRoutes?: CreateKumikoServerOptions["extraRoutes"];
254
+ /** Hook for app-wired co-running components — symmetric to
255
+ * runProdApp.wire (kumiko-framework#3050). */
256
+ readonly wire?: CreateKumikoServerOptions["wire"];
254
257
  /** Feature-toggle resolver — durchgereicht an createKumikoServer →
255
258
  * setupTestStack. Sprint-8 Tier-Composition: per-Tenant unterschied-
256
259
  * liche features aktiv via globalFeatureToggleRuntime. Pattern in
@@ -486,6 +489,7 @@ export async function runDevApp(options: RunDevAppOptions): Promise<KumikoServer
486
489
  ...(options.anonymousAccess !== undefined && { anonymousAccess: options.anonymousAccess }),
487
490
  ...(options.files !== undefined && { files: options.files }),
488
491
  ...(options.extraRoutes !== undefined && { extraRoutes: options.extraRoutes }),
492
+ ...(options.wire !== undefined && { wire: options.wire }),
489
493
  ...(finalEffectiveFeatures !== undefined && {
490
494
  effectiveFeatures: finalEffectiveFeatures,
491
495
  }),
@@ -72,7 +72,7 @@ export type ScaffoldAppOptions = {
72
72
  readonly cwd?: string;
73
73
  /** npm-version-pin for @cosmicdrift/* deps. Default "*" for latest. */
74
74
  readonly frameworkVersion?: string;
75
- /** Bundled-features to mount in run-config.ts. Default: secrets + sessions
75
+ /** Bundled-features to mount in run-config.ts. Default: secrets + auth-foundation + sessions
76
76
  * (the historical foundation). create-kumiko-app passes the picker output
77
77
  * here so the generated APP_FEATURES reflects the user's selection. */
78
78
  readonly features?: ReadonlyArray<ScaffoldFeatureEntry>;
@@ -386,6 +386,13 @@ const FOUNDATION_FEATURES: ReadonlyArray<ScaffoldFeatureEntry> = [
386
386
  exportName: "createSecretsFeature",
387
387
  callExpression: "createSecretsFeature()",
388
388
  },
389
+ // sessions' `sessionStore` extension requires auth-foundation to be mounted.
390
+ {
391
+ name: "auth-foundation",
392
+ importPath: "@cosmicdrift/kumiko-bundled-features/auth-foundation",
393
+ exportName: "authFoundationFeature",
394
+ callExpression: "authFoundationFeature",
395
+ },
389
396
  {
390
397
  name: "sessions",
391
398
  importPath: "@cosmicdrift/kumiko-bundled-features/sessions",
@@ -790,7 +797,7 @@ services:
790
797
  ports:
791
798
  - "127.0.0.1:5432:5432"
792
799
  volumes:
793
- - kumiko-pg:/var/lib/postgresql/data
800
+ - kumiko-pg:/var/lib/postgresql
794
801
  redis:
795
802
  image: redis:7
796
803
  ports: