@cosmicdrift/kumiko-dev-server 0.391.0 → 0.392.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/bin/kumiko-dev.ts CHANGED
@@ -1,19 +1,18 @@
1
1
  #!/usr/bin/env bun
2
- // kumiko-dev — Wrapper-Skript für sample dev-scripts. Spawnt den
3
- // übergebenen Server-Entry und respawnt automatisch.
2
+ // kumiko-dev: wrapper script for sample dev scripts. Spawns the given
3
+ // server entry and respawns it automatically.
4
4
  //
5
- // Restart-Policy:
6
- // - exit 75 (EX_TEMPFAIL, vom dev-server beim Schema-Change ausgelöst,
7
- // weil Bun's Module-Cache einen Process-Restart erzwingt): sofortiger
8
- // respawn ohne Crash-Loop-Anrechnung — das ist erwartetes Verhalten.
9
- // - jeder andere non-zero exit: respawn mit kurzem Backoff, aber
10
- // Crash-Loop-Schutz via createCrashTracker. Verhindert Endlos-Loop
11
- // bei syntaktisch totem bin/main.ts und gibt dem User trotzdem
12
- // "Live-Edit"-Feeling: ein Code-Fehler in einem Feature killt nicht
13
- // die ganze bun-dev-Session.
14
- // - Signal-killed (SIGINT/SIGTERM): wir folgen dem Caller, exit 0.
5
+ // Restart policy:
6
+ // - exit 75 (EX_TEMPFAIL, raised by the dev-server on a schema change
7
+ // because Bun's module cache forces a process restart): respawn
8
+ // immediately without counting towards the crash loop. This is expected.
9
+ // - any other non-zero exit: respawn with a short backoff, guarded by
10
+ // createCrashTracker. Prevents an endless loop on a syntactically broken
11
+ // bin/main.ts while keeping the "live edit" feel: a code error in one
12
+ // feature does not kill the whole bun dev session.
13
+ // - killed by a signal (SIGINT/SIGTERM): follow the caller, exit 0.
15
14
  //
16
- // Nutzung: `kumiko-dev src/app/server.ts` in package.json:
15
+ // Usage: `kumiko-dev src/app/server.ts` in package.json:
17
16
  //
18
17
  // "scripts": { "dev": "kumiko-dev src/app/server.ts" }
19
18
 
@@ -37,13 +36,13 @@ if (entry === undefined || entry === "") {
37
36
  process.exit(2);
38
37
  }
39
38
 
40
- // Restliche Args (z.B. --port 4175) reichen wir an den Server durch.
39
+ // Pass the remaining args (e.g. --port 4175) through to the server.
41
40
  const passthroughArgs = process.argv.slice(3);
42
41
 
43
- // Bun's eigener Pfad steht in process.argv[0] — den nutzen wir, damit
44
- // `bun --env-file=...` Flags vom Caller nicht verloren gehen. Wir
45
- // reichen NICHT alle Bun-Flags durch (kompliziert mit shell-Escaping);
46
- // dev-scripts in samples setzen Env-Vars direkt im Aufruf wenn nötig.
42
+ // Bun's own path is in process.argv[0] — we use it so
43
+ // `bun --env-file=...` flags from the caller aren't lost. We
44
+ // do NOT pass through all Bun flags (complicated with shell escaping);
45
+ // dev-scripts in samples set env vars directly in the invocation when needed.
47
46
  const bunPath = process.argv[0] ?? "bun";
48
47
 
