@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.
Files changed (66) hide show
  1. package/bin/kumiko-schema-check.ts +7 -0
  2. package/package.json +12 -7
  3. package/src/__tests__/build-prod-bundle.integration.test.ts +1 -1
  4. package/src/__tests__/build-server-bundle.test.ts +62 -0
  5. package/src/__tests__/compose-stacks.test.ts +271 -0
  6. package/src/__tests__/create-kumiko-server-errors.test.ts +54 -0
  7. package/src/__tests__/create-kumiko-server-options.test.ts +33 -0
  8. package/src/__tests__/create-kumiko-server.integration.test.ts +128 -1
  9. package/src/__tests__/discover-format.test.ts +1 -1
  10. package/src/__tests__/env-schema.integration.test.ts +1 -1
  11. package/src/__tests__/few-shot-corpus.test.ts +16 -11
  12. package/src/__tests__/merge-extra-context.test.ts +56 -0
  13. package/src/__tests__/resolve-stylesheet.test.ts +16 -0
  14. package/src/__tests__/saas-identity-wire.integration.test.ts +430 -0
  15. package/src/__tests__/scaffold-app-feature.test.ts +59 -6
  16. package/src/__tests__/scaffold-app.test.ts +34 -2
  17. package/src/__tests__/schema-apply.integration.test.ts +5 -2
  18. package/src/__tests__/setup-test-stack-from-features.integration.test.ts +30 -0
  19. package/src/__tests__/walkthrough.integration.test.ts +22 -6
  20. package/src/build-server-bundle.ts +33 -15
  21. package/src/build.ts +1 -2
  22. package/src/codegen/__tests__/render-codegen.test.ts +2 -1
  23. package/src/codegen/__tests__/run-codegen.test.ts +2 -2
  24. package/src/codegen/__tests__/strict-mode-diagnostics.test.ts +6 -1
  25. package/src/codegen/__tests__/watch.test.ts +1 -2
  26. package/src/codegen/render.ts +6 -1
  27. package/src/codegen/scan-events.ts +7 -5
  28. package/src/codegen/watch.ts +7 -4
  29. package/src/compose-stacks.ts +184 -0
  30. package/src/create-kumiko-server.ts +28 -5
  31. package/src/few-shot-corpus.ts +4 -3
  32. package/src/index.ts +30 -12
  33. package/src/run-dev-app.ts +195 -61
  34. package/src/scaffold-app-feature.ts +118 -15
  35. package/src/scaffold-app.ts +151 -79
  36. package/src/scaffold-demo-tasks.ts +233 -0
  37. package/src/schema-apply.ts +15 -2
  38. package/src/schema-check-core.ts +1 -1
  39. package/src/setup-test-stack-from-features.ts +61 -0
  40. package/src/welcome-banner.ts +1 -4
  41. package/src/__tests__/boot-extra-context.test.ts +0 -140
  42. package/src/__tests__/build-prod-bundle.test.ts +0 -311
  43. package/src/__tests__/cache-headers.test.ts +0 -83
  44. package/src/__tests__/compose-features-wiring.integration.test.ts +0 -382
  45. package/src/__tests__/compose-features.test.ts +0 -129
  46. package/src/__tests__/config-seed-boot.integration.test.ts +0 -158
  47. package/src/__tests__/inject-schema.test.ts +0 -62
  48. package/src/__tests__/renderer-web-css-relocation.integration.test.ts +0 -85
  49. package/src/__tests__/renderer-web-shell-sentinel.test.ts +0 -35
  50. package/src/__tests__/require-env.test.ts +0 -29
  51. package/src/__tests__/resolve-auth-mail.test.ts +0 -69
  52. package/src/__tests__/resolve-tailwind-cli.test.ts +0 -81
  53. package/src/__tests__/run-prod-app-env-source.test.ts +0 -157
  54. package/src/__tests__/run-prod-app-spec.test.ts +0 -57
  55. package/src/__tests__/run-prod-app.integration.test.ts +0 -840
  56. package/src/__tests__/session-wiring.test.ts +0 -51
  57. package/src/__tests__/try-hono-first.test.ts +0 -63
  58. package/src/boot/apply-boot-seeds.ts +0 -18
  59. package/src/build-prod-bundle.ts +0 -697
  60. package/src/compose-features.ts +0 -145
  61. package/src/extra-routes-deps.ts +0 -47
  62. package/src/inject-schema.ts +0 -24
  63. package/src/resolve-tailwind-cli.ts +0 -45
  64. package/src/run-prod-app.ts +0 -1492
  65. package/src/session-wiring.ts +0 -29
  66. package/src/try-hono-first.ts +0 -46
