@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
@@ -1,145 +0,0 @@
1
- // composeFeatures — single source of truth für die Feature-Liste die
2
- // Boot UND Schema-Generator sehen.
3
- //
4
- // Sowohl runDevApp als auch runProdApp mischen im auth-mode dieselben
5
- // vier Bundled-Features dazu (config + user + tenant + auth-email-pw).
6
- // Damit der drizzle-Schema-Generator pro App genau dieselbe Feature-
7
- // Liste sieht wie die Runtime, leben die Komposition hier — beide
8
- // Bootstrap-Wrapper UND der per-app drizzle/generate.ts rufen sie auf.
9
- //
10
- // Reihenfolge: Infrastruktur-Features (config/user/tenant) zuerst, dann
11
- // auth-email-password, dann die App-Features. Spätere Features dürfen
12
- // auf Frühere referenzieren (z.B. authClaims-Hooks an user/tenant).
13
-
14
- import {
15
- type AuthEmailPasswordOptions,
16
- type AuthMailLocale,
17
- createAuthEmailPasswordFeature,
18
- type EmailVerificationOptions,
19
- type InviteOptions,
20
- type PasswordResetOptions,
21
- type SignupOptions,
22
- } from "@cosmicdrift/kumiko-bundled-features/auth-email-password";
23
- import { createConfigFeature } from "@cosmicdrift/kumiko-bundled-features/config";
24
- import { createTenantFeature } from "@cosmicdrift/kumiko-bundled-features/tenant";
25
- import { createUserFeature } from "@cosmicdrift/kumiko-bundled-features/user";
26
- import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
27
-
28
- export type ComposeFeaturesOptions = {
29
- /** When true, prepends config + user + tenant + auth-email-password
30
- * before the app features. Mirror of "auth-mode" in run{Dev,Prod}App. */
31
- readonly includeBundled: boolean;
32
- /** Optional auth-feature-options durchgereicht an
33
- * createAuthEmailPasswordFeature. Wenn passwordReset / emailVerification
34
- * hier gesetzt sind, registriert das Feature die request-/confirm-
35
- * Handler — sonst NICHT (500 wenn die routes via auth-routes.ts
36
- * gemounted sind aber kein Handler dispatcht). Hand-in-hand mit dem
37
- * passwordReset-Block in RunProdAppAuthOptions / RunDevAppAuthOptions. */
38
- readonly authOptions?: AuthEmailPasswordOptions;
39
- };
40
-
41
- export function composeFeatures(
42
- appFeatures: readonly FeatureDefinition[],
43
- options: ComposeFeaturesOptions,
44
- ): FeatureDefinition[] {
45
- if (!options.includeBundled) return [...appFeatures];
46
-
47
- // Bundled foundation goes first so its instances carry the runDevApp /
48
- // runProdApp `authOptions` (passwordReset wiring etc.). App-features that
49
- // ALSO declare one of these names — e.g. the create-kumiko-app picker
50
- // hands back `createAuthEmailPasswordFeature()` because the user ticked
51
- // it — would otherwise crash createRegistry with "Duplicate feature".
52
- // Drop the app-side duplicates and warn so the user can clean run-config.
53
- const bundled = [
54
- createConfigFeature(),
55
- createUserFeature(),
56
- createTenantFeature(),
57
- createAuthEmailPasswordFeature(options.authOptions ?? {}),
58
- ];
59
- const bundledNames = new Set(bundled.map((f) => f.name));
60
- const filteredApp: FeatureDefinition[] = [];
61
- for (const f of appFeatures) {
62
- if (bundledNames.has(f.name)) {
63
- // biome-ignore lint/suspicious/noConsole: boot-time UX warning
64
- console.warn(
65
- `[composeFeatures] "${f.name}" already auto-mounted via includeBundled — dropping the explicit copy from APP_FEATURES. Remove it from run-config.ts to silence this warning.`,
66
- );
67
- continue;
68
- }
69
- filteredApp.push(f);
70
- }
71
- return [...bundled, ...filteredApp];
72
- }
73
-
74
- /** Shape eines beliebigen run{Prod,Dev}App-Auth-Blocks der eine
75
- * PasswordReset/EmailVerification-Konfiguration tragen kann. Die
76
- * Wrapper-API (PasswordResetSetup) extends die Feature-API
77
- * (PasswordResetOptions), darum reicht ein structural-typed
78
- * Lookup auf den auth-only-Subset. Erlaubt buildComposeAuthOptions
79
- * mit RunProd- UND RunDev-AuthOptions zu callen ohne den Helper
80
- * doppelt zu bauen. */
81
- export type AuthOptionsCarrier = {
82
- readonly passwordReset?: PasswordResetOptions;
83
- readonly emailVerification?: EmailVerificationOptions;
84
- readonly signup?: SignupOptions;
85
- readonly invite?: InviteOptions;
86
- };
87
-
88
- /** Baut den authOptions-Block für composeFeatures aus einem
89
- * Wrapper-Auth-Block. Reicht NUR die feature-side-Felder
90
- * (hmacSecret, tokenTtlMinutes, mode) durch — die mail-side
91
- * (sendResetEmail/appResetUrl) gehört in die auth-routes-config
92
- * und wird vom Wrapper separat verdrahtet.
93
- *
94
- * Returnt undefined wenn weder passwordReset noch emailVerification
95
- * gesetzt sind (composeFeatures default-deny: KEINE handler in der
96
- * Registry registriert, /api/auth/request-password-reset etc. bleiben
97
- * 401/404). */
98
- type MailFlowFields = {
99
- readonly appUrl: string;
100
- readonly tokenTtlMinutes?: number;
101
- readonly appName?: string;
102
- readonly locale?: AuthMailLocale;
103
- };
104
-
105
- // The magic-link mail fields shared by all four flows. Conditional spreads omit
106
- // undefined keys (exactOptionalPropertyTypes) and avoid the property-write that
107
- // trips noPropertyAccessFromIndexSignature on the type-aliased option shapes.
108
- // reset/verify layer hmacSecret (+ mode) on top.
109
- function pickMailFields(src: MailFlowFields): MailFlowFields {
110
- return {
111
- appUrl: src.appUrl,
112
- ...(src.tokenTtlMinutes !== undefined && { tokenTtlMinutes: src.tokenTtlMinutes }),
113
- ...(src.appName !== undefined && { appName: src.appName }),
114
- ...(src.locale !== undefined && { locale: src.locale }),
115
- };
116
- }
117
-
118
- export function buildComposeAuthOptions(
119
- auth: AuthOptionsCarrier | undefined,
120
- ): AuthEmailPasswordOptions | undefined {
121
- if (!auth) return undefined;
122
- const opts: { -readonly [K in keyof AuthEmailPasswordOptions]: AuthEmailPasswordOptions[K] } = {};
123
- if (auth.passwordReset) {
124
- opts.passwordReset = {
125
- hmacSecret: auth.passwordReset.hmacSecret,
126
- ...pickMailFields(auth.passwordReset),
127
- };
128
- }
129
- if (auth.emailVerification) {
130
- opts.emailVerification = {
131
- hmacSecret: auth.emailVerification.hmacSecret,
132
- ...pickMailFields(auth.emailVerification),
133
- ...(auth.emailVerification.mode !== undefined && { mode: auth.emailVerification.mode }),
134
- };
135
- }
136
- if (auth.signup) {
137
- opts.signup = pickMailFields(auth.signup);
138
- }
139
- if (auth.invite) {
140
- opts.invite = pickMailFields(auth.invite);
141
- }
142
- return opts.passwordReset || opts.emailVerification || opts.signup || opts.invite
143
- ? opts
144
- : undefined;
145
- }
@@ -1,47 +0,0 @@
1
- import { ROLES } from "@cosmicdrift/kumiko-framework/auth";
2
- import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
3
- import {
4
- createSystemUser,
5
- type Registry,
6
- type SessionUser,
7
- type TenantId,
8
- type WriteResult,
9
- } from "@cosmicdrift/kumiko-framework/engine";
10
- import type Redis from "ioredis";
11
-
12
- /** Deps für `extraRoutes` — geteilt zwischen runProdApp (prod) und
13
- * createKumikoServer (dev), damit die beiden Pfade nicht driften.
14
- * Naming: `deps` statt `ctx` weil im Framework `ctx` der HandlerContext
15
- * mit user/tenant/registry ist — hier ist der Scope absichtlich kleiner
16
- * (Routes laufen außerhalb der Auth/Tenant-Pipeline). */
17
- export type ExtraRoutesSystemDeps = {
18
- readonly db: DbConnection;
19
- readonly redis: Redis;
20
- /** Feature-registry — z.B. für Plugin-Lookups via
21
- * `registry.getExtensionUsages("subscriptionProvider")`. */
22
- readonly registry: Registry;
23
- /** Schreibt durch den /api/*-Command-Dispatcher (gleiche Idempotency/
24
- * Job-Hooks) — aber als auto-konstruierter SystemAdmin des Ziel-
25
- * Tenants, OHNE Access-Check der Route. Privilege-Scope: SystemAdmin
26
- * ist die höchste nicht-tenant-scoped Rolle — der Call erreicht JEDEN
27
- * SystemAdmin-gegateten Handler auf jedem Tenant; das Rollen-Set ist
28
- * nicht konfigurierbar. Nur für Pfade, die ihre Authentizität selbst
29
- * beweisen (Provider-Webhook-Signaturen,
30
- * createSubscriptionWebhookHandler et al.). */
31
- readonly dispatchSystemWrite: (args: {
32
- readonly handlerQn: string;
33
- readonly payload: unknown;
34
- readonly tenantId: TenantId;
35
- }) => Promise<WriteResult>;
36
- };
37
-
38
- type SystemWriteDispatcher = {
39
- readonly write: (handlerQn: string, payload: unknown, user: SessionUser) => Promise<WriteResult>;
40
- };
41
-
42
- export function makeDispatchSystemWrite(
43
- dispatcher: SystemWriteDispatcher,
44
- ): ExtraRoutesSystemDeps["dispatchSystemWrite"] {
45
- return ({ handlerQn, payload, tenantId }) =>
46
- dispatcher.write(handlerQn, payload, createSystemUser(tenantId, [ROLES.SystemAdmin]));
47
- }
@@ -1,24 +0,0 @@
1
- // Injiziert das Server-aufgelöste AppSchema in das HTML-Template damit
2
- // createKumikoApp() es synchron unter `window.__KUMIKO_SCHEMA__` vorfindet.
3
- // JSON ist valides JS — direkt eingebettet, der Browser parsed das
4
- // Object-Literal nativ.
5
- //
6
- // Geteilt zwischen dev-server (Schema kommt frisch aus dem laufenden
7
- // Prozess) und prod-server (Schema wird beim Boot einmal berechnet und
8
- // in die statisch ausgelieferte index.html injiziert). Beide Pfade
9
- // nutzen dieselbe Tag-Form damit `createKumikoApp` den Lookup nicht je
10
- // nach Kontext anders machen muss.
11
- //
12
- // Idempotenz: wenn das HTML schon einen __KUMIKO_SCHEMA__-Marker hat,
13
- // wird nicht doppelt injected — verhindert dass repeated index.html-
14
- // Reads (prod) oder bereits-vorbereitete templates (custom CI-builds)
15
- // stacking-Tags produzieren.
16
-
17
- export function injectSchema(html: string, schemaJson: string): string {
18
- if (html.includes("__KUMIKO_SCHEMA__")) return html;
19
- const tag = `<script>window.__KUMIKO_SCHEMA__=${schemaJson};</script>`;
20
- if (html.includes('<script src="/client.js"')) {
21
- return html.replace('<script src="/client.js"', `${tag}<script src="/client.js"`);
22
- }
23
- return html.includes("</body>") ? html.replace("</body>", `${tag}</body>`) : html + tag;
24
- }
@@ -1,45 +0,0 @@
1
- // Resolved den lokal installierten @tailwindcss/cli-Bin auf seinen
2
- // absoluten Dateipfad. Wir vermeiden `bunx`, weil das auch bei lokal
3
- // installiertem Package noch das Registry-Manifest fragt und ohne Netz
4
- // mit `FailedToOpenSocket` stirbt.
5
- //
6
- // Ausgelagert aus create-kumiko-server.ts, damit unter vitest/Node
7
- // testbar (Bun-Branch + Resolve-Fail-Branch) ohne den Server zu
8
- // booten.
9
-
10
- import { dirname, resolve } from "node:path";
11
-
12
- type BunResolver = { resolveSync: (id: string, from: string) => string };
13
-
14
- export type ResolveTailwindCliDeps = {
15
- readonly bun?: BunResolver;
16
- readonly cwd: string;
17
- };
18
-
19
- export function resolveTailwindCli(deps: ResolveTailwindCliDeps): string | undefined {
20
- if (deps.bun === undefined) return undefined;
21
- try {
22
- const pkgJsonPath = deps.bun.resolveSync("@tailwindcss/cli/package.json", deps.cwd);
23
- // bin: { tailwindcss: "./dist/index.mjs" } → absoluter Pfad
24
- return resolve(pkgJsonPath, "..", "dist", "index.mjs");
25
- } catch {
26
- return undefined;
27
- }
28
- }
29
-
30
- /** Tailwind v4 resolves `@import "tailwindcss"` relative to the entry
31
- * CSS file. Skip renderer-web fallback (or fail build) when peer deps
32
- * aren't resolvable from that directory — e.g. Bun cache copies of
33
- * `@cosmicdrift/kumiko-renderer-web` outside a hoisted node_modules tree. */
34
- export function canResolveTailwindStylesheet(
35
- entryCss: string,
36
- deps: ResolveTailwindCliDeps,
37
- ): boolean {
38
- if (deps.bun === undefined) return false;
39
- try {
40
- deps.bun.resolveSync("tailwindcss", dirname(entryCss));
41
- return true;
42
- } catch {
43
- return false;
44
- }
45
- }