@cosmicdrift/kumiko-dev-server 0.359.0 → 0.361.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/dist/create-kumiko-server.d.ts +1 -0
- package/dist/create-kumiko-server.js +7 -5
- package/dist/is-persistent-dev-db.d.ts +1 -0
- package/dist/is-persistent-dev-db.js +4 -0
- package/dist/resolve-dev-pii-kms.d.ts +13 -0
- package/dist/resolve-dev-pii-kms.js +33 -0
- package/dist/run-dev-app.d.ts +6 -6
- package/dist/run-dev-app.js +25 -6
- package/package.json +6 -6
- package/src/changes.json +7 -0
|
@@ -6,6 +6,7 @@ import { type TestStack, type TestStackOptions } from "@cosmicdrift/kumiko-frame
|
|
|
6
6
|
import { type PageHeadResolver, type PageHeadSystemQuery } from "@cosmicdrift/kumiko-headless/apex";
|
|
7
7
|
import { type SystemWireDeps } from "@cosmicdrift/kumiko-server-runtime/extra-routes-deps";
|
|
8
8
|
type BunServer = typeof Bun extends undefined ? unknown : ReturnType<typeof Bun.serve>;
|
|
9
|
+
export declare const logInfo: (msg: string) => void;
|
|
9
10
|
/** Multi-entry mode for apps that ship several separate bundles (e.g.
|
|
10
11
|
* publicstatus: `admin.<base>` loads the admin UI, otherwise the public
|
|
11
12
|
* page).
|
|
@@ -20,7 +20,7 @@ import { readFile, realpath, watch } from "node:fs/promises";
|
|
|
20
20
|
import { tmpdir } from "node:os";
|
|
21
21
|
import { join, resolve, sep } from "node:path";
|
|
22
22
|
import { resolveAnonymousAccessFromRegistry } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
|
|
23
|
-
import { createClientIpResolver, generateToken, } from "@cosmicdrift/kumiko-framework/api";
|
|
23
|
+
import { AUTH_COOKIE_BASE, CSRF_COOKIE_BASE, createClientIpResolver, generateToken, sessionCookieName, } from "@cosmicdrift/kumiko-framework/api";
|
|
24
24
|
import { buildAppSchema } from "@cosmicdrift/kumiko-framework/engine";
|
|
25
25
|
import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
26
26
|
import { pushEntityProjectionTables, setupTestStack, TestUsers, } from "@cosmicdrift/kumiko-framework/stack";
|
|
@@ -32,6 +32,7 @@ import { RENDERER_WEB_FONT_FILE_PATTERN, RENDERER_WEB_FONTS_URL_PREFIX, resolveR
|
|
|
32
32
|
import { buildRequestBoundSystemQuery } from "@cosmicdrift/kumiko-server-runtime/request-bound-system-query";
|
|
33
33
|
import { canResolveTailwindStylesheet, resolveTailwindCli, } from "@cosmicdrift/kumiko-server-runtime/resolve-tailwind-cli";
|
|
34
34
|
import { stripNoRouteMatchHeader, tryHonoFirst, } from "@cosmicdrift/kumiko-server-runtime/try-hono-first";
|
|
35
|
+
import { isPersistentDevDb } from "./is-persistent-dev-db.js";
|
|
35
36
|
// Runtime-detection. The dev-server is meant to run under Bun (Kumiko's
|
|
36
37
|
// target runtime), but the test-suite runs under vitest on Node — we
|
|
37
38
|
// gate every Bun.* call so the module at least LOADS under Node, and
|
|
@@ -40,12 +41,13 @@ import { stripNoRouteMatchHeader, tryHonoFirst, } from "@cosmicdrift/kumiko-serv
|
|
|
40
41
|
const hasBun = typeof globalThis.Bun !== "undefined";
|
|
41
42
|
// @wrapper-known semantic-alias
|
|
42
43
|
// biome-ignore lint/suspicious/noConsole: dev-server status logging
|
|
43
|
-
const logInfo = (msg) => console.log(msg);
|
|
44
|
+
export const logInfo = (msg) => console.log(msg);
|
|
44
45
|
// @wrapper-known semantic-alias
|
|
45
46
|
// biome-ignore lint/suspicious/noConsole: dev-server error logging
|
|
46
47
|
const logError = (...args) => console.error(...args);
|
|
47
|
-
|
|
48
|
-
const
|
|
48
|
+
// The dev server speaks plain http, so its minted cookies are never Secure and keep the unprefixed names.
|
|
49
|
+
const CSRF_COOKIE = sessionCookieName(CSRF_COOKIE_BASE, { secure: false });
|
|
50
|
+
const AUTH_COOKIE = sessionCookieName(AUTH_COOKIE_BASE, { secure: false });
|
|
49
51
|
// Duplicated in packages/testing/src/e2e/constants.ts: the Playwright config
|
|
50
52
|
// runs under Node and cannot import this module.
|
|
51
53
|
export const STYLESHEET_WATCH_ENV = "KUMIKO_DEV_STYLESHEET_WATCH";
|
|
@@ -588,7 +590,7 @@ export async function createKumikoServer(options) {
|
|
|
588
590
|
// on purpose — every dev-server pattern benefits from the same
|
|
589
591
|
// toggle, not just one sample.
|
|
590
592
|
const devDbName = process.env["KUMIKO_DEV_DB_NAME"];
|
|
591
|
-
const persistentDb =
|
|
593
|
+
const persistentDb = isPersistentDevDb(process.env);
|
|
592
594
|
// Redis is shared across boots AND across apps (unlike the per-boot test
|
|
593
595
|
// DB, which is ephemeral or persistent per KUMIKO_DEV_DB_NAME), so every
|
|
594
596
|
// boot needs its own queue-name prefix — otherwise two dev/e2e servers
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isPersistentDevDb(env?: Record<string, string | undefined>): boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type KmsAdapter } from "@cosmicdrift/kumiko-framework/crypto";
|
|
2
|
+
export type DevPiiKmsResolution = {
|
|
3
|
+
readonly kms: KmsAdapter | undefined;
|
|
4
|
+
readonly blindIndexKey: string | undefined;
|
|
5
|
+
readonly mode: "app-kms" | "ephemeral-default" | "plaintext";
|
|
6
|
+
};
|
|
7
|
+
type DevPiiKmsOptions = {
|
|
8
|
+
readonly kms?: KmsAdapter | false;
|
|
9
|
+
readonly blindIndexKey?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function captureEphemeralDevPiiKms(resolution: DevPiiKmsResolution): () => void;
|
|
12
|
+
export declare function resolveDevPiiKms(options: DevPiiKmsOptions, env?: Record<string, string | undefined>): DevPiiKmsResolution;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { configureBlindIndexKey, configuredBlindIndexKey, configuredPiiSubjectKms, configurePiiSubjectKms, InMemoryKmsAdapter, } from "@cosmicdrift/kumiko-framework/crypto";
|
|
3
|
+
import { isPersistentDevDb } from "./is-persistent-dev-db.js";
|
|
4
|
+
// Call right after configuring the resolved values. The returned restore only
|
|
5
|
+
// unsets what the ephemeral default installed, so a KMS a later test or the app
|
|
6
|
+
// configured survives.
|
|
7
|
+
export function captureEphemeralDevPiiKms(resolution) {
|
|
8
|
+
if (resolution.mode !== "ephemeral-default")
|
|
9
|
+
return () => { };
|
|
10
|
+
const ownKms = configuredPiiSubjectKms();
|
|
11
|
+
const ownBlindIndexKey = configuredBlindIndexKey();
|
|
12
|
+
return () => {
|
|
13
|
+
if (ownKms !== undefined && configuredPiiSubjectKms() === ownKms) {
|
|
14
|
+
configurePiiSubjectKms(undefined);
|
|
15
|
+
}
|
|
16
|
+
if (ownBlindIndexKey !== undefined && configuredBlindIndexKey() === ownBlindIndexKey) {
|
|
17
|
+
configureBlindIndexKey(undefined);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function resolveDevPiiKms(options, env = process.env) {
|
|
22
|
+
if (options.kms !== undefined && options.kms !== false) {
|
|
23
|
+
return { kms: options.kms, blindIndexKey: options.blindIndexKey, mode: "app-kms" };
|
|
24
|
+
}
|
|
25
|
+
if (options.kms === false || isPersistentDevDb(env)) {
|
|
26
|
+
return { kms: undefined, blindIndexKey: options.blindIndexKey, mode: "plaintext" };
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
kms: new InMemoryKmsAdapter(),
|
|
30
|
+
blindIndexKey: options.blindIndexKey ?? randomBytes(32).toString("base64"),
|
|
31
|
+
mode: "ephemeral-default",
|
|
32
|
+
};
|
|
33
|
+
}
|
package/dist/run-dev-app.d.ts
CHANGED
|
@@ -120,12 +120,12 @@ export type RunDevAppOptions = {
|
|
|
120
120
|
* `createEnvMasterKeyProvider`. Override für KMS-Backends. Nur relevant
|
|
121
121
|
* wenn das secrets-Feature gemountet ist. Symmetrisch zu runProdApp. */
|
|
122
122
|
readonly masterKey?: MasterKeyProvider;
|
|
123
|
-
/** Subject
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
readonly kms?: KmsAdapter;
|
|
123
|
+
/** Subject KMS for PII-annotated fields, the dev counterpart of runProdApp({ kms }).
|
|
124
|
+
* Unset: an ephemeral DB gets an InMemoryKmsAdapter plus a throwaway blind-index key;
|
|
125
|
+
* a persistent dev DB (KUMIKO_DEV_DB_NAME) stays plaintext with a boot warning, pass
|
|
126
|
+
* createPgKmsAdapter against that DB there (in-memory DEKs vanish on restart).
|
|
127
|
+
* `false`: explicit plaintext, with the boot warning. */
|
|
128
|
+
readonly kms?: KmsAdapter | false;
|
|
129
129
|
/** 32-Byte-Key (base64) für Blind-Index-HMACs — Pflicht sobald `kms`
|
|
130
130
|
* gesetzt ist und lookupable-Felder gemountet sind (sonst Boot-Abbruch,
|
|
131
131
|
* symmetrisch zu runProdApp). */
|
package/dist/run-dev-app.js
CHANGED
|
@@ -30,7 +30,8 @@ import { buildComposeAuthOptions, composeFeatures, } from "@cosmicdrift/kumiko-s
|
|
|
30
30
|
import { assertPiiBootInvariants } from "@cosmicdrift/kumiko-server-runtime/pii-boot-gate";
|
|
31
31
|
import { assertSessionBootInvariants } from "@cosmicdrift/kumiko-server-runtime/session-boot-gate";
|
|
32
32
|
import { watchAndRegenerate } from "./codegen/index.js";
|
|
33
|
-
import { createKumikoServer, } from "./create-kumiko-server.js";
|
|
33
|
+
import { createKumikoServer, logInfo, } from "./create-kumiko-server.js";
|
|
34
|
+
import { captureEphemeralDevPiiKms, resolveDevPiiKms } from "./resolve-dev-pii-kms.js";
|
|
34
35
|
import { renderWelcomeBanner } from "./welcome-banner.js";
|
|
35
36
|
import { addConfigAccessorFactory, buildBootExtraContext, loggerFromExtraContext, requireEnv, resolveAuthMail, } from "@cosmicdrift/kumiko-server-runtime/run-prod-app";
|
|
36
37
|
export async function runDevApp(options) {
|
|
@@ -128,13 +129,18 @@ export async function runDevApp(options) {
|
|
|
128
129
|
// Subject-KMS + Blind-Index (symmetrisch zu runProdApp). Dev warnt bei
|
|
129
130
|
// Klartext-PII statt zu failen; kms+lookupable ohne blindIndexKey bricht
|
|
130
131
|
// auch hier (Lookups wären in jedem Modus kaputt).
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
const piiKms = resolveDevPiiKms(options);
|
|
133
|
+
configurePiiSubjectKms(piiKms.kms);
|
|
134
|
+
configureBlindIndexKey(piiKms.blindIndexKey);
|
|
135
|
+
const releaseEphemeralPiiKms = captureEphemeralDevPiiKms(piiKms);
|
|
133
136
|
assertPiiBootInvariants(features, {
|
|
134
|
-
kms:
|
|
135
|
-
blindIndexKey:
|
|
137
|
+
kms: piiKms.kms,
|
|
138
|
+
blindIndexKey: piiKms.blindIndexKey,
|
|
136
139
|
mode: "dev",
|
|
137
140
|
});
|
|
141
|
+
if (piiKms.mode === "ephemeral-default") {
|
|
142
|
+
logInfo("[runDevApp] PII encrypted with in-memory KMS (ephemeral DB); pass kms: false to opt out");
|
|
143
|
+
}
|
|
138
144
|
// Throwaway registry, built once and reused below — mirrors runProdApp's
|
|
139
145
|
// `createRegistry(features)` right after validateBoot (#2027). Symmetric
|
|
140
146
|
// sessionStoreProviderMounted check to runProdApp's (registry.getExtension-
|
|
@@ -364,6 +370,9 @@ export async function runDevApp(options) {
|
|
|
364
370
|
await seed(stack);
|
|
365
371
|
}
|
|
366
372
|
},
|
|
373
|
+
}).catch((error) => {
|
|
374
|
+
releaseEphemeralPiiKms();
|
|
375
|
+
throw error;
|
|
367
376
|
});
|
|
368
377
|
if (options.welcomeBanner) {
|
|
369
378
|
const overrides = typeof options.welcomeBanner === "object" ? options.welcomeBanner : {};
|
|
@@ -382,7 +391,17 @@ export async function runDevApp(options) {
|
|
|
382
391
|
// biome-ignore lint/suspicious/noConsole: boot-time UX print, opt-in via welcomeBanner.
|
|
383
392
|
console.log(`\n${banner}\n`);
|
|
384
393
|
}
|
|
385
|
-
return
|
|
394
|
+
return {
|
|
395
|
+
...handle,
|
|
396
|
+
stop: async () => {
|
|
397
|
+
try {
|
|
398
|
+
await handle.stop();
|
|
399
|
+
}
|
|
400
|
+
finally {
|
|
401
|
+
releaseEphemeralPiiKms();
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
};
|
|
386
405
|
}
|
|
387
406
|
// Exported for the wiring-contract test (config-resolver-default.integration):
|
|
388
407
|
// pins that the dev configResolver-default carries the ENV→app-override bridge
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-dev-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.361.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>",
|
|
@@ -64,11 +64,11 @@
|
|
|
64
64
|
"kumiko-upgrade": "./bin/kumiko-upgrade.ts"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
68
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
69
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
70
|
-
"@cosmicdrift/kumiko-renderer-web": "0.
|
|
71
|
-
"@cosmicdrift/kumiko-server-runtime": "0.
|
|
67
|
+
"@cosmicdrift/kumiko-bundled-features": "0.361.0",
|
|
68
|
+
"@cosmicdrift/kumiko-framework": "0.361.0",
|
|
69
|
+
"@cosmicdrift/kumiko-headless": "0.361.0",
|
|
70
|
+
"@cosmicdrift/kumiko-renderer-web": "0.361.0",
|
|
71
|
+
"@cosmicdrift/kumiko-server-runtime": "0.361.0",
|
|
72
72
|
"bun-types": "^1.3.13",
|
|
73
73
|
"ts-morph": "^28.0.0",
|
|
74
74
|
"zod": "^4.4.3"
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.361.0",
|
|
4
|
+
"type": "breaking",
|
|
5
|
+
"title": "runDevApp encrypts PII on ephemeral dev DBs by default",
|
|
6
|
+
"detail": "Without a kms option, runDevApp now configures an InMemoryKmsAdapter and a random blind-index key when KUMIKO_DEV_DB_NAME is unset. With a persistent dev DB nothing changes (plaintext plus warning, pass createPgKmsAdapter to encrypt). RunDevAppOptions.kms also accepts false for explicit plaintext. An app-provided kms keeps its previous behavior, including the boot abort for lookupable fields without a blindIndexKey.",
|
|
7
|
+
"migration": "e2e or dev scripts that read PII columns raw must decrypt them or pass kms false to runDevApp. Apps that pass their own kms are unaffected."
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"version": "0.354.0",
|
|
4
11
|
"type": "fix",
|