@cosmicdrift/kumiko-dev-server 0.329.0 → 0.330.2

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 (125) hide show
  1. package/README.md +17 -0
  2. package/bin/kumiko-build.ts +3 -2
  3. package/bin/kumiko-dev.ts +1 -1
  4. package/bin/kumiko-init-deploy.ts +1 -1
  5. package/bin/kumiko-schema-check.ts +4 -1
  6. package/dist/build-server-bundle.d.ts +31 -0
  7. package/dist/build-server-bundle.js +282 -0
  8. package/dist/build.d.ts +2 -0
  9. package/dist/build.js +5 -0
  10. package/dist/cli.d.ts +5 -0
  11. package/dist/cli.js +9 -0
  12. package/dist/codegen/index.d.ts +4 -0
  13. package/dist/codegen/index.js +4 -0
  14. package/dist/codegen/render.d.ts +55 -0
  15. package/dist/codegen/render.js +273 -0
  16. package/dist/codegen/run-codegen.d.ts +20 -0
  17. package/dist/codegen/run-codegen.js +219 -0
  18. package/dist/codegen/scan-events.d.ts +76 -0
  19. package/dist/codegen/scan-events.js +447 -0
  20. package/dist/codegen/watch.d.ts +33 -0
  21. package/dist/codegen/watch.js +112 -0
  22. package/dist/compose-stacks.d.ts +60 -0
  23. package/dist/compose-stacks.js +127 -0
  24. package/dist/crash-tracker.d.ts +17 -0
  25. package/dist/crash-tracker.js +37 -0
  26. package/dist/create-kumiko-server.d.ts +195 -0
  27. package/dist/create-kumiko-server.js +1040 -0
  28. package/dist/env-schema.d.ts +15 -0
  29. package/dist/env-schema.js +60 -0
  30. package/dist/few-shot-corpus.d.ts +72 -0
  31. package/dist/few-shot-corpus.js +262 -0
  32. package/dist/framework-version.d.ts +1 -0
  33. package/dist/framework-version.js +31 -0
  34. package/dist/index.d.ts +22 -0
  35. package/dist/index.js +33 -0
  36. package/dist/init-deploy-cli.d.ts +10 -0
  37. package/dist/init-deploy-cli.js +111 -0
  38. package/dist/kebab.d.ts +1 -0
  39. package/{src/kebab.ts → dist/kebab.js} +2 -3
  40. package/dist/run-dev-app.d.ts +174 -0
  41. package/dist/run-dev-app.js +403 -0
  42. package/dist/scaffold-app-feature.d.ts +16 -0
  43. package/dist/scaffold-app-feature.js +240 -0
  44. package/dist/scaffold-app.d.ts +45 -0
  45. package/dist/scaffold-app.js +852 -0
  46. package/dist/scaffold-demo-tasks.d.ts +8 -0
  47. package/{src/scaffold-demo-tasks.ts → dist/scaffold-demo-tasks.js} +69 -98
  48. package/dist/scaffold-deploy.d.ts +92 -0
  49. package/dist/scaffold-deploy.js +254 -0
  50. package/dist/scaffold-feature.d.ts +26 -0
  51. package/dist/scaffold-feature.js +163 -0
  52. package/dist/schema-apply.d.ts +11 -0
  53. package/dist/schema-apply.js +95 -0
  54. package/dist/schema-check-core.d.ts +2 -0
  55. package/{src/schema-check-core.ts → dist/schema-check-core.js} +8 -10
  56. package/dist/schema-tables-auth-mode.d.ts +2 -0
  57. package/{src/schema-tables-auth-mode.ts → dist/schema-tables-auth-mode.js} +2 -10
  58. package/dist/schema-tables-minimal.d.ts +2 -0
  59. package/{src/schema-tables-minimal.ts → dist/schema-tables-minimal.js} +2 -11
  60. package/dist/setup-test-stack-from-features.d.ts +11 -0
  61. package/dist/setup-test-stack-from-features.js +42 -0
  62. package/dist/welcome-banner.d.ts +15 -0
  63. package/dist/welcome-banner.js +27 -0
  64. package/package.json +71 -24
  65. package/src/changes.json +6 -0
  66. package/src/__tests__/build-prod-bundle.integration.test.ts +0 -282
  67. package/src/__tests__/build-server-bundle.test.ts +0 -228
  68. package/src/__tests__/classify-change.test.ts +0 -87
  69. package/src/__tests__/compose-stacks.test.ts +0 -273
  70. package/src/__tests__/config-resolver-default.integration.test.ts +0 -165
  71. package/src/__tests__/crash-tracker.test.ts +0 -89
  72. package/src/__tests__/create-kumiko-server-errors.test.ts +0 -54
  73. package/src/__tests__/create-kumiko-server-options.test.ts +0 -33
  74. package/src/__tests__/create-kumiko-server.integration.test.ts +0 -1007
  75. package/src/__tests__/discover-format.test.ts +0 -32
  76. package/src/__tests__/env-schema.integration.test.ts +0 -248
  77. package/src/__tests__/env-schema.test.ts +0 -132
  78. package/src/__tests__/few-shot-corpus.test.ts +0 -316
  79. package/src/__tests__/framework-version.test.ts +0 -45
  80. package/src/__tests__/init-deploy-cli.test.ts +0 -113
  81. package/src/__tests__/kumiko-schema-check.test.ts +0 -56
  82. package/src/__tests__/merge-extra-context.test.ts +0 -67
  83. package/src/__tests__/resolve-stylesheet.test.ts +0 -132
  84. package/src/__tests__/run-dev-app-boot-validation.test.ts +0 -38
  85. package/src/__tests__/run-dev-app.integration.test.ts +0 -290
  86. package/src/__tests__/runtime-classification.test.ts +0 -26
  87. package/src/__tests__/saas-identity-wire.integration.test.ts +0 -427
  88. package/src/__tests__/scaffold-app-feature.test.ts +0 -216
  89. package/src/__tests__/scaffold-app.test.ts +0 -630
  90. package/src/__tests__/scaffold-demo-tasks.test.ts +0 -47
  91. package/src/__tests__/scaffold-deploy.test.ts +0 -464
  92. package/src/__tests__/scaffold-dev-cycle.integration.test.ts +0 -109
  93. package/src/__tests__/scaffold-feature.test.ts +0 -143
  94. package/src/__tests__/schema-apply.integration.test.ts +0 -191
  95. package/src/__tests__/setup-test-stack-from-features.integration.test.ts +0 -30
  96. package/src/__tests__/setup-test-stack-job-run-logger.integration.test.ts +0 -72
  97. package/src/__tests__/walkthrough.integration.test.ts +0 -140
  98. package/src/__tests__/welcome-banner.test.ts +0 -42
  99. package/src/build-server-bundle.ts +0 -346
  100. package/src/build.ts +0 -28
  101. package/src/codegen/__tests__/render-codegen.test.ts +0 -100
  102. package/src/codegen/__tests__/run-codegen.test.ts +0 -665
  103. package/src/codegen/__tests__/strict-mode-diagnostics.test.ts +0 -347
  104. package/src/codegen/__tests__/watch.test.ts +0 -222
  105. package/src/codegen/index.ts +0 -19
  106. package/src/codegen/render.ts +0 -305
  107. package/src/codegen/run-codegen.ts +0 -278
  108. package/src/codegen/scan-events.ts +0 -565
  109. package/src/codegen/watch.ts +0 -140
  110. package/src/compose-stacks.ts +0 -193
  111. package/src/crash-tracker.ts +0 -56
  112. package/src/create-kumiko-server.ts +0 -1397
  113. package/src/env-schema.ts +0 -72
  114. package/src/few-shot-corpus.ts +0 -370
  115. package/src/framework-version.ts +0 -39
  116. package/src/index.ts +0 -105
  117. package/src/init-deploy-cli.ts +0 -148
  118. package/src/run-dev-app.ts +0 -694
  119. package/src/scaffold-app-feature.ts +0 -283
  120. package/src/scaffold-app.ts +0 -1033
  121. package/src/scaffold-deploy.ts +0 -393
  122. package/src/scaffold-feature.ts +0 -226
  123. package/src/schema-apply.ts +0 -123
  124. package/src/setup-test-stack-from-features.ts +0 -80
  125. package/src/welcome-banner.ts +0 -43