@@ -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 callList = effective.map((f) => f.callExpression).join(", ");
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: `[${callList}] as const`,
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 für die Feature-Komposition deiner App.",
371
- "// config/user/tenant/auth-email-password werden via",
372
- "// composeFeatures(includeBundled:true) automatisch ergänzt wenn",
373
- "// runProdApp mit `auth: {…}` aufgerufen wird (siehe bin/main.ts).",
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
- "// Neue features hinzufügen:",
376
- "// - bunx @cosmicdrift/kumiko-cli add feature <name> (DX-2, automatisch)",
377
- "// - oder: hand-edit + import unten ergänzen",
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: ["composeFeatures", "frameworkCoreEnvSchema", "runProdApp"],
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
- "// (siehe @cosmicdrift/kumiko-dev-server runProdApp). Echter Dev-Boot",
479
- "// passiert via `bunx kumiko dev` (in-repo dev-tool) mit Docker-stack — DX-1.0 deckt nur",
480
- "// den boot-mode-Pfad ab; `kumiko dev` kommt in einer späteren DX-Phase.",
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-bootstrap. `bun --watch bin/dev.ts` (siehe package.json scripts.dev)",
551
- "// startet einen full-featured Dev-Server mit Auto-Reload bei Code-Änderungen.",
552
- "// setupTestStack legt fehlende Entity-Tabellen automatisch anneues",
553
- "// r.entity(...) in einem Feature führt beim nächsten Reboot zu CREATE TABLE,",
554
- "// kein manuelles `kumiko schema apply` nötig (das gilt nur für Prod).",
555
- "// Persistent-DB via KUMIKO_DEV_DB_NAME (.env) damit Admin + Daten Reboots überleben.",
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 automaticallya 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-Entry. runDevApp's clientEntry-Option bundlet diese Datei zu",
567
- "// /client.js und das Default-HTML lädt sie. createKumikoApp liest das",
568
- "// Schema aus dem window-globalen (das injectSchema im dev-server setzt)",
569
- "// und mountet die Routen.",
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
- "// DefaultAppShell liefert die Sidebar + Topbarohne `shell` rendert",
572
- "// createKumikoApp das aktive Screen ohne Layout-Wrapper (= nach Login",
573
- "// nur ein nackter Banner statt der App). emailPasswordClient() bringt",
574
- "// Login-Screen + Session-Provider ohne ihn bliebe /login leer.",
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
- "// Neue Client-Plugins (z.B. notificationsClient()) hier in clientFeatures",
577
- "// hinzusymmetrisch zu APP_FEATURES auf der Server-Seite.",
618
+ "// Add new client plugins (e.g. notificationsClient()) to clientFeatures",
619
+ "// heresymmetric to APP_FEATURES on the server side.",
578
620
  "",
579
621
  'import { emailPasswordClient } from "@cosmicdrift/kumiko-bundled-features/auth-email-password/web";',
580
- 'import { createKumikoApp, DefaultAppShell } from "@cosmicdrift/kumiko-renderer-web";',
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: DefaultAppShell,",
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) braucht TEST_DATABASE_URL.
593
- # Production (bun bin/main.ts → runProdApp) braucht DATABASE_URL.
594
- # Beide zeigen im Default auf denselben lokalen Postgres — runDevApp legt
595
- # darunter eine eigene "<KUMIKO_DEV_DB_NAME>"-Datenbank an.
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: persistente DB für \`bun dev\`. Ohne diesen Var startet jeder Reboot
608
- # eine frische kumiko_test_<random>-DB → Admin-Login + Daten weg bei jedem Edit.
609
- # Mit Var bleibt die DB zwischen Reboots erhalten (Schema-Pushes sind idempotent).
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
- // Local Postgres + Redis for `bun dev`. Ports + credentials match the *_URL
615
- // defaults in renderEnvExample, so `docker compose up -d` (referenced by the
616
- // README) just works with the generated .env. Named pg volume so dev data
617
- // survives `docker compose down` (pairs with KUMIKO_DEV_DB_NAME persistence).
618
- // Ports bind to 127.0.0.1 only: the dev DB (postgres/postgres) and auth-less
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
- image: postgres:18
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("\n")
650
- : "- `secrets` (foundation)\n- `sessions` (foundation)";
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 \`bun create kumiko-app\`. Boots out-of-the-box with the picked
654
- feature stack mounted. Add features by editing \`src/run-config.ts\` or via
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 or remove.
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
- # edit .env — set JWT_SECRET + KUMIKO_SECRETS_MASTER_KEY_V1, point DATABASE_URL/REDIS_URL at a real PG+Redis
669
- docker compose up -d # if you don't have PG+Redis running already
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 dev-server prints a welcome banner with the URL + admin login when ready.
674
- Edits to \`src/features/**\` trigger a process restart (\`bun --watch\`); new
675
- \`r.entity(...)\` calls auto-create tables on reboot no manual migration.
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-dev-server/compose-features";',
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 schema-CLI for the production bundle. The deploy",
753
- "// migrate-step runs `bun /app/kumiko.js schema apply`; kumiko-build bundles",
754
- "// this file to dist-server/kumiko.js.",
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-dev-server/compose-features";',
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
- 'if (cmd !== "schema") {',
762
- " // biome-ignore lint/suspicious/noConsole: CLI output is the feature.",
763
- ' console.error("\\n Unbekannt: kumiko " + (cmd ?? "") + " — nur \'kumiko schema <sub>\' im Standalone-Bundle.\\n");',
764
- " process.exit(1);",
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
- "const out = { log: (l: string) => console.log(l), err: (l: string) => console.error(l) };",
770
- "process.exit(await runSchemaCli(rest, process.env.INIT_CWD ?? process.cwd(), out, { features }));",
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
- if (entry.callExpression.endsWith("()")) {
790
- if (typeof exp !== "function") {
791
- throw new Error(`scaffoldApp: ${entry.exportName} is not callable (${entry.importPath})`);
792
- }
793
- instances.push((exp as () => FeatureDefinition)());
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