@cosmicdrift/kumiko-dev-server 0.112.0 → 0.113.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.112.0",
3
+ "version": "0.113.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>",
@@ -50,8 +50,8 @@
50
50
  "kumiko-schema-check": "./bin/kumiko-schema-check.ts"
51
51
  },
52
52
  "dependencies": {
53
- "@cosmicdrift/kumiko-bundled-features": "0.112.0",
54
- "@cosmicdrift/kumiko-framework": "0.112.0",
53
+ "@cosmicdrift/kumiko-bundled-features": "0.113.0",
54
+ "@cosmicdrift/kumiko-framework": "0.113.0",
55
55
  "ts-morph": "^28.0.0"
56
56
  },
57
57
  "publishConfig": {
@@ -98,12 +98,11 @@ describe("composeFeatures", () => {
98
98
  });
99
99
 
100
100
  test("app feature duplicating a bundled name is dropped (no createRegistry crash)", () => {
101
- // Bug discovered during Phase 3 recording: create-kumiko-app's picker
102
- // hands back createAuthEmailPasswordFeature() because the user ticked
103
- // it in the recommended set; runDevApp then adds its OWN bundled
104
- // copy via includeBundled:true, and createRegistry throws "Duplicate
105
- // feature: auth-email-password". The dedupe path keeps the bundled
106
- // instance (it carries authOptions wiring) and drops the app stub.
101
+ // create-kumiko-app's picker hands back createAuthEmailPasswordFeature()
102
+ // because the user ticked it in the recommended set; runDevApp then adds
103
+ // its OWN bundled copy via includeBundled:true, and createRegistry throws
104
+ // "Duplicate feature: auth-email-password". The dedupe path keeps the
105
+ // bundled instance (it carries authOptions wiring) and drops the app stub.
107
106
  const warnSpy = spyOn(console, "warn").mockImplementation(() => {});
108
107
  const features = composeFeatures([pickerAuthDupe, noopFeature], {
109
108
  includeBundled: true,
@@ -6,7 +6,7 @@
6
6
  // `relation "kumiko_events" does not exist`. Dieser Test fährt den echten
7
7
  // Pfad gegen eine leere DB + den idempotenten Re-Run.
8
8
 
9
- import { afterAll, beforeAll, describe, expect, test } from "bun:test";
9
+ import { afterAll, beforeAll, describe, expect, spyOn, test } from "bun:test";
10
10
  import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
11
11
  import { tmpdir } from "node:os";
12
12
  import { join } from "node:path";
@@ -76,7 +76,7 @@ describe("runSchemaApply", () => {
76
76
  expect(await tableExists(conn.db, "public.read_thing")).toBe(true);
77
77
  });
78
78
 
79
- test("rebuild-Marker für nicht-registrierte Tabelle → kein Crash, 0", async () => {
79
+ test("rebuild-Marker für nicht-registrierte Tabelle → kein Crash, 0, aber laut warnen (522/3)", async () => {
80
80
  writeFileSync(
81
81
  join(migDir, "0002_more.sql"),
82
82
  `CREATE TABLE "read_more" ("id" text PRIMARY KEY);`,
@@ -86,7 +86,10 @@ describe("runSchemaApply", () => {
86
86
  JSON.stringify({ version: 1, tables: ["read_more"] }),
87
87
  );
88
88
 
89
+ const warn = spyOn(console, "warn").mockImplementation(() => {});
89
90
  expect(await runSchemaApply({ ...APPLY, appCwd })).toBe(0);
90
91
  expect(await tableExists(conn.db, "public.read_more")).toBe(true);
92
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining('Table "read_more"'));
93
+ warn.mockRestore();
91
94
  });
92
95
  });
@@ -238,8 +238,13 @@ export async function runDevApp(options: RunDevAppOptions): Promise<KumikoServer
238
238
  });
239
239
 
240
240
  // An explicitly wired file provider (options.files) satisfies the
241
- // FILE_STORAGE_PROVIDER boot gate — set it before validateBoot runs.
242
- if (options.files !== undefined && process.env["FILE_STORAGE_PROVIDER"] === undefined) {
241
+ // FILE_STORAGE_PROVIDER boot gate — set it before validateBoot runs. Only
242
+ // if WE set it (532/2): deleting it after use instead of leaving a
243
+ // permanent process.env mutation, so a second runDevApp call in the same
244
+ // process (no files this time) doesn't fall through the gate falsely.
245
+ const setFileStorageProviderEnv =
246
+ options.files !== undefined && process.env["FILE_STORAGE_PROVIDER"] === undefined;
247
+ if (setFileStorageProviderEnv) {
243
248
  process.env["FILE_STORAGE_PROVIDER"] = "configured";
244
249
  }
245
250
 
@@ -248,7 +253,11 @@ export async function runDevApp(options: RunDevAppOptions): Promise<KumikoServer
248
253
  // die früher nur runProdApp fing und sonst erst den Prod-Pod im
249
254
  // CrashLoopBackOff sterben ließ (#359). Wirft synchron, bevor ein
250
255
  // Socket oder Watcher (codegen-Write) aufgeht.
251
- validateBoot(features);
256
+ try {
257
+ validateBoot(features);
258
+ } finally {
259
+ if (setFileStorageProviderEnv) delete process.env["FILE_STORAGE_PROVIDER"];
260
+ }
252
261
  warnIfNonUtcServerTimeZone();
253
262
  validateAppCustomScreenWriteQns(process.cwd(), collectWriteHandlerQns(features));
254
263
 
@@ -100,7 +100,16 @@ async function rebuildAffectedProjections(
100
100
  const projections = new Set<string>();
101
101
  for (const table of changedTables) {
102
102
  const name = tableToProjection.get(table);
103
- if (name) projections.add(name);
103
+ if (name) {
104
+ projections.add(name);
105
+ } else {
106
+ // 522/3: a table in a .rebuild.json marker that no longer matches any
107
+ // registered projection would otherwise rebuild nothing and exit 0 —
108
+ // indistinguishable from "nothing needed a rebuild".
109
+ console.warn(
110
+ ` ⚠ Table "${table}" is in a rebuild marker but matches no registered projection — skipped.`,
111
+ );
112
+ }
104
113
  }
105
114
  if (projections.size === 0) return;
106
115
 
@@ -37,10 +37,7 @@ export function renderWelcomeBanner(input: WelcomeBannerInput): string {
37
37
  return [top, ...padded, bottom].join("\n");
38
38
  }
39
39
 
40
- // Plain monospace-cell width counts each codepoint as one cell. Good
41
- // enough for ASCII + the small set of arrows/checkmarks used above; if a
42
- // real wide-char ever lands in the banner the row alignment will drift,
43
- // caught visually by the snapshot test.
40
+ // ponytail: codepoint width; ok for ASCII + arrows, add a width-lib if CJK ever lands here.
44
41
  function stringWidth(s: string): number {
45
42
  return [...s].length;
46
43
  }