49
48
  const crashTracker = createCrashTracker({
@@ -61,7 +60,7 @@ const spawnServer = (): void => {
61
60
 
62
61
  child.on("exit", (code, signal) => {
63
62
  if (signal !== null) {
64
- // Signal-killed (z.B. SIGINT): Caller wollte raus → wir auch.
63
+ // Signal-killed (e.g. SIGINT): the caller wanted out → so do we.
65
64
  process.exit(0);
66
65
  return;
67
66
  }
@@ -74,8 +73,8 @@ const spawnServer = (): void => {
74
73
  process.exit(0);
75
74
  return;
76
75
  }
77
- // Non-zero exit ohne Schema-Change: Code-Fehler oder transienter
78
- // Crash. Respawn mit Backoff + Crash-Loop-Schutz.
76
+ // Non-zero exit without a schema change: code error or transient
77
+ // crash. Respawn with backoff + crash-loop protection.
79
78
  const now = Date.now();
80
79
  const allowed = crashTracker.noteCrash(now);
81
80
  if (!allowed) {
@@ -40,6 +40,10 @@
40
40
  // worker threads, dynamic require). Added to
41
41
  // dist-server/package.json#dependencies.
42
42
  //
43
+ // OPTIONAL_RUNTIME_EXTERNALS — external in every build, but listed in
44
+ // dist-server/package.json#dependencies only when an
45
+ // emitted file imports them or the app declares them.
46
+ //
43
47
  // BUILD_ONLY_EXTERNALS — referenced only transitively in the framework,
44
48
  // never by app code. Tree-shaking removes them from
45
49
  // the bundle; the external marker only disables
@@ -54,13 +58,6 @@ const hasBun = typeof Bun !== "undefined";
54
58
  // ioredis), and dynamic require (postgres, temporal-polyfill) — anything that
55
59
  // breaks under Bun bundling. (drizzle-kit/drizzle-orm are excluded: the
56
60
  // migration path now uses the framework's runMigrationsFromDir, not drizzle-kit.)
57
- // meilisearch is deliberately here, not in BUILD_ONLY_EXTERNALS below — apps
58
- // that import createMeilisearchAdapter (kumiko-framework/search/meilisearch)
59
- // reference the package at runtime (no native binding, plain HTTP client,
60
- // but a real runtime import). A production crash in money-horse,
61
- // "Cannot find package 'meilisearch'", occurred because it used to be in
62
- // BUILD_ONLY_EXTERNALS instead. Its version comes from the framework's
63
- // peerDependencies range.
64
61
  const RUNTIME_EXTERNALS = [
65
62
  "@node-rs/argon2",
66
63
  "sharp",
@@ -70,8 +67,12 @@ const RUNTIME_EXTERNALS = [
70
67
  "temporal-polyfill",
71
68
  "pino",
72
69
  "pino-pretty",
73
- "meilisearch",
74
70
  ];
71
+ // An optional framework peer: apps that import createMeilisearchAdapter reference
72
+ // it at runtime, so it must stay external (as BUILD_ONLY_EXTERNALS it crashed
73
+ // money-horse with "Cannot find package 'meilisearch'"), but apps without search
74
+ // should not install it.
75
+ const OPTIONAL_RUNTIME_EXTERNALS = ["meilisearch"];
75
76
  // Packages that appear only in the build stack (transitive framework imports),
76
77
  // but are not referenced by app code. Without the external marker,
77
78
  // bun build fails on dynamic imports. Tree-shaking removes them from the
@@ -136,6 +137,7 @@ export async function buildServerBundle(options = {}) {
136
137
  const extraEntryPaths = resolveExtraEntries(cwd, options.extraEntries ?? []);
137
138
  const externals = [
138
139
  ...RUNTIME_EXTERNALS,
140
+ ...OPTIONAL_RUNTIME_EXTERNALS,
139
141
  ...BUILD_ONLY_EXTERNALS,
140
142
  ...(options.extraRuntimeExternals ?? []),
141
143
  ...(options.extraBuildOnlyExternals ?? []),
@@ -169,7 +171,9 @@ export async function buildServerBundle(options = {}) {
169
171
  // split entry points and sometimes reports ~0 for the entry stub.
170
172
  const entries = [];
171
173
  const chunks = [];
174
+ const emittedCode = [];
172
175
  for (const out of result.outputs) {
176
+ emittedCode.push(await out.text());
173
177
  const base = out.path.split("/").pop() ?? out.path;
174
178
  if (out.kind === "entry-point") {
175
179
  const desired = ENTRY_RENAMES[base] ?? base;
@@ -184,8 +188,11 @@ export async function buildServerBundle(options = {}) {
184
188
  }
185
189
  entries.sort((a, b) => a.file.localeCompare(b.file));
186
190
  const totalBytes = [...entries, ...chunks].reduce((sum, e) => sum + e.sizeBytes, 0);
191
+ const declaredAppDependencies = readDeclaredAppDependencies(cwd);
192
+ const neededOptionalExternals = OPTIONAL_RUNTIME_EXTERNALS.filter((pkg) => declaredAppDependencies.has(pkg) || emittedCode.some((code) => importsBarePackage(code, pkg)));
187
193
  const runtimeDeps = await resolveRuntimeDepsVersions(cwd, repoRoot, [
188
194
  ...RUNTIME_EXTERNALS,
195
+ ...neededOptionalExternals,
189
196
  ...(options.extraRuntimeExternals ?? []),
190
197
  ]);
191
198
  const runtimePkg = {
@@ -272,7 +279,23 @@ export function readExtraRuntimeExternals(cwd) {
272
279
  return [];
273
280
  return list.filter((x) => typeof x === "string" && x.trim().length > 0);
274
281
  }
275
- // Resolve versions for RUNTIME_EXTERNALS: first from installed
282
+ // Static, dynamic and CommonJS imports of an external survive bundling as a bare specifier
283
+ // (require becomes __require(...)).
284
+ function importsBarePackage(code, pkg) {
285
+ const escaped = pkg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
286
+ return new RegExp(`(?:\\bfrom\\s*|\\bimport\\s*\\(?\\s*|\\w*[rR]equire\\w*\\s*\\(\\s*)["'\`]${escaped}["'\`]`).test(code);
287
+ }
288
+ function readDeclaredAppDependencies(cwd) {
289
+ const appPkg = join(cwd, "package.json");
290
+ if (!existsSync(appPkg))
291
+ return new Set();
292
+ const parsed = parseJsonSafe(readFileSync(appPkg, "utf-8"), {});
293
+ return new Set([
294
+ ...Object.keys(parsed.dependencies ?? {}),
295
+ ...Object.keys(parsed.optionalDependencies ?? {}),
296
+ ]);
297
+ }
298
+ // Resolve versions for the runtime externals: first from installed
276
299
  // node_modules/@cosmicdrift/*-packages (relative to cwd, which works for
277
300
  // any consumer app), then additionally from packages/framework + bundled-
278
301
  // features (the framework's own workspace checkout). Fall back to "*"
@@ -28,9 +28,9 @@ export type ScaffoldDeployDetected = {
28
28
  /** ES-Operations seed-migrations (`seeds/`). Required for apps that
29
29
  * use the es-ops feature. */
30
30
  readonly hasSeeds: boolean;
31
- /** Private @cosmicdriftgamestudio/* GH-Packages → Dockerfile needs to
32
- * pass NPM_AUTH_TOKEN as build-arg + re-export it as $GITHUB_TOKEN inside
33
- * the build-stage (bunfig.toml/.npmrc read $GITHUB_TOKEN). */
31
+ /** Private @cosmicdriftgamestudio/* GH-Packages → the build-stage install
32
+ * mounts the BuildKit secret npm_auth_token as $GITHUB_TOKEN
33
+ * (bunfig.toml/.npmrc read $GITHUB_TOKEN). */
34
34
  readonly hasPrivateGhPackages: boolean;
35
35
  /** A `workspaces` root, or a `file:`/`workspace:`/`link:` dependency spec
36
36
  * — bun needs the whole build context on disk to resolve the lockfile,
@@ -17,6 +17,7 @@ import { parseJsonOrThrow } from "@cosmicdrift/kumiko-framework/utils";
17
17
  import * as z from "zod";
18
18
  import { isKebabSegment } from "./kebab.js";
19
19
  const REGISTRY_CONFIG_FILES = ["bunfig.toml", ".npmrc"];
20
+ const NPM_AUTH_SECRET_MOUNT = "--mount=type=secret,id=npm_auth_token,env=GITHUB_TOKEN";
20
21
  const TEMPLATE_FILES = [
21
22
  { template: "Dockerfile.template", output: "Dockerfile" },
22
23
  {
@@ -49,7 +50,7 @@ export function renderDeployFiles(options) {
49
50
  const sourceDir = options.sourceDir ?? destinationRoot;
50
51
  const detected = detectOptionalSurfaces(sourceDir, options.appName, options.ensuredRegistryConfigFiles ?? []);
51
52
  if (detected.hasPrivateGhPackages && detected.registryConfigFiles.length === 0) {
52
- // The Dockerfile only forwards the auth build-arg; without a registry
53
+ // The Dockerfile only mounts the auth secret; without a registry
53
54
  // config scope entry bun resolves @cosmicdriftgamestudio/* against npmjs
54
55
  // and the image build fails late with a 404.
55
56
  throw new Error(`renderDeployFiles: private @cosmicdriftgamestudio/* deps need a bunfig.toml or .npmrc in ${sourceDir} with a scope entry for the private registry`);
@@ -60,6 +61,7 @@ export function renderDeployFiles(options) {
60
61
  port: String(port),
61
62
  githubOrg,
62
63
  installManifests,
64
+ npmAuthSecretMount: detected.hasPrivateGhPackages ? `${NPM_AUTH_SECRET_MOUNT} ` : "",
63
65
  dbUser: detected.dbUser,
64
66
  dbName: detected.dbName,
65
67
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-dev-server",
3
- "version": "0.391.0",
3
+ "version": "0.392.0",
4
4
  "description": "Dev-tooling for Kumiko apps: local dev-server bootstrap (runDevApp), scaffolding. 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": "MIT",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -77,15 +77,15 @@
77
77
  "kumiko-upgrade": "./bin/kumiko-upgrade.ts"
78
78
  },
79
79
  "dependencies": {
80
- "@cosmicdrift/kumiko-bundled-features": "0.391.0",
81
- "@cosmicdrift/kumiko-feature-ast": "0.391.0",
82
- "@cosmicdrift/kumiko-framework": "0.391.0",
83
- "@cosmicdrift/kumiko-renderer": "0.391.0",
84
- "@cosmicdrift/kumiko-renderer-mail-html": "0.391.0",
85
- "@cosmicdrift/kumiko-guards": "0.391.0",
86
- "@cosmicdrift/kumiko-server-runtime": "0.391.0",
87
- "@cosmicdrift/kumiko-testing": "0.391.0",
88
- "@cosmicdrift/kumiko-types": "0.391.0",
80
+ "@cosmicdrift/kumiko-bundled-features": "0.392.0",
81
+ "@cosmicdrift/kumiko-feature-ast": "0.392.0",
82
+ "@cosmicdrift/kumiko-framework": "0.392.0",
83
+ "@cosmicdrift/kumiko-renderer": "0.392.0",
84
+ "@cosmicdrift/kumiko-renderer-mail-html": "0.392.0",
85
+ "@cosmicdrift/kumiko-guards": "0.392.0",
86
+ "@cosmicdrift/kumiko-server-runtime": "0.392.0",
87
+ "@cosmicdrift/kumiko-testing": "0.392.0",
88
+ "@cosmicdrift/kumiko-types": "0.392.0",
89
89
  "ts-morph": "^28.0.0",
90
90
  "zod": "^4.6.7"
91
91
  },
package/src/changes.json CHANGED
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.392.0",
4
+ "type": "breaking",
5
+ "title": "The managed Dockerfile reads the registry token from a BuildKit secret",
6
+ "migration": "Image builds pass the token as a BuildKit secret instead of a build-arg: docker build --secret id=npm_auth_token,env=NPM_AUTH_TOKEN, or secrets: npm_auth_token=... in docker/build-push-action. The infra _docker-build-push workflow already sends both. Run kumiko-upgrade --apply to rewrite deploy/Dockerfile; a Dockerfile with a kumiko-managed: local override changes by hand: drop ARG NPM_AUTH_TOKEN and ENV GITHUB_TOKEN and start the build-stage install with RUN --mount=type=secret,id=npm_auth_token,env=GITHUB_TOKEN bun install."
7
+ },
8
+ {
9
+ "version": "0.392.0",
10
+ "type": "improvement",
11
+ "title": "meilisearch lands in dist-server dependencies only when the bundle imports it or the app declares it",
12
+ "detail": "The package stays external in every server build, but apps without search no longer get it as a production dependency in dist-server/package.json. An app that imports createMeilisearchAdapter, or lists meilisearch in dependencies or optionalDependencies, keeps it with the framework's peer range."
13
+ },
2
14
  {
3
15
  "version": "0.390.0",
4
16
  "type": "fix",
@@ -12,13 +12,6 @@
12
12
  # Build context: repository root (the full source tree is only used in the build stage).
13
13
 
14
14
  ARG BUN_VERSION=1.4.4
15
- {{#hasPrivateGhPackages}}
16
- # Registry auth for @cosmicdriftgamestudio: CI passes the token as
17
- # NPM_AUTH_TOKEN via --build-arg. Multi-stage ARG scoping needs the
18
- # re-declaration inside the stage that uses it; this top-level ARG only
19
- # makes it settable via `docker build`.
20
- ARG NPM_AUTH_TOKEN=
21
- {{/hasPrivateGhPackages}}
22
15
  # Build identity — passed in by the CI workflow via --build-arg. Defaults
23
16
  # for local container builds.
24
17
  ARG BUILD_VERSION=dev
@@ -28,9 +21,10 @@ ARG BUILD_TIME=unknown
28
21
  FROM oven/bun:${BUN_VERSION}-alpine AS build
29
22
  WORKDIR /app
30
23
  {{#hasPrivateGhPackages}}
31
- # bunfig.toml/.npmrc resolve the @cosmicdriftgamestudio scope via $GITHUB_TOKEN.
32
- ARG NPM_AUTH_TOKEN
33
- ENV GITHUB_TOKEN=${NPM_AUTH_TOKEN}
24
+ # Registry auth for @cosmicdriftgamestudio: CI passes the token as the BuildKit
25
+ # secret npm_auth_token. The install RUN mounts it as $GITHUB_TOKEN, which
26
+ # bunfig.toml/.npmrc read, so it never lands in a layer, the image history or
27
+ # the registry build cache.
34
28
  {{/hasPrivateGhPackages}}
35
29
 
36
30
  {{#installFromFullTree}}
@@ -38,14 +32,14 @@ ENV GITHUB_TOKEN=${NPM_AUTH_TOKEN}
38
32
  # disk for bun to resolve the lockfile — a partial COPY makes bun treat it
39
33
  # as empty and rewrite it.
40
34
  COPY . .
41
- RUN bun install --frozen-lockfile
35
+ RUN {{npmAuthSecretMount}}bun install --frozen-lockfile
42
36
  RUN bun run build
43
37
  {{/installFromFullTree}}
44
38
  {{#installFromManifests}}
45
39
  # Manifests first for Docker layer cache — bun install only invalidates on
46
40
  # dependency change, not on every source edit.
47
41
  COPY {{installManifests}} ./
48
- RUN bun install --frozen-lockfile
42
+ RUN {{npmAuthSecretMount}}bun install --frozen-lockfile
49
43
  COPY . .
50
44
  RUN bun run build
51
45
  {{/installFromManifests}}