@cosmicdrift/kumiko-dev-server 0.197.0 → 0.197.1

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.197.0",
3
+ "version": "0.197.1",
4
4
  "description": "Dev-tooling for Kumiko apps: local dev-server bootstrap (runDevApp), scaffolding, codegen. Not shipped into production node_modules — see @cosmicdrift/kumiko-server-runtime for the prod boot path.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -54,9 +54,9 @@
54
54
  "kumiko-schema-check": "./bin/kumiko-schema-check.ts"
55
55
  },
56
56
  "dependencies": {
57
- "@cosmicdrift/kumiko-bundled-features": "0.197.0",
58
- "@cosmicdrift/kumiko-framework": "0.197.0",
59
- "@cosmicdrift/kumiko-server-runtime": "0.197.0",
57
+ "@cosmicdrift/kumiko-bundled-features": "0.197.1",
58
+ "@cosmicdrift/kumiko-framework": "0.197.1",
59
+ "@cosmicdrift/kumiko-server-runtime": "0.197.1",
60
60
  "ts-morph": "^28.0.0"
61
61
  },
62
62
  "publishConfig": {
@@ -5,7 +5,10 @@
5
5
  // run-prod-app forwarding pair. Without it a typo or wrong spread-key on the dev
6
6
  // path would silently drop the fail-closed guard and dev/prod would diverge.
7
7
 
8
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
8
+ import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
9
+ import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
10
+ import { createPersonalAccessTokensFeature } from "@cosmicdrift/kumiko-bundled-features/personal-access-tokens";
11
+ import { createSessionsFeature } from "@cosmicdrift/kumiko-bundled-features/sessions";
9
12
  import {
10
13
  requireTemplateResolver,
11
14
  TEXT_BLOCK_KIND,
@@ -16,8 +19,10 @@ import {
16
19
  createTextField,
17
20
  defineFeature,
18
21
  defineQueryHandler,
22
+ type TenantId,
19
23
  } from "@cosmicdrift/kumiko-framework/engine";
20
24
  import { TestUsers } from "@cosmicdrift/kumiko-framework/stack";
25
+ import * as jose from "jose";
21
26
  import { z } from "zod";
22
27
  import type { KumikoServerHandle } from "../create-kumiko-server";
23
28
  import { runDevApp } from "../run-dev-app";
@@ -174,3 +179,66 @@ describe("runDevApp — extraContext merge order: app values win over boot defau
174
179
  expect(res?.title).toBe("from caller extraContext");
175
180
  });
176
181
  });
182
+
183
+ // runDevApp had no equivalent of runProdApp's session boot gate (#1262/#1275):
184
+ // a forgotten sessions mount left session-list silently empty instead of
185
+ // saying anything (#2027). Unlike prod, dev warns rather than aborts —
186
+ // mirrors assertPiiBootInvariants' dev/prod split.
187
+ describe("runDevApp — session boot gate (#2027)", () => {
188
+ const ADMIN = {
189
+ email: "session-gate-dev@example.eu",
190
+ password: "test-pw-strong-1234",
191
+ displayName: "Admin",
192
+ emailVerified: true,
193
+ memberships: [
194
+ {
195
+ tenantId: "00000000-0000-4000-8000-000000000002" as TenantId,
196
+ tenantKey: "session-gate-dev",
197
+ tenantName: "Session Gate Dev",
198
+ roles: ["Admin"],
199
+ },
200
+ ],
201
+ };
202
+
203
+ test("auth mounted, sessions feature missing → warns but still boots (dev doesn't abort)", async () => {
204
+ const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
205
+ try {
206
+ handle = await runDevApp({
207
+ features: [validFeature()],
208
+ port: 0,
209
+ auth: { admin: ADMIN },
210
+ });
211
+ expect(handle).toBeDefined();
212
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("sessionStore"));
213
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("[runDevApp]"));
214
+ } finally {
215
+ warnSpy.mockRestore();
216
+ }
217
+ });
218
+
219
+ test("auth mounted, sessions feature mounted → login actually creates a session (sid on the JWT)", async () => {
220
+ // The bug this issue reports: a mounted sessions feature whose sessionCreator
221
+ // never reached the auth config, so login never got a `sid` and
222
+ // store_user_sessions never got a row — session-list stayed empty with no
223
+ // error anywhere. A boot that doesn't throw proves nothing about that; only
224
+ // a real login + decoded JWT does.
225
+ handle = await runDevApp({
226
+ features: [
227
+ authFoundationFeature,
228
+ createPersonalAccessTokensFeature({ scopes: {} }),
229
+ createSessionsFeature(),
230
+ ],
231
+ port: 0,
232
+ auth: { admin: ADMIN },
233
+ });
234
+
235
+ const login = await handle.stack.http.raw("POST", "/api/auth/login", {
236
+ email: ADMIN.email,
237
+ password: ADMIN.password,
238
+ });
239
+ expect(login.status).toBe(200);
240
+ const body = (await login.json()) as { token?: string };
241
+ expect(body.token).toBeTypeOf("string");
242
+ expect(typeof jose.decodeJwt(body.token!).jti).toBe("string");
243
+ });
244
+ });
@@ -78,6 +78,7 @@ import {
78
78
  composeFeatures,
79
79
  } from "@cosmicdrift/kumiko-server-runtime/compose-features";
