@cosmicdrift/kumiko-dev-server 1.0.0 → 2.0.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/bin/kumiko-schema-check.ts +7 -0
- package/package.json +12 -7
- package/src/__tests__/build-prod-bundle.integration.test.ts +1 -1
- package/src/__tests__/build-server-bundle.test.ts +62 -0
- package/src/__tests__/compose-stacks.test.ts +271 -0
- package/src/__tests__/create-kumiko-server-errors.test.ts +54 -0
- package/src/__tests__/create-kumiko-server-options.test.ts +33 -0
- package/src/__tests__/create-kumiko-server.integration.test.ts +128 -1
- package/src/__tests__/discover-format.test.ts +1 -1
- package/src/__tests__/env-schema.integration.test.ts +1 -1
- package/src/__tests__/few-shot-corpus.test.ts +16 -11
- package/src/__tests__/merge-extra-context.test.ts +56 -0
- package/src/__tests__/resolve-stylesheet.test.ts +16 -0
- package/src/__tests__/saas-identity-wire.integration.test.ts +430 -0
- package/src/__tests__/scaffold-app-feature.test.ts +59 -6
- package/src/__tests__/scaffold-app.test.ts +34 -2
- package/src/__tests__/schema-apply.integration.test.ts +5 -2
- package/src/__tests__/setup-test-stack-from-features.integration.test.ts +30 -0
- package/src/__tests__/walkthrough.integration.test.ts +22 -6
- package/src/build-server-bundle.ts +33 -15
- package/src/build.ts +1 -2
- package/src/codegen/__tests__/render-codegen.test.ts +2 -1
- package/src/codegen/__tests__/run-codegen.test.ts +2 -2
- package/src/codegen/__tests__/strict-mode-diagnostics.test.ts +6 -1
- package/src/codegen/__tests__/watch.test.ts +1 -2
- package/src/codegen/render.ts +6 -1
- package/src/codegen/scan-events.ts +7 -5
- package/src/codegen/watch.ts +7 -4
- package/src/compose-stacks.ts +184 -0
- package/src/create-kumiko-server.ts +28 -5
- package/src/few-shot-corpus.ts +4 -3
- package/src/index.ts +30 -12
- package/src/run-dev-app.ts +195 -61
- package/src/scaffold-app-feature.ts +118 -15
- package/src/scaffold-app.ts +151 -79
- package/src/scaffold-demo-tasks.ts +233 -0
- package/src/schema-apply.ts +15 -2
- package/src/schema-check-core.ts +1 -1
- package/src/setup-test-stack-from-features.ts +61 -0
- package/src/welcome-banner.ts +1 -4
- package/src/__tests__/boot-extra-context.test.ts +0 -140
- package/src/__tests__/build-prod-bundle.test.ts +0 -311
- package/src/__tests__/cache-headers.test.ts +0 -83
- package/src/__tests__/compose-features-wiring.integration.test.ts +0 -382
- package/src/__tests__/compose-features.test.ts +0 -129
- package/src/__tests__/config-seed-boot.integration.test.ts +0 -158
- package/src/__tests__/inject-schema.test.ts +0 -62
- package/src/__tests__/renderer-web-css-relocation.integration.test.ts +0 -85
- package/src/__tests__/renderer-web-shell-sentinel.test.ts +0 -35
- package/src/__tests__/require-env.test.ts +0 -29
- package/src/__tests__/resolve-auth-mail.test.ts +0 -69
- package/src/__tests__/resolve-tailwind-cli.test.ts +0 -81
- package/src/__tests__/run-prod-app-env-source.test.ts +0 -157
- package/src/__tests__/run-prod-app-spec.test.ts +0 -57
- package/src/__tests__/run-prod-app.integration.test.ts +0 -840
- package/src/__tests__/session-wiring.test.ts +0 -51
- package/src/__tests__/try-hono-first.test.ts +0 -63
- package/src/boot/apply-boot-seeds.ts +0 -18
- package/src/build-prod-bundle.ts +0 -697
- package/src/compose-features.ts +0 -145
- package/src/extra-routes-deps.ts +0 -47
- package/src/inject-schema.ts +0 -24
- package/src/resolve-tailwind-cli.ts +0 -45
- package/src/run-prod-app.ts +0 -1492
- package/src/session-wiring.ts +0 -29
- package/src/try-hono-first.ts +0 -46
package/src/scaffold-app.ts
CHANGED
|
@@ -19,9 +19,17 @@ import {
|
|
|
19
19
|
writeSnapshotJson,
|
|
20
20
|
} from "@cosmicdrift/kumiko-framework/db";
|
|
21
21
|
import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
22
|
+
import { composeFeatures } from "@cosmicdrift/kumiko-server-runtime/compose-features";
|
|
22
23
|
import { IndentationText, Project, VariableDeclarationKind } from "ts-morph";
|
|
23
|
-
import { composeFeatures } from "./compose-features";
|
|
24
24
|
import { isKebabSegment } from "./kebab";
|
|
25
|
+
import {
|
|
26
|
+
createDemoTasksFeature,
|
|
27
|
+
renderDemoSeedFile,
|
|
28
|
+
renderDemoTasksFeatureFile,
|
|
29
|
+
renderDemoTasksI18n,
|
|
30
|
+
renderDemoTasksIndex,
|
|
31
|
+
renderDemoTasksWebIndex,
|
|
32
|
+
} from "./scaffold-demo-tasks";
|
|
25
33
|
import { scaffoldDeploy } from "./scaffold-deploy";
|
|
26
34
|
|
|
27
35
|
// Single bundled-feature entry the scaffolder mounts into run-config.ts.
|
|
@@ -34,6 +42,8 @@ export type ScaffoldFeatureEntry = {
|
|
|
34
42
|
readonly importPath: string;
|
|
35
43
|
readonly exportName: string;
|
|
36
44
|
readonly callExpression: string;
|
|
45
|
+
/** Runtime args for factory-style exports that need config the codegen text can't parse back out (e.g. `{ scopes: {} }`). Omit for zero-arg factories and object-style exports. */
|
|
46
|
+
readonly callArgs?: readonly unknown[];
|
|
37
47
|
};
|
|
38
48
|
|
|
39
49
|
export type ScaffoldAppOptions = {
|
|
@@ -95,6 +105,21 @@ export async function scaffoldApp(options: ScaffoldAppOptions): Promise<Scaffold
|
|
|
95
105
|
write(join(destination, "src", "run-config.ts"), renderRunConfig(options.features));
|
|
96
106
|
files.push("src/run-config.ts");
|
|
97
107
|
|
|
108
|
+
mkdirSync(join(destination, "src", "features", "tasks", "web"), { recursive: true });
|
|
109
|
+
write(join(destination, "src", "features", "tasks", "feature.ts"), renderDemoTasksFeatureFile());
|
|
110
|
+
files.push("src/features/tasks/feature.ts");
|
|
111
|
+
write(join(destination, "src", "features", "tasks", "i18n.ts"), renderDemoTasksI18n());
|
|
112
|
+
files.push("src/features/tasks/i18n.ts");
|
|
113
|
+
write(join(destination, "src", "features", "tasks", "index.ts"), renderDemoTasksIndex());
|
|
114
|
+
files.push("src/features/tasks/index.ts");
|
|
115
|
+
write(
|
|
116
|
+
join(destination, "src", "features", "tasks", "web", "index.ts"),
|
|
117
|
+
renderDemoTasksWebIndex(),
|
|
118
|
+
);
|
|
119
|
+
files.push("src/features/tasks/web/index.ts");
|
|
120
|
+
write(join(destination, "src", "seed.ts"), renderDemoSeedFile());
|
|
121
|
+
files.push("src/seed.ts");
|
|
122
|
+
|
|
98
123
|
write(join(destination, "kumiko", "schema.ts"), renderKumikoSchema());
|
|
99
124
|
files.push("kumiko/schema.ts");
|
|
100
125
|
|
|
@@ -107,7 +132,7 @@ export async function scaffoldApp(options: ScaffoldAppOptions): Promise<Scaffold
|
|
|
107
132
|
write(join(destination, "bin", "kumiko.ts"), renderBinKumiko());
|
|
108
133
|
files.push("bin/kumiko.ts");
|
|
109
134
|
|
|
110
|
-
write(join(destination, "src", "client.tsx"), renderClient());
|
|
135
|
+
write(join(destination, "src", "client.tsx"), renderClient(options.name));
|
|
111
136
|
files.push("src/client.tsx");
|
|
112
137
|
|
|
113
138
|
write(join(destination, "src", "styles.css"), renderStylesCss());
|
|
@@ -163,6 +188,7 @@ function renderPackageJson(name: string, version: string): string {
|
|
|
163
188
|
"@cosmicdrift/kumiko-dev-server": version,
|
|
164
189
|
"@cosmicdrift/kumiko-framework": version,
|
|
165
190
|
"@cosmicdrift/kumiko-renderer-web": version,
|
|
191
|
+
"@cosmicdrift/kumiko-server-runtime": version,
|
|
166
192
|
react: "^19.2.6",
|
|
167
193
|
"react-dom": "^19.2.6",
|
|
168
194
|
zod: "^4.4.3",
|
|
@@ -170,6 +196,8 @@ function renderPackageJson(name: string, version: string): string {
|
|
|
170
196
|
devDependencies: {
|
|
171
197
|
"@biomejs/biome": "^2.4.15",
|
|
172
198
|
"@tailwindcss/cli": "^4.3.0",
|
|
199
|
+
"@types/react": "^19.2.0",
|
|
200
|
+
"@types/react-dom": "^19.2.0",
|
|
173
201
|
"bun-types": "^1.3.14",
|
|
174
202
|
tailwindcss: "^4.3.0",
|
|
175
203
|
typescript: "^6.0.3",
|
|
@@ -345,15 +373,19 @@ function renderRunConfig(features?: ReadonlyArray<ScaffoldFeatureEntry>): string
|
|
|
345
373
|
for (const [importPath, namedImports] of grouped) {
|
|
346
374
|
sf.addImportDeclaration({ moduleSpecifier: importPath, namedImports });
|
|
347
375
|
}
|
|
376
|
+
sf.addImportDeclaration({
|
|
377
|
+
moduleSpecifier: "./features/tasks",
|
|
378
|
+
namedImports: ["tasksFeature"],
|
|
379
|
+
});
|
|
348
380
|
|
|
349
|
-
const
|
|
381
|
+
const callExprs = [...effective.map((f) => f.callExpression), "tasksFeature"];
|
|
350
382
|
sf.addVariableStatement({
|
|
351
383
|
declarationKind: VariableDeclarationKind.Const,
|
|
352
384
|
isExported: true,
|
|
353
385
|
declarations: [
|
|
354
386
|
{
|
|
355
387
|
name: "APP_FEATURES",
|
|
356
|
-
initializer: `[${
|
|
388
|
+
initializer: `[${callExprs.join(", ")}] as const`,
|
|
357
389
|
},
|
|
358
390
|
],
|
|
359
391
|
});
|
|
@@ -367,14 +399,14 @@ function renderRunConfig(features?: ReadonlyArray<ScaffoldFeatureEntry>): string
|
|
|
367
399
|
sf.insertText(
|
|
368
400
|
0,
|
|
369
401
|
[
|
|
370
|
-
"// Single source of truth
|
|
371
|
-
"// config/user/tenant/auth-email-password
|
|
372
|
-
"// composeFeatures(includeBundled:true)
|
|
373
|
-
"//
|
|
402
|
+
"// Single source of truth for your app's feature composition.",
|
|
403
|
+
"// config/user/tenant/auth-email-password are added automatically",
|
|
404
|
+
"// via composeFeatures(includeBundled:true) when runProdApp is called",
|
|
405
|
+
"// with `auth: {…}` (see bin/main.ts).",
|
|
374
406
|
"//",
|
|
375
|
-
"//
|
|
376
|
-
"// - bunx @cosmicdrift/kumiko-cli add feature <name> (DX-2,
|
|
377
|
-
"// -
|
|
407
|
+
"// Add new features:",
|
|
408
|
+
"// - bunx @cosmicdrift/kumiko-cli add feature <name> (DX-2, automatic)",
|
|
409
|
+
"// - or: hand-edit + add the import below",
|
|
378
410
|
"",
|
|
379
411
|
"",
|
|
380
412
|
].join("\n"),
|
|
@@ -390,7 +422,11 @@ function renderMain(appName: string): string {
|
|
|
390
422
|
|
|
391
423
|
sf.addImportDeclaration({
|
|
392
424
|
moduleSpecifier: "@cosmicdrift/kumiko-dev-server",
|
|
393
|
-
namedImports: ["
|
|
425
|
+
namedImports: ["frameworkCoreEnvSchema"],
|
|
426
|
+
});
|
|
427
|
+
sf.addImportDeclaration({
|
|
428
|
+
moduleSpecifier: "@cosmicdrift/kumiko-server-runtime",
|
|
429
|
+
namedImports: ["composeFeatures", "runProdApp"],
|
|
394
430
|
});
|
|
395
431
|
sf.addImportDeclaration({
|
|
396
432
|
moduleSpecifier: "@cosmicdrift/kumiko-framework/engine",
|
|
@@ -475,9 +511,9 @@ function renderMain(appName: string): string {
|
|
|
475
511
|
[
|
|
476
512
|
"// Production-bootstrap. KUMIKO_DRY_RUN_ENV=boot exits after",
|
|
477
513
|
"// composeFeatures + validateBoot + createRegistry without DB/Redis-connect",
|
|
478
|
-
"// (
|
|
479
|
-
"//
|
|
480
|
-
"//
|
|
514
|
+
"// (see @cosmicdrift/kumiko-server-runtime runProdApp). The real dev boot",
|
|
515
|
+
"// runs via `bunx kumiko dev` (in-repo dev-tool) with a Docker stack — DX-1.0",
|
|
516
|
+
"// only covers the boot-mode path; `kumiko dev` lands in a later DX phase.",
|
|
481
517
|
"",
|
|
482
518
|
"",
|
|
483
519
|
].join("\n"),
|
|
@@ -504,6 +540,10 @@ function renderDev(appName: string): string {
|
|
|
504
540
|
moduleSpecifier: "../src/run-config",
|
|
505
541
|
namedImports: ["APP_FEATURES"],
|
|
506
542
|
});
|
|
543
|
+
sf.addImportDeclaration({
|
|
544
|
+
moduleSpecifier: "../src/seed",
|
|
545
|
+
namedImports: ["seedDemoTasks"],
|
|
546
|
+
});
|
|
507
547
|
|
|
508
548
|
sf.addVariableStatement({
|
|
509
549
|
declarationKind: VariableDeclarationKind.Const,
|
|
@@ -522,6 +562,7 @@ function renderDev(appName: string): string {
|
|
|
522
562
|
writer.writeLine("features: APP_FEATURES,");
|
|
523
563
|
writer.writeLine("welcomeBanner: true,");
|
|
524
564
|
writer.writeLine(`clientEntry: "./src/client.tsx",`);
|
|
565
|
+
writer.writeLine("seeds: [seedDemoTasks],");
|
|
525
566
|
writer.write("auth: ").inlineBlock(() => {
|
|
526
567
|
writer.write("admin: ").inlineBlock(() => {
|
|
527
568
|
writer.writeLine(`email: "admin@${appName}.local",`);
|
|
@@ -547,12 +588,12 @@ function renderDev(appName: string): string {
|
|
|
547
588
|
sf.insertText(
|
|
548
589
|
0,
|
|
549
590
|
[
|
|
550
|
-
"// Dev
|
|
551
|
-
"//
|
|
552
|
-
"// setupTestStack
|
|
553
|
-
"// r.entity(...) in
|
|
554
|
-
"//
|
|
555
|
-
"// Persistent
|
|
591
|
+
"// Dev bootstrap. `bun --watch bin/dev.ts` (see package.json scripts.dev)",
|
|
592
|
+
"// starts a full-featured dev server with auto-reload on code changes.",
|
|
593
|
+
"// setupTestStack creates missing entity tables automatically — a new",
|
|
594
|
+
"// r.entity(...) in a feature becomes a CREATE TABLE on the next reboot,",
|
|
595
|
+
"// no manual `kumiko schema apply` needed (that's prod-only).",
|
|
596
|
+
"// Persistent DB via KUMIKO_DEV_DB_NAME (.env) so admin + data survive reboots.",
|
|
556
597
|
"",
|
|
557
598
|
"",
|
|
558
599
|
].join("\n"),
|
|
@@ -561,27 +602,39 @@ function renderDev(appName: string): string {
|
|
|
561
602
|
return sf.getFullText();
|
|
562
603
|
}
|
|
563
604
|
|
|
564
|
-
function renderClient(): string {
|
|
605
|
+
function renderClient(appName: string): string {
|
|
565
606
|
return [
|
|
566
|
-
"// Browser
|
|
567
|
-
"// /client.js
|
|
568
|
-
"//
|
|
569
|
-
"//
|
|
607
|
+
"// Browser entry. runDevApp's clientEntry option bundles this file to",
|
|
608
|
+
"// /client.js and the default HTML loads it. createKumikoApp reads the",
|
|
609
|
+
"// schema from the window global (injectSchema in the dev-server sets it)",
|
|
610
|
+
"// and mounts the routes.",
|
|
570
611
|
"//",
|
|
571
|
-
"//
|
|
572
|
-
"//
|
|
573
|
-
"//
|
|
574
|
-
"//
|
|
612
|
+
"// AppShell wraps DefaultAppShell to supply `brand` — createKumikoApp's",
|
|
613
|
+
"// shell option only injects schema + children. Without `shell` the",
|
|
614
|
+
"// active screen renders with no layout wrapper (a bare banner after",
|
|
615
|
+
"// login instead of the app). emailPasswordClient() brings the login",
|
|
616
|
+
"// screen + session provider — without it /login stays empty.",
|
|
575
617
|
"//",
|
|
576
|
-
"//
|
|
577
|
-
"//
|
|
618
|
+
"// Add new client plugins (e.g. notificationsClient()) to clientFeatures",
|
|
619
|
+
"// here — symmetric to APP_FEATURES on the server side.",
|
|
578
620
|
"",
|
|
579
621
|
'import { emailPasswordClient } from "@cosmicdrift/kumiko-bundled-features/auth-email-password/web";',
|
|
580
|
-
'import {
|
|
622
|
+
'import { tasksClient } from "./features/tasks/web";',
|
|
623
|
+
'import { type AppSchema, createKumikoApp, DefaultAppShell } from "@cosmicdrift/kumiko-renderer-web";',
|
|
624
|
+
'import type { ReactNode } from "react";',
|
|
625
|
+
"",
|
|
626
|
+
"function AppShell({ children, schema }: { children: ReactNode; schema: AppSchema }): ReactNode {",
|
|
627
|
+
" return (",
|
|
628
|
+
// html-ok: appName is kebab-validated (a-z/0-9/-); this is generated .tsx source, not runtime HTML.
|
|
629
|
+
` <DefaultAppShell brand={<span className="font-semibold tracking-tight">${appName}</span>} schema={schema}>`,
|
|
630
|
+
" {children}",
|
|
631
|
+
" </DefaultAppShell>",
|
|
632
|
+
" );",
|
|
633
|
+
"}",
|
|
581
634
|
"",
|
|
582
635
|
"createKumikoApp({",
|
|
583
|
-
" shell:
|
|
584
|
-
" clientFeatures: [emailPasswordClient()],",
|
|
636
|
+
" shell: AppShell,",
|
|
637
|
+
" clientFeatures: [emailPasswordClient(), tasksClient],",
|
|
585
638
|
"});",
|
|
586
639
|
"",
|
|
587
640
|
].join("\n");
|
|
@@ -589,10 +642,10 @@ function renderClient(): string {
|
|
|
589
642
|
|
|
590
643
|
function renderEnvExample(appName: string): string {
|
|
591
644
|
const devDb = `${appName.replace(/-/g, "_")}_dev`;
|
|
592
|
-
return `# bun dev (runDevApp → setupTestStack)
|
|
593
|
-
# Production (bun bin/main.ts → runProdApp)
|
|
594
|
-
#
|
|
595
|
-
#
|
|
645
|
+
return `# bun dev (runDevApp → setupTestStack) needs TEST_DATABASE_URL.
|
|
646
|
+
# Production (bun bin/main.ts → runProdApp) needs DATABASE_URL.
|
|
647
|
+
# Both default to the same local Postgres — runDevApp creates its own
|
|
648
|
+
# "<KUMIKO_DEV_DB_NAME>" database underneath.
|
|
596
649
|
TEST_DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/postgres
|
|
597
650
|
DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/app
|
|
598
651
|
REDIS_URL=redis://127.0.0.1:6379
|
|
@@ -604,26 +657,27 @@ JWT_SECRET=change-me-min-32-chars-change-me-min-32
|
|
|
604
657
|
# Generate with: openssl rand -base64 32
|
|
605
658
|
KUMIKO_SECRETS_MASTER_KEY_V1=
|
|
606
659
|
|
|
607
|
-
# Dev-only:
|
|
608
|
-
#
|
|
609
|
-
#
|
|
660
|
+
# Dev-only: persistent DB for \`bun dev\`. Without this var every reboot starts
|
|
661
|
+
# a fresh kumiko_test_<random> DB → admin login + data gone on every edit.
|
|
662
|
+
# With it the DB persists across reboots (schema pushes are idempotent).
|
|
610
663
|
KUMIKO_DEV_DB_NAME=${devDb}
|
|
611
664
|
`;
|
|
612
665
|
}
|
|
613
666
|
|
|
614
|
-
//
|
|
615
|
-
//
|
|
616
|
-
//
|
|
617
|
-
//
|
|
618
|
-
//
|
|
619
|
-
// Redis must not be reachable from the LAN on a machine without a firewall.
|
|
667
|
+
// Ports + credentials match the *_URL defaults in renderEnvExample, so
|
|
668
|
+
// `docker compose up -d` just works with the generated .env. Named pg volume
|
|
669
|
+
// so dev data survives `docker compose down` (pairs with KUMIKO_DEV_DB_NAME
|
|
670
|
+
// persistence) — the loopback-binding rationale is in the generated file's
|
|
671
|
+
// own comment (657/1), no need to duplicate it here.
|
|
620
672
|
function renderDockerCompose(): string {
|
|
621
673
|
return `# Local Postgres + Redis for \`bun dev\`. Matches the *_URL defaults in .env.example.
|
|
622
674
|
# Start: docker compose up -d · Stop: docker compose down · Reset: docker compose down -v
|
|
623
675
|
# Ports bind to 127.0.0.1 only — weak dev credentials must not be exposed on the LAN.
|
|
624
676
|
services:
|
|
625
677
|
postgres:
|
|
626
|
-
|
|
678
|
+
# Pinned to the project's own compose-file tag (663/1) — Alpine variant
|
|
679
|
+
# (~90MB vs ~400MB) and a reproducible patch version, bump on PG18 minors.
|
|
680
|
+
image: postgres:18.3-alpine
|
|
627
681
|
environment:
|
|
628
682
|
POSTGRES_USER: postgres
|
|
629
683
|
POSTGRES_PASSWORD: postgres
|
|
@@ -646,33 +700,36 @@ function renderReadme(
|
|
|
646
700
|
): string {
|
|
647
701
|
const featureList =
|
|
648
702
|
features && features.length > 0
|
|
649
|
-
? features.map((f) => `- \`${f.name}\``).join(
|
|
650
|
-
|
|
703
|
+
? [...features.map((f) => `- \`${f.name}\``), "- `tasks` (demo — list + edit screens)"].join(
|
|
704
|
+
"\n",
|
|
705
|
+
)
|
|
706
|
+
: "- `secrets` (foundation)\n- `sessions` (foundation)\n- `tasks` (demo — list + edit screens)";
|
|
651
707
|
return `# ${appName}
|
|
652
708
|
|
|
653
|
-
Scaffolded by \`
|
|
654
|
-
|
|
655
|
-
\`bunx @cosmicdrift/kumiko-cli add feature <name>\`.
|
|
709
|
+
Scaffolded by \`kumiko new app\`. Includes a demo **tasks** feature with list +
|
|
710
|
+
edit screens, sidebar nav, and seeded rows — \`bun dev\` shows a working admin UI
|
|
711
|
+
after login. Add more features via \`bunx @cosmicdrift/kumiko-cli add feature <name>\`.
|
|
656
712
|
|
|
657
713
|
## Mounted features
|
|
658
714
|
|
|
659
715
|
${featureList}
|
|
660
716
|
|
|
661
|
-
Edit \`src/run-config.ts\` to add
|
|
717
|
+
Edit \`src/run-config.ts\` to add bundled features. The demo lives in
|
|
718
|
+
\`src/features/tasks/\`.
|
|
662
719
|
|
|
663
|
-
## First run
|
|
720
|
+
## First run (browser)
|
|
664
721
|
|
|
665
722
|
\`\`\`sh
|
|
666
723
|
bun install
|
|
667
724
|
cp .env.example .env
|
|
668
|
-
#
|
|
669
|
-
docker compose up -d # if you
|
|
725
|
+
# set JWT_SECRET + KUMIKO_SECRETS_MASTER_KEY_V1 in .env
|
|
726
|
+
docker compose up -d # local Postgres + Redis (skip if you already have them)
|
|
670
727
|
bun dev
|
|
671
728
|
\`\`\`
|
|
672
729
|
|
|
673
|
-
The
|
|
674
|
-
|
|
675
|
-
|
|
730
|
+
The welcome banner prints the URL (default \`http://localhost:4173\`) and admin
|
|
731
|
+
login. Sign in as \`admin@${appName}.local\` / \`changeme\`, then open **Tasks**
|
|
732
|
+
in the sidebar — demo rows are pre-seeded.
|
|
676
733
|
|
|
677
734
|
## Boot-only smoke (no DB needed)
|
|
678
735
|
|
|
@@ -705,10 +762,12 @@ deploys. Build context = app repo root; migrations ship in \`kumiko/migrations/\
|
|
|
705
762
|
## Architecture
|
|
706
763
|
|
|
707
764
|
- \`src/run-config.ts\` — single source of truth: which features your app mounts (\`APP_FEATURES\`, \`HAS_AUTH\`).
|
|
765
|
+
- \`src/features/tasks/\` — demo feature (entity + handlers + screens + nav).
|
|
766
|
+
- \`src/seed.ts\` — dev seed for demo tasks (\`bun dev\` only).
|
|
708
767
|
- \`kumiko/schema.ts\` — same feature set → \`ENTITY_METAS\` for \`kumiko schema\`.
|
|
709
768
|
- \`bin/dev.ts\` — dev-server entry (\`bun dev\`).
|
|
710
769
|
- \`bin/main.ts\` — production-bootstrap (\`bun run start\`).
|
|
711
|
-
- \`bin/kumiko.ts\` — schema-CLI bundled into \`dist-server/kumiko.js\`.
|
|
770
|
+
- \`bin/kumiko.ts\` — schema + consumer-ops CLI bundled into \`dist-server/kumiko.js\`.
|
|
712
771
|
- \`docker-compose.yml\` — local Postgres + Redis for \`bun dev\`.
|
|
713
772
|
|
|
714
773
|
For full docs see https://docs.kumiko.rocks.
|
|
@@ -731,7 +790,7 @@ function renderKumikoSchema(): string {
|
|
|
731
790
|
"// Computes table-metas from the SAME composeFeatures(APP_FEATURES) the",
|
|
732
791
|
"// runtime sees (runProdApp/runDevApp) — migration and runtime cannot drift.",
|
|
733
792
|
"",
|
|
734
|
-
'import { composeFeatures } from "@cosmicdrift/kumiko-
|
|
793
|
+
'import { composeFeatures } from "@cosmicdrift/kumiko-server-runtime/compose-features";',
|
|
735
794
|
'import { collectTableMetas, type EntityTableMeta } from "@cosmicdrift/kumiko-framework/db";',
|
|
736
795
|
'import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";',
|
|
737
796
|
'import { APP_FEATURES, HAS_AUTH } from "../src/run-config";',
|
|
@@ -749,25 +808,32 @@ function renderBinKumiko(): string {
|
|
|
749
808
|
return [
|
|
750
809
|
"#!/usr/bin/env bun",
|
|
751
810
|
"",
|
|
752
|
-
"// Standalone kumiko
|
|
753
|
-
"//
|
|
754
|
-
"//
|
|
811
|
+
"// Standalone kumiko CLI for the production bundle. The deploy migrate-step",
|
|
812
|
+
"// runs `bun /app/kumiko.js schema apply`; ops runs `bun /app/kumiko.js",
|
|
813
|
+
"// consumer status|restart <name>` to recover a dead event consumer without",
|
|
814
|
+
"// ad-hoc SQL. kumiko-build bundles this file to dist-server/kumiko.js.",
|
|
755
815
|
"",
|
|
756
|
-
'import { composeFeatures } from "@cosmicdrift/kumiko-
|
|
816
|
+
'import { composeFeatures } from "@cosmicdrift/kumiko-server-runtime/compose-features";',
|
|
817
|
+
'import { runConsumerCli } from "@cosmicdrift/kumiko-framework/consumer-cli";',
|
|
757
818
|
'import { runSchemaCli } from "@cosmicdrift/kumiko-framework/schema-cli";',
|
|
758
819
|
'import { APP_FEATURES, HAS_AUTH } from "../src/run-config";',
|
|
759
820
|
"",
|
|
760
821
|
"const [, , cmd, ...rest] = Bun.argv;",
|
|
761
|
-
|
|
762
|
-
"
|
|
763
|
-
|
|
764
|
-
"
|
|
822
|
+
"// biome-ignore lint/suspicious/noConsole: CLI output is the feature.",
|
|
823
|
+
"const out = { log: (l: string) => console.log(l), err: (l: string) => console.error(l) };",
|
|
824
|
+
"",
|
|
825
|
+
'if (cmd === "schema") {',
|
|
826
|
+
" const features = composeFeatures([...APP_FEATURES], { includeBundled: HAS_AUTH });",
|
|
827
|
+
" process.exit(await runSchemaCli(rest, process.env.INIT_CWD ?? process.cwd(), out, { features }));",
|
|
828
|
+
"}",
|
|
829
|
+
"",
|
|
830
|
+
'if (cmd === "consumer") {',
|
|
831
|
+
" process.exit(await runConsumerCli(rest, out));",
|
|
765
832
|
"}",
|
|
766
833
|
"",
|
|
767
|
-
"const features = composeFeatures([...APP_FEATURES], { includeBundled: HAS_AUTH });",
|
|
768
834
|
"// biome-ignore lint/suspicious/noConsole: CLI output is the feature.",
|
|
769
|
-
"
|
|
770
|
-
"process.exit(
|
|
835
|
+
'console.error("\\n Unknown: kumiko " + (cmd ?? "") + " — only \'kumiko schema <sub>\' or \'kumiko consumer <sub>\' in the standalone bundle.\\n");',
|
|
836
|
+
"process.exit(1);",
|
|
771
837
|
"",
|
|
772
838
|
].join("\n");
|
|
773
839
|
}
|
|
@@ -786,15 +852,21 @@ async function instantiateScaffoldFeatures(
|
|
|
786
852
|
`scaffoldApp: ${entry.importPath} missing export ${entry.exportName} for ${entry.callExpression}`,
|
|
787
853
|
);
|
|
788
854
|
}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
855
|
+
const looksCallable = entry.callExpression.endsWith(")");
|
|
856
|
+
if (looksCallable !== (typeof exp === "function")) {
|
|
857
|
+
throw new Error(
|
|
858
|
+
`scaffoldApp: ${entry.importPath} export ${entry.exportName} is ${
|
|
859
|
+
typeof exp === "function" ? "" : "not "
|
|
860
|
+
}callable but callExpression "${entry.callExpression}" says otherwise`,
|
|
861
|
+
);
|
|
862
|
+
}
|
|
863
|
+
if (typeof exp === "function") {
|
|
864
|
+
instances.push((exp as (...args: unknown[]) => FeatureDefinition)(...(entry.callArgs ?? [])));
|
|
794
865
|
} else {
|
|
795
866
|
instances.push(exp as FeatureDefinition);
|
|
796
867
|
}
|
|
797
868
|
}
|
|
869
|
+
instances.push(createDemoTasksFeature());
|
|
798
870
|
return instances;
|
|
799
871
|
}
|
|
800
872
|
|