@cosmicdrift/kumiko-dev-server 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-dev-server",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Development server bootstrap for Kumiko apps. Bundles the client, mints dev-JWTs, injects the resolved AppSchema, and seeds an admin. Not for production.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -0,0 +1,32 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { formatBuildResult } from "../build-prod-bundle";
6
+ import { discoverServerEntry } from "../build-server-bundle";
7
+
8
+ describe("discoverServerEntry", () => {
9
+ test("finds bin/main.ts when present", () => {
10
+ const dir = mkdtempSync(join(tmpdir(), "kumiko-discover-"));
11
+ mkdirSync(join(dir, "bin"));
12
+ writeFileSync(join(dir, "bin/main.ts"), "export {};\n", "utf8");
13
+ expect(discoverServerEntry(dir)).toBe(join(dir, "bin/main.ts"));
14
+ });
15
+
16
+ test("returns undefined when no entry exists", () => {
17
+ const dir = mkdtempSync(join(tmpdir(), "kumiko-discover-empty-"));
18
+ expect(discoverServerEntry(dir)).toBeUndefined();
19
+ });
20
+ });
21
+
22
+ describe("formatBuildResult", () => {
23
+ test("includes outDir and manifest entries", () => {
24
+ const out = formatBuildResult(
25
+ { outDir: "dist/client", manifest: { "app.js": "app.abc123.js" } },
26
+ 42,
27
+ );
28
+ expect(out).toContain("dist/client");
29
+ expect(out).toContain("app.js");
30
+ expect(out).toContain("42ms");
31
+ });
32
+ });
@@ -0,0 +1,50 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { renderDefineFile, renderInlineSchemasFile, renderTypesAugmentation } from "../render";
6
+ import type { ScannedEvent } from "../scan-events";
7
+
8
+ describe("renderTypesAugmentation", () => {
9
+ test("emits empty augmentation when no events", () => {
10
+ const out = renderTypesAugmentation([], "/tmp/app/.kumiko");
11
+ expect(out).toContain("interface KumikoEventTypeMap");
12
+ expect(out).toContain("no r.defineEvent calls discovered yet");
13
+ });
14
+ });
15
+
16
+ describe("renderInlineSchemasFile", () => {
17
+ test("returns undefined when no inline schemas", () => {
18
+ expect(renderInlineSchemasFile([], "/tmp/app")).toBeUndefined();
19
+ });
20
+
21
+ test("uses app-root-relative paths in source comments", () => {
22
+ const appRoot = mkdtempSync(join(tmpdir(), "kumiko-codegen-"));
23
+ const featurePath = join(appRoot, "src", "feature.ts");
24
+ mkdirSync(join(appRoot, "src"), { recursive: true });
25
+ writeFileSync(featurePath, "// stub", "utf-8");
26
+ const events: ScannedEvent[] = [
27
+ {
28
+ qualifiedName: "inventory:event:product-archived",
29
+ schemaSource: {
30
+ kind: "inline",
31
+ schemaSource: "z.object({ reason: z.string() })",
32
+ generatedConstName: "_kg_inventory__productArchived",
33
+ },
34
+ featureFilePath: featurePath,
35
+ source: { file: featurePath, line: 92 },
36
+ },
37
+ ];
38
+ const out = renderInlineSchemasFile(events, appRoot);
39
+ expect(out).toContain("// inventory:event:product-archived — from src/feature.ts:92");
40
+ expect(out).not.toContain(appRoot);
41
+ });
42
+ });
43
+
44
+ describe("renderDefineFile", () => {
45
+ test("emits defineWriteHandler wrapper with type reference", () => {
46
+ const out = renderDefineFile();
47
+ expect(out).toContain("defineWriteHandler");
48
+ expect(out).toContain("types.generated.d.ts");
49
+ });
50
+ });
@@ -22,6 +22,7 @@
22
22
  // actual change, so mtime doesn't tick and the TS language server
23
23
  // doesn't reload every 100ms.
24
24
 
25
+ import { relative } from "node:path";
25
26
  import type { ScannedEvent } from "./scan-events";
26
27
  import { rewriteImportPath } from "./scan-events";
27
28
 
@@ -115,7 +116,10 @@ export function renderTypesAugmentation(
115
116
  * the schema. Returns undefined when no inline-schemas exist (so the
116
117
  * runner can skip writing the file entirely).
117
118
  */
118
- export function renderInlineSchemasFile(events: readonly ScannedEvent[]): string | undefined {
119
+ export function renderInlineSchemasFile(
120
+ events: readonly ScannedEvent[],
121
+ appRootAbs: string,
122
+ ): string | undefined {
119
123
  const inlines = events.filter((ev) => ev.schemaSource.kind === "inline");
120
124
  if (inlines.length === 0) return undefined;
121
125
 
@@ -139,8 +143,9 @@ export function renderInlineSchemasFile(events: readonly ScannedEvent[]): string
139
143
  });
140
144
  for (const ev of sorted) {
141
145
  if (ev.schemaSource.kind !== "inline") continue;
146
+ const sourcePath = relative(appRootAbs, ev.featureFilePath).split("\\").join("/");
142
147
  lines.push(
143
- `// ${ev.qualifiedName} — from ${ev.featureFilePath}:${ev.source.line}`,
148
+ `// ${ev.qualifiedName} — from ${sourcePath}:${ev.source.line}`,
144
149
  `export const ${ev.schemaSource.generatedConstName} = ${ev.schemaSource.schemaSource};`,
145
150
  "",
146
151
  );
@@ -68,7 +68,7 @@ export function runCodegen(opts: CodegenOptions): CodegenResult {
68
68
 
69
69
  const typesContent = renderTypesAugmentation(scan.events, outputDir);
70
70
  const defineContent = renderDefineFile();
71
- const schemasContent = renderInlineSchemasFile(scan.events);
71
+ const schemasContent = renderInlineSchemasFile(scan.events, opts.appRoot);
72
72
  // package.json — turns `.kumiko/` into a real installable package
73
73
  // named `@app/define`. Apps that declare
74
74
  // "@app/define": "link:./.kumiko"
@@ -41,6 +41,7 @@
41
41
 
42
42
  import { readdirSync, statSync } from "node:fs";
43
43
  import { join, relative, resolve, sep } from "node:path";
44
+ import { toKebab } from "@cosmicdrift/kumiko-framework/engine";
44
45
  import {
45
46
  type CallExpression,
46
47
  type ImportDeclaration,
@@ -51,27 +52,6 @@ import {
51
52
  SyntaxKind,
52
53
  } from "ts-morph";
53
54
 
54
- /**
55
- * Replicates `packages/framework/src/engine/qualified-name.ts:toKebab`.
56
- * Frame's r.defineEvent runs the feature-name + event-name through this
57
- * helper before joining them — `defineFeature("driverOrders")` writes
58
- * events under `driver-orders:event:...`. We MUST mirror the same
59
- * transform here, otherwise the augmentation key drifts from the
60
- * runtime event-type and strict-mode would catch a phantom mismatch.
61
- *
62
- * Inlined (instead of imported from framework) to keep the codegen
63
- * package boundary clean — codegen doesn't depend on the runtime
64
- * framework, only the framework-source-tree via paths-mapping at the
65
- * caller's compile.
66
- */
67
- function toKebab(input: string): string {
68
- return input
69
- .replace(/\./g, "-")
70
- .replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
71
- .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
72
- .toLowerCase();
73
- }
74
-
75
55
  export type ScannedEvent = {
76
56
  /** Qualified event-name as it appears in the events table:
77
57
  * `<feature>:event:<inner>`. The KumikoEventTypeMap key. */