@cosmicdrift/kumiko-server-runtime 0.373.0 → 0.377.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/dist/boot/env-gate.d.ts +23 -0
- package/dist/boot/env-gate.js +136 -0
- package/dist/boot/worker-boot-core.d.ts +3 -0
- package/dist/boot/worker-boot-core.js +10 -2
- package/dist/boot-options/demo-mode.d.ts +7 -1
- package/dist/boot-options/demo-mode.js +5 -2
- package/dist/boot-options/feature-toggles-option.d.ts +19 -0
- package/dist/boot-options/feature-toggles-option.js +25 -0
- package/dist/boot-options/file-storage-option.d.ts +11 -2
- package/dist/boot-options/file-storage-option.js +48 -9
- package/dist/boot-options/mail-option.d.ts +8 -3
- package/dist/boot-options/mail-option.js +21 -6
- package/dist/boot-options/option-env-schema.js +30 -9
- package/dist/boot-options/resolve-boot-options.d.ts +9 -1
- package/dist/boot-options/resolve-boot-options.js +37 -4
- package/dist/boot-options/stack-boot-requirements.d.ts +5 -2
- package/dist/boot-options/stack-boot-requirements.js +23 -8
- package/dist/build-prod-bundle.d.ts +3 -3
- package/dist/build-prod-bundle.js +5 -5
- package/dist/extra-routes-deps.d.ts +5 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +3 -0
- package/dist/renderer-web-fonts.js +1 -1
- package/dist/request-bound-system-query.d.ts +1 -1
- package/dist/resolve-tailwind-cli.d.ts +1 -1
- package/dist/resolve-tailwind-cli.js +1 -1
- package/dist/run-prod-app-boot-context.d.ts +4 -0
- package/dist/run-prod-app-boot-context.js +12 -5
- package/dist/run-prod-app-static-files.js +1 -1
- package/dist/run-prod-app.d.ts +22 -12
- package/dist/run-prod-app.js +45 -144
- package/dist/run-worker-app.d.ts +23 -4
- package/dist/run-worker-app.js +51 -5
- package/package.json +7 -8
- package/src/changes.json +51 -0
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { BILLING_FOUNDATION_FEATURE } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
|
|
2
2
|
import { DELIVERY_FEATURE } from "@cosmicdrift/kumiko-bundled-features/delivery";
|
|
3
|
+
import { FEATURE_TOGGLES_FEATURE } from "@cosmicdrift/kumiko-bundled-features/feature-toggles";
|
|
3
4
|
import { SUBSCRIPTION_STRIPE_FEATURE } from "@cosmicdrift/kumiko-bundled-features/subscription-stripe";
|
|
4
5
|
import { TIER_ENGINE_FEATURE } from "@cosmicdrift/kumiko-bundled-features/tier-engine";
|
|
5
6
|
import { resolveBillingOption } from "./billing-option.js";
|
|
6
7
|
import { buildDemoModeRoute, withDemoReadOnlyFetch } from "./demo-mode.js";
|
|
8
|
+
import { resolveFeatureTogglesOption, } from "./feature-toggles-option.js";
|
|
7
9
|
import { FILE_FOUNDATION_FEATURE, resolveFileStorageOption, } from "./file-storage-option.js";
|
|
8
10
|
import { resolveKmsMode } from "./kms-mode.js";
|
|
9
11
|
import { CHANNEL_EMAIL_FEATURE, resolveMailOption } from "./mail-option.js";
|
|
10
12
|
import { resolveSeedShortcuts } from "./seed-shortcuts.js";
|
|
13
|
+
import { stackBootWarnings } from "./stack-boot-requirements.js";
|
|
11
14
|
export function isSeedFnList(seeds) {
|
|
12
15
|
return Array.isArray(seeds);
|
|
13
16
|
}
|
|
@@ -22,6 +25,35 @@ function rejectDoubleMount(mounted, option, feature) {
|
|
|
22
25
|
throw new Error(`[runProdApp] ${option} mounts ${feature} itself — remove it from \`features\` or drop the option.`);
|
|
23
26
|
}
|
|
24
27
|
}
|
|
28
|
+
// The old option was a function the app computed; ignoring it silently would drop an operator kill-switch.
|
|
29
|
+
export function rejectRemovedEffectiveFeatures(options) {
|
|
30
|
+
if ("effectiveFeatures" in options) {
|
|
31
|
+
throw new Error("[runProdApp] The `effectiveFeatures` option was removed — operator switches no longer take effect through it. Set `featureToggles: true` (it mounts feature-toggles and layers the switches over the tier resolver) and drop the `feature-toggles` feature from `features`.");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function wireFeatureToggles(options, mounted) {
|
|
35
|
+
rejectRemovedEffectiveFeatures(options);
|
|
36
|
+
if (options.featureToggles) {
|
|
37
|
+
rejectDoubleMount(mounted, "featureToggles", FEATURE_TOGGLES_FEATURE);
|
|
38
|
+
return resolveFeatureTogglesOption();
|
|
39
|
+
}
|
|
40
|
+
if (mounted.has(FEATURE_TOGGLES_FEATURE)) {
|
|
41
|
+
throw new Error(`[runProdApp] ${FEATURE_TOGGLES_FEATURE} is in \`features\` but \`featureToggles\` is not set — its switches would not reach the dispatcher. Set \`featureToggles: true\` and remove the feature from \`features\`.`);
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
function warnAboutBootOptions(options) {
|
|
46
|
+
const bootWarnings = stackBootWarnings(options).map((line) => `stacks in \`features\` expect boot options that are not set:\n${line}`);
|
|
47
|
+
if (options.runSingleInstance === false &&
|
|
48
|
+
(options.mail || options.fileStorage || options.featureToggles) &&
|
|
49
|
+
options.workerMountsFeatures !== true) {
|
|
50
|
+
bootWarnings.push("runSingleInstance:false — delivery and file jobs run in runWorkerApp. Pass the same mail / fileStorage / featureToggles options there, or set workerMountsFeatures: true if it mounts those features itself.");
|
|
51
|
+
}
|
|
52
|
+
if (bootWarnings.length > 0) {
|
|
53
|
+
// biome-ignore lint/suspicious/noConsole: boot-time warn, no logger configured this early
|
|
54
|
+
console.warn(`[runProdApp] ${bootWarnings.join("\n[runProdApp] ")}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
25
57
|
/** Runs after the env is validated and KEK slots are decrypted, before any
|
|
26
58
|
* connection: every misconfiguration of the new options fails here. */
|
|
27
59
|
export async function resolveBootOptionWiring(options, envSource) {
|
|
@@ -56,14 +88,14 @@ export async function resolveBootOptionWiring(options, envSource) {
|
|
|
56
88
|
features.push(billing.feature);
|
|
57
89
|
extraRoutes.push(billing.route);
|
|
58
90
|
}
|
|
91
|
+
const featureToggles = wireFeatureToggles(options, mounted);
|
|
92
|
+
if (featureToggles)
|
|
93
|
+
features.push(featureToggles.feature);
|
|
59
94
|
const { demoMode } = options;
|
|
60
95
|
if (demoMode) {
|
|
61
96
|
extraRoutes.push(buildDemoModeRoute(demoMode, envSource));
|
|
62
97
|
}
|
|
63
|
-
|
|
64
|
-
// biome-ignore lint/suspicious/noConsole: boot-time warn, no logger configured this early
|
|
65
|
-
console.warn("[runProdApp] runSingleInstance:false — delivery and file jobs run in runWorkerApp, which does not read mail / fileStorage. Mount the same channel-email and file provider features there.");
|
|
66
|
-
}
|
|
98
|
+
warnAboutBootOptions(options);
|
|
67
99
|
const seeds = options.seeds ?? [];
|
|
68
100
|
const shortcutSeeds = isSeedFnList(seeds)
|
|
69
101
|
? []
|
|
@@ -80,6 +112,7 @@ export async function resolveBootOptionWiring(options, envSource) {
|
|
|
80
112
|
extraRoutes,
|
|
81
113
|
envDefaults,
|
|
82
114
|
kms,
|
|
115
|
+
...(featureToggles && { featureToggles }),
|
|
83
116
|
shortcutSeeds,
|
|
84
117
|
customSeeds,
|
|
85
118
|
mailConfigured: options.mail !== undefined,
|
|
@@ -2,5 +2,8 @@ import { type BootOptionName } from "@cosmicdrift/kumiko-framework/engine";
|
|
|
2
2
|
import type { RunProdAppOptions } from "../run-prod-app.ts";
|
|
3
3
|
export type StackBootOptionFields = Pick<RunProdAppOptions, "features" | "kms" | "allowPlaintextPii" | BootOptionName>;
|
|
4
4
|
/** Reads `bootRequires` from the raw feature list: dedupe may later drop the
|
|
5
|
-
* tagged copy of a feature two stacks share.
|
|
6
|
-
|
|
5
|
+
* tagged copy of a feature two stacks share. `scope` limits the check to the
|
|
6
|
+
* options the calling boot function can set at all (runDevApp has no `signals`). */
|
|
7
|
+
export declare function assertStackBootRequirements(options: StackBootOptionFields, scope?: readonly BootOptionName[]): void;
|
|
8
|
+
/** Requirements a stack marked `severity: "warn"`: logged at boot, never fatal. */
|
|
9
|
+
export declare function stackBootWarnings(options: StackBootOptionFields): string[];
|
|
@@ -25,6 +25,10 @@ const OPTION_CHECKS = {
|
|
|
25
25
|
isSatisfied: (options, mounted) => options.billing !== undefined || mountsExtension(mounted, SUBSCRIPTION_PROVIDER_EXTENSION),
|
|
26
26
|
hint: "set billing: { stripe: { priceToTier }, defaultTier }",
|
|
27
27
|
},
|
|
28
|
+
featureToggles: {
|
|
29
|
+
isSatisfied: (options) => options.featureToggles === true,
|
|
30
|
+
hint: "set featureToggles: true",
|
|
31
|
+
},
|
|
28
32
|
demoMode: {
|
|
29
33
|
isSatisfied: (options) => options.demoMode !== undefined,
|
|
30
34
|
hint: "set demoMode: { readOnly }",
|
|
@@ -42,22 +46,33 @@ const OPTION_CHECKS = {
|
|
|
42
46
|
hint: "drop rateLimit: false",
|
|
43
47
|
},
|
|
44
48
|
};
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
export function assertStackBootRequirements(options) {
|
|
48
|
-
const gaps = new Set();
|
|
49
|
+
function unsatisfiedRequirements(options, severity, scope) {
|
|
50
|
+
const lines = new Set();
|
|
49
51
|
for (const feature of options.features) {
|
|
50
52
|
const requirement = feature.bootRequires;
|
|
51
|
-
if (requirement === undefined)
|
|
53
|
+
if (requirement === undefined || (requirement.severity ?? "error") !== severity)
|
|
52
54
|
continue;
|
|
53
55
|
for (const option of requirement.options) {
|
|
56
|
+
if (scope !== undefined && !scope.includes(option))
|
|
57
|
+
continue;
|
|
54
58
|
const check = OPTION_CHECKS[option];
|
|
55
59
|
if (!check.isSatisfied(options, options.features)) {
|
|
56
|
-
|
|
60
|
+
lines.add(` - ${requirement.stack} needs the boot option \`${option}\`: ${check.hint}`);
|
|
57
61
|
}
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
return [...lines];
|
|
65
|
+
}
|
|
66
|
+
/** Reads `bootRequires` from the raw feature list: dedupe may later drop the
|
|
67
|
+
* tagged copy of a feature two stacks share. `scope` limits the check to the
|
|
68
|
+
* options the calling boot function can set at all (runDevApp has no `signals`). */
|
|
69
|
+
export function assertStackBootRequirements(options, scope) {
|
|
70
|
+
const gaps = unsatisfiedRequirements(options, "error", scope);
|
|
71
|
+
if (gaps.length > 0) {
|
|
72
|
+
throw new Error(`[runProdApp] stacks in \`features\` need boot options that are not set:\n${gaps.join("\n")}`);
|
|
62
73
|
}
|
|
63
74
|
}
|
|
75
|
+
/** Requirements a stack marked `severity: "warn"`: logged at boot, never fatal. */
|
|
76
|
+
export function stackBootWarnings(options) {
|
|
77
|
+
return unsatisfiedRequirements(options, "warn");
|
|
78
|
+
}
|
|
@@ -4,9 +4,9 @@ export type BuildProdBundleOptions = {
|
|
|
4
4
|
readonly cwd?: string;
|
|
5
5
|
/** Output-Folder relativ zu cwd. Default: "dist". */
|
|
6
6
|
readonly outDir?: string;
|
|
7
|
-
/** Stylesheet
|
|
8
|
-
* @cosmicdrift/kumiko-renderer
|
|
9
|
-
* `false`
|
|
7
|
+
/** Stylesheet override. Default: src/styles.css first, then
|
|
8
|
+
* @cosmicdrift/kumiko-renderer/web/styles.css when a clientEntry exists.
|
|
9
|
+
* `false` disables the CSS pipeline explicitly. */
|
|
10
10
|
readonly stylesheet?: string | false;
|
|
11
11
|
} & ClientEntriesConfig;
|
|
12
12
|
/** One declared client entry, read from package.json `kumiko.clientEntries`.
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// Convention for everything else (all optional, missing → skipped):
|
|
15
15
|
//
|
|
16
16
|
// src/styles.css → Tailwind one-shot
|
|
17
|
-
// (oder fallback auf @cosmicdrift/kumiko-renderer
|
|
17
|
+
// (oder fallback auf @cosmicdrift/kumiko-renderer/web/styles.css
|
|
18
18
|
// wenn nur clientEntry da ist und kein eigenes CSS)
|
|
19
19
|
// public/ → rsync 1:1 (kein Hash — User-bewusste URLs)
|
|
20
20
|
// public/index.html | index.html → Template, Placeholder-Tags ersetzt:
|
|
@@ -60,7 +60,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
|
60
60
|
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
61
61
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
62
62
|
import { isPlainObject, parseJsonOrThrow } from "@cosmicdrift/kumiko-framework/utils";
|
|
63
|
-
import { escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
|
|
63
|
+
import { escapeHtmlAttr } from "@cosmicdrift/kumiko-renderer/headless";
|
|
64
64
|
import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
|
|
65
65
|
import { BUNDLED_ASSETS_DIST_DIR, readBundledAssetDeclarations, resolveBundledAssetSource, } from "./bundled-assets.js";
|
|
66
66
|
import { CSP_HASHES_FILE, inlineScriptHashes } from "./csp-hashes.js";
|
|
@@ -458,7 +458,7 @@ function resolveStylesheetEntry(cwd, clientEntry, override) {
|
|
|
458
458
|
if (!hasBun)
|
|
459
459
|
return undefined;
|
|
460
460
|
try {
|
|
461
|
-
const resolved = Bun.resolveSync("@cosmicdrift/kumiko-renderer
|
|
461
|
+
const resolved = Bun.resolveSync("@cosmicdrift/kumiko-renderer/web/styles.css", cwd);
|
|
462
462
|
if (!canResolveTailwindStylesheet(resolved, { bun: Bun, cwd })) {
|
|
463
463
|
return undefined;
|
|
464
464
|
}
|
|
@@ -483,8 +483,8 @@ export function assertRendererWebShellPresent(css, stylesheet) {
|
|
|
483
483
|
return;
|
|
484
484
|
throw new Error(`[kumiko build] renderer-web-Fallback-CSS ohne Shell-Klasse "${RENDERER_WEB_SHELL_SENTINEL}" — ` +
|
|
485
485
|
"die DefaultAppShell/WorkspaceShell-Styles fehlen, prod würde unstyled rendern. Meist ist " +
|
|
486
|
-
"@cosmicdrift/kumiko-renderer
|
|
487
|
-
'Fix: src/styles.css anlegen mit `@import "@cosmicdrift/kumiko-renderer
|
|
486
|
+
"@cosmicdrift/kumiko-renderer/web nicht dort installiert wo Tailwind seine Source scannt. " +
|
|
487
|
+
'Fix: src/styles.css anlegen mit `@import "@cosmicdrift/kumiko-renderer/web/styles.css";` ' +
|
|
488
488
|
'(+ `@source "./**/*.{ts,tsx}";` um auch eigene Komponenten zu scannen).');
|
|
489
489
|
}
|
|
490
490
|
// The compiled CSS points at absolute /assets/kumiko/fonts/<file>.woff2 URLs
|
|
@@ -2,6 +2,7 @@ import type { SystemDispatchArgs } from "@cosmicdrift/kumiko-framework/api";
|
|
|
2
2
|
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
3
3
|
import type { Registry, WriteResult } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
4
|
import type { MetricsHandle } from "@cosmicdrift/kumiko-framework/observability";
|
|
5
|
+
import type { EnvelopeCipher, MasterKeyProvider } from "@cosmicdrift/kumiko-framework/secrets";
|
|
5
6
|
import type { Redis } from "ioredis";
|
|
6
7
|
export type { SystemDispatchArgs } from "@cosmicdrift/kumiko-framework/api";
|
|
7
8
|
export { makeDispatchSystemQuery, makeDispatchSystemWrite, } from "@cosmicdrift/kumiko-framework/api";
|
|
@@ -17,6 +18,10 @@ export type SystemWireDeps = {
|
|
|
17
18
|
/** Feature-registry — e.g. for plugin-lookups via
|
|
18
19
|
* `registry.getExtensionUsages("subscriptionProvider")`. */
|
|
19
20
|
readonly registry: Registry;
|
|
21
|
+
/** The boot's master key keyring; absent when no `KUMIKO_SECRETS_MASTER_KEY_V<n>` is set. */
|
|
22
|
+
readonly masterKeyProvider?: MasterKeyProvider;
|
|
23
|
+
/** Cipher for `encrypted: true` config keys, same instance and DEK cache as the handlers use. */
|
|
24
|
+
readonly configEncryption?: EnvelopeCipher;
|
|
20
25
|
/** Same contract as `ctx.metricsFor`: unregistered metric names no-op. */
|
|
21
26
|
readonly metricsFor: (featureName: string) => MetricsHandle;
|
|
22
27
|
/** Writes through the /api/*-command-dispatcher (same idempotency/job
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
export { type BillingBootOption, resolveBillingOption } from "./boot-options/billing-option.ts";
|
|
2
|
-
export type { DemoAccountRole, DemoModeNotice, DemoModeOption, } from "./boot-options/demo-mode.ts";
|
|
2
|
+
export type { DemoAccountEntry, DemoAccountRole, DemoModeNotice, DemoModeOption, } from "./boot-options/demo-mode.ts";
|
|
3
3
|
export { buildDemoModeRoute, withDemoReadOnlyFetch, } from "./boot-options/demo-mode.ts";
|
|
4
4
|
export type { FileStorageBootOption, FileStorageProviderName, } from "./boot-options/file-storage-option.ts";
|
|
5
5
|
export type { KmsMode } from "./boot-options/kms-mode.ts";
|
|
6
|
-
export type { MailBootOption } from "./boot-options/mail-option.ts";
|
|
6
|
+
export type { MailBootOption, MailRecipientScope } from "./boot-options/mail-option.ts";
|
|
7
|
+
export { withBootOptionEnvBlocks } from "./boot-options/option-env-schema.ts";
|
|
7
8
|
export type { SignalsOption } from "./boot-options/process-signals.ts";
|
|
9
|
+
export { type BootOptionFields, type BootOptionWiring, isSeedFnList, rejectRemovedEffectiveFeatures, resolveBootOptionWiring, } from "./boot-options/resolve-boot-options.ts";
|
|
8
10
|
export type { ProdSeedsConfig, ProdSeedTenant } from "./boot-options/seed-shortcuts.ts";
|
|
11
|
+
export { assertStackBootRequirements } from "./boot-options/stack-boot-requirements.ts";
|
|
9
12
|
export { type BundledAssetDeclaration, type BundledAssetOptions, readBundledAsset, resolveBundledAsset, } from "./bundled-assets.ts";
|
|
10
13
|
export { type ComposeFeaturesOptions, composeFeatures } from "./compose-features.ts";
|
|
11
14
|
export type { RateLimitOption } from "./rate-limit-defaults.ts";
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
// Dev-Tooling mehr in ihre node_modules.
|
|
5
5
|
export { resolveBillingOption } from "./boot-options/billing-option.js";
|
|
6
6
|
export { buildDemoModeRoute, withDemoReadOnlyFetch, } from "./boot-options/demo-mode.js";
|
|
7
|
+
export { withBootOptionEnvBlocks } from "./boot-options/option-env-schema.js";
|
|
8
|
+
export { isSeedFnList, rejectRemovedEffectiveFeatures, resolveBootOptionWiring, } from "./boot-options/resolve-boot-options.js";
|
|
9
|
+
export { assertStackBootRequirements } from "./boot-options/stack-boot-requirements.js";
|
|
7
10
|
export { readBundledAsset, resolveBundledAsset, } from "./bundled-assets.js";
|
|
8
11
|
export { composeFeatures } from "./compose-features.js";
|
|
9
12
|
export { runBootstrap } from "./run-bootstrap.js";
|
|
@@ -17,7 +17,7 @@ export function resolveRendererWebFontsDir(searchFrom) {
|
|
|
17
17
|
return undefined;
|
|
18
18
|
for (const from of searchFrom) {
|
|
19
19
|
try {
|
|
20
|
-
const stylesheet = Bun.resolveSync("@cosmicdrift/kumiko-renderer
|
|
20
|
+
const stylesheet = Bun.resolveSync("@cosmicdrift/kumiko-renderer/web/styles.css", from);
|
|
21
21
|
return join(dirname(stylesheet), "fonts");
|
|
22
22
|
}
|
|
23
23
|
catch {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ClientIpResolver } from "@cosmicdrift/kumiko-framework/api";
|
|
2
2
|
import { type SessionUser } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
-
import type { PageHeadSystemQuery } from "@cosmicdrift/kumiko-headless/apex";
|
|
3
|
+
import type { PageHeadSystemQuery } from "@cosmicdrift/kumiko-renderer/headless/apex";
|
|
4
4
|
export type QueryDispatcher = {
|
|
5
5
|
readonly query: (type: string, payload: unknown, user: SessionUser) => Promise<unknown>;
|
|
6
6
|
};
|
|
@@ -9,6 +9,6 @@ export declare function resolveTailwindCli(deps: ResolveTailwindCliDeps): string
|
|
|
9
9
|
/** Tailwind v4 resolves `@import "tailwindcss"` relative to the entry
|
|
10
10
|
* CSS file. Skip renderer-web fallback (or fail build) when peer deps
|
|
11
11
|
* aren't resolvable from that directory — e.g. Bun cache copies of
|
|
12
|
-
* `@cosmicdrift/kumiko-renderer
|
|
12
|
+
* `@cosmicdrift/kumiko-renderer/web` outside a hoisted node_modules tree. */
|
|
13
13
|
export declare function canResolveTailwindStylesheet(entryCss: string, deps: ResolveTailwindCliDeps): boolean;
|
|
14
14
|
export {};
|
|
@@ -22,7 +22,7 @@ export function resolveTailwindCli(deps) {
|
|
|
22
22
|
/** Tailwind v4 resolves `@import "tailwindcss"` relative to the entry
|
|
23
23
|
* CSS file. Skip renderer-web fallback (or fail build) when peer deps
|
|
24
24
|
* aren't resolvable from that directory — e.g. Bun cache copies of
|
|
25
|
-
* `@cosmicdrift/kumiko-renderer
|
|
25
|
+
* `@cosmicdrift/kumiko-renderer/web` outside a hoisted node_modules tree. */
|
|
26
26
|
export function canResolveTailwindStylesheet(entryCss, deps) {
|
|
27
27
|
if (deps.bun === undefined)
|
|
28
28
|
return false;
|
|
@@ -14,6 +14,8 @@ export declare function addConfigAccessorFactory<T extends {
|
|
|
14
14
|
readonly configResolver?: ConfigResolver;
|
|
15
15
|
}>(resolved: T, registry: Registry): T;
|
|
16
16
|
export declare function loggerFromExtraContext(extraContext: Record<string, unknown>): Logger | undefined;
|
|
17
|
+
/** Config key (`feature:config:name`) to value; wins over the key's env variable. */
|
|
18
|
+
export type ConfigOverridesInput = Readonly<Record<string, string | number | boolean>>;
|
|
17
19
|
type BootExtraContextOptions = {
|
|
18
20
|
readonly db: DbConnection;
|
|
19
21
|
readonly features: readonly FeatureDefinition[];
|
|
@@ -33,6 +35,8 @@ type BootExtraContextOptions = {
|
|
|
33
35
|
readonly log?: Logger;
|
|
34
36
|
/** Signs `unsubscribe: true` notifications' mail links; see runProdApp `unsubscribe`. */
|
|
35
37
|
readonly unsubscribeLinks?: UnsubscribeLinkConfig;
|
|
38
|
+
/** App-level config values (tenant model, branding, seo ...) above the env bridge. */
|
|
39
|
+
readonly appOverrides?: ConfigOverridesInput;
|
|
36
40
|
};
|
|
37
41
|
export declare function buildBootExtraContext(opts: BootExtraContextOptions): Record<string, unknown>;
|
|
38
42
|
/** Die Auth-Felder die resolveAuthMail liest/normalisiert — beide
|
|
@@ -3,7 +3,7 @@ import { makeAuthPaths, } from "@cosmicdrift/kumiko-bundled-features/auth-email-
|
|
|
3
3
|
import { resolveSessionStore } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
|
|
4
4
|
import { bindMfaRevokeAllOtherSessionsFromFeature, bindRevokeAllPatTokensFromFeature, } from "@cosmicdrift/kumiko-bundled-features/auth-mfa";
|
|
5
5
|
import { createSmtpTransportFromEnv } from "@cosmicdrift/kumiko-bundled-features/channel-email";
|
|
6
|
-
import { buildEnvConfigOverrides, createConfigAccessorFactory, createConfigResolver, } from "@cosmicdrift/kumiko-bundled-features/config";
|
|
6
|
+
import { buildEnvConfigOverrides, createConfigAccessorFactory, createConfigResolver, validateAppOverrides, } from "@cosmicdrift/kumiko-bundled-features/config";
|
|
7
7
|
import { collectChannels, createDeliveryNotifyFactory, createDeliveryService, DELIVERY_FEATURE, } from "@cosmicdrift/kumiko-bundled-features/delivery";
|
|
8
8
|
import { bindPatAutoRevokeOnPasswordChangeFromFeature, revokeAllPatTokensForUser, } from "@cosmicdrift/kumiko-bundled-features/personal-access-tokens";
|
|
9
9
|
import { createSecretsContext, SECRETS_FEATURE_NAME, } from "@cosmicdrift/kumiko-bundled-features/secrets";
|
|
@@ -23,9 +23,13 @@ import { resolveBootCrypto } from "./boot/boot-crypto.js";
|
|
|
23
23
|
export function addConfigAccessorFactory(resolved, registry) {
|
|
24
24
|
if (!resolved.configResolver)
|
|
25
25
|
return resolved;
|
|
26
|
+
// Both keys, always overriding an app-supplied value: the factory must derive from the
|
|
27
|
+
// effective resolver (fw#3313); the underscore alias stays until the next major.
|
|
28
|
+
const configAccessorFactory = createConfigAccessorFactory(registry, resolved.configResolver);
|
|
26
29
|
return {
|
|
27
30
|
...resolved,
|
|
28
|
-
|
|
31
|
+
configAccessorFactory,
|
|
32
|
+
_configAccessorFactory: configAccessorFactory,
|
|
29
33
|
};
|
|
30
34
|
}
|
|
31
35
|
// Framework-Default-Provider für den AppContext — gleicher Mechanismus wie
|
|
@@ -117,9 +121,12 @@ function bootCryptoContext(crypto) {
|
|
|
117
121
|
...(crypto.configCipher && { configEncryption: crypto.configCipher }),
|
|
118
122
|
};
|
|
119
123
|
}
|
|
120
|
-
function createBootConfigResolver(registry, envSource, crypto) {
|
|
124
|
+
function createBootConfigResolver(registry, envSource, crypto, appOverrides) {
|
|
125
|
+
const envOverrides = buildEnvConfigOverrides(registry, envSource);
|
|
121
126
|
return createConfigResolver({
|
|
122
|
-
appOverrides:
|
|
127
|
+
appOverrides: appOverrides
|
|
128
|
+
? new Map([...envOverrides, ...validateAppOverrides(registry, appOverrides)])
|
|
129
|
+
: envOverrides,
|
|
123
130
|
...(crypto.configCipher && { cipher: crypto.configCipher }),
|
|
124
131
|
});
|
|
125
132
|
}
|
|
@@ -128,7 +135,7 @@ function createBootConfigResolver(registry, envSource, crypto) {
|
|
|
128
135
|
// stays auth-only.
|
|
129
136
|
function resolveDeliveryConfigResolver(opts, hasDeliveryFeature, crypto) {
|
|
130
137
|
return opts.hasAuth || hasDeliveryFeature
|
|
131
|
-
? createBootConfigResolver(opts.registry, opts.envSource, crypto)
|
|
138
|
+
? createBootConfigResolver(opts.registry, opts.envSource, crypto, opts.appOverrides)
|
|
132
139
|
: undefined;
|
|
133
140
|
}
|
|
134
141
|
function bootDeliveryContext(opts, parts) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { join, resolve, sep } from "node:path";
|
|
2
2
|
import { cachedResponse, computeStrongEtag, computeWeakEtag, createClientIpResolver, } from "@cosmicdrift/kumiko-framework/api";
|
|
3
3
|
import { rankPreferredLocales } from "@cosmicdrift/kumiko-framework/i18n";
|
|
4
|
-
import { resolveAndInjectPageHead } from "@cosmicdrift/kumiko-headless/apex";
|
|
4
|
+
import { resolveAndInjectPageHead } from "@cosmicdrift/kumiko-renderer/headless/apex";
|
|
5
5
|
import { errorCodeOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
6
6
|
import { ASSETS_DIR } from "./build-prod-bundle.js";
|
|
7
7
|
import { BUNDLED_ASSETS_DIST_DIR } from "./bundled-assets.js";
|
package/dist/run-prod-app.d.ts
CHANGED
|
@@ -3,15 +3,14 @@ import { type AccountLockoutOptions, type AccountUnlockOptions, type AuthMailLoc
|
|
|
3
3
|
import { type SeedAdminOptions } from "@cosmicdrift/kumiko-bundled-features/auth-email-password/seeding";
|
|
4
4
|
import { type ExtraRouteDefinition, type KumikoServeEnv, type PostAuthLandingResolver, type SseBroker } from "@cosmicdrift/kumiko-framework/api";
|
|
5
5
|
import { type KmsAdapter } from "@cosmicdrift/kumiko-framework/crypto";
|
|
6
|
-
import { type
|
|
6
|
+
import { type FeatureDefinition, type ValidateBootOptions } from "@cosmicdrift/kumiko-framework/engine";
|
|
7
7
|
import { type AllInOneEntrypoint, type ApiEntrypoint } from "@cosmicdrift/kumiko-framework/entrypoint";
|
|
8
|
-
import {
|
|
9
|
-
import { type DryRunEnvMode } from "@cosmicdrift/kumiko-framework/env/dry-run";
|
|
8
|
+
import type { ComposedEnvSchema, KumikoBootError } from "@cosmicdrift/kumiko-framework/env";
|
|
10
9
|
import type { ErrorDocsConfig } from "@cosmicdrift/kumiko-framework/errors";
|
|
11
10
|
import type { JobRunnerOptions } from "@cosmicdrift/kumiko-framework/jobs";
|
|
12
11
|
import { type ObservabilityOptions, type ObservabilityProvider } from "@cosmicdrift/kumiko-framework/observability";
|
|
13
12
|
import type { MasterKeyProvider } from "@cosmicdrift/kumiko-framework/secrets";
|
|
14
|
-
import type { PageHeadMeta, PageHeadResolver, PageHeadSystemQuery } from "@cosmicdrift/kumiko-headless/apex";
|
|
13
|
+
import type { PageHeadMeta, PageHeadResolver, PageHeadSystemQuery } from "@cosmicdrift/kumiko-renderer/headless/apex";
|
|
15
14
|
import { Redis } from "ioredis";
|
|
16
15
|
import type { BillingBootOption } from "./boot-options/billing-option.ts";
|
|
17
16
|
import type { DemoModeOption } from "./boot-options/demo-mode.ts";
|
|
@@ -23,15 +22,15 @@ import type { ProdSeedsConfig } from "./boot-options/seed-shortcuts.ts";
|
|
|
23
22
|
import { type SystemWireDeps } from "./extra-routes-deps.ts";
|
|
24
23
|
import { type RateLimitOption } from "./rate-limit-defaults.ts";
|
|
25
24
|
import { type UnsubscribeOption } from "./resolve-unsubscribe-wiring.ts";
|
|
25
|
+
import { type ConfigOverridesInput } from "./run-prod-app-boot-context.ts";
|
|
26
26
|
import { type SecurityHeadersOption } from "./security-headers.ts";
|
|
27
27
|
import { type SeedCreateFileRef } from "./seed-dispatcher.ts";
|
|
28
|
+
export { parseRunMode } from "./boot/env-gate.ts";
|
|
28
29
|
export { buildBunServeOptions } from "./bun-serve-options.ts";
|
|
29
30
|
export { resolveUnsubscribeWiring, type UnsubscribeOption, } from "./resolve-unsubscribe-wiring.ts";
|
|
30
|
-
export { addConfigAccessorFactory, buildBootExtraContext, loggerFromExtraContext, resolveAuthMail, } from "./run-prod-app-boot-context.ts";
|
|
31
|
+
export { addConfigAccessorFactory, buildBootExtraContext, type ConfigOverridesInput, loggerFromExtraContext, resolveAuthMail, } from "./run-prod-app-boot-context.ts";
|
|
31
32
|
export { staticCachePolicy } from "./run-prod-app-static-files.ts";
|
|
32
33
|
export declare function requireEnv(name: string, src?: Record<string, string | undefined>, context?: string): string;
|
|
33
|
-
type RunMode = DryRunEnvMode;
|
|
34
|
-
export declare function parseRunMode(raw: string | undefined): RunMode | null;
|
|
35
34
|
/** Wrapper API for the password-reset flow.
|
|
36
35
|
*
|
|
37
36
|
* Since the delivery migration PasswordResetOptions carries `appUrl`
|
|
@@ -192,6 +191,9 @@ export type ProdSeedFn = (deps: {
|
|
|
192
191
|
/** Stores bytes as a fileRef of `tenantId` (same validation and storage
|
|
193
192
|
* layout as POST /files) without a helper write handler. */
|
|
194
193
|
createFileRef: SeedCreateFileRef;
|
|
194
|
+
/** Present when a master key is configured: decrypts the same keys the handlers do, so a seed need not resolve the KEK itself. */
|
|
195
|
+
masterKeyProvider?: import("@cosmicdrift/kumiko-framework/secrets").MasterKeyProvider;
|
|
196
|
+
configEncryption?: import("@cosmicdrift/kumiko-framework/secrets").EnvelopeCipher;
|
|
195
197
|
}) => Promise<void>;
|
|
196
198
|
/** Boot-Time-Deps die `extraContext` + `anonymousAccess` Factories als
|
|
197
199
|
* Argument bekommen. Closure dann in der returned Config (z.B. ein
|
|
@@ -206,6 +208,9 @@ export type RunProdAppDeps = {
|
|
|
206
208
|
readonly sseBroker: SseBroker;
|
|
207
209
|
/** The env the boot runs on: process env or `envSource`, with the boot-option defaults (e.g. `fileStorage`) underneath. */
|
|
208
210
|
readonly env: Readonly<Record<string, string | undefined>>;
|
|
211
|
+
/** Present when a master key is configured; the same keyring the handlers use. */
|
|
212
|
+
readonly masterKeyProvider?: import("@cosmicdrift/kumiko-framework/secrets").MasterKeyProvider;
|
|
213
|
+
readonly configEncryption?: import("@cosmicdrift/kumiko-framework/secrets").EnvelopeCipher;
|
|
209
214
|
};
|
|
210
215
|
export type AnonymousAccessOption = import("@cosmicdrift/kumiko-framework/api").AnonymousAccessConfig | ((deps: RunProdAppDeps) => import("@cosmicdrift/kumiko-framework/api").AnonymousAccessConfig);
|
|
211
216
|
export type ExtraContextOption = Record<string, unknown> | ((deps: RunProdAppDeps) => Record<string, unknown>);
|
|
@@ -394,6 +399,9 @@ export type RunProdAppOptions = {
|
|
|
394
399
|
* @example
|
|
395
400
|
* billing: { stripe: { priceToTier: { [env.STRIPE_PRICE_PRO]: "pro" } }, defaultTier: "free" } */
|
|
396
401
|
readonly billing?: BillingBootOption;
|
|
402
|
+
/** Global operator switches: mounts `feature-toggles` and layers them over the tier resolver, so
|
|
403
|
+
* an operator can switch a feature off for every tenant. */
|
|
404
|
+
readonly featureToggles?: boolean;
|
|
397
405
|
/** Live-demo mode: read-only gate in the fetch chain plus the anonymous
|
|
398
406
|
* `GET /demo-mode` route. See `DemoModeOption`.
|
|
399
407
|
* @example
|
|
@@ -418,6 +426,9 @@ export type RunProdAppOptions = {
|
|
|
418
426
|
* jobs and queued delivery attempts (email/push/chat) stay pending and the
|
|
419
427
|
* read side stays empty (2026-06-11 incident class). */
|
|
420
428
|
readonly runSingleInstance?: boolean;
|
|
429
|
+
/** With `runSingleInstance: false`: the worker mounts the same mail / fileStorage features
|
|
430
|
+
* (its own boot options or by hand), so the boot warning about them stays silent. */
|
|
431
|
+
readonly workerMountsFeatures?: boolean;
|
|
421
432
|
/** Job-Block. Wenn das Feature `r.job(...)` registriert, wird er
|
|
422
433
|
* automatisch verdrahtet (siehe runSingleInstance). */
|
|
423
434
|
readonly jobs?: {
|
|
@@ -474,11 +485,10 @@ export type RunProdAppOptions = {
|
|
|
474
485
|
* the fetch-handler directly (Bun.serve isn't available under vitest +
|
|
475
486
|
* node), then call handle.listen() manually if needed. */
|
|
476
487
|
readonly autoListen?: boolean;
|
|
477
|
-
/**
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
|
|
481
|
-
readonly effectiveFeatures?: EffectiveFeaturesResolver;
|
|
488
|
+
/** App-level config values (tenant model, branding, seo ...) keyed by the qualified config
|
|
489
|
+
* key. Validated against the registered keys at boot; they win over the key's env variable
|
|
490
|
+
* and sit below tenant and user values. */
|
|
491
|
+
readonly appOverrides?: ConfigOverridesInput;
|
|
482
492
|
/** Composed Zod-schema for env-validation (from `composeEnvSchema({
|
|
483
493
|
* core, features, extend })` in @cosmicdrift/kumiko-framework/env):
|
|
484
494
|
* - `process.env` is parsed against it BEFORE any boot work; missing
|