@cosmicdrift/kumiko-dev-server 0.214.0 → 0.215.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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-dev-server",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Dev-tooling for Kumiko apps: local dev-server bootstrap (runDevApp), scaffolding, codegen.
|
|
3
|
+
"version": "0.215.0",
|
|
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.
|
|
59
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
60
|
-
"@cosmicdrift/kumiko-server-runtime": "0.
|
|
62
|
+
"@cosmicdrift/kumiko-bundled-features": "0.215.0",
|
|
63
|
+
"@cosmicdrift/kumiko-framework": "0.215.0",
|
|
64
|
+
"@cosmicdrift/kumiko-server-runtime": "0.215.0",
|
|
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 });
|
package/src/compose-stacks.ts
CHANGED
|
@@ -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).
|
package/src/scaffold-app.ts
CHANGED
|
@@ -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
|