@cosmicdrift/kumiko-dev-server 0.79.2 → 0.80.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.
|
|
3
|
+
"version": "0.80.0",
|
|
4
4
|
"description": "Development server bootstrap for Kumiko apps. Bundles the client, mints dev-JWTs, injects the resolved AppSchema, and seeds an admin. Not for production.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"kumiko-schema-check": "./bin/kumiko-schema-check.ts"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
54
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
53
|
+
"@cosmicdrift/kumiko-bundled-features": "0.80.0",
|
|
54
|
+
"@cosmicdrift/kumiko-framework": "0.80.0",
|
|
55
55
|
"ts-morph": "^28.0.0"
|
|
56
56
|
},
|
|
57
57
|
"publishConfig": {
|
|
@@ -27,6 +27,7 @@ describe("scaffoldApp", () => {
|
|
|
27
27
|
"src/run-config.ts",
|
|
28
28
|
"bin/main.ts",
|
|
29
29
|
"bin/dev.ts",
|
|
30
|
+
"src/client.tsx",
|
|
30
31
|
".env.example",
|
|
31
32
|
"README.md",
|
|
32
33
|
]);
|
|
@@ -48,11 +49,12 @@ describe("scaffoldApp", () => {
|
|
|
48
49
|
expect(pkg.dependencies["@cosmicdrift/kumiko-bundled-features"]).toBe("^0.13.0");
|
|
49
50
|
expect(pkg.dependencies["@cosmicdrift/kumiko-dev-server"]).toBe("^0.13.0");
|
|
50
51
|
expect(pkg.dependencies["@cosmicdrift/kumiko-framework"]).toBe("^0.13.0");
|
|
52
|
+
expect(pkg.dependencies["@cosmicdrift/kumiko-renderer-web"]).toBe("^0.13.0");
|
|
51
53
|
expect(pkg.scripts["boot"]).toContain("KUMIKO_DRY_RUN_ENV=boot");
|
|
52
54
|
expect(pkg.scripts["dev"]).toBe("bun --watch bin/dev.ts");
|
|
53
55
|
});
|
|
54
56
|
|
|
55
|
-
test("bin/dev.ts contains runDevApp + welcomeBanner + admin login", () => {
|
|
57
|
+
test("bin/dev.ts contains runDevApp + welcomeBanner + admin login + clientEntry", () => {
|
|
56
58
|
const dest = join(tmp, "my-shop");
|
|
57
59
|
scaffoldApp({ name: "my-shop", destination: dest });
|
|
58
60
|
|
|
@@ -61,6 +63,25 @@ describe("scaffoldApp", () => {
|
|
|
61
63
|
expect(dev).toContain("welcomeBanner: true");
|
|
62
64
|
expect(dev).toContain("admin@my-shop.local");
|
|
63
65
|
expect(dev).toContain(`password: "changeme"`);
|
|
66
|
+
// Without clientEntry the default HTML still <script src="/client.js">'s,
|
|
67
|
+
// and the dev-server 404s on it — every fresh scaffold rendered blank.
|
|
68
|
+
expect(dev).toContain(`clientEntry: "./src/client.tsx"`);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("src/client.tsx bundles createKumikoApp + emailPasswordClient + DefaultAppShell", () => {
|
|
72
|
+
const dest = join(tmp, "my-shop");
|
|
73
|
+
scaffoldApp({ name: "my-shop", destination: dest });
|
|
74
|
+
|
|
75
|
+
const client = readFileSync(join(dest, "src/client.tsx"), "utf-8");
|
|
76
|
+
expect(client).toContain("createKumikoApp");
|
|
77
|
+
expect(client).toContain("emailPasswordClient");
|
|
78
|
+
// Without shell:DefaultAppShell post-login renders only the active
|
|
79
|
+
// screen (no sidebar / topbar) — the e2e hero-demo proved this dead-
|
|
80
|
+
// ends on "Screen not found" because routing has no layout chrome.
|
|
81
|
+
expect(client).toContain("DefaultAppShell");
|
|
82
|
+
expect(client).toContain("shell: DefaultAppShell");
|
|
83
|
+
expect(client).toContain('from "@cosmicdrift/kumiko-renderer-web"');
|
|
84
|
+
expect(client).toContain('from "@cosmicdrift/kumiko-bundled-features/auth-email-password/web"');
|
|
64
85
|
});
|
|
65
86
|
|
|
66
87
|
test(".env.example carries KUMIKO_DEV_DB_NAME default so reboots are persistent", () => {
|
|
@@ -71,6 +92,18 @@ describe("scaffoldApp", () => {
|
|
|
71
92
|
expect(env).toContain("KUMIKO_DEV_DB_NAME=my_shop_dev");
|
|
72
93
|
});
|
|
73
94
|
|
|
95
|
+
test(".env.example lists both TEST_DATABASE_URL (bun dev) + DATABASE_URL (prod)", () => {
|
|
96
|
+
const dest = join(tmp, "my-shop");
|
|
97
|
+
scaffoldApp({ name: "my-shop", destination: dest });
|
|
98
|
+
|
|
99
|
+
const env = readFileSync(join(dest, ".env.example"), "utf-8");
|
|
100
|
+
// runDevApp → setupTestStack needs TEST_DATABASE_URL; previously the
|
|
101
|
+
// template only listed DATABASE_URL, so every fresh `bun dev` crashed
|
|
102
|
+
// with "Missing required env var: TEST_DATABASE_URL".
|
|
103
|
+
expect(env).toContain("TEST_DATABASE_URL=");
|
|
104
|
+
expect(env).toContain("DATABASE_URL=");
|
|
105
|
+
});
|
|
106
|
+
|
|
74
107
|
test("README lists the mounted features dynamically", () => {
|
|
75
108
|
const dest = join(tmp, "my-shop");
|
|
76
109
|
scaffoldApp({
|
|
@@ -173,25 +206,25 @@ describe("scaffoldApp", () => {
|
|
|
173
206
|
name: "custom-features",
|
|
174
207
|
destination: dest,
|
|
175
208
|
features: [
|
|
176
|
-
{
|
|
177
|
-
name: "tenant",
|
|
178
|
-
importPath: "@cosmicdrift/kumiko-bundled-features/tenant",
|
|
179
|
-
exportName: "createTenantFeature",
|
|
180
|
-
callExpression: "createTenantFeature()",
|
|
181
|
-
},
|
|
182
209
|
{
|
|
183
210
|
name: "billing-foundation",
|
|
184
211
|
importPath: "@cosmicdrift/kumiko-bundled-features/billing-foundation",
|
|
185
212
|
exportName: "billingFoundationFeature",
|
|
186
213
|
callExpression: "billingFoundationFeature",
|
|
187
214
|
},
|
|
215
|
+
{
|
|
216
|
+
name: "delivery",
|
|
217
|
+
importPath: "@cosmicdrift/kumiko-bundled-features/delivery",
|
|
218
|
+
exportName: "createDeliveryFeature",
|
|
219
|
+
callExpression: "createDeliveryFeature()",
|
|
220
|
+
},
|
|
188
221
|
],
|
|
189
222
|
});
|
|
190
223
|
const cfg = readFileSync(join(dest, "src/run-config.ts"), "utf-8");
|
|
191
|
-
expect(cfg).toContain('from "@cosmicdrift/kumiko-bundled-features/tenant"');
|
|
192
224
|
expect(cfg).toContain('from "@cosmicdrift/kumiko-bundled-features/billing-foundation"');
|
|
193
|
-
expect(cfg).toContain("
|
|
225
|
+
expect(cfg).toContain('from "@cosmicdrift/kumiko-bundled-features/delivery"');
|
|
194
226
|
expect(cfg).toContain("billingFoundationFeature");
|
|
227
|
+
expect(cfg).toContain("createDeliveryFeature()");
|
|
195
228
|
expect(cfg).not.toContain("createSecretsFeature");
|
|
196
229
|
expect(cfg).not.toContain("createSessionsFeature");
|
|
197
230
|
});
|
|
@@ -204,6 +237,55 @@ describe("scaffoldApp", () => {
|
|
|
204
237
|
expect(cfg).toContain("createSessionsFeature()");
|
|
205
238
|
});
|
|
206
239
|
|
|
240
|
+
test("features-param: composeFeatures auto-mounted (config/user/tenant/auth-email-password) are filtered out — no boot-time dedupe-warn spam", () => {
|
|
241
|
+
const dest = join(tmp, "filtered");
|
|
242
|
+
scaffoldApp({
|
|
243
|
+
name: "filtered",
|
|
244
|
+
destination: dest,
|
|
245
|
+
features: [
|
|
246
|
+
// Picker's recommended set includes the 4 auto-mounted bundled
|
|
247
|
+
// features; without the filter they'd land in APP_FEATURES and
|
|
248
|
+
// composeFeatures would log 4 dedupe warnings on every boot.
|
|
249
|
+
{
|
|
250
|
+
name: "config",
|
|
251
|
+
importPath: "@cosmicdrift/kumiko-bundled-features/config",
|
|
252
|
+
exportName: "createConfigFeature",
|
|
253
|
+
callExpression: "createConfigFeature()",
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: "user",
|
|
257
|
+
importPath: "@cosmicdrift/kumiko-bundled-features/user",
|
|
258
|
+
exportName: "createUserFeature",
|
|
259
|
+
callExpression: "createUserFeature()",
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: "tenant",
|
|
263
|
+
importPath: "@cosmicdrift/kumiko-bundled-features/tenant",
|
|
264
|
+
exportName: "createTenantFeature",
|
|
265
|
+
callExpression: "createTenantFeature()",
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
name: "auth-email-password",
|
|
269
|
+
importPath: "@cosmicdrift/kumiko-bundled-features/auth-email-password",
|
|
270
|
+
exportName: "createAuthEmailPasswordFeature",
|
|
271
|
+
callExpression: "createAuthEmailPasswordFeature()",
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
name: "delivery",
|
|
275
|
+
importPath: "@cosmicdrift/kumiko-bundled-features/delivery",
|
|
276
|
+
exportName: "createDeliveryFeature",
|
|
277
|
+
callExpression: "createDeliveryFeature()",
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
});
|
|
281
|
+
const cfg = readFileSync(join(dest, "src/run-config.ts"), "utf-8");
|
|
282
|
+
expect(cfg).not.toContain("createConfigFeature");
|
|
283
|
+
expect(cfg).not.toContain("createUserFeature");
|
|
284
|
+
expect(cfg).not.toContain("createTenantFeature");
|
|
285
|
+
expect(cfg).not.toContain("createAuthEmailPasswordFeature");
|
|
286
|
+
expect(cfg).toContain("createDeliveryFeature()");
|
|
287
|
+
});
|
|
288
|
+
|
|
207
289
|
test("deterministic tenantId for same name (reproducible boots)", () => {
|
|
208
290
|
const a = join(tmp, "a");
|
|
209
291
|
const b = join(tmp, "b");
|
package/src/scaffold-app.ts
CHANGED
|
@@ -83,6 +83,9 @@ export function scaffoldApp(options: ScaffoldAppOptions): ScaffoldAppResult {
|
|
|
83
83
|
write(join(destination, "bin", "dev.ts"), renderDev(options.name));
|
|
84
84
|
files.push("bin/dev.ts");
|
|
85
85
|
|
|
86
|
+
write(join(destination, "src", "client.tsx"), renderClient());
|
|
87
|
+
files.push("src/client.tsx");
|
|
88
|
+
|
|
86
89
|
write(join(destination, ".env.example"), renderEnvExample(options.name));
|
|
87
90
|
files.push(".env.example");
|
|
88
91
|
|
|
@@ -113,6 +116,7 @@ function renderPackageJson(name: string, version: string): string {
|
|
|
113
116
|
"@cosmicdrift/kumiko-bundled-features": version,
|
|
114
117
|
"@cosmicdrift/kumiko-dev-server": version,
|
|
115
118
|
"@cosmicdrift/kumiko-framework": version,
|
|
119
|
+
"@cosmicdrift/kumiko-renderer-web": version,
|
|
116
120
|
zod: "^4.4.3",
|
|
117
121
|
},
|
|
118
122
|
},
|
|
@@ -168,11 +172,20 @@ const FOUNDATION_FEATURES: ReadonlyArray<ScaffoldFeatureEntry> = [
|
|
|
168
172
|
},
|
|
169
173
|
];
|
|
170
174
|
|
|
175
|
+
// composeFeatures({ includeBundled: true }) auto-mountet diese 4 Foundation-
|
|
176
|
+
// Features. Sie hier nochmal in APP_FEATURES zu schreiben löste den dedupe-
|
|
177
|
+
// warn-Spam im scaffolded `bun dev` aus (PR #599 hat den createRegistry-
|
|
178
|
+
// Crash gefangen, der Spam blieb bis hier). Filter wirkt defensiv: auch wenn
|
|
179
|
+
// jemand scaffoldApp() direkt mit allen Bundled-Feature-Entries aufruft
|
|
180
|
+
// rutschen die 4 nicht in run-config.ts.
|
|
181
|
+
const COMPOSE_AUTO_MOUNTED_NAMES = new Set(["config", "user", "tenant", "auth-email-password"]);
|
|
182
|
+
|
|
171
183
|
function renderRunConfig(features?: ReadonlyArray<ScaffoldFeatureEntry>): string {
|
|
172
184
|
const project = newTsProject();
|
|
173
185
|
const sf = project.createSourceFile("run-config.ts", "");
|
|
174
186
|
|
|
175
|
-
const
|
|
187
|
+
const filtered = (features ?? []).filter((f) => !COMPOSE_AUTO_MOUNTED_NAMES.has(f.name));
|
|
188
|
+
const effective = filtered.length > 0 ? filtered : FOUNDATION_FEATURES;
|
|
176
189
|
const grouped = new Map<string, string[]>();
|
|
177
190
|
for (const entry of effective) {
|
|
178
191
|
const existing = grouped.get(entry.importPath) ?? [];
|
|
@@ -352,6 +365,7 @@ function renderDev(appName: string): string {
|
|
|
352
365
|
.inlineBlock(() => {
|
|
353
366
|
writer.writeLine("features: APP_FEATURES,");
|
|
354
367
|
writer.writeLine("welcomeBanner: true,");
|
|
368
|
+
writer.writeLine(`clientEntry: "./src/client.tsx",`);
|
|
355
369
|
writer.write("auth: ").inlineBlock(() => {
|
|
356
370
|
writer.write("admin: ").inlineBlock(() => {
|
|
357
371
|
writer.writeLine(`email: "admin@${appName}.local",`);
|
|
@@ -391,8 +405,39 @@ function renderDev(appName: string): string {
|
|
|
391
405
|
return sf.getFullText();
|
|
392
406
|
}
|
|
393
407
|
|
|
408
|
+
function renderClient(): string {
|
|
409
|
+
return [
|
|
410
|
+
"// Browser-Entry. runDevApp's clientEntry-Option bundlet diese Datei zu",
|
|
411
|
+
"// /client.js und das Default-HTML lädt sie. createKumikoApp liest das",
|
|
412
|
+
"// Schema aus dem window-globalen (das injectSchema im dev-server setzt)",
|
|
413
|
+
"// und mountet die Routen.",
|
|
414
|
+
"//",
|
|
415
|
+
"// DefaultAppShell liefert die Sidebar + Topbar — ohne `shell` rendert",
|
|
416
|
+
"// createKumikoApp das aktive Screen ohne Layout-Wrapper (= nach Login",
|
|
417
|
+
"// nur ein nackter Banner statt der App). emailPasswordClient() bringt",
|
|
418
|
+
"// Login-Screen + Session-Provider — ohne ihn bliebe /login leer.",
|
|
419
|
+
"//",
|
|
420
|
+
"// Neue Client-Plugins (z.B. notificationsClient()) hier in clientFeatures",
|
|
421
|
+
"// hinzu — symmetrisch zu APP_FEATURES auf der Server-Seite.",
|
|
422
|
+
"",
|
|
423
|
+
'import { emailPasswordClient } from "@cosmicdrift/kumiko-bundled-features/auth-email-password/web";',
|
|
424
|
+
'import { createKumikoApp, DefaultAppShell } from "@cosmicdrift/kumiko-renderer-web";',
|
|
425
|
+
"",
|
|
426
|
+
"createKumikoApp({",
|
|
427
|
+
" shell: DefaultAppShell,",
|
|
428
|
+
" clientFeatures: [emailPasswordClient()],",
|
|
429
|
+
"});",
|
|
430
|
+
"",
|
|
431
|
+
].join("\n");
|
|
432
|
+
}
|
|
433
|
+
|
|
394
434
|
function renderEnvExample(appName: string): string {
|
|
395
|
-
|
|
435
|
+
const devDb = `${appName.replace(/-/g, "_")}_dev`;
|
|
436
|
+
return `# bun dev (runDevApp → setupTestStack) braucht TEST_DATABASE_URL.
|
|
437
|
+
# Production (bun bin/main.ts → runProdApp) braucht DATABASE_URL.
|
|
438
|
+
# Beide zeigen im Default auf denselben lokalen Postgres — runDevApp legt
|
|
439
|
+
# darunter eine eigene "<KUMIKO_DEV_DB_NAME>"-Datenbank an.
|
|
440
|
+
TEST_DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/postgres
|
|
396
441
|
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/app
|
|
397
442
|
REDIS_URL=redis://127.0.0.1:6379
|
|
398
443
|
|
|
@@ -406,7 +451,7 @@ KUMIKO_SECRETS_MASTER_KEY_V1=
|
|
|
406
451
|
# Dev-only: persistente DB für \`bun dev\`. Ohne diesen Var startet jeder Reboot
|
|
407
452
|
# eine frische kumiko_test_<random>-DB → Admin-Login + Daten weg bei jedem Edit.
|
|
408
453
|
# Mit Var bleibt die DB zwischen Reboots erhalten (Schema-Pushes sind idempotent).
|
|
409
|
-
KUMIKO_DEV_DB_NAME=${
|
|
454
|
+
KUMIKO_DEV_DB_NAME=${devDb}
|
|
410
455
|
`;
|
|
411
456
|
}
|
|
412
457
|
|