@cosmicdrift/kumiko-server-runtime 0.327.0 → 0.328.1
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 +8 -4
- package/src/__tests__/build-prod-bundle.inprocess.test.ts +40 -0
- package/src/__tests__/renderer-web-theme-plugin.integration.test.ts +1 -1
- package/src/boot/pii-event-backfill-on-boot.ts +43 -0
- package/src/build-prod-bundle.ts +22 -0
- package/src/changes.json +6 -0
- package/src/renderer-web-fonts.ts +33 -0
- package/src/run-prod-app-static-files.ts +2 -0
- package/src/run-prod-app.ts +7 -0
- package/src/run-worker-app.ts +7 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-server-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.328.1",
|
|
4
4
|
"description": "Production server-boot runtime for Kumiko apps: connections, schema-drift-gate, seeds, lifecycle, graceful shutdown. Symmetric to kumiko-dev-server's runDevApp, without dev/scaffold/codegen tooling.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -62,6 +62,10 @@
|
|
|
62
62
|
"types": "./src/extra-routes-deps.ts",
|
|
63
63
|
"default": "./src/extra-routes-deps.ts"
|
|
64
64
|
},
|
|
65
|
+
"./renderer-web-fonts": {
|
|
66
|
+
"types": "./src/renderer-web-fonts.ts",
|
|
67
|
+
"default": "./src/renderer-web-fonts.ts"
|
|
68
|
+
},
|
|
65
69
|
"./resolve-tailwind-cli": {
|
|
66
70
|
"types": "./src/resolve-tailwind-cli.ts",
|
|
67
71
|
"default": "./src/resolve-tailwind-cli.ts"
|
|
@@ -76,9 +80,9 @@
|
|
|
76
80
|
}
|
|
77
81
|
},
|
|
78
82
|
"dependencies": {
|
|
79
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
80
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
81
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
83
|
+
"@cosmicdrift/kumiko-bundled-features": "0.328.1",
|
|
84
|
+
"@cosmicdrift/kumiko-framework": "0.328.1",
|
|
85
|
+
"@cosmicdrift/kumiko-headless": "0.328.1",
|
|
82
86
|
"temporal-polyfill": "^0.3.2"
|
|
83
87
|
},
|
|
84
88
|
"publishConfig": {
|
|
@@ -118,6 +118,46 @@ describe("buildProdBundle in-process (Bun.build)", () => {
|
|
|
118
118
|
}
|
|
119
119
|
});
|
|
120
120
|
|
|
121
|
+
test("renderer-web stylesheet → woff2 files land where the CSS points, no external font URL", async () => {
|
|
122
|
+
const dir = await mkdtemp(join(REPO_ROOT, ".inproc-fonts-"));
|
|
123
|
+
const cwd = join(dir, "app");
|
|
124
|
+
try {
|
|
125
|
+
await mkdir(join(cwd, "src"), { recursive: true });
|
|
126
|
+
await writeFile(
|
|
127
|
+
join(cwd, "src/client.ts"),
|
|
128
|
+
`const root = document.getElementById("root"); if (root) root.textContent = "hi";`,
|
|
129
|
+
);
|
|
130
|
+
await writeFile(
|
|
131
|
+
join(cwd, "src/styles.css"),
|
|
132
|
+
`@import "@cosmicdrift/kumiko-renderer-web/styles.css";\n`,
|
|
133
|
+
);
|
|
134
|
+
await mkdir(join(cwd, "public"), { recursive: true });
|
|
135
|
+
await writeFile(
|
|
136
|
+
join(cwd, "public/index.html"),
|
|
137
|
+
`<!doctype html><html><head><link rel="stylesheet" href="/styles.css" /></head><body><div id="root"></div><script type="module" src="/client.js"></script></body></html>`,
|
|
138
|
+
);
|
|
139
|
+
await writeFile(join(cwd, "package.json"), `{"name":"inproc-fonts","private":true}`);
|
|
140
|
+
|
|
141
|
+
const result = await buildProdBundle({ cwd, stylesheet: "src/styles.css" });
|
|
142
|
+
|
|
143
|
+
const css = await readFile(join(cwd, "dist", result.manifest["styles.css"] ?? ""), "utf8");
|
|
144
|
+
const referenced = [
|
|
145
|
+
...css.matchAll(/url\("?(\/assets\/kumiko\/fonts\/[a-z0-9-]+\.woff2)"?\)/g),
|
|
146
|
+
].map((m) => m[1] ?? "");
|
|
147
|
+
expect(referenced.length).toBeGreaterThanOrEqual(10);
|
|
148
|
+
for (const url of referenced) {
|
|
149
|
+
const file = join(cwd, "dist", url);
|
|
150
|
+
expect(existsSync(file)).toBe(true);
|
|
151
|
+
// woff2 magic number
|
|
152
|
+
expect((await readFile(file)).subarray(0, 4).toString("latin1")).toBe("wOF2");
|
|
153
|
+
}
|
|
154
|
+
expect(css).not.toMatch(/url\(\s*["']?(https?:)?\/\//);
|
|
155
|
+
expect(css).not.toContain("data:font");
|
|
156
|
+
} finally {
|
|
157
|
+
await rm(dir, { recursive: true, force: true });
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
121
161
|
test("missing stylesheet override → tailwind rejects", async () => {
|
|
122
162
|
const dir = await mkdtemp(join(REPO_ROOT, ".inproc-styles-miss-"));
|
|
123
163
|
const cwd = join(dir, "app");
|
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from "node:url";
|
|
|
11
11
|
import { runTailwindOnce } from "../build-prod-bundle";
|
|
12
12
|
|
|
13
13
|
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../../../..");
|
|
14
|
-
const FRAMEWORK_LIGHT_PRIMARY = "--color-primary:#
|
|
14
|
+
const FRAMEWORK_LIGHT_PRIMARY = "--color-primary:#1a1c1e";
|
|
15
15
|
|
|
16
16
|
function bunAvailable(): boolean {
|
|
17
17
|
try {
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
2
|
+
import type { Registry } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
4
|
+
import { runPiiEventBackfill } from "@cosmicdrift/kumiko-framework/migrations";
|
|
5
|
+
|
|
6
|
+
export const SKIP_PII_BACKFILL_ENV = "KUMIKO_SKIP_PII_BACKFILL";
|
|
7
|
+
|
|
8
|
+
type ShutdownRegistry = {
|
|
9
|
+
registerShutdownHook(name: string, fn: () => Promise<void>): void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Runs in the background instead of blocking boot: the first run after a bump
|
|
13
|
+
// scans the whole store and would delay readiness in proportion to its size.
|
|
14
|
+
// Every batch is its own transaction, so an abort at any point is safe.
|
|
15
|
+
export function startPiiEventBackfillOnBoot(opts: {
|
|
16
|
+
readonly db: DbConnection;
|
|
17
|
+
readonly registry: Registry;
|
|
18
|
+
readonly lifecycle: ShutdownRegistry;
|
|
19
|
+
readonly envSource: Readonly<Record<string, string | undefined>>;
|
|
20
|
+
}): void {
|
|
21
|
+
if (opts.envSource[SKIP_PII_BACKFILL_ENV] === "1") {
|
|
22
|
+
// skip: operator opted out via KUMIKO_SKIP_PII_BACKFILL
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const log = createFallbackLogger("pii-event-backfill");
|
|
27
|
+
const abortController = new AbortController();
|
|
28
|
+
const run = runPiiEventBackfill(opts.db, opts.registry, {
|
|
29
|
+
signal: abortController.signal,
|
|
30
|
+
}).then(
|
|
31
|
+
() => undefined,
|
|
32
|
+
(e: unknown) => {
|
|
33
|
+
log.error("PII event backfill crashed; it will be retried on the next boot", {
|
|
34
|
+
error: e instanceof Error ? e.message : String(e),
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
opts.lifecycle.registerShutdownHook("piiEventBackfill", async () => {
|
|
40
|
+
abortController.abort();
|
|
41
|
+
await run;
|
|
42
|
+
});
|
|
43
|
+
}
|
package/src/build-prod-bundle.ts
CHANGED
|
@@ -53,6 +53,11 @@ import { tmpdir } from "node:os";
|
|
|
53
53
|
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
54
54
|
import { isPlainObject, parseJsonOrThrow } from "@cosmicdrift/kumiko-framework/utils";
|
|
55
55
|
import { Temporal } from "temporal-polyfill";
|
|
56
|
+
import {
|
|
57
|
+
RENDERER_WEB_FONT_FILE_PATTERN,
|
|
58
|
+
RENDERER_WEB_FONTS_DIST_DIR,
|
|
59
|
+
resolveRendererWebFontsDir,
|
|
60
|
+
} from "./renderer-web-fonts";
|
|
56
61
|
import { canResolveTailwindStylesheet, resolveTailwindCli } from "./resolve-tailwind-cli";
|
|
57
62
|
|
|
58
63
|
// Bun-Runtime-Check als module-level Konstante: alle Build-Schritte
|
|
@@ -186,6 +191,7 @@ export async function buildProdBundle(options: BuildProdBundleOptions = {}): Pro
|
|
|
186
191
|
const filename = `styles-${hash}.css`;
|
|
187
192
|
await writeFile(join(assetsDir, filename), css);
|
|
188
193
|
manifest["styles.css"] = `/${ASSETS_DIR}/${filename}`;
|
|
194
|
+
await copyRendererWebFonts(cwd, outDir);
|
|
189
195
|
}
|
|
190
196
|
|
|
191
197
|
// 4. Bun.build pro Entry (multi-entry produces N bundles + shared chunks).
|
|
@@ -641,6 +647,22 @@ export function assertRendererWebShellPresent(css: string, stylesheet: ResolvedS
|
|
|
641
647
|
);
|
|
642
648
|
}
|
|
643
649
|
|
|
650
|
+
// The compiled CSS points at absolute /assets/kumiko/fonts/<file>.woff2 URLs
|
|
651
|
+
// (see renderer-web-fonts.ts), so the files have to sit at exactly that path in
|
|
652
|
+
// dist. Skipped when renderer-web isn't resolvable — then no such CSS exists.
|
|
653
|
+
async function copyRendererWebFonts(cwd: string, outDir: string): Promise<void> {
|
|
654
|
+
const fontsDir = resolveRendererWebFontsDir([cwd]);
|
|
655
|
+
// skip: renderer-web not resolvable, so no font-referencing CSS exists.
|
|
656
|
+
if (fontsDir === undefined || !existsSync(fontsDir)) return;
|
|
657
|
+
const target = join(outDir, RENDERER_WEB_FONTS_DIST_DIR);
|
|
658
|
+
await mkdir(target, { recursive: true });
|
|
659
|
+
for (const file of readdirSync(fontsDir)) {
|
|
660
|
+
if (RENDERER_WEB_FONT_FILE_PATTERN.test(file)) {
|
|
661
|
+
await cp(join(fontsDir, file), join(target, file));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
644
666
|
// @internal — exported nur für Unit-Tests.
|
|
645
667
|
export function discoverHtmlTemplate(cwd: string): string | undefined {
|
|
646
668
|
for (const candidate of ["index.html", "public/index.html"]) {
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.328.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "Production build copies the renderer-web fonts",
|
|
6
|
+
"detail": "`buildProdBundle` copies the IBM Plex woff2 files to `dist/assets/kumiko/fonts/`, where the compiled CSS expects them. The new export `@cosmicdrift/kumiko-server-runtime/renderer-web-fonts` holds the URL prefix, the file name allowlist and the resolver, and the woff2 MIME type is known to the static file server."
|
|
7
|
+
},
|
|
2
8
|
{
|
|
3
9
|
"version": "0.323.0",
|
|
4
10
|
"type": "breaking",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// The renderer-web stylesheet references its IBM Plex woff2 files by absolute
|
|
2
|
+
// same-origin URL. Relative url() would 404: the CSS is compiled into a temp
|
|
3
|
+
// dir (dev) or hashed into dist/assets (prod), away from the source fonts.
|
|
4
|
+
// data: URIs are out because apps run with `font-src 'self'`.
|
|
5
|
+
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
|
|
8
|
+
export const RENDERER_WEB_FONTS_URL_PREFIX = "/assets/kumiko/fonts/";
|
|
9
|
+
|
|
10
|
+
/** dist-relative folder the prod build copies the fonts into. */
|
|
11
|
+
export const RENDERER_WEB_FONTS_DIST_DIR = "assets/kumiko/fonts";
|
|
12
|
+
|
|
13
|
+
/** Allowlist for servable font file names; also rules out path segments. */
|
|
14
|
+
export const RENDERER_WEB_FONT_FILE_PATTERN = /^[a-z0-9-]+\.woff2$/;
|
|
15
|
+
|
|
16
|
+
const hasBun = typeof (globalThis as { Bun?: unknown }).Bun !== "undefined";
|
|
17
|
+
|
|
18
|
+
/** Directory of the woff2 files next to renderer-web's styles.css, or
|
|
19
|
+
* undefined when the package can't be resolved from any `searchFrom` dir
|
|
20
|
+
* (or outside Bun, which does the package-exports resolution). */
|
|
21
|
+
export function resolveRendererWebFontsDir(searchFrom: readonly string[]): string | undefined {
|
|
22
|
+
if (!hasBun) return undefined;
|
|
23
|
+
const bun = (globalThis as { Bun: { resolveSync: (id: string, from: string) => string } }).Bun;
|
|
24
|
+
for (const from of searchFrom) {
|
|
25
|
+
try {
|
|
26
|
+
const stylesheet = bun.resolveSync("@cosmicdrift/kumiko-renderer-web/styles.css", from);
|
|
27
|
+
return join(dirname(stylesheet), "fonts");
|
|
28
|
+
} catch {
|
|
29
|
+
// try the next search root
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
@@ -110,6 +110,8 @@ export function mimeTypeFor(filePath: string): string {
|
|
|
110
110
|
return "application/xml; charset=utf-8";
|
|
111
111
|
case "webmanifest":
|
|
112
112
|
return "application/manifest+json";
|
|
113
|
+
case "woff2":
|
|
114
|
+
return "font/woff2";
|
|
113
115
|
default:
|
|
114
116
|
return "application/octet-stream";
|
|
115
117
|
}
|
package/src/run-prod-app.ts
CHANGED
|
@@ -149,6 +149,7 @@ import Redis from "ioredis";
|
|
|
149
149
|
import { applyBootSeeds } from "./boot/apply-boot-seeds";
|
|
150
150
|
import { resolveBootCrypto } from "./boot/boot-crypto";
|
|
151
151
|
import { jobRunLoggerCallbacks } from "./boot/job-run-logger";
|
|
152
|
+
import { startPiiEventBackfillOnBoot } from "./boot/pii-event-backfill-on-boot";
|
|
152
153
|
import { buildBunServeOptions, resolveDerivedMaxRequestBodySize } from "./bun-serve-options";
|
|
153
154
|
import { buildComposeAuthOptions, composeFeatures } from "./compose-features";
|
|
154
155
|
import { makeDispatchSystemWrite, type SystemWireDeps } from "./extra-routes-deps";
|
|
@@ -1301,6 +1302,12 @@ export async function runProdApp(options: RunProdAppOptions): Promise<ProdAppHan
|
|
|
1301
1302
|
}
|
|
1302
1303
|
|
|
1303
1304
|
await entrypoint.start();
|
|
1305
|
+
startPiiEventBackfillOnBoot({
|
|
1306
|
+
db,
|
|
1307
|
+
registry,
|
|
1308
|
+
lifecycle: entrypoint.lifecycle,
|
|
1309
|
+
envSource,
|
|
1310
|
+
});
|
|
1304
1311
|
|
|
1305
1312
|
// 11. Build the fetch-handler. Static-fallback for non-/api/ paths
|
|
1306
1313
|
// wired via a wrapper so Hono owns /api/* + extraRoutes and disk
|
package/src/run-worker-app.ts
CHANGED
|
@@ -60,6 +60,7 @@ import { warnIfNonUtcServerTimeZone } from "@cosmicdrift/kumiko-framework/time";
|
|
|
60
60
|
import Redis from "ioredis";
|
|
61
61
|
import { resolveBootCrypto } from "./boot/boot-crypto";
|
|
62
62
|
import { jobRunLoggerCallbacks } from "./boot/job-run-logger";
|
|
63
|
+
import { startPiiEventBackfillOnBoot } from "./boot/pii-event-backfill-on-boot";
|
|
63
64
|
import { composeFeatures } from "./compose-features";
|
|
64
65
|
import { makeDispatchSystemWrite, type SystemWireDeps } from "./extra-routes-deps";
|
|
65
66
|
import { assertPiiBootInvariants } from "./pii-boot-gate";
|
|
@@ -320,6 +321,12 @@ export async function runWorkerApp(options: RunWorkerAppOptions): Promise<Worker
|
|
|
320
321
|
};
|
|
321
322
|
|
|
322
323
|
await entrypoint.start();
|
|
324
|
+
startPiiEventBackfillOnBoot({
|
|
325
|
+
db,
|
|
326
|
+
registry,
|
|
327
|
+
lifecycle: entrypoint.lifecycle,
|
|
328
|
+
envSource,
|
|
329
|
+
});
|
|
323
330
|
|
|
324
331
|
if (options.wireComponents) {
|
|
325
332
|
await options.wireComponents({
|