@cosmicdrift/kumiko-dev-server 0.214.0 → 0.215.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,7 +1,7 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-dev-server",
3
- "version": "0.214.0",
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.",
3
+ "version": "0.215.1",
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>",
7
7
  "repository": {
@@ -30,6 +30,10 @@
30
30
  "types": "./src/compose-stacks.ts",
31
31
  "default": "./src/compose-stacks.ts"
32
32
  },
33
+ "./env-schema": {
34
+ "types": "./src/env-schema.ts",
35
+ "default": "./src/env-schema.ts"
36
+ },
33
37
  "./setup-test-stack-from-features": {
34
38
  "types": "./src/setup-test-stack-from-features.ts",
35
39
  "default": "./src/setup-test-stack-from-features.ts"
@@ -55,9 +59,9 @@
55
59
  "kumiko-upgrade": "./bin/kumiko-upgrade.ts"
56
60
  },
57
61
  "dependencies": {
58
- "@cosmicdrift/kumiko-bundled-features": "0.214.0",
59
- "@cosmicdrift/kumiko-framework": "0.214.0",
60
- "@cosmicdrift/kumiko-server-runtime": "0.214.0",
62
+ "@cosmicdrift/kumiko-bundled-features": "0.215.1",
63
+ "@cosmicdrift/kumiko-framework": "0.215.1",
64
+ "@cosmicdrift/kumiko-server-runtime": "0.215.1",
61
65
  "ts-morph": "^28.0.0"
62
66
  },
