@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 +20 -21
- package/dist/build-server-bundle.js +32 -9
- package/dist/scaffold-deploy.d.ts +3 -3
- package/dist/scaffold-deploy.js +3 -1
- package/package.json +10 -10
- package/src/changes.json +12 -0
- package/templates/deploy/Dockerfile.template +6 -12
package/bin/kumiko-dev.ts
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
// kumiko-dev
|
|
3
|
-
//
|
|
2
|
+
// kumiko-dev: wrapper script for sample dev scripts. Spawns the given
|
|
3
|
+
// server entry and respawns it automatically.
|
|
4
4
|
//
|
|
5
|
-
// Restart
|
|
6
|
-
// - exit 75 (EX_TEMPFAIL,
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// -
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
44
|
-
// `bun --env-file=...`
|
|
45
|
-
//
|
|
46
|
-
// dev-scripts in samples
|
|
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 (
|
|
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
|
|
78
|
-
//
|
|
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
|
-
//
|
|
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 →
|
|
32
|
-
*
|
|
33
|
-
*
|
|
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,
|
package/dist/scaffold-deploy.js
CHANGED
|
@@ -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
|
|
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.
|
|
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.
|
|
81
|
-
"@cosmicdrift/kumiko-feature-ast": "0.
|
|
82
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
83
|
-
"@cosmicdrift/kumiko-renderer": "0.
|
|
84
|
-
"@cosmicdrift/kumiko-renderer-mail-html": "0.
|
|
85
|
-
"@cosmicdrift/kumiko-guards": "0.
|
|
86
|
-
"@cosmicdrift/kumiko-server-runtime": "0.
|
|
87
|
-
"@cosmicdrift/kumiko-testing": "0.
|
|
88
|
-
"@cosmicdrift/kumiko-types": "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
|
-
#
|
|
32
|
-
|
|
33
|
-
|
|
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}}
|