80
80
  import { assertPiiBootInvariants } from "@cosmicdrift/kumiko-server-runtime/pii-boot-gate";
81
+ import { assertSessionBootInvariants } from "@cosmicdrift/kumiko-server-runtime/session-boot-gate";
81
82
  import { watchAndRegenerate } from "./codegen";
82
83
  import {
83
84
  type CreateKumikoServerOptions,
@@ -371,13 +372,21 @@ export async function runDevApp(options: RunDevAppOptions): Promise<KumikoServer
371
372
  blindIndexKey: options.blindIndexKey,
372
373
  mode: "dev",
373
374
  });
374
- const cfgExtra = effectiveAuth
375
- ? mergeConfigResolverDefault(
376
- options.extraContext,
377
- createRegistry(features),
378
- envSource,
379
- bootCrypto.configCipher,
380
- )
375
+ // Throwaway registry, built once and reused below — mirrors runProdApp's
376
+ // `createRegistry(features)` right after validateBoot (#2027). Symmetric
377
+ // sessionStoreProviderMounted check to runProdApp's (registry.getExtension-
378
+ // Usages, not a raw features.some(...) scan) so dev and prod never ask the
379
+ // same question two different ways again.
380
+ const registry = effectiveAuth ? createRegistry(features) : undefined;
381
+ const sessionStoreProviderMounted =
382
+ registry !== undefined && registry.getExtensionUsages(EXT_SESSION_STORE).length > 0;
383
+ assertSessionBootInvariants({
384
+ hasAuth: Boolean(effectiveAuth),
385
+ sessionStoreProviderMounted,
386
+ mode: "dev",
387
+ });
388
+ const cfgExtra = registry
389
+ ? mergeConfigResolverDefault(options.extraContext, registry, envSource, bootCrypto.configCipher)
381
390
  : options.extraContext;
382
391
  // Auto-wire templateResolver (immer) + secrets (feature-gated), symmetrisch zu
383
392
  // runProdApp. Anders als prod existiert die db hier erst im Factory-deps
@@ -444,10 +453,9 @@ export async function runDevApp(options: RunDevAppOptions): Promise<KumikoServer
444
453
  : {};
445
454
 
446
455
  // Parity with runProdApp (#1372 review): gate on sessionStore provider, not
447
- // the sessions feature name — a custom provider must also wire auth callbacks.
448
- const sessionStoreProviderMounted = features.some((f) =>
449
- f.extensionUsages.some((u) => u.extensionName === EXT_SESSION_STORE),
450
- );
456
+ // the sessions feature name — a custom provider must also wire auth
457
+ // callbacks. sessionStoreProviderMounted computed above via the registry
458
+ // (same check as runProdApp, #2027) not recomputed here.
451
459
  const sessionAuthFragment =
452
460
  effectiveAuth && sessionStoreProviderMounted
453
461
  ? {