@@ -0,0 +1,15 @@
1
+ import * as z from "zod";
2
+ /** Env-vars read by framework-core (api/server, db/connection,
3
+ * dev-server/run-prod-app). NOT including feature-specific vars.
4
+ *
5
+ * PORT-default "3000" is the same fallback as
6
+ * `packages/dev-server/src/run-prod-app.ts:533` — keep in sync when the
7
+ * call-site is refactored to consume parsed env (Sprint 9.5 Phase 4). */
8
+ export declare const frameworkCoreEnvSchema: z.ZodObject<{
9
+ PORT: z.ZodDefault<z.ZodString>;
10
+ DATABASE_URL: z.ZodURL;
11
+ REDIS_URL: z.ZodURL;
12
+ KUMIKO_INSTANCE_ID: z.ZodOptional<z.ZodString>;
13
+ KUMIKO_SKIP_ES_OPS: z.ZodOptional<z.ZodString>;
14
+ }, z.core.$strip>;
15
+ export type FrameworkCoreEnv = z.infer<typeof frameworkCoreEnvSchema>;
@@ -0,0 +1,60 @@
1
+ // @runtime runtime
2
+ // Consumed at prod boot via composeEnvSchema (same as compose-stacks),
3
+ // not only by tooling — keep runtime-isolation-safe (kumiko-framework#2340).
4
+ //
5
+ // Framework-core env-schema — the vars that runProdApp + buildServer
6
+ // + the connection-pool read directly from process.env today. Apps merge
7
+ // this into their app-wide schema via `composeEnvSchema({ core, ... })`.
8
+ //
9
+ // Sprint 9.2 is purely additive: the schema is exposed, but the call-sites
10
+ // in run-prod-app.ts (requireEnv("DATABASE_URL"), …) and api/server.ts
11
+ // (process.env["KUMIKO_INSTANCE_ID"]) keep reading process.env directly.
12
+ // Apps that opt into the schema get aggregated boot-validation errors
13
+ // BEFORE those legacy reads run; apps that don't, behave as before.
14
+ //
15
+ // Feature-specific vars (JWT_SECRET, KUMIKO_SECRETS_MASTER_KEY_*) live
16
+ // in their owning feature's envSchema — Phase 2 (bundled-features).
17
+ import * as z from "zod";
18
+ /** Env-vars read by framework-core (api/server, db/connection,
19
+ * dev-server/run-prod-app). NOT including feature-specific vars.
20
+ *
21
+ * PORT-default "3000" is the same fallback as
22
+ * `packages/dev-server/src/run-prod-app.ts:533` — keep in sync when the
23
+ * call-site is refactored to consume parsed env (Sprint 9.5 Phase 4). */
24
+ export const frameworkCoreEnvSchema = z.object({
25
+ PORT: z
26
+ .string()
27
+ .regex(/^\d+$/, "PORT must be a positive integer string")
28
+ .default("3000")
29
+ .describe("HTTP listen port. runProdApp defaults to 3000 when unset."),
30
+ DATABASE_URL: z
31
+ // `protocol` is matched against the URL scheme without the trailing
32
+ // colon — `postgres://`/`postgresql://` pass, `https://`/`ftp://` are
33
+ // rejected so the error message's promise actually holds at boot.
34
+ .url({ protocol: /^postgres(ql)?$/, error: "DATABASE_URL must be a valid postgres:// URL" })
35
+ .describe("Primary Postgres connection string (write + read).")
36
+ .meta({ kumiko: { pulumi: { secret: true } } }),
37
+ REDIS_URL: z
38
+ // `redis://` + the TLS variant `rediss://` pass; other schemes reject.
39
+ .url({ protocol: /^rediss?$/, error: "REDIS_URL must be a valid redis:// URL" })
40
+ .describe("Redis connection string for SSE-broker + job-queues.")
41
+ .meta({ kumiko: { pulumi: { secret: true } } }),
42
+ KUMIKO_INSTANCE_ID: z
43
+ .string()
44
+ .min(1)
45
+ .optional()
46
+ .describe("Stable per-process identifier (pod name, hostname). " +
47
+ "Multi-instance deploys SHOULD set this so per-instance consumers " +
48
+ "(SSE) don't accumulate orphaned cursor-rows on restart."),
49
+ // `z.string().optional()` (not `z.literal("1")`) — the run-prod-app
50
+ // call-site (`envSource["KUMIKO_SKIP_ES_OPS"] !== "1"`) ignores any
51
+ // value other than literal "1". A stricter schema would reject e.g.
52
+ // "true" / "yes" that the runtime silently ignores, surfacing
53
+ // boot-errors for inputs the framework doesn't actually care about.
54
+ KUMIKO_SKIP_ES_OPS: z
55
+ .string()
56
+ .optional()
57
+ .describe("Set to '1' to skip event-store ops (seed/migrate) at boot. " +
58
+ "Any other value is treated as 'not set' by run-prod-app. " +
59
+ "Used by integration-test stacks that manage ES-ops out-of-band."),
60
+ });
@@ -0,0 +1,72 @@
1
+ import { type FeaturePattern, type ParseError } from "@cosmicdrift/kumiko-framework/engine";
2
+ export type AuthoringStyle = "canonical" | "legacy";
3
+ export type CorpusWarning = {
4
+ /** Repo-relative path to the file that triggered the warning. */
5
+ readonly sourcePath: string;
6
+ /** Human-readable explanation. Currently only "parser-throw" but
7
+ * kept open-ended so future builders can add more (e.g.
8
+ * "duplicate-id", "no-feature-name"). */
9
+ readonly message: string;
10
+ };
11
+ export type FewShotEntry = {
12
+ /** Stable id derived from the path (`samples/recipes/basic-entity`). */
13
+ readonly id: string;
14
+ /** Repo-relative path to the feature-file. */
15
+ readonly sourcePath: string;
16
+ /** Repo-relative path to the workspace package.json (for description lookup). */
17
+ readonly packageJsonPath: string | undefined;
18
+ /** Workspace name (`@cosmicdrift/kumiko-sample-basic-entity`). */
19
+ readonly packageName: string | undefined;
20
+ /** English description from package.json. */
21
+ readonly description: string | undefined;
22
+ /** Feature name from `defineFeature("...", ...)`. Undefined if the
23
+ * parser couldn't read it (rare — implies a non-feature file got
24
+ * picked up). */
25
+ readonly featureName: string | undefined;
26
+ /** Pattern-categories present in this feature, deduplicated. Useful
27
+ * as topic-tags for retrieval ("show me cross-cutting examples"). */
28
+ readonly tags: readonly string[];
29
+ /** Counts per pattern-kind — quick stats without scanning the
30
+ * patterns array. */
31
+ readonly patternsByKind: Readonly<Record<string, number>>;
32
+ /** All parsed patterns. Same shape the parser emits. */
33
+ readonly patterns: readonly FeaturePattern[];
34
+ /** Errors the parser raised. Empty for canonical-form features. */
35
+ readonly parseErrors: readonly ParseError[];
36
+ /** `canonical` (clean parse) or `legacy` (parser couldn't read it
37
+ * fully — Factory-style / identifier-refs). */
38
+ readonly authoringStyle: AuthoringStyle;
39
+ /** Raw source text — kept verbatim so the LLM can train on the
40
+ * exact byte-form, including comments + whitespace. */
41
+ readonly rawSource: string;
42
+ };
43
+ export type FewShotCorpus = {
44
+ /** ISO-instant when the corpus was generated. */
45
+ readonly generatedAt: string;
46
+ /** Total entry count, broken down by authoringStyle for quick stats. */
47
+ readonly totals: {
48
+ readonly all: number;
49
+ readonly canonical: number;
50
+ readonly legacy: number;
51
+ };
52
+ readonly entries: readonly FewShotEntry[];
53
+ /** Files that were discovered but couldn't be turned into entries.
54
+ * Surfaces parser crashes + duplicate-id collisions instead of
55
+ * swallowing them — the regenerate-script reports these to the user
56
+ * and the drift-test asserts the count stays constant. */
57
+ readonly warnings: readonly CorpusWarning[];
58
+ };
59
+ export type BuildFewShotCorpusOptions = {
60
+ /** Repo root — used for relative-path output and security guards. */
61
+ readonly repoRoot: string;
62
+ /** Folders to scan recursively. Defaults to samples/* + bundled-features. */
63
+ readonly scanRoots?: readonly string[];
64
+ };
65
+ export declare function buildFewShotCorpus(options: BuildFewShotCorpusOptions): FewShotCorpus;
66
+ /**
67
+ * Stable id from the source path. Drops the leading `samples/` or
68
+ * `packages/` prefix and the trailing `/src/feature.ts` suffix so the
69
+ * id reads like the canonical short name (`basic-entity`,
70
+ * `bundled-features/auth-email-password`).
71
+ */
72
+ export declare function pathToId(sourcePath: string): string;
@@ -0,0 +1,262 @@
1
+ // buildFewShotCorpus — extracts feature-files from the repo into a
2
+ // structured JSON corpus that L2 (Prompt-Pipeline) feeds into the LLM
3
+ // at generation time. One entry per feature-file, four data sources
4
+ // per entry: raw source text, package.json description, parsed
5
+ // FeaturePattern[], plus pattern-categories from the C4 library.
6
+ //
7
+ // **Why a checked-in JSON file (docs/few-shot-corpus.json):**
8
+ // - Offline-consumable: AI-Builder can prompt without re-parsing
9
+ // 30+ feature files at request time.
10
+ // - Diff-reviewable: when a feature changes, the corpus diff shows
11
+ // up in the PR — humans see how the example surface shifted.
12
+ // - Portable: the corpus is a flat JSON, can ship to a hosted LLM
13
+ // service / fine-tuning pipeline / customer environment without
14
+ // dragging the framework's parser along.
15
+ //
16
+ // **Authoring-Style classification:**
17
+ // - `canonical` → 0 ParseErrors. The example is "do it like this"
18
+ // for the LLM.
19
+ // - `legacy` → has ParseErrors. The example shows what code looks
20
+ // like in the wild (Factory-style, identifier-refs) and is useful
21
+ // for understanding intent — but the LLM should NOT replicate
22
+ // this style. L2 marks legacy entries as "do not generate".
23
+ //
24
+ // **Why the corpus includes legacy entries but the Designer does not:**
25
+ // The two consumers want different slices of the same data. L2 needs
26
+ // counter-examples (showing the LLM what *not* to emit raises the
27
+ // chance of clean output), so legacy entries are kept and tagged.
28
+ // The Designer can only round-trip canonical-form patterns through
29
+ // the AST-Patcher — legacy entries would render as read-only with
30
+ // no edit affordance, which is worse than hiding them. So the corpus
31
+ // builder is permissive, and the Designer filters at read-time.
32
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
33
+ import { dirname, join, relative, resolve } from "node:path";
34
+ import { PATTERN_LIBRARY, parseFeatureFile, } from "@cosmicdrift/kumiko-framework/engine";
35
+ // =============================================================================
36
+ // Builder
37
+ // =============================================================================
38
+ const FEATURE_FILE_PATTERN = /(?:^|\/)(feature|.*\.feature)\.ts$/;
39
+ const DEFAULT_SCAN_ROOTS = [
40
+ "samples/recipes",
41
+ "samples/apps",
42
+ "samples/showcases",
43
+ "packages/bundled-features/src",
44
+ ];
45
+ // Static timestamp keeps the JSON output deterministic across CI runs —
46
+ // the regenerate-script could overwrite this with a real timestamp,
47
+ // but drift-tests compare structural data, not timestamps. Centralized
48
+ // here so the build path and any future inspector use the same value.
49
+ const STATIC_GENERATED_AT = "1970-01-01T00:00:00Z";
50
+ export function buildFewShotCorpus(options) {
51
+ const repoRoot = resolve(options.repoRoot);
52
+ const scanRoots = (options.scanRoots ?? DEFAULT_SCAN_ROOTS).map((r) => resolve(repoRoot, r));
53
+ const featureFiles = [];
54
+ for (const root of scanRoots) {
55
+ if (!existsSync(root))
56
+ continue;
57
+ walkDir(root, featureFiles);
58
+ }
59
+ featureFiles.sort();
60
+ const entries = [];
61
+ const warnings = [];
62
+ const seenIds = new Map();
63
+ for (const filePath of featureFiles) {
64
+ const result = buildEntry(filePath, repoRoot);
65
+ if (result.warning) {
66
+ warnings.push(result.warning);
67
+ continue;
68
+ }
69
+ const entry = result.entry;
70
+ const previousPath = seenIds.get(entry.id);
71
+ if (previousPath) {
72
+ // Two feature-files mapped to the same id. The corpus uses ids
73
+ // for retrieval — duplicates would silently overwrite each other
74
+ // in any consumer that built a Map<id, entry>. Surface as a
75
+ // warning, drop the second occurrence.
76
+ warnings.push({
77
+ sourcePath: entry.sourcePath,
78
+ message: `duplicate-id: collides with ${previousPath}`,
79
+ });
80
+ continue;
81
+ }
82
+ seenIds.set(entry.id, entry.sourcePath);
83
+ entries.push(entry);
84
+ }
85
+ const canonical = entries.filter((e) => e.authoringStyle === "canonical").length;
86
+ return {
87
+ generatedAt: STATIC_GENERATED_AT,
88
+ totals: {
89
+ all: entries.length,
90
+ canonical,
91
+ legacy: entries.length - canonical,
92
+ },
93
+ entries,
94
+ warnings,
95
+ };
96
+ }
97
+ function buildEntry(filePath, repoRoot) {
98
+ const sourcePath = relative(repoRoot, filePath);
99
+ let parsed;
100
+ try {
101
+ parsed = parseFeatureFile(filePath);
102
+ }
103
+ catch (err) {
104
+ // ts-morph couldn't read the file (syntax-error, IO problem, weird
105
+ // encoding). Skip the entry but record *why* — silent skip used to
106
+ // hide newly broken feature-files until L2 hit them.
107
+ const detail = err instanceof Error ? err.message : String(err);
108
+ return {
109
+ warning: { sourcePath, message: `parser-throw: ${detail}` },
110
+ };
111
+ }
112
+ const rawSource = readFileSync(filePath, "utf8");
113
+ const id = pathToId(sourcePath);
114
+ const pkgInfo = findPackageJson(filePath, repoRoot);
115
+ const tags = collectTags(parsed.patterns);
116
+ const patternsByKind = countPatternsByKind(parsed.patterns);
117
+ const authoringStyle = parsed.errors.length === 0 ? "canonical" : "legacy";
118
+ // SourceLocation.file is an absolute path coming out of the parser —
119
+ // strip it to repo-relative so the corpus diff stays stable across
120
+ // machines / CI runners. Same for ParseError.source.file.
121
+ const patterns = parsed.patterns.map((p) => relativizeSources(p, repoRoot));
122
+ const parseErrors = parsed.errors.map((e) => ({
123
+ ...e,
124
+ source: { ...e.source, file: relative(repoRoot, e.source.file) },
125
+ }));
126
+ return {
127
+ entry: {
128
+ id,
129
+ sourcePath,
130
+ packageJsonPath: pkgInfo?.relPath,
131
+ packageName: pkgInfo?.name,
132
+ description: pkgInfo?.description,
133
+ featureName: parsed.featureName,
134
+ tags,
135
+ patternsByKind,
136
+ patterns,
137
+ parseErrors,
138
+ authoringStyle,
139
+ rawSource,
140
+ },
141
+ };
142
+ }
143
+ /**
144
+ * Recursively walk a value and rewrite every nested SourceLocation's
145
+ * `file` field to be repo-relative. Identifies SourceLocation by
146
+ * structural shape (`{ file, start, end, raw }`) rather than by
147
+ * type-tag — the parsed objects are plain JSON-ish at this point and
148
+ * a discriminator would complicate the renderer.
149
+ *
150
+ * Typed `unknown → unknown` so the walker stays honest about what it
151
+ * sees. The single boundary cast lives at the call site
152
+ * (`as FeaturePattern`) where the input contract is known.
153
+ */
154
+ function relativizeSources(value, repoRoot) {
155
+ if (Array.isArray(value)) {
156
+ return value.map((v) => relativizeSources(v, repoRoot));
157
+ }
158
+ if (value && typeof value === "object") {
159
+ const obj = value;
160
+ if (typeof obj["file"] === "string" &&
161
+ typeof obj["raw"] === "string" &&
162
+ typeof obj["start"] === "object" &&
163
+ typeof obj["end"] === "object") {
164
+ return { ...obj, file: relative(repoRoot, obj["file"]) };
165
+ }
166
+ const out = {};
167
+ for (const [k, v] of Object.entries(obj)) {
168
+ out[k] = relativizeSources(v, repoRoot);
169
+ }
170
+ return out;
171
+ }
172
+ return value;
173
+ }
174
+ // =============================================================================
175
+ // Helpers
176
+ // =============================================================================
177
+ /**
178
+ * Stable id from the source path. Drops the leading `samples/` or
179
+ * `packages/` prefix and the trailing `/src/feature.ts` suffix so the
180
+ * id reads like the canonical short name (`basic-entity`,
181
+ * `bundled-features/auth-email-password`).
182
+ */
183
+ export function pathToId(sourcePath) {
184
+ return sourcePath
185
+ .replace(/^(samples|packages)\//, "")
186
+ .replace(/\/src\/feature\.ts$/, "")
187
+ .replace(/\/feature\.ts$/, "");
188
+ }
189
+ function walkDir(dir, acc) {
190
+ let entries;
191
+ try {
192
+ entries = readdirSync(dir, { withFileTypes: true });
193
+ }
194
+ catch {
195
+ // skip: directory missing/unreadable, nothing to walk
196
+ return;
197
+ }
198
+ for (const entry of entries) {
199
+ const name = String(entry.name);
200
+ const full = join(dir, name);
201
+ if (entry.isDirectory()) {
202
+ if (name === "node_modules" || name === "dist" || name === "dist-server")
203
+ continue;
204
+ if (name.startsWith("."))
205
+ continue;
206
+ walkDir(full, acc);
207
+ }
208
+ else if (entry.isFile() && FEATURE_FILE_PATTERN.test(full)) {
209
+ acc.push(full);
210
+ }
211
+ }
212
+ }
213
+ /**
214
+ * Walk upward from `featureFile` to find the nearest `package.json` and
215
+ * pluck name + description out of it. Stops at `repoRoot`.
216
+ */
217
+ function findPackageJson(featureFile, repoRoot) {
218
+ let dir = dirname(featureFile);
219
+ const stopAt = resolve(repoRoot);
220
+ while (true) {
221
+ const candidate = join(dir, "package.json");
222
+ if (existsSync(candidate)) {
223
+ try {
224
+ const pkg = JSON.parse(readFileSync(candidate, "utf8"));
225
+ return {
226
+ relPath: relative(repoRoot, candidate),
227
+ name: typeof pkg.name === "string" ? pkg.name : undefined,
228
+ description: typeof pkg.description === "string" ? pkg.description : undefined,
229
+ };
230
+ }
231
+ catch {
232
+ // Malformed package.json — keep walking up.
233
+ }
234
+ }
235
+ const parent = dirname(dir);
236
+ if (parent === dir || dir === stopAt)
237
+ return undefined;
238
+ dir = parent;
239
+ }
240
+ }
241
+ /**
242
+ * Collect the union of pattern-categories present in the feature.
243
+ * Categories come from the pattern-library — the same vocabulary the
244
+ * Designer / AI-Builder uses for filtering ("show me background-jobs
245
+ * examples").
246
+ */
247
+ function collectTags(patterns) {
248
+ const tags = new Set();
249
+ for (const p of patterns) {
250
+ const schema = PATTERN_LIBRARY[p.kind];
251
+ if (schema)
252
+ tags.add(schema.category);
253
+ }
254
+ return [...tags].sort();
255
+ }
256
+ function countPatternsByKind(patterns) {
257
+ const counts = {};
258
+ for (const p of patterns) {
259
+ counts[p.kind] = (counts[p.kind] ?? 0) + 1;
260
+ }
261
+ return counts;
262
+ }
@@ -0,0 +1 @@
1
+ export declare function resolveFrameworkVersion(startDir?: string): string | undefined;
@@ -0,0 +1,31 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { parseJsonSafe } from "@cosmicdrift/kumiko-framework/utils";
4
+ const DEV_SERVER_PACKAGE = "@cosmicdrift/kumiko-dev-server";
5
+ function isDevServerManifest(value) {
6
+ return (typeof value === "object" &&
7
+ value !== null &&
8
+ "name" in value &&
9
+ value.name === DEV_SERVER_PACKAGE &&
10
+ "version" in value &&
11
+ typeof value.version === "string" &&
12
+ value.version !== "");
13
+ }
14
+ // All @cosmicdrift packages release in lockstep (changesets fixed-group), so
15
+ // the dev-server's own version is the framework version.
16
+ export function resolveFrameworkVersion(startDir = import.meta.dir) {
17
+ let dir = startDir;
18
+ while (true) {
19
+ const candidate = join(dir, "package.json");
20
+ if (existsSync(candidate)) {
21
+ // A corrupt or foreign package.json on the way up must not abort the search.
22
+ const manifest = parseJsonSafe(readFileSync(candidate, "utf-8"), undefined);
23
+ if (isDevServerManifest(manifest))
24
+ return `^${manifest.version}`;
25
+ }
26
+ const parent = dirname(dir);
27
+ if (parent === dir)
28
+ return undefined;
29
+ dir = parent;
30
+ }
31
+ }
@@ -0,0 +1,22 @@
1
+ export { type ComposeFeaturesOptions, composeFeatures, } from "@cosmicdrift/kumiko-server-runtime/compose-features";
2
+ export { type CodegenOptions, type CodegenResult, runCodegen, type ScannedEvent, type ScanWarning, scanEvents, } from "./codegen/index.js";
3
+ export { composeFileStack, composeGdprStack, composeIdentityStack, composeMailStack, composeOpsStack, composePagesStack, composeRendererStack, composeUserDataRightsStack, type FileProviderKind, type FileStackOptions, type GdprStackOptions, type GdprStackOrder, type IdentityStackOptions, type MailStackOptions, type MailTransportKind, type OpsStackOptions, type PagesStackOptions, stackFeatureNames, type UserDataRightsStackOptions, } from "./compose-stacks.js";
4
+ export { type CreateKumikoServerOptions, createKumikoServer, type KumikoServerHandle, PROD_BUNDLES_ENV, resolveStylesheet, STYLESHEET_WATCH_ENV, } from "./create-kumiko-server.js";
5
+ export { type FrameworkCoreEnv, frameworkCoreEnvSchema } from "./env-schema.js";
6
+ export type { AuthoringStyle, BuildFewShotCorpusOptions, CorpusWarning, FewShotCorpus, FewShotEntry, } from "./few-shot-corpus.js";
7
+ export { buildFewShotCorpus, pathToId } from "./few-shot-corpus.js";
8
+ export { resolveFrameworkVersion } from "./framework-version.js";
9
+ export type { CliOutput, RunInitDeployCliOptions } from "./init-deploy-cli.js";
10
+ export { runInitDeployCli } from "./init-deploy-cli.js";
11
+ export type { RunDevAppAuthOptions, RunDevAppOptions, SeedFn } from "./run-dev-app.js";
12
+ export { runDevApp } from "./run-dev-app.js";
13
+ export type { ScaffoldAppOptions, ScaffoldAppResult, ScaffoldFeatureEntry, } from "./scaffold-app.js";
14
+ export { scaffoldApp } from "./scaffold-app.js";
15
+ export type { ScaffoldAppFeatureOptions, ScaffoldAppFeatureResult, } from "./scaffold-app-feature.js";
16
+ export { runConfigPathForApp, scaffoldAppFeature } from "./scaffold-app-feature.js";
17
+ export type { CheckDeployDriftResult, DeployDriftEntry, RenderDeployFilesOptions, RenderDeployFilesResult, RenderedDeployFile, ScaffoldDeployDetected, ScaffoldDeployOptions, ScaffoldDeployResult, ScaffoldedFile, } from "./scaffold-deploy.js";
18
+ export { checkDeployDrift, renderDeployFiles, scaffoldDeploy } from "./scaffold-deploy.js";
19
+ export type { ScaffoldFeatureOptions, ScaffoldFeatureResult } from "./scaffold-feature.js";
20
+ export { scaffoldFeature } from "./scaffold-feature.js";
21
+ export { type SetupTestStackFromFeaturesOptions, setupTestStackFromFeatures, type TestStackPreset, } from "./setup-test-stack-from-features.js";
22
+ export { renderWelcomeBanner, type WelcomeBannerInput } from "./welcome-banner.js";
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ // Public API für den Kumiko-Dev-Server. Zwei Schichten:
2
+ //
3
+ // - createKumikoServer (low-level)
4
+ // Bun.serve-Wrapper der Client-Bundle, /styles.css, AppSchema-
5
+ // Injection, SSE-Reload und einen Auto-Mint-JWT-Modus liefert. Nimmt
6
+ // features + clientEntry direkt an, kein Auth-Auto-Wiring. Wer
7
+ // einen ungewöhnlichen Auth-Setup braucht (alternative Membership-
8
+ // Query, eigener Rate-Limiter, custom Login-Routes) geht hier rein.
9
+ //
10
+ // - runDevApp (high-level)
11
+ // Mischt die Standard-Features (config/user/tenant/auth-email-
12
+ // password) automatisch dazu wenn `auth` gesetzt ist, wired die
13
+ // Login-Routes + Error-Map, ruft seedAdminGuarded im onAfterSetup. Default
14
+ // für Sample-Apps und Showcases — 5-10 Zeilen Bootstrap statt 50.
15
+ export { composeFeatures, } from "@cosmicdrift/kumiko-server-runtime/compose-features";
16
+ // Build-Toolchain (buildProdBundle, Bun.build, Tailwind-Pipeline, ts-morph) lebt
17
+ // im Sub-Path-Export `@cosmicdrift/kumiko-dev-server/build`. Damit zieht der Main-Barrel
18
+ // kein Bun-Toolchain-Bundle mehr in Production-Reads (z.B. wenn drizzle-kit
19
+ // die App-Config unter Node lädt).
20
+ export { runCodegen, scanEvents, } from "./codegen/index.js";
21
+ export { composeFileStack, composeGdprStack, composeIdentityStack, composeMailStack, composeOpsStack, composePagesStack, composeRendererStack, composeUserDataRightsStack, stackFeatureNames, } from "./compose-stacks.js";
22
+ export { createKumikoServer, PROD_BUNDLES_ENV, resolveStylesheet, STYLESHEET_WATCH_ENV, } from "./create-kumiko-server.js";
23
+ export { frameworkCoreEnvSchema } from "./env-schema.js";
24
+ export { buildFewShotCorpus, pathToId } from "./few-shot-corpus.js";
25
+ export { resolveFrameworkVersion } from "./framework-version.js";
26
+ export { runInitDeployCli } from "./init-deploy-cli.js";
27
+ export { runDevApp } from "./run-dev-app.js";
28
+ export { scaffoldApp } from "./scaffold-app.js";
29
+ export { runConfigPathForApp, scaffoldAppFeature } from "./scaffold-app-feature.js";
30
+ export { checkDeployDrift, renderDeployFiles, scaffoldDeploy } from "./scaffold-deploy.js";
31
+ export { scaffoldFeature } from "./scaffold-feature.js";
32
+ export { setupTestStackFromFeatures, } from "./setup-test-stack-from-features.js";
33
+ export { renderWelcomeBanner } from "./welcome-banner.js";
@@ -0,0 +1,10 @@
1
+ export type CliOutput = {
2
+ readonly log: (msg: string) => void;
3
+ readonly err: (msg: string) => void;
4
+ };
5
+ export type RunInitDeployCliOptions = {
6
+ readonly argv: readonly string[];
7
+ readonly cwd: string;
8
+ readonly out: CliOutput;
9
+ };
10
+ export declare function runInitDeployCli({ argv, cwd, out, }: RunInitDeployCliOptions): Promise<number>;
@@ -0,0 +1,111 @@
1
+ // `kumiko-init-deploy` CLI — the shared implementation behind both the
2
+ // standalone `kumiko-init-deploy` bin (published, app-facing) and the
3
+ // monorepo's `bin/commands/init-deploy.ts` (delegates here, no duplicated
4
+ // logic).
5
+ import { existsSync, readFileSync } from "node:fs";
6
+ import { join, relative } from "node:path";
7
+ import { getFlag, getNumberFlag, getStringFlag, parseArgs, } from "@cosmicdrift/kumiko-framework/arg-parser";
8
+ import * as z from "zod";
9
+ import { checkDeployDrift, scaffoldDeploy } from "./scaffold-deploy.js";
10
+ const USAGE = "Usage: kumiko-init-deploy --app <name> [--port <n>] [--github-org <org>] [--out <dir>] [--force | --check]";
11
+ const packageNameSchema = z.object({ name: z.string().optional() });
12
+ /** package.json `name` without its `@scope/` prefix, if present and valid. */
13
+ function defaultAppName(cwd) {
14
+ const pkgPath = join(cwd, "package.json");
15
+ if (!existsSync(pkgPath))
16
+ return undefined;
17
+ try {
18
+ const pkg = packageNameSchema.parse(JSON.parse(readFileSync(pkgPath, "utf-8")));
19
+ if (!pkg.name)
20
+ return undefined;
21
+ const slash = pkg.name.indexOf("/");
22
+ return pkg.name.startsWith("@") && slash !== -1 ? pkg.name.slice(slash + 1) : pkg.name;
23
+ }
24
+ catch {
25
+ return undefined;
26
+ }
27
+ }
28
+ function targetOptions({ appName, port, githubOrg, destination }) {
29
+ return {
30
+ appName,
31
+ ...(port !== undefined && { port }),
32
+ ...(githubOrg !== undefined && { githubOrg }),
33
+ destination,
34
+ };
35
+ }
36
+ function reportDrift(target, out) {
37
+ const result = checkDeployDrift(targetOptions(target));
38
+ if (result.drifted.length === 0) {
39
+ out.log(" ✓ deploy/ is in sync with the current templates.");
40
+ return 0;
41
+ }
42
+ out.err("");
43
+ out.err(` ✗ ${result.drifted.length} drifted file(s):`);
44
+ for (const d of result.drifted) {
45
+ out.err(` ${relative(target.destination, d.path)} — ${d.reason}`);
46
+ }
47
+ out.err("");
48
+ out.err(" run kumiko-init-deploy --force to regenerate.");
49
+ out.err("");
50
+ return 1;
51
+ }
52
+ function fileMarker(file) {
53
+ if (!file.written)
54
+ return "SKIPPED ";
55
+ return file.reason === "force" ? "OVERWRITTEN" : "WRITTEN ";
56
+ }
57
+ function reportScaffold(target, force, out) {
58
+ const result = scaffoldDeploy({ ...targetOptions(target), force });
59
+ out.log("");
60
+ out.log(` ✓ Deploy scaffolding generated — ${target.appName}`);
61
+ for (const f of result.files) {
62
+ out.log(` ${fileMarker(f)} ${relative(target.destination, f.path)}`);
63
+ }
64
+ if (result.files.some((f) => !f.written)) {
65
+ out.log("");
66
+ out.log(" Some files were skipped because they already exist. Re-run with --force to overwrite.");
67
+ }
68
+ out.log("");
69
+ out.log(" Next steps:");
70
+ out.log(" 1. Review deploy/Dockerfile — adjust if your app needs extra COPY/ENV steps.");
71
+ out.log(" 2. Wire your image-build workflow to build deploy/Dockerfile and push it.");
72
+ out.log(" 3. Configure the runtime env-vars for your platform.");
73
+ out.log("");
74
+ return 0;
75
+ }
76
+ export async function runInitDeployCli({ argv, cwd, out, }) {
77
+ const args = parseArgs(argv);
78
+ const force = getFlag(args, "force");
79
+ const check = getFlag(args, "check");
80
+ if (force && check) {
81
+ out.err("");
82
+ out.err(" --check and --force are mutually exclusive.");
83
+ out.err(` ${USAGE}`);
84
+ out.err("");
85
+ return 2;
86
+ }
87
+ const appName = getStringFlag(args, "app") ?? defaultAppName(cwd);
88
+ if (!appName) {
89
+ out.err("");
90
+ out.err(' --app <name> is required (no package.json "name" found to default from).');
91
+ out.err(` ${USAGE}`);
92
+ out.err("");
93
+ return 1;
94
+ }
95
+ const target = {
96
+ appName,
97
+ port: getNumberFlag(args, "port"),
98
+ githubOrg: getStringFlag(args, "github-org"),
99
+ destination: getStringFlag(args, "out") ?? cwd,
100
+ };
101
+ try {
102
+ return check ? reportDrift(target, out) : reportScaffold(target, force, out);
103
+ }
104
+ catch (err) {
105
+ const msg = err instanceof Error ? err.message : String(err);
106
+ out.err("");
107
+ out.err(` ${msg}`);
108
+ out.err("");
109
+ return 1;
110
+ }
111
+ }
@@ -0,0 +1 @@
1
+ export declare function isKebabSegment(value: string): boolean;
@@ -1,8 +1,7 @@
1
1
  // Segment-strict: rejects trailing/double hyphen so the name is a valid
2
2
  // package-name + folder (`my-shop`, not `my-` or `my--shop`).
3
3
  const KEBAB_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
4
-
5
4
  // @wrapper-known semantic-alias
6
- export function isKebabSegment(value: string): boolean {
7
- return KEBAB_RE.test(value);
5
+ export function isKebabSegment(value) {
6
+ return KEBAB_RE.test(value);
8
7
  }