63
67
  "publishConfig": {
@@ -0,0 +1,39 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { readFileSync } from "node:fs";
3
+ import { fileURLToPath } from "node:url";
4
+ import { frameworkCoreEnvSchema as envSchemaViaRelative } from "../env-schema";
5
+
6
+ // Mirrors infra/guards/runtime-isolation-classify.ts `classifyByDirective`
7
+ // as of kumiko-framework#2337 (first 600 bytes, first 8 lines,
8
+ // `// @runtime <kind>`) — guards against a reformat of compose-stacks.ts
9
+ // pushing the directive out of that window. Doesn't catch infra changing
10
+ // the window/regex itself; that lives in the infra repo's own tests.
11
+ function classifyByDirective(filePath: string): string | null {
12
+ const head = readFileSync(filePath, "utf8").slice(0, 600);
13
+ for (const line of head.split("\n").slice(0, 8)) {
14
+ const match = line.match(/\/\/\s*@runtime\s+(\w+)/);
15
+ if (match) return match[1] ?? null;
16
+ }
17
+ return null;
18
+ }
19
+
20
+ describe("compose-stacks.ts runtime directive", () => {
21
+ it("carries a @runtime runtime directive the guard's classifyByDirective will find", () => {
22
+ const composeStacksPath = fileURLToPath(new URL("../compose-stacks.ts", import.meta.url));
23
+ expect(classifyByDirective(composeStacksPath)).toBe("runtime");
24
+ });
25
+ });
26
+
27
+ describe("@cosmicdrift/kumiko-dev-server/env-schema subpath", () => {
28
+ it("resolves the same schema as the relative import, and it parses", async () => {
29
+ const { frameworkCoreEnvSchema: envSchemaViaSubpath } = await import(
30
+ "@cosmicdrift/kumiko-dev-server/env-schema"
31
+ );
32
+ expect(envSchemaViaSubpath).toBe(envSchemaViaRelative);
33
+ const parsed = envSchemaViaSubpath.parse({
34
+ DATABASE_URL: "postgres://localhost:5432/db",
35
+ REDIS_URL: "redis://localhost:6379",
36
+ });
37
+ expect(parsed.PORT).toBe("3000");
38
+ });
39
+ });
@@ -154,6 +154,19 @@ describe("scaffoldApp", () => {
154
154
  expect(env).toContain("DATABASE_URL=");
155
155
  });
156
156
 
157
+ // kumiko-framework#2330: unset (not pre-filled) so the default stays on
158
+ // resolveKmsWiring's plaintext fallback — a half-filled trio would throw
159
+ // the all-or-none error instead of booting.
160
+ test(".env.example documents the PLATFORM_KEK / SUBJECT_KEYS_DATABASE_URL / KUMIKO_BLIND_INDEX_KEY trio, unset by default", async () => {
161
+ const dest = join(tmp, "my-shop");
162
+ await scaffoldApp({ name: "my-shop", destination: dest });
163
+
164
+ const env = readFileSync(join(dest, ".env.example"), "utf-8");
165
+ expect(env).toContain("PLATFORM_KEK=\n");
166
+ expect(env).toContain("SUBJECT_KEYS_DATABASE_URL=\n");
167
+ expect(env).toContain("KUMIKO_BLIND_INDEX_KEY=\n");
168
+ });
169
+
157
170
  test("docker-compose.yml ports + credentials match the .env.example *_URL defaults", async () => {
158
171
  const dest = join(tmp, "my-shop");
159
172
  await scaffoldApp({ name: "my-shop", destination: dest });
@@ -198,6 +211,15 @@ describe("scaffoldApp", () => {
198
211
  expect(readme).toContain("- `delivery`");
199
212
  });
200
213
 
214
+ test("README documents the plaintext-PII default until PLATFORM_KEK/SUBJECT_KEYS_DATABASE_URL/KUMIKO_BLIND_INDEX_KEY are set", async () => {
215
+ const dest = join(tmp, "my-shop");
216
+ await scaffoldApp({ name: "my-shop", destination: dest });
217
+
218
+ const readme = readFileSync(join(dest, "README.md"), "utf-8");
219
+ expect(readme).toContain("PLATFORM_KEK");
220
+ expect(readme).toContain("plaintext");
221
+ });
222
+
201
223
  test("callExpression/export-callability mismatch throws instead of silently mis-instantiating", async () => {
202
224
  const dest = join(tmp, "my-shop");
203
225
  await expect(
@@ -243,6 +265,22 @@ describe("scaffoldApp", () => {
243
265
  );
244
266
  });
245
267
 
268
+ // kumiko-framework#2330: the --yes recommended set mounts PII-annotated
269
+ // entities (user, tenant-invitation, fileRef via user-data-rights), so
270
+ // runProdApp's PII boot gate throws BOOT ABORTED unless kms/allowPlaintextPii
271
+ // is wired. resolveKmsWiring supplies a real KMS when the env trio is set,
272
+ // otherwise an explicit, logged plaintext fallback — never a silent one.
273
+ test("bin/main.ts wires resolveKmsWiring into runProdApp so the PII boot gate doesn't abort", async () => {
274
+ const dest = join(tmp, "my-shop");
275
+ await scaffoldApp({ name: "my-shop", destination: dest });
276
+
277
+ const main = readFileSync(join(dest, "bin/main.ts"), "utf-8");
278
+ expect(main).toContain('from "@cosmicdrift/kumiko-framework/crypto"');
279
+ expect(main).toContain("resolveKmsWiring(process.env");
280
+ expect(main).toContain('if ("allowPlaintextPii" in kmsWiring)');
281
+ expect(main).toContain("...kmsWiring,");
282
+ });
283
+
246
284
  test("src/run-config.ts mounts secrets + sessions + tasks + HAS_AUTH", async () => {
247
285
  const dest = join(tmp, "my-shop");
248
286
  await scaffoldApp({ name: "my-shop", destination: dest });
@@ -1,3 +1,9 @@
1
+ // @runtime runtime
2
+ // Despite living in this "dev"-marked package, every preset here composes
3
+ // @cosmicdrift/kumiko-bundled-features factories, and run-config.ts in every
4
+ // app repo feeds it into the runProdApp boot path, not just runDevApp
5
+ // (kumiko-framework#2337).
6
+ //
1
7
  // Composable SaaS stack presets — explicit opt-in blocks for run-config.ts.
2
8
  // Presets export feature instances only; no implicit PAT scopes, anonymousAccess,
3
9
  // or tier maps (those stay in bin/server.ts / app run-config).
@@ -437,6 +437,10 @@ function renderMain(appName: string): string {
437
437
  moduleSpecifier: "@cosmicdrift/kumiko-framework/env",
438
438
  namedImports: ["composeEnvSchema"],
439
439
  });
440
+ sf.addImportDeclaration({
441
+ moduleSpecifier: "@cosmicdrift/kumiko-framework/crypto",
442
+ namedImports: ["resolveKmsWiring"],
443
+ });
440
444
  sf.addImportDeclaration({
441
445
  moduleSpecifier: "../src/run-config",
442
446
  namedImports: ["APP_FEATURES", "HAS_AUTH"],
@@ -477,6 +481,37 @@ function renderMain(appName: string): string {
477
481
  ],
478
482
  });
479
483
 
484
+ // Subject-key KMS for the pii-annotated entities the --yes recommended set
485
+ // mounts (user, tenant-invitation, fileRef). Without the PLATFORM_KEK /
486
+ // SUBJECT_KEYS_DATABASE_URL / KUMIKO_BLIND_INDEX_KEY trio this falls back
487
+ // to plaintext PII with a loud boot warning — fine to get running locally,
488
+ // never for a real deploy (kumiko-framework#2330).
489
+ sf.addVariableStatement({
490
+ declarationKind: VariableDeclarationKind.Const,
491
+ declarations: [
492
+ {
493
+ name: "kmsWiring",
494
+ initializer: (writer) => {
495
+ writer.write("resolveKmsWiring(process.env, ").inlineBlock(() => {
496
+ writer.writeLine(`logPrefix: "[${appName}]",`);
497
+ writer.writeLine(
498
+ `plaintextReason: "no PLATFORM_KEK / SUBJECT_KEYS_DATABASE_URL / KUMIKO_BLIND_INDEX_KEY set — see .env.example",`,
499
+ );
500
+ });
501
+ writer.write(")");
502
+ },
503
+ },
504
+ ],
505
+ });
506
+
507
+ sf.addStatements((writer) => {
508
+ writer.write('if ("allowPlaintextPii" in kmsWiring) ').inlineBlock(() => {
509
+ writer.writeLine(
510
+ `console.warn(\`[${appName}] PII IS STORED IN PLAINTEXT — \${kmsWiring.allowPlaintextPii}\`);`,
511
+ );
512
+ });
513
+ });
514
+
480
515
  sf.addStatements((writer) => {
481
516
  writer
482
517
  .write("await runProdApp(")
@@ -484,6 +519,7 @@ function renderMain(appName: string): string {
484
519
  writer.writeLine("features: APP_FEATURES,");
485
520
  writer.writeLine("envSchema,");
486
521
  writer.writeLine('staticDir: "./dist",');
522
+ writer.writeLine("...kmsWiring,");
487
523
  writer.write("auth: ").inlineBlock(() => {
488
524
  writer.write("admin: ").inlineBlock(() => {
489
525
  writer.writeLine(`email: "admin@${appName}.local",`);
@@ -661,6 +697,14 @@ JWT_SECRET=change-me-min-32-chars-change-me-min-32
661
697
  # Generate with: openssl rand -base64 32
662
698
  KUMIKO_SECRETS_MASTER_KEY_V1=
663
699
 
700
+ # Subject-keys KMS for PII fields (user, tenant-invitation, fileRef). Unset by
701
+ # default — bin/main.ts then boots with plaintext PII and a loud warning, fine
702
+ # to get running locally. Set all three before a real deploy (crypto-shredding
703
+ # needs it for GDPR erasure): PLATFORM_KEK generate with: openssl rand -base64 32
704
+ PLATFORM_KEK=
705
+ SUBJECT_KEYS_DATABASE_URL=
706
+ KUMIKO_BLIND_INDEX_KEY=
707
+
664
708
  # Dev-only: persistent DB for \`bun dev\`. Without this var every reboot starts
665
709
  # a fresh kumiko_test_<random> DB → admin login + data gone on every edit.
666
710
  # With it the DB persists across reboots (schema pushes are idempotent).
@@ -744,6 +788,11 @@ bun run boot
744
788
  Runs \`KUMIKO_DRY_RUN_ENV=boot bun bin/main.ts\` — validates feature composition
745
789
  + env schema, exits 0 without touching DB/Redis. Useful in CI.
746
790
 
791
+ Without \`PLATFORM_KEK\` / \`SUBJECT_KEYS_DATABASE_URL\` / \`KUMIKO_BLIND_INDEX_KEY\`
792
+ set (see \`.env.example\`), both \`bun run boot\` and \`bun run start\` boot with
793
+ PII fields (user, tenant-invitation, fileRef) stored in plaintext and a loud
794
+ warning — set all three before a real deploy.
795
+
747
796
  ## Production build + schema
748
797
 
749
798
  \`\`\`sh