@cosmicdrift/kumiko-server-runtime 0.385.0 → 0.387.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/LICENSE +21 -57
- package/dist/boot/boot-crypto.js +1 -2
- package/dist/boot/worker-boot-core.js +2 -2
- package/dist/build-prod-bundle.d.ts +16 -15
- package/dist/build-prod-bundle.js +106 -108
- package/dist/extra-routes-deps.d.ts +1 -1
- package/dist/index.js +4 -4
- package/dist/pii-boot-gate.js +1 -1
- package/dist/resolve-tailwind-cli.js +5 -7
- package/dist/run-prod-app-boot-context.d.ts +2 -2
- package/dist/run-prod-app-boot-context.js +21 -22
- package/dist/run-prod-app-static-files.js +43 -50
- package/dist/run-prod-app.d.ts +73 -85
- package/dist/run-prod-app.js +97 -114
- package/dist/run-worker-app.js +5 -5
- package/dist/session-boot-gate.d.ts +1 -1
- package/dist/session-boot-gate.js +1 -1
- package/dist/session-wiring.d.ts +1 -1
- package/dist/session-wiring.js +1 -1
- package/dist/try-hono-first.js +1 -1
- package/package.json +8 -8
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
// buildProdBundle — Production
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// produzieren ein deploybares dist/.
|
|
1
|
+
// buildProdBundle — Production build for Kumiko apps. A generic build step
|
|
2
|
+
// with no app-specific knowledge: convention discovery reads the app structure;
|
|
3
|
+
// Bun.build + Tailwind + public-folder copying produce a deployable dist/.
|
|
5
4
|
//
|
|
6
|
-
// Client entries (
|
|
5
|
+
// Client entries (explicit, no filename inference):
|
|
7
6
|
//
|
|
8
7
|
// package.json "kumiko.clientEntries": [{ name, sourceFile, htmlPath? }]
|
|
9
8
|
// → one Bun.build bundle per declared entry
|
|
@@ -14,23 +13,22 @@
|
|
|
14
13
|
// Convention for everything else (all optional, missing → skipped):
|
|
15
14
|
//
|
|
16
15
|
// src/styles.css → Tailwind one-shot
|
|
17
|
-
// (
|
|
18
|
-
//
|
|
19
|
-
// public/ → rsync 1:1 (
|
|
20
|
-
// public/index.html | index.html →
|
|
16
|
+
// (or fallback to @cosmicdrift/kumiko-renderer/web/styles.css
|
|
17
|
+
// when only clientEntry exists and no custom CSS)
|
|
18
|
+
// public/ → rsync 1:1 (no hash — URLs are user-facing)
|
|
19
|
+
// public/index.html | index.html → template with placeholder tags replaced:
|
|
21
20
|
// <script type="module" src="/client.js"> → /assets/client-<hash>.js
|
|
22
21
|
// <link href="/styles.css"> → /assets/styles-<hash>.css
|
|
23
|
-
// (
|
|
22
|
+
// (no HTML, vanilla) → default HTML without asset tags
|
|
24
23
|
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
// auch im Dev-Server liefert.
|
|
24
|
+
// Failure mode: if client.tsx or Tailwind produced output but the HTML has no
|
|
25
|
+
// matching placeholder, the build throws with the exact snippet to copy in.
|
|
26
|
+
// No silent injection — the HTML should behave as it does in the dev server.
|
|
29
27
|
//
|
|
30
28
|
// Output:
|
|
31
29
|
//
|
|
32
30
|
// dist/
|
|
33
|
-
// index.html ←
|
|
31
|
+
// index.html ← tags with hashed URLs
|
|
34
32
|
// assets/
|
|
35
33
|
// client-<hash>.js ← entry
|
|
36
34
|
// <chunk>-<hash>.js ← split chunks
|
|
@@ -41,12 +39,12 @@
|
|
|
41
39
|
// csp-hashes.json ← sha256 of every inline <script> in the HTML above
|
|
42
40
|
// <public/* 1:1> ← favicon.ico, robots.txt, og-image.png, …
|
|
43
41
|
//
|
|
44
|
-
// Cache
|
|
42
|
+
// Cache headers (set by runProdApp, not here):
|
|
45
43
|
//
|
|
46
44
|
// /assets/* → public, max-age=31536000, immutable
|
|
47
45
|
// /index.html, /sw.js → no-cache, must-revalidate
|
|
48
46
|
// /manifest.json → no-cache
|
|
49
|
-
//
|
|
47
|
+
// everything else (public/) → default (auto-cache)
|
|
50
48
|
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
51
49
|
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
52
50
|
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
@@ -66,18 +64,18 @@ import { BUNDLED_ASSETS_DIST_DIR, readBundledAssetDeclarations, resolveBundledAs
|
|
|
66
64
|
import { CSP_HASHES_FILE, inlineScriptHashes } from "./csp-hashes.js";
|
|
67
65
|
import { RENDERER_WEB_FONT_FILE_PATTERN, RENDERER_WEB_FONTS_DIST_DIR, resolveRendererWebFontsDir, } from "./renderer-web-fonts.js";
|
|
68
66
|
import { canResolveTailwindStylesheet, resolveTailwindCli } from "./resolve-tailwind-cli.js";
|
|
69
|
-
// Bun
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
67
|
+
// Bun runtime check as a module-level constant: every build step (Tailwind via
|
|
68
|
+
// Bun.spawn, client bundling via Bun.build, and stylesheet resolution via
|
|
69
|
+
// Bun.resolveSync) is Bun-only. Per-function inline checks could diverge; one
|
|
70
|
+
// constant keeps the checks consistent.
|
|
73
71
|
const hasBun = typeof Bun !== "undefined";
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
// (
|
|
77
|
-
//
|
|
78
|
-
// interactive-widget=resizes-content
|
|
79
|
-
//
|
|
80
|
-
//
|
|
72
|
+
// The default HTML is used only when the app author provides no index.html.
|
|
73
|
+
// It has no asset placeholders because this path is intended for vanilla apps
|
|
74
|
+
// (public/ only); apps that need JS/CSS must provide an index.html with the
|
|
75
|
+
// appropriate placeholder tags.
|
|
76
|
+
// interactive-widget=resizes-content: keeps `position: fixed` bottom bars
|
|
77
|
+
// anchored above a mobile keyboard instead of behind it. iOS Safari ignores
|
|
78
|
+
// the key; renderer-web's useKeyboardInset covers it via window.visualViewport.
|
|
81
79
|
export const DEFAULT_HTML = `<!doctype html>
|
|
82
80
|
<html lang="en">
|
|
83
81
|
<head>
|
|
@@ -91,8 +89,8 @@ export const DEFAULT_HTML = `<!doctype html>
|
|
|
91
89
|
</html>
|
|
92
90
|
`;
|
|
93
91
|
/** Folder name relative to dist/ for hashed assets (JS, CSS, file-loader
|
|
94
|
-
* outputs). Exported
|
|
95
|
-
*
|
|
92
|
+
* outputs). Exported so runProdApp uses the same convention for cache headers
|
|
93
|
+
* and cannot drift. */
|
|
96
94
|
export const ASSETS_DIR = "assets";
|
|
97
95
|
const ASSET_LOADERS = {
|
|
98
96
|
".png": "file",
|
|
@@ -111,7 +109,7 @@ export async function buildProdBundle(options = {}) {
|
|
|
111
109
|
const cwd = resolve(options.cwd ?? process.cwd());
|
|
112
110
|
const outDir = resolve(cwd, options.outDir ?? "dist");
|
|
113
111
|
const assetsDir = join(outDir, ASSETS_DIR);
|
|
114
|
-
// 1. Discovery
|
|
112
|
+
// 1. Discovery
|
|
115
113
|
const clientEntries = resolveClientEntries(cwd, options);
|
|
116
114
|
const firstClientSource = clientEntries[0]?.sourceFile;
|
|
117
115
|
const stylesheet = resolveStylesheetEntry(cwd, firstClientSource, options.stylesheet);
|
|
@@ -128,9 +126,9 @@ export async function buildProdBundle(options = {}) {
|
|
|
128
126
|
await mkdir(outDir, { recursive: true });
|
|
129
127
|
await mkdir(assetsDir, { recursive: true });
|
|
130
128
|
const manifest = {};
|
|
131
|
-
// 3. Tailwind one-shot (
|
|
132
|
-
//
|
|
133
|
-
//
|
|
129
|
+
// 3. Tailwind one-shot (before JS, because the JS loader would encounter .css
|
|
130
|
+
// if client.tsx imports "./foo.css" — that case is not supported here;
|
|
131
|
+
// Tailwind is the only CSS source).
|
|
134
132
|
if (stylesheet) {
|
|
135
133
|
const css = await runTailwindOnce(stylesheet.path, cwd);
|
|
136
134
|
assertRendererWebShellPresent(css, stylesheet);
|
|
@@ -140,19 +138,19 @@ export async function buildProdBundle(options = {}) {
|
|
|
140
138
|
manifest["styles.css"] = `/${ASSETS_DIR}/${filename}`;
|
|
141
139
|
await copyRendererWebFonts(cwd, outDir);
|
|
142
140
|
}
|
|
143
|
-
// 4. Bun.build
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
// sich den renderer-web-core).
|
|
141
|
+
// 4. Bun.build per entry (multi-entry produces N bundles + shared chunks).
|
|
142
|
+
// A single Bun.build call with all entrypoints deduplicates shared chunks
|
|
143
|
+
// and keeps hashes deterministic, matching publicstatus's split-tree
|
|
144
|
+
// pattern (admin + public share renderer-web-core).
|
|
148
145
|
if (clientEntries.length > 0) {
|
|
149
146
|
const built = await buildClientBundles(clientEntries, assetsDir);
|
|
150
147
|
for (const [manifestKey, filename] of Object.entries(built)) {
|
|
151
148
|
manifest[manifestKey] = `/${ASSETS_DIR}/${filename}`;
|
|
152
149
|
}
|
|
153
150
|
}
|
|
154
|
-
// 5. Public-
|
|
155
|
-
//
|
|
151
|
+
// 5. Public-folder rsync (excluding index.html / *.html templates, which are
|
|
152
|
+
// rendered separately). The filter list contains the entries' template
|
|
153
|
+
// basenames.
|
|
156
154
|
const templateBasenames = new Set(clientEntries.map((e) => basenameOf(e.htmlPath)));
|
|
157
155
|
if (hasPublicDir) {
|
|
158
156
|
await copyPublicFolder(publicDir, outDir, templateBasenames);
|
|
@@ -160,21 +158,21 @@ export async function buildProdBundle(options = {}) {
|
|
|
160
158
|
// 5a. Declared read-only assets (package.json "kumiko.assets"), copied after
|
|
161
159
|
// public/ so a same-named public file cannot shadow them.
|
|
162
160
|
await copyBundledAssets(cwd, outDir, bundledAssets);
|
|
163
|
-
// 5b. Build
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
// ponytail: id
|
|
167
|
-
//
|
|
168
|
-
//
|
|
161
|
+
// 5b. Build identity: hash of the content-hashed asset URLs plus a readable
|
|
162
|
+
// timestamp. Baked into index.html and written to build-info.json
|
|
163
|
+
// (update awareness, enabled by default).
|
|
164
|
+
// ponytail: id depends only on assets — a server-only deploy with no
|
|
165
|
+
// client-bundle change keeps the id, so no banner appears (the UI is not
|
|
166
|
+
// stale). Couple it to the server version when needed.
|
|
169
167
|
const buildInfo = Object.keys(manifest).length > 0
|
|
170
168
|
? { id: computeBuildId(manifest), builtAt: Temporal.Now.instant().toString() }
|
|
171
169
|
: undefined;
|
|
172
|
-
// 6. HTML
|
|
173
|
-
//
|
|
174
|
-
//
|
|
170
|
+
// 6. Render HTML for each entry. Convention: one HTML file per client entry,
|
|
171
|
+
// each with its own script tag. The server (runProdApp.hostDispatch)
|
|
172
|
+
// serves the matching file for each host.
|
|
175
173
|
if (clientEntries.length === 0) {
|
|
176
|
-
// Vanilla
|
|
177
|
-
//
|
|
174
|
+
// Vanilla public-only app: no HTML files need rendering; the public folder
|
|
175
|
+
// was already copied.
|
|
178
176
|
}
|
|
179
177
|
else {
|
|
180
178
|
for (const entry of clientEntries) {
|
|
@@ -204,7 +202,7 @@ const SOURCE_EXTENSION_PATTERN = /\.(?:tsx?|jsx?)$/;
|
|
|
204
202
|
* `kumiko-build`/`kumiko build` via `@cosmicdrift/kumiko-dev-server/build`,
|
|
205
203
|
* not just from unit tests.
|
|
206
204
|
*
|
|
207
|
-
* Precedence (
|
|
205
|
+
* Precedence (explicit declaration, no filename inference):
|
|
208
206
|
* 1. `declared.clientEntry` and `declared.clientEntries` both set → throw.
|
|
209
207
|
* 2. `declared.clientEntries` (non-empty) → one entry per declaration.
|
|
210
208
|
* 3. `declared.clientEntry` → single entry, name "client".
|
|
@@ -320,7 +318,7 @@ function resolveConventionEntries(cwd) {
|
|
|
320
318
|
}
|
|
321
319
|
return [];
|
|
322
320
|
}
|
|
323
|
-
//
|
|
321
|
+
// Legacy multi-entry convention: any src/client-<suffix>.tsx(x) became
|
|
324
322
|
// its own bundle. Detecting these without a declaration means an unmigrated
|
|
325
323
|
// app would silently ship without their bundles — throw instead.
|
|
326
324
|
function findLegacyClientFiles(cwd) {
|
|
@@ -436,9 +434,10 @@ function discoverHtmlTemplateFor(cwd, basename) {
|
|
|
436
434
|
}
|
|
437
435
|
return undefined;
|
|
438
436
|
}
|
|
439
|
-
//
|
|
440
|
-
// (app-layout.tsx: "flex min-h-screen …").
|
|
441
|
-
//
|
|
437
|
+
// The outer wrapper of DefaultAppShell/WorkspaceShell carries this utility
|
|
438
|
+
// (app-layout.tsx: "flex min-h-screen …"). It is a real Tailwind utility in
|
|
439
|
+
// the compiled output, so it provides a stable substring sentinel without CSS
|
|
440
|
+
// escaping.
|
|
442
441
|
const RENDERER_WEB_SHELL_SENTINEL = "min-h-screen";
|
|
443
442
|
function resolveStylesheetEntry(cwd, clientEntry, override) {
|
|
444
443
|
if (override === false)
|
|
@@ -446,13 +445,13 @@ function resolveStylesheetEntry(cwd, clientEntry, override) {
|
|
|
446
445
|
if (typeof override === "string") {
|
|
447
446
|
return { path: resolve(cwd, override), isRendererWebFallback: false };
|
|
448
447
|
}
|
|
449
|
-
//
|
|
448
|
+
// An app's own styles.css takes precedence over the default.
|
|
450
449
|
const local = resolve(cwd, "src/styles.css");
|
|
451
450
|
if (existsSync(local))
|
|
452
451
|
return { path: local, isRendererWebFallback: false };
|
|
453
|
-
//
|
|
454
|
-
// Sample
|
|
455
|
-
//
|
|
452
|
+
// Otherwise, fall back to renderer-web/styles.css only when a client exists.
|
|
453
|
+
// Sample apps and showcases all use this, matching the dev server so local
|
|
454
|
+
// and production builds behave identically.
|
|
456
455
|
if (!clientEntry)
|
|
457
456
|
return undefined;
|
|
458
457
|
if (!hasBun)
|
|
@@ -468,12 +467,12 @@ function resolveStylesheetEntry(cwd, clientEntry, override) {
|
|
|
468
467
|
return undefined;
|
|
469
468
|
}
|
|
470
469
|
}
|
|
471
|
-
// Build-
|
|
472
|
-
// web/styles.css
|
|
473
|
-
//
|
|
474
|
-
//
|
|
475
|
-
//
|
|
476
|
-
// @internal —
|
|
470
|
+
// Build-time guard against unstyled production output: when the build falls
|
|
471
|
+
// back to renderer-web/styles.css but the compiled CSS lacks the shell sentinel,
|
|
472
|
+
// DefaultAppShell styles were not scanned (renderer-web is not installed where
|
|
473
|
+
// Tailwind scans, or an @source regression occurred).
|
|
474
|
+
// Fail rather than shipping a 15KB image that renders unstyled in production.
|
|
475
|
+
// @internal — exported only for unit tests.
|
|
477
476
|
export function assertRendererWebShellPresent(css, stylesheet) {
|
|
478
477
|
// skip: not the renderer-web fallback stylesheet, invariant doesn't apply
|
|
479
478
|
if (!stylesheet.isRendererWebFallback)
|
|
@@ -517,7 +516,7 @@ async function copyBundledAssets(cwd, outDir, declarations) {
|
|
|
517
516
|
await cp(source, join(target, declaration.name));
|
|
518
517
|
}
|
|
519
518
|
}
|
|
520
|
-
// @internal — exported
|
|
519
|
+
// @internal — exported only for unit tests.
|
|
521
520
|
export function discoverHtmlTemplate(cwd) {
|
|
522
521
|
for (const candidate of ["index.html", "public/index.html"]) {
|
|
523
522
|
const path = resolve(cwd, candidate);
|
|
@@ -529,7 +528,7 @@ export function discoverHtmlTemplate(cwd) {
|
|
|
529
528
|
// ---------------------------------------------------------------------------
|
|
530
529
|
// Build steps
|
|
531
530
|
// ---------------------------------------------------------------------------
|
|
532
|
-
// @internal —
|
|
531
|
+
// @internal — exported only for unit tests (relocation test for the @source layout).
|
|
533
532
|
export async function runTailwindOnce(entry, cwd) {
|
|
534
533
|
if (!hasBun) {
|
|
535
534
|
throw new Error("[kumiko build] Tailwind one-shot requires Bun (Bun.spawn) — run via `bun run …` or `bun kumiko build`.");
|
|
@@ -602,9 +601,9 @@ export function isEntryOutputFor(outName, baseName) {
|
|
|
602
601
|
return false;
|
|
603
602
|
return /^[A-Za-z0-9]+$/.test(outName.slice(prefix.length, -".js".length));
|
|
604
603
|
}
|
|
605
|
-
/** Multi-entry
|
|
606
|
-
* chunks
|
|
607
|
-
* manifestKey → hashed
|
|
604
|
+
/** Multi-entry build: one Bun.build call with all entrypoints, so shared
|
|
605
|
+
* chunks are deduplicated and hashes remain deterministic. Returns a map of
|
|
606
|
+
* manifestKey → hashed filename (basename, without the /assets/ prefix). */
|
|
608
607
|
async function buildClientBundles(entries, outDir) {
|
|
609
608
|
if (!hasBun) {
|
|
610
609
|
throw new Error("[kumiko build] requires Bun — run via `bun run …` or `bun kumiko build`.");
|
|
@@ -615,9 +614,9 @@ async function buildClientBundles(entries, outDir) {
|
|
|
615
614
|
target: "browser",
|
|
616
615
|
splitting: true,
|
|
617
616
|
minify: true,
|
|
618
|
-
//
|
|
619
|
-
//
|
|
620
|
-
//
|
|
617
|
+
// No source maps in production: 1.6 MB+ of container waste, plus exposed
|
|
618
|
+
// reverse-engineerable source code. Development has its own sourcemaps via
|
|
619
|
+
// create-kumiko-server.ts.
|
|
621
620
|
sourcemap: "none",
|
|
622
621
|
naming: {
|
|
623
622
|
entry: "[name]-[hash].[ext]",
|
|
@@ -637,9 +636,9 @@ async function buildClientBundles(entries, outDir) {
|
|
|
637
636
|
if (entryOutputs.length !== entries.length) {
|
|
638
637
|
throw new Error(`[kumiko build] expected ${entries.length} entry-point outputs, got ${entryOutputs.length}`);
|
|
639
638
|
}
|
|
640
|
-
// Bun.build
|
|
641
|
-
// `src/client-admin.tsx` → `client-admin-<hash>.js`.
|
|
642
|
-
//
|
|
639
|
+
// Bun.build names entry files after the source basename (without the extension):
|
|
640
|
+
// `src/client-admin.tsx` → `client-admin-<hash>.js`. Map each entry output
|
|
641
|
+
// back to its ClientEntry by matching the basename.
|
|
643
642
|
const result = {};
|
|
644
643
|
for (const entry of entries) {
|
|
645
644
|
const baseName = (entry.sourceFile.split("/").pop() ?? "").replace(SOURCE_EXTENSION_PATTERN, "");
|
|
@@ -655,9 +654,9 @@ function basenameOf(p) {
|
|
|
655
654
|
return p.split("/").pop() ?? p;
|
|
656
655
|
}
|
|
657
656
|
async function copyPublicFolder(src, dst, templateBasenames) {
|
|
658
|
-
// HTML
|
|
659
|
-
//
|
|
660
|
-
//
|
|
657
|
+
// HTML templates are rendered separately rather than copied blindly, because
|
|
658
|
+
// copying them would overwrite the injected version (for example index.html
|
|
659
|
+
// or admin.html in a multi-entry build).
|
|
661
660
|
await cp(src, dst, {
|
|
662
661
|
recursive: true,
|
|
663
662
|
filter: (source) => {
|
|
@@ -674,10 +673,10 @@ async function copyPublicFolder(src, dst, templateBasenames) {
|
|
|
674
673
|
// HTML render
|
|
675
674
|
// ---------------------------------------------------------------------------
|
|
676
675
|
async function renderHtml(templatePath, manifest, entry, buildInfo) {
|
|
677
|
-
// Edge
|
|
678
|
-
//
|
|
679
|
-
//
|
|
680
|
-
//
|
|
676
|
+
// Edge case: no custom HTML template exists, but Bun.build or Tailwind produced
|
|
677
|
+
// output. DEFAULT_HTML has no placeholders (it is a vanilla template), so
|
|
678
|
+
// injectAssetTags would fail anyway. Report a clear error with a snippet to
|
|
679
|
+
// copy in.
|
|
681
680
|
if (!templatePath && Object.keys(manifest).length > 0) {
|
|
682
681
|
throw new Error(buildMissingTemplateError(manifest, entry));
|
|
683
682
|
}
|
|
@@ -691,7 +690,7 @@ function buildMissingTemplateError(manifest, entry) {
|
|
|
691
690
|
? ` <link rel="stylesheet" href="/styles.css" />\n`
|
|
692
691
|
: "";
|
|
693
692
|
const jsLine = manifest[entry.manifestKey]
|
|
694
|
-
? // html-ok: Error-
|
|
693
|
+
? // html-ok: Error-help text (tag snippet as text), never rendered.
|
|
695
694
|
` <script type="module" src="/${entry.manifestKey}"></script>\n`
|
|
696
695
|
: "";
|
|
697
696
|
const sourceBasename = basenameOf(entry.sourceFile);
|
|
@@ -723,18 +722,17 @@ function buildMissingTemplateError(manifest, entry) {
|
|
|
723
722
|
`Der Build ersetzt /styles.css und /${entry.manifestKey} durch die gehashten URLs.`);
|
|
724
723
|
}
|
|
725
724
|
const BUILD_META_NAME = "kumiko-build";
|
|
726
|
-
// @internal — exported
|
|
725
|
+
// @internal — exported only for unit tests.
|
|
727
726
|
//
|
|
728
|
-
//
|
|
729
|
-
//
|
|
730
|
-
// - `<script type="module" src="/client
|
|
731
|
-
// - `<
|
|
732
|
-
//
|
|
733
|
-
// Der Build ersetzt sie durch die gehashten URLs.
|
|
727
|
+
// The HTML template MUST contain placeholder tags for every asset of this entry:
|
|
728
|
+
// - `<script type="module" src="/client.js">` for single-mode entry "client"
|
|
729
|
+
// - `<script type="module" src="/client-<name>.js">` for multi-mode entry "<name>"
|
|
730
|
+
// - `<link href="/styles.css">` for styles shared by all entries
|
|
731
|
+
// The build replaces them with hashed URLs.
|
|
734
732
|
//
|
|
735
|
-
//
|
|
736
|
-
//
|
|
737
|
-
//
|
|
733
|
+
// If an expected tag is missing, the build throws an error with the exact
|
|
734
|
+
// snippet to copy in — no silent injection, which would hide differences
|
|
735
|
+
// between development and production HTML.
|
|
738
736
|
export function injectAssetTags(html, manifest, entry, buildInfo) {
|
|
739
737
|
let result = html;
|
|
740
738
|
// Meta tag instead of an inline script: strict CSPs (script-src 'self') block inline scripts.
|
|
@@ -755,12 +753,12 @@ export function injectAssetTags(html, manifest, entry, buildInfo) {
|
|
|
755
753
|
hashedAssetHint: "/assets/styles-<hash>.css",
|
|
756
754
|
}));
|
|
757
755
|
}
|
|
758
|
-
// html-ok: cssUrl
|
|
756
|
+
// html-ok: cssUrl is a self-generated manifest asset path (hashed name).
|
|
759
757
|
result = result.replace(placeholder[0], `<link rel="stylesheet" href="${cssUrl}" />`);
|
|
760
758
|
}
|
|
761
759
|
const jsUrl = manifest[entry.manifestKey];
|
|
762
760
|
if (jsUrl && !result.includes(jsUrl)) {
|
|
763
|
-
// Placeholder
|
|
761
|
+
// Placeholder pattern: src="/client.js" or src="/client-<name>.js"
|
|
764
762
|
const placeholderRx = new RegExp(`<script\\b[^>]*src="\\/${entry.manifestKey.replace(/\./g, "\\.")}"[^>]*><\\/script>`);
|
|
765
763
|
const placeholder = placeholderRx.exec(result);
|
|
766
764
|
if (!placeholder) {
|
|
@@ -773,13 +771,13 @@ export function injectAssetTags(html, manifest, entry, buildInfo) {
|
|
|
773
771
|
hashedAssetHint: `/assets/${baseAssetName}-<hash>.js`,
|
|
774
772
|
}));
|
|
775
773
|
}
|
|
776
|
-
// html-ok: jsUrl
|
|
774
|
+
// html-ok: jsUrl is a self-generated manifest asset path (hashed name).
|
|
777
775
|
result = result.replace(placeholder[0], `<script type="module" src="${jsUrl}"></script>`);
|
|
778
776
|
}
|
|
779
777
|
return result;
|
|
780
778
|
}
|
|
781
|
-
/**
|
|
782
|
-
* (script
|
|
779
|
+
/** Consistent error format for missing asset tags in the HTML template
|
|
780
|
+
* (the script tag for the JS bundle or the stylesheet link for Tailwind). */
|
|
783
781
|
function buildMissingTagError(args) {
|
|
784
782
|
const tpl = basenameOf(args.htmlPath);
|
|
785
783
|
return (`[kumiko build] ${tpl} hat keinen Entry-Tag für /${args.assetKey} — füg ${args.insertHint} ein:\n` +
|
|
@@ -794,10 +792,10 @@ function buildMissingTagError(args) {
|
|
|
794
792
|
function shortHash(content) {
|
|
795
793
|
return createHash("sha256").update(content).digest("hex").slice(0, 8);
|
|
796
794
|
}
|
|
797
|
-
// Build
|
|
798
|
-
//
|
|
799
|
-
//
|
|
800
|
-
// @internal — exported
|
|
795
|
+
// Build identity from the content-hashed asset URLs. Sorted for determinism
|
|
796
|
+
// (manifest-key order does not matter). Identical source produces the same id;
|
|
797
|
+
// a changed asset produces a different id.
|
|
798
|
+
// @internal — exported only for unit tests.
|
|
801
799
|
export function computeBuildId(manifest) {
|
|
802
800
|
return createHash("sha256")
|
|
803
801
|
.update(Object.values(manifest).sort().join("\n"))
|
|
@@ -807,9 +805,9 @@ export function computeBuildId(manifest) {
|
|
|
807
805
|
// ---------------------------------------------------------------------------
|
|
808
806
|
// CLI output
|
|
809
807
|
// ---------------------------------------------------------------------------
|
|
810
|
-
/**
|
|
811
|
-
*
|
|
812
|
-
*
|
|
808
|
+
/** Formats a BuildResult as a CLI-friendly, multi-line summary with ANSI
|
|
809
|
+
* colors. Used by both `kumiko build` and the hoisted `kumiko-build` binary
|
|
810
|
+
* so their output remains consistent. */
|
|
813
811
|
export function formatBuildResult(result, durationMs) {
|
|
814
812
|
const dim = "\x1b[2m";
|
|
815
813
|
const green = "\x1b[32m";
|
|
@@ -7,7 +7,7 @@ import type { Redis } from "ioredis";
|
|
|
7
7
|
export type { SystemDispatchArgs } from "@cosmicdrift/kumiko-framework/api";
|
|
8
8
|
export { makeDispatchSystemQuery, makeDispatchSystemWrite, } from "@cosmicdrift/kumiko-framework/api";
|
|
9
9
|
/** Deps for the `wire` hook (runProdApp/createKumikoServer, after
|
|
10
|
-
* buildServer has mounted `extraRoutes`
|
|
10
|
+
* buildServer has mounted `extraRoutes`) and
|
|
11
11
|
* for `runWorkerApp.wireComponents`. Naming: `deps` not `ctx` because
|
|
12
12
|
* `ctx` is the framework's HandlerContext (user/tenant/registry) — this
|
|
13
13
|
* scope is deliberately smaller and has NO `app` (routes are declared via
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// Public API
|
|
2
|
-
// runDevApp (kumiko-dev-server),
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// Public API for Kumiko's production-server boot. Symmetric with
|
|
2
|
+
// runDevApp (kumiko-dev-server), but without Dev/Scaffold/Codegen
|
|
3
|
+
// tooling (ts-morph) as a dependency, so production apps do not pull
|
|
4
|
+
// development tooling into their node_modules.
|
|
5
5
|
export { resolveBillingOption } from "./boot-options/billing-option.js";
|
|
6
6
|
export { buildDemoModeRoute, withDemoReadOnlyFetch, } from "./boot-options/demo-mode.js";
|
|
7
7
|
export { resolveFeatureTogglesOption, } from "./boot-options/feature-toggles-option.js";
|
package/dist/pii-boot-gate.js
CHANGED
|
@@ -13,7 +13,7 @@ export function assertPiiBootInvariants(features, opts) {
|
|
|
13
13
|
const piiEntities = features.flatMap((feature) => Object.entries(feature.entities ?? {})
|
|
14
14
|
.filter(([, entity]) => collectPiiSubjectFields(entity).length > 0)
|
|
15
15
|
.map(([name]) => name));
|
|
16
|
-
//
|
|
16
|
+
// A declared event stance is worth as much as an entity annotation —
|
|
17
17
|
// encryptEventPayloadPii no-ops without a KMS, so a catalogued event writes
|
|
18
18
|
// plaintext PII into kumiko_events with nothing to crypto-shred later.
|
|
19
19
|
const piiEvents = features.flatMap((feature) => Object.values(feature.events ?? {})
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// mit `FailedToOpenSocket` stirbt.
|
|
1
|
+
// Resolves the locally installed @tailwindcss/cli binary to its absolute path.
|
|
2
|
+
// We avoid `bunx` because it still requests the registry manifest for a locally
|
|
3
|
+
// installed package and fails with `FailedToOpenSocket` when offline.
|
|
5
4
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// booten.
|
|
5
|
+
// Kept separate from create-kumiko-server.ts so the Bun and resolution-failure
|
|
6
|
+
// branches can be tested under Vitest/Node without booting the server.
|
|
9
7
|
import { dirname, resolve } from "node:path";
|
|
10
8
|
export function resolveTailwindCli(deps) {
|
|
11
9
|
if (deps.bun === undefined)
|
|
@@ -39,8 +39,8 @@ type BootExtraContextOptions = {
|
|
|
39
39
|
readonly appOverrides?: ConfigOverridesInput;
|
|
40
40
|
};
|
|
41
41
|
export declare function buildBootExtraContext(opts: BootExtraContextOptions): Record<string, unknown>;
|
|
42
|
-
/**
|
|
43
|
-
*
|
|
42
|
+
/** The auth fields read and normalized by resolveAuthMail; both
|
|
43
|
+
* app auth types (prod + dev) satisfy this structurally. */
|
|
44
44
|
type AuthMailNormalizable = {
|
|
45
45
|
readonly mail?: AuthMailOptions;
|
|
46
46
|
readonly passwordReset?: PasswordResetSetup;
|
|
@@ -17,14 +17,14 @@ import { resolveBootCrypto } from "./boot/boot-crypto.js";
|
|
|
17
17
|
// Boot-time context helpers for runProdApp: ctx-extra-context wiring
|
|
18
18
|
// (templateResolver/delivery/secrets/config-resolver), auth-mail convenience
|
|
19
19
|
// normalization, and prod session-auth wiring. Split out of run-prod-app.ts
|
|
20
|
-
//
|
|
20
|
+
// — mechanical relocation, these are self-contained pure
|
|
21
21
|
// functions, no closure over runProdApp's local boot state.
|
|
22
22
|
// Shared with runDevApp (mergeConfigResolverDefault) for dev/prod parity.
|
|
23
23
|
export function addConfigAccessorFactory(resolved, registry) {
|
|
24
24
|
if (!resolved.configResolver)
|
|
25
25
|
return resolved;
|
|
26
26
|
// Both keys, always overriding an app-supplied value: the factory must derive from the
|
|
27
|
-
// effective resolver
|
|
27
|
+
// effective resolver; the underscore alias stays until the next major.
|
|
28
28
|
const configAccessorFactory = createConfigAccessorFactory(registry, resolved.configResolver);
|
|
29
29
|
return {
|
|
30
30
|
...resolved,
|
|
@@ -32,20 +32,19 @@ export function addConfigAccessorFactory(resolved, registry) {
|
|
|
32
32
|
_configAccessorFactory: configAccessorFactory,
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
-
// Framework-
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
// (
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
// passiert beim Caller (App gewinnt).
|
|
35
|
+
// Framework-default providers for AppContext, using the same mechanism as
|
|
36
|
+
// tenantTierResolver auto-wiring (findTierResolverUsage): a mounted feature declares
|
|
37
|
+
// the need, the default comes from db/env, and the app can override the exception.
|
|
38
|
+
// templateResolver is always wired (createTemplateResolverApi never throws; it only
|
|
39
|
+
// creates a db-bound accessor). secrets is auto-wired only when the secrets feature is
|
|
40
|
+
// mounted AND a KEK is available (masterKey override OR env KEK present); otherwise it
|
|
41
|
+
// is skipped so eager KEK detection and provider/cipher construction remain in
|
|
42
|
+
// boot/boot-crypto.ts (envHasMasterKek, resolveBootCrypto), shared with runDevApp.
|
|
43
|
+
// A production misconfiguration (secrets mounted without a KEK) is caught by
|
|
44
|
+
// secretsEnvSchema during boot; without the env-schema path, requireSecretsContext
|
|
45
|
+
// throws with a wiring hint on the first ctx.secrets access. configResolver is only
|
|
46
|
+
// wired in auth mode. Exported and pure for unit tests; the caller merges it with
|
|
47
|
+
// app values (the app wins).
|
|
49
48
|
// Prod/dev parity for ctx.notify: without this `_notifyFactory` is only wired
|
|
50
49
|
// in tests (createDeliveryTestContext), so ctx.notify is undefined at runtime
|
|
51
50
|
// and every notification silently skips. sseBroker optional (email/push don't
|
|
@@ -219,17 +218,17 @@ export function resolveAuthMail(auth, hmacSecret, envSource, mailWiring = {}) {
|
|
|
219
218
|
const withResolvedSecrets = backfillTokenSecrets(auth, tokenSecret);
|
|
220
219
|
if (!auth.mail)
|
|
221
220
|
return withResolvedSecrets;
|
|
222
|
-
// SMTP-presence gate:
|
|
223
|
-
//
|
|
224
|
-
// (channel-email),
|
|
225
|
-
//
|
|
221
|
+
// SMTP-presence gate: without SMTP_HOST in the environment, no flow is wired
|
|
222
|
+
// (the routes would otherwise return 500). Mail delivery itself runs through
|
|
223
|
+
// delivery (channel-email), not this transport; it only detects whether mail is
|
|
224
|
+
// configured.
|
|
226
225
|
// An app transport from runProdApp's `mail` option replaces the probe.
|
|
227
226
|
if (mailWiring.mailConfigured !== true &&
|
|
228
227
|
!createSmtpTransportFromEnv(envSource, { fallbackFrom: auth.mail.from ?? "noreply@localhost" })) {
|
|
229
228
|
return withResolvedSecrets;
|
|
230
229
|
}
|
|
231
230
|
const paths = makeAuthPaths(auth.mail.paths);
|
|
232
|
-
// appName/locale
|
|
231
|
+
// appName/locale flow into all four flow options because they all send through delivery.
|
|
233
232
|
const mailPresentation = {
|
|
234
233
|
...(auth.mail.appName !== undefined && { appName: auth.mail.appName }),
|
|
235
234
|
...(auth.mail.locale !== undefined && { locale: auth.mail.locale }),
|
|
@@ -266,7 +265,7 @@ export function resolveAuthMail(auth, hmacSecret, envSource, mailWiring = {}) {
|
|
|
266
265
|
};
|
|
267
266
|
}
|
|
268
267
|
export async function buildProdSessionAuth(db, registry, sessionsFeature, mfaFeature, patFeature) {
|
|
269
|
-
// Resolve the sessions feature sessionStore provider
|
|
268
|
+
// Resolve the sessions feature sessionStore provider.
|
|
270
269
|
const store = await resolveSessionStore({ db, registry });
|
|
271
270
|
if (sessionsFeature) {
|
|
272
271
|
bindAutoRevokeFromFeature(sessionsFeature)?.(store.massRevoker);
|