@cosmicdrift/kumiko-dev-server 0.362.0 → 0.363.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.
@@ -43,7 +43,7 @@ import { existsSync, readFileSync, statSync } from "node:fs";
43
43
  import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
44
44
  import { dirname, join, resolve } from "node:path";
45
45
  import { parseJsonOrThrow, parseJsonSafe } from "@cosmicdrift/kumiko-framework/utils";
46
- const hasBun = typeof globalThis.Bun !== "undefined";
46
+ const hasBun = typeof Bun !== "undefined";
47
47
  // Pakete die das Bundle zur Laufzeit weiter referenziert (`from "<pkg>"` nach
48
48
  // Tree-Shake). Native bindings (argon2), worker-thread-Loader (bullmq,
49
49
  // ioredis), dynamic-require (postgres, temporal-polyfill) — alles was unter
@@ -118,7 +118,7 @@ export async function buildServerBundle(options = {}) {
118
118
  entrypoints,
119
119
  outdir: outDir,
120
120
  target: "bun",
121
- external: externals,
121
+ external: [...externals],
122
122
  splitting: true,
123
123
  sourcemap: "none",
124
124
  naming: { entry: "[name].js", chunk: "chunk-[hash].js" },
@@ -25,12 +25,12 @@ import { mailTransportInMemoryFeature } from "@cosmicdrift/kumiko-bundled-featur
25
25
  import { mailTransportSmtpFeature } from "@cosmicdrift/kumiko-bundled-features/mail-transport-smtp";
26
26
  import { createRateLimitingFeature } from "@cosmicdrift/kumiko-bundled-features/rate-limiting";
27
27
  import { createRendererFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/renderer-foundation";
28
- import { createRendererSimpleFeature } from "@cosmicdrift/kumiko-bundled-features/renderer-simple";
29
28
  import { createSessionsFeature } from "@cosmicdrift/kumiko-bundled-features/sessions";
30
29
  import { createTemplateResolverFeature } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
31
30
  import { createTenantLifecycleFeature } from "@cosmicdrift/kumiko-bundled-features/tenant-lifecycle";
32
31
  import { createUserDataRightsFeature, } from "@cosmicdrift/kumiko-bundled-features/user-data-rights";
33
32
  import { createUserDataRightsDefaultsFeature } from "@cosmicdrift/kumiko-bundled-features/user-data-rights-defaults";
33
+ import { createRendererMailHtmlFeature } from "@cosmicdrift/kumiko-renderer-mail-html";
34
34
  export function stackFeatureNames(features) {
35
35
  return features.map((f) => f.name);
36
36
  }
@@ -48,7 +48,7 @@ export function composeRendererStack() {
48
48
  return [
49
49
  createTemplateResolverFeature(),
50
50
  createRendererFoundationFeature(),
51
- createRendererSimpleFeature(),
51
+ createRendererMailHtmlFeature(),
52
52
  ];
53
53
  }
54
54
  export function composeMailStack(options) {
@@ -32,13 +32,14 @@ import { RENDERER_WEB_FONT_FILE_PATTERN, RENDERER_WEB_FONTS_URL_PREFIX, resolveR
32
32
  import { buildRequestBoundSystemQuery } from "@cosmicdrift/kumiko-server-runtime/request-bound-system-query";
33
33
  import { canResolveTailwindStylesheet, resolveTailwindCli, } from "@cosmicdrift/kumiko-server-runtime/resolve-tailwind-cli";
34
34
  import { stripNoRouteMatchHeader, tryHonoFirst, } from "@cosmicdrift/kumiko-server-runtime/try-hono-first";
35
+ import { errorCodeOf } from "@cosmicdrift/kumiko-types/type-guards";
35
36
  import { isPersistentDevDb } from "./is-persistent-dev-db.js";
36
37
  // Runtime-detection. The dev-server is meant to run under Bun (Kumiko's
37
38
  // target runtime), but the test-suite runs under vitest on Node — we
38
39
  // gate every Bun.* call so the module at least LOADS under Node, and
39
40
  // tests drive the fetch-handler directly instead of going through
40
41
  // Bun.serve + real sockets.
41
- const hasBun = typeof globalThis.Bun !== "undefined";
42
+ const hasBun = typeof Bun !== "undefined";
42
43
  // @wrapper-known semantic-alias
43
44
  // biome-ignore lint/suspicious/noConsole: dev-server status logging
44
45
  export const logInfo = (msg) => console.log(msg);
@@ -243,7 +244,7 @@ async function servePublicFile(pathname, publicDir) {
243
244
  return { bytes, mime: publicFileMimeType(filePath) };
244
245
  }
245
246
  catch (err) {
246
- const code = err.code;
247
+ const code = errorCodeOf(err);
247
248
  if (code === "ENOENT" || code === "EISDIR" || code === "ENOTDIR" || code === "ENAMETOOLONG") {
248
249
  return undefined;
249
250
  }
@@ -292,7 +293,7 @@ async function watchDir(dir, onChange, signal, watchEvents) {
292
293
  // signal.abort() wirft AbortError aus dem async-iterator; das ist
293
294
  // gewollt und kein Fehler. Andere Errors weiterreichen.
294
295
  // skip: AbortSignal fired the abort, this is expected teardown not a real error
295
- if (err.name === "AbortError")
296
+ if (typeof err === "object" && err !== null && "name" in err && err.name === "AbortError")
296
297
  return;
297
298
  throw err;
298
299
  }
@@ -370,14 +371,11 @@ function expandWatchPatterns(patterns) {
370
371
  out.push(resolve(p));
371
372
  continue;
372
373
  }
373
- // expandWatchPatterns wird nur unter Bun aufgerufen (createKumikoServer
374
- // ist Bun-only); der ?.! -dance ist nötig weil TS Bun nicht im
375
- // globalThis-default sieht. Wenn Bun fehlt, ist der Aufrufstapel eh
376
- // schon fail-fast unten in Bun.serve.
377
- const BunRef = globalThis.Bun;
378
- if (!BunRef)
374
+ // Only reachable under Bun (createKumikoServer is Bun-only); without Bun the
375
+ // call stack fails fast in Bun.serve anyway.
376
+ if (typeof Bun === "undefined")
379
377
  throw new Error("expandWatchPatterns requires Bun.Glob");
380
- const glob = new BunRef.Glob(p);
378
+ const glob = new Bun.Glob(p);
381
379
  const matches = Array.from(glob.scanSync({ onlyFiles: false, cwd: process.cwd() }));
382
380
  for (const m of matches) {
383
381
  const abs = resolve(m);
@@ -434,9 +432,8 @@ export function resolveStylesheet(options) {
434
432
  return undefined;
435
433
  }
436
434
  try {
437
- const resolved = globalThis.Bun.resolveSync("@cosmicdrift/kumiko-renderer-web/styles.css", process.cwd());
438
- const bun = globalThis.Bun;
439
- if (!canResolveTailwindStylesheet(resolved, { bun, cwd: process.cwd() })) {
435
+ const resolved = Bun.resolveSync("@cosmicdrift/kumiko-renderer-web/styles.css", process.cwd());
436
+ if (!canResolveTailwindStylesheet(resolved, { bun: Bun, cwd: process.cwd() })) {
440
437
  return undefined;
441
438
  }
442
439
  return resolved;
@@ -460,9 +457,7 @@ export function resolveStylesheetWatch(options, env = process.env) {
460
457
  // the initial build fails (flaky network, missing dependency), the server
461
458
  // boots without CSS instead of dying.
462
459
  async function startTailwindPipeline(entryCss, { watch }) {
463
- const bunResolver = hasBun
464
- ? globalThis.Bun
465
- : undefined;
460
+ const bunResolver = hasBun ? Bun : undefined;
466
461
  const cliPath = resolveTailwindCli({ bun: bunResolver, cwd: process.cwd() });
467
462
  if (cliPath === undefined) {
468
463
  logError("[kumiko-server] @tailwindcss/cli nicht auflösbar — booting ohne CSS-Pipeline. " +
@@ -770,7 +765,7 @@ export async function createKumikoServer(options) {
770
765
  content = await readFile(file, "utf-8");
771
766
  }
772
767
  catch (error) {
773
- if (error.code !== "ENOENT")
768
+ if (errorCodeOf(error) !== "ENOENT")
774
769
  throw error;
775
770
  return new Response(`hostDispatch: file not found: ${file}`, { status: 500 });
776
771
  }
@@ -850,7 +845,7 @@ export async function createKumikoServer(options) {
850
845
  });
851
846
  }
852
847
  catch (err) {
853
- if (err.code === "ENOENT") {
848
+ if (errorCodeOf(err) === "ENOENT") {
854
849
  return new Response("not found", { status: 404 });
855
850
  }
856
851
  throw err;
@@ -963,7 +958,7 @@ export async function createKumikoServer(options) {
963
958
  // damit Dev und Prod genau dieselben SSE-relevanten Defaults nutzen
964
959
  // (idleTimeout: 0). Spec-Test in run-prod-app-spec.test.ts pinst das.
965
960
  const server = hasBun
966
- ? globalThis.Bun.serve(buildBunServeOptions(port, handleFetch, resolveDerivedMaxRequestBodySize(stack.registry), {
961
+ ? Bun.serve(buildBunServeOptions(port, handleFetch, resolveDerivedMaxRequestBodySize(stack.registry), {
967
962
  upgradeFetch: async (req, env) => stripNoRouteMatchHeader(await stack.app.fetch(req, env)),
968
963
  }))
969
964
  : undefined;
@@ -32,6 +32,7 @@
32
32
  import { existsSync, readdirSync, readFileSync } from "node:fs";
33
33
  import { dirname, join, relative, resolve } from "node:path";
34
34
  import { PATTERN_LIBRARY, parseFeatureFile, } from "@cosmicdrift/kumiko-framework/engine";
35
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
35
36
  // =============================================================================
36
37
  // Builder
37
38
  // =============================================================================
@@ -155,16 +156,15 @@ function relativizeSources(value, repoRoot) {
155
156
  if (Array.isArray(value)) {
156
157
  return value.map((v) => relativizeSources(v, repoRoot));
157
158
  }
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"]) };
159
+ if (isPlainObject(value)) {
160
+ if (typeof value["file"] === "string" &&
161
+ typeof value["raw"] === "string" &&
162
+ typeof value["start"] === "object" &&
163
+ typeof value["end"] === "object") {
164
+ return { ...value, file: relative(repoRoot, value["file"]) };
165
165
  }
166
166
  const out = {};
167
- for (const [k, v] of Object.entries(obj)) {
167
+ for (const [k, v] of Object.entries(value)) {
168
168
  out[k] = relativizeSources(v, repoRoot);
169
169
  }
170
170
  return out;
@@ -222,11 +222,13 @@ function findPackageJson(featureFile, repoRoot) {
222
222
  if (existsSync(candidate)) {
223
223
  try {
224
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
- };
225
+ if (isPlainObject(pkg)) {
226
+ return {
227
+ relPath: relative(repoRoot, candidate),
228
+ name: typeof pkg["name"] === "string" ? pkg["name"] : undefined,
229
+ description: typeof pkg["description"] === "string" ? pkg["description"] : undefined,
230
+ };
231
+ }
230
232
  }
231
233
  catch {
232
234
  // Malformed package.json — keep walking up.
@@ -22,7 +22,7 @@ import { TenantQueries } from "@cosmicdrift/kumiko-bundled-features/tenant";
22
22
  import { createInMemoryLoginRateLimiter, parseTrustedProxyHopsEnv, TRUSTED_PROXY_HOPS_ENV, } from "@cosmicdrift/kumiko-framework/api";
23
23
  import { configureBlindIndexKey, configurePiiSubjectKms, } from "@cosmicdrift/kumiko-framework/crypto";
24
24
  import { configureEntityFieldEncryption } from "@cosmicdrift/kumiko-framework/db";
25
- import { collectWriteHandlerQns, createRegistry, FILE_STORAGE_PROVIDER_BOOT_SENTINEL, FILE_STORAGE_PROVIDER_ENV, findTierResolverUsage, validateAppCustomScreenWriteQns, validateBoot, } from "@cosmicdrift/kumiko-framework/engine";
25
+ import { collectWriteHandlerQns, createRegistry, FILE_STORAGE_PROVIDER_BOOT_SENTINEL, FILE_STORAGE_PROVIDER_ENV, findTierResolverUsage, isTierResolverPlugin, validateAppCustomScreenWriteQns, validateBoot, } from "@cosmicdrift/kumiko-framework/engine";
26
26
  import { warnIfNonUtcServerTimeZone } from "@cosmicdrift/kumiko-framework/time";
27
27
  import { applyBootSeeds } from "@cosmicdrift/kumiko-server-runtime/boot/apply-boot-seeds";
28
28
  import { resolveBootCrypto } from "@cosmicdrift/kumiko-server-runtime/boot/boot-crypto";
@@ -323,6 +323,9 @@ export async function runDevApp(options) {
323
323
  // tier-assignment can immediately read tier-cuts).
324
324
  if (tierResolverUsage) {
325
325
  const plugin = tierResolverUsage.options;
326
+ if (!isTierResolverPlugin(plugin)) {
327
+ throw new Error("tier-resolver registration options must provide a build() factory");
328
+ }
326
329
  tierResolverHolder.resolver = await plugin.build({
327
330
  db: stack.db,
328
331
  registry: stack.registry,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-dev-server",
3
- "version": "0.362.0",
3
+ "version": "0.363.0",
4
4
  "description": "Dev-tooling for Kumiko apps: local dev-server bootstrap (runDevApp), scaffolding, codegen. Its compose-stacks/env-schema subpaths are consumed at prod boot too — see @cosmicdrift/kumiko-server-runtime for the prod runner itself.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -64,11 +64,13 @@
64
64
  "kumiko-upgrade": "./bin/kumiko-upgrade.ts"
65
65
  },
66
66
  "dependencies": {
67
- "@cosmicdrift/kumiko-bundled-features": "0.362.0",
68
- "@cosmicdrift/kumiko-framework": "0.362.0",
69
- "@cosmicdrift/kumiko-headless": "0.362.0",
70
- "@cosmicdrift/kumiko-renderer-web": "0.362.0",
71
- "@cosmicdrift/kumiko-server-runtime": "0.362.0",
67
+ "@cosmicdrift/kumiko-bundled-features": "0.363.0",
68
+ "@cosmicdrift/kumiko-framework": "0.363.0",
69
+ "@cosmicdrift/kumiko-headless": "0.363.0",
70
+ "@cosmicdrift/kumiko-renderer-mail-html": "0.363.0",
71
+ "@cosmicdrift/kumiko-renderer-web": "0.363.0",
72
+ "@cosmicdrift/kumiko-server-runtime": "0.363.0",
73
+ "@cosmicdrift/kumiko-types": "0.363.0",
72
74
  "bun-types": "^1.3.13",
73
75
  "ts-morph": "^28.0.0",
74
76
  "zod": "^4.4.3"
package/src/changes.json CHANGED
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.363.0",
4
+ "type": "improvement",
5
+ "title": "composeRendererStack includes renderer-mail-html",
6
+ "detail": "The renderer stack now also mounts createRendererMailHtmlFeature, which the auth mails need. Apps that already list renderer-mail-html next to composeRendererStack() drop their own copy, createRegistry rejects duplicate features."
7
+ },
8
+ {
9
+ "version": "0.363.0",
10
+ "type": "improvement",
11
+ "title": "composeRendererStack no longer mounts renderer-simple",
12
+ "detail": "renderer-simple is removed, so the renderer stack is template-resolver, renderer-foundation and renderer-mail-html."
13
+ },
2
14
  {
3
15
  "version": "0.361.0",
4
16
  "type": "breaking",