@cosmicdrift/kumiko-dev-server 0.363.0 → 0.365.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.
@@ -110,9 +110,9 @@ export async function buildServerBundle(options = {}) {
110
110
  await mkdir(outDir, { recursive: true });
111
111
  // Ein Bun.build-Call mit allen Entries + splitting → das Framework wird
112
112
  // einmal als shared chunk abgelegt statt pro Entry inlined.
113
- const optionalSiblingEntries = ["worker.ts", "bootstrap.ts"]
114
- .map((file) => join(dirname(serverEntry), file))
115
- .filter((path) => existsSync(path));
113
+ const optionalSiblingEntries = ["worker", "bootstrap"]
114
+ .map((baseName) => discoverSiblingEntry(dirname(serverEntry), baseName))
115
+ .filter((path) => path !== undefined);
116
116
  const entrypoints = [serverEntry, ...(kumikoCli ? [kumikoCli] : []), ...optionalSiblingEntries];
117
117
  const result = await Bun.build({
118
118
  entrypoints,
@@ -162,12 +162,15 @@ export async function buildServerBundle(options = {}) {
162
162
  return { outDir, entries, chunks, totalBytes, runtimeDeps };
163
163
  }
164
164
  export function discoverServerEntry(cwd) {
165
- const tsEntry = join(cwd, "bin/main.ts");
166
- if (existsSync(tsEntry))
167
- return tsEntry;
168
- const jsEntry = join(cwd, "bin/main.js");
169
- if (existsSync(jsEntry))
170
- return jsEntry;
165
+ return discoverSiblingEntry(join(cwd, "bin"), "main");
166
+ }
167
+ // `.ts` wins over `.js`, same precedence for the server entry and its siblings.
168
+ function discoverSiblingEntry(dir, baseName) {
169
+ for (const extension of [".ts", ".js"]) {
170
+ const candidate = join(dir, `${baseName}${extension}`);
171
+ if (existsSync(candidate))
172
+ return candidate;
173
+ }
171
174
  return undefined;
172
175
  }
173
176
  // Walking up vom App-cwd bis ein Verzeichnis mit `bin/kumiko.ts` gefunden wird.
package/dist/cli.d.ts CHANGED
@@ -2,4 +2,3 @@ export { buildProdBundle, buildServerBundle, discoverServerEntry, formatBuildRes
2
2
  export { formatScanWarning, runCodegen } from "./codegen/index.js";
3
3
  export { createCrashTracker } from "./crash-tracker.js";
4
4
  export { runInitDeployCli } from "./init-deploy-cli.js";
5
- export { implicitAuthModeFeatureNames, resolveGeneratePath } from "./schema-check-core.js";
package/dist/cli.js CHANGED
@@ -6,4 +6,3 @@ export { buildProdBundle, buildServerBundle, discoverServerEntry, formatBuildRes
6
6
  export { formatScanWarning, runCodegen } from "./codegen/index.js";
7
7
  export { createCrashTracker } from "./crash-tracker.js";
8
8
  export { runInitDeployCli } from "./init-deploy-cli.js";
9
- export { implicitAuthModeFeatureNames, resolveGeneratePath } from "./schema-check-core.js";
@@ -2,6 +2,7 @@ import { type AuthMfaFeatureOptions } from "@cosmicdrift/kumiko-bundled-features
2
2
  import { type LegalPagesOptions } from "@cosmicdrift/kumiko-bundled-features/legal-pages";
3
3
  import { type UserDataRightsOptions } from "@cosmicdrift/kumiko-bundled-features/user-data-rights";
4
4
  import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
5
+ import { type MailBrandSuffixResolver, type MailFooterResolver } from "@cosmicdrift/kumiko-renderer-mail-html";
5
6
  export type FileProviderKind = "inmemory" | "s3" | "s3-env";
6
7
  export type MailTransportKind = "inmemory" | "smtp";
7
8
  export type GdprStackOrder = "retention-first" | "compliance-first";
@@ -48,7 +49,13 @@ export type IdentityStackOptions = {
48
49
  };
49
50
  export declare function stackFeatureNames(features: readonly FeatureDefinition[]): string[];
50
51
  export declare function composePagesStack(options?: PagesStackOptions): FeatureDefinition[];
51
- export declare function composeRendererStack(): FeatureDefinition[];
52
+ export type RendererStackOptions = {
53
+ /** Platform imprint, privacy link and footer text under every mail; see createRendererMailHtmlFeature. */
54
+ readonly mailFooter?: MailFooterResolver;
55
+ /** Word after the tenant name in the mail's brand row, e.g. "Status". */
56
+ readonly mailBrandSuffix?: MailBrandSuffixResolver;
57
+ };
58
+ export declare function composeRendererStack(options?: RendererStackOptions): FeatureDefinition[];
52
59
  export declare function composeMailStack(options: MailStackOptions): FeatureDefinition[];
53
60
  export declare function composeFileStack(options: FileStackOptions): FeatureDefinition[];
54
61
  /** Retention + compliance (+ optional lifecycle/sessions). UDR/files mount separately — order varies per app. */
@@ -30,7 +30,7 @@ import { createTemplateResolverFeature } from "@cosmicdrift/kumiko-bundled-featu
30
30
  import { createTenantLifecycleFeature } from "@cosmicdrift/kumiko-bundled-features/tenant-lifecycle";
31
31
  import { createUserDataRightsFeature, } from "@cosmicdrift/kumiko-bundled-features/user-data-rights";
32
32
  import { createUserDataRightsDefaultsFeature } from "@cosmicdrift/kumiko-bundled-features/user-data-rights-defaults";
33
- import { createRendererMailHtmlFeature } from "@cosmicdrift/kumiko-renderer-mail-html";
33
+ import { createRendererMailHtmlFeature, } from "@cosmicdrift/kumiko-renderer-mail-html";
34
34
  export function stackFeatureNames(features) {
35
35
  return features.map((f) => f.name);
36
36
  }
@@ -44,11 +44,14 @@ export function composePagesStack(options = {}) {
44
44
  createLegalPagesFeature(options.wrapLayout !== undefined ? { wrapLayout: options.wrapLayout } : {}),
45
45
  ];
46
46
  }
47
- export function composeRendererStack() {
47
+ export function composeRendererStack(options = {}) {
48
48
  return [
49
49
  createTemplateResolverFeature(),
50
50
  createRendererFoundationFeature(),
51
- createRendererMailHtmlFeature(),
51
+ createRendererMailHtmlFeature({
52
+ ...(options.mailFooter !== undefined && { footer: options.mailFooter }),
53
+ ...(options.mailBrandSuffix !== undefined && { brandSuffix: options.mailBrandSuffix }),
54
+ }),
52
55
  ];
53
56
  }
54
57
  export function composeMailStack(options) {
package/dist/index.d.ts CHANGED
@@ -11,7 +11,7 @@ export { runInitDeployCli } from "./init-deploy-cli.js";
11
11
  export type { RunDevAppAuthOptions, RunDevAppOptions, SeedFn } from "./run-dev-app.js";
12
12
  export { runDevApp } from "./run-dev-app.js";
13
13
  export type { ScaffoldAppOptions, ScaffoldAppResult, ScaffoldFeatureEntry, } from "./scaffold-app.js";
14
- export { scaffoldApp } from "./scaffold-app.js";
14
+ export { SCAFFOLD_BIOME_VERSION, scaffoldApp } from "./scaffold-app.js";
15
15
  export type { ScaffoldAppFeatureOptions, ScaffoldAppFeatureResult, } from "./scaffold-app-feature.js";
16
16
  export { runConfigPathForApp, scaffoldAppFeature } from "./scaffold-app-feature.js";
17
17
  export type { CheckDeployDriftResult, DeployDriftEntry, RenderDeployFilesOptions, RenderDeployFilesResult, RenderedDeployFile, ScaffoldDeployDetected, ScaffoldDeployOptions, ScaffoldDeployResult, ScaffoldedFile, } from "./scaffold-deploy.js";
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@ export { buildFewShotCorpus, pathToId } from "./few-shot-corpus.js";
25
25
  export { resolveFrameworkVersion } from "./framework-version.js";
26
26
  export { runInitDeployCli } from "./init-deploy-cli.js";
27
27
  export { runDevApp } from "./run-dev-app.js";
28
- export { scaffoldApp } from "./scaffold-app.js";
28
+ export { SCAFFOLD_BIOME_VERSION, scaffoldApp } from "./scaffold-app.js";
29
29
  export { runConfigPathForApp, scaffoldAppFeature } from "./scaffold-app-feature.js";
30
30
  export { checkDeployDrift, renderDeployFiles, scaffoldDeploy } from "./scaffold-deploy.js";
31
31
  export { scaffoldFeature } from "./scaffold-feature.js";
@@ -2,6 +2,8 @@ import { type KmsAdapter } from "@cosmicdrift/kumiko-framework/crypto";
2
2
  export type DevPiiKmsResolution = {
3
3
  readonly kms: KmsAdapter | undefined;
4
4
  readonly blindIndexKey: string | undefined;
5
+ /** True only when the ephemeral default minted the key, not when the app passed one. */
6
+ readonly generatedBlindIndexKey: boolean;
5
7
  readonly mode: "app-kms" | "ephemeral-default" | "plaintext";
6
8
  };
7
9
  type DevPiiKmsOptions = {
@@ -8,7 +8,9 @@ export function captureEphemeralDevPiiKms(resolution) {
8
8
  if (resolution.mode !== "ephemeral-default")
9
9
  return () => { };
10
10
  const ownKms = configuredPiiSubjectKms();
11
- const ownBlindIndexKey = configuredBlindIndexKey();
11
+ const ownBlindIndexKey = resolution.generatedBlindIndexKey
12
+ ? configuredBlindIndexKey()
13
+ : undefined;
12
14
  return () => {
13
15
  if (ownKms !== undefined && configuredPiiSubjectKms() === ownKms) {
14
16
  configurePiiSubjectKms(undefined);
@@ -20,14 +22,25 @@ export function captureEphemeralDevPiiKms(resolution) {
20
22
  }
21
23
  export function resolveDevPiiKms(options, env = process.env) {
22
24
  if (options.kms !== undefined && options.kms !== false) {
23
- return { kms: options.kms, blindIndexKey: options.blindIndexKey, mode: "app-kms" };
25
+ return {
26
+ kms: options.kms,
27
+ blindIndexKey: options.blindIndexKey,
28
+ generatedBlindIndexKey: false,
29
+ mode: "app-kms",
30
+ };
24
31
  }
25
32
  if (options.kms === false || isPersistentDevDb(env)) {
26
- return { kms: undefined, blindIndexKey: options.blindIndexKey, mode: "plaintext" };
33
+ return {
34
+ kms: undefined,
35
+ blindIndexKey: options.blindIndexKey,
36
+ generatedBlindIndexKey: false,
37
+ mode: "plaintext",
38
+ };
27
39
  }
28
40
  return {
29
41
  kms: new InMemoryKmsAdapter(),
30
42
  blindIndexKey: options.blindIndexKey ?? randomBytes(32).toString("base64"),
43
+ generatedBlindIndexKey: options.blindIndexKey === undefined,
31
44
  mode: "ephemeral-default",
32
45
  };
33
46
  }
@@ -22,7 +22,7 @@ import { TenantQueries } from "@cosmicdrift/kumiko-bundled-features/tenant";
22
22
  import { createInMemoryLoginRateLimiter, parseTrustedProxyHopsEnv, TRUSTED_PROXY_HOPS_ENV, } from "@cosmicdrift/kumiko-framework/api";
23
23
  import { configureBlindIndexKey, configurePiiSubjectKms, } from "@cosmicdrift/kumiko-framework/crypto";
24
24
  import { configureEntityFieldEncryption } from "@cosmicdrift/kumiko-framework/db";
25
- import { collectWriteHandlerQns, createRegistry, FILE_STORAGE_PROVIDER_BOOT_SENTINEL, FILE_STORAGE_PROVIDER_ENV, findTierResolverUsage, isTierResolverPlugin, validateAppCustomScreenWriteQns, validateBoot, } from "@cosmicdrift/kumiko-framework/engine";
25
+ import { collectWriteHandlerQns, createRegistry, FILE_STORAGE_PROVIDER_BOOT_SENTINEL, FILE_STORAGE_PROVIDER_ENV, findTierResolverUsage, requireTierResolverPlugin, validateAppCustomScreenWriteQns, validateBoot, } from "@cosmicdrift/kumiko-framework/engine";
26
26
  import { warnIfNonUtcServerTimeZone } from "@cosmicdrift/kumiko-framework/time";
27
27
  import { applyBootSeeds } from "@cosmicdrift/kumiko-server-runtime/boot/apply-boot-seeds";
28
28
  import { resolveBootCrypto } from "@cosmicdrift/kumiko-server-runtime/boot/boot-crypto";
@@ -67,7 +67,13 @@ export async function runDevApp(options) {
67
67
  // die früher nur runProdApp fing und sonst erst den Prod-Pod im
68
68
  // CrashLoopBackOff sterben ließ (#359). Wirft synchron, bevor ein
69
69
  // Socket oder Watcher (codegen-Write) aufgeht.
70
- validateBoot(features, bootOptions);
70
+ // Resolved before validateBoot: an app-supplied masterKey provider must satisfy the
71
+ // encrypted-field gate here exactly as it does in runProdApp.
72
+ const bootCrypto = resolveBootCrypto(envSource, options.masterKey);
73
+ validateBoot(features, {
74
+ ...(bootCrypto.entityFieldCipher && { entityFieldCipher: bootCrypto.entityFieldCipher }),
75
+ ...bootOptions,
76
+ });
71
77
  warnIfNonUtcServerTimeZone();
72
78
  validateAppCustomScreenWriteQns(process.cwd(), collectWriteHandlerQns(features));
73
79
  // Codegen + File-Watcher — schreibt `<appRoot>/.kumiko/types.generated.d.ts`
@@ -122,7 +128,6 @@ export async function runDevApp(options) {
122
128
  // throwaway-Registry hier extrahiert nur die config-Keys, weil der
123
129
  // configResolver vor dem Server-Boot konstruiert wird (stack.registry
124
130
  // gibt's erst onAfterSetup) — createKumikoServer baut intern seine eigene.
125
- const bootCrypto = resolveBootCrypto(envSource, options.masterKey);
126
131
  // App-wide cipher for `encrypted: true` entity fields (symmetrisch zu
127
132
  // runProdApp) — executors resolve it lazily.
128
133
  configureEntityFieldEncryption(bootCrypto.entityFieldCipher);
@@ -197,6 +202,13 @@ export async function runDevApp(options) {
197
202
  let tokenVerifier;
198
203
  const patFeature = features.find((f) => f.name === PAT_FEATURE);
199
204
  const mfaFeature = features.find((f) => f.name === AUTH_MFA_FEATURE);
205
+ if (effectiveAuth &&
206
+ mfaFeature &&
207
+ registry?.getWriteHandler(AuthHandlers.switchTenantMfaGate) === undefined) {
208
+ throw new Error(`[runDevApp] auth-mfa is mounted but ${AuthHandlers.switchTenantMfaGate} is not registered: ` +
209
+ "createAuthEmailPasswordFeature registers it only when mfaStatusChecker is set, and without it " +
210
+ "POST /auth/switch-tenant would mint sessions without the second factor");
211
+ }
200
212
  const patAuthFragment = patFeature
201
213
  ? {
202
214
  tokenVerifier: (rawToken) => {
@@ -322,11 +334,7 @@ export async function runDevApp(options) {
322
334
  // on the resolver being live (e.g. seed that writes a SystemAdmin's
323
335
  // tier-assignment can immediately read tier-cuts).
324
336
  if (tierResolverUsage) {
325
- const plugin = tierResolverUsage.options;
326
- if (!isTierResolverPlugin(plugin)) {
327
- throw new Error("tier-resolver registration options must provide a build() factory");
328
- }
329
- tierResolverHolder.resolver = await plugin.build({
337
+ tierResolverHolder.resolver = await requireTierResolverPlugin(tierResolverUsage).build({
330
338
  db: stack.db,
331
339
  registry: stack.registry,
332
340
  cacheSync: stack.cacheSync,
@@ -0,0 +1,3 @@
1
+ export declare const FRAMEWORK_AGENTS_IMPORT = "@node_modules/@cosmicdrift/kumiko-framework/AGENTS.md";
2
+ export declare function renderAgentsMd(): string;
3
+ export declare function renderClaudeMd(): string;
@@ -0,0 +1,17 @@
1
+ export const FRAMEWORK_AGENTS_IMPORT = "@node_modules/@cosmicdrift/kumiko-framework/AGENTS.md";
2
+ const FRAMEWORK_AGENTS_PATH = "node_modules/@cosmicdrift/kumiko-framework/AGENTS.md";
3
+ const FRAMEWORK_AGENTS_DIR = "node_modules/@cosmicdrift/kumiko-framework/agents/";
4
+ export function renderAgentsMd() {
5
+ return `# Agent instructions
6
+
7
+ This app is built on Kumiko. The framework guide for coding agents ships with the installed package and matches its version:
8
+
9
+ - \`${FRAMEWORK_AGENTS_PATH}\`: start here.
10
+ - \`${FRAMEWORK_AGENTS_DIR}\`: generated indexes of bundled features, widgets and tested examples.
11
+
12
+ Read the framework guide before you add or change a feature. App-specific rules go below.
13
+ `;
14
+ }
15
+ export function renderClaudeMd() {
16
+ return `${FRAMEWORK_AGENTS_IMPORT}\n@AGENTS.md\n`;
17
+ }
@@ -8,8 +8,7 @@
8
8
  // Auto-mount via ts-morph: opens src/run-config.ts, finds
9
9
  // `export const APP_FEATURES = [...]`, prepends import + appends entry.
10
10
  // User's promise "defineFeature → nichts woanders eintragen" is met
11
- // for the run-config side. Drizzle FEATURE_IMPORT_REGISTRY is NOT
12
- // touched here — DX-4 auto-discovery resolves that.
11
+ // for the run-config side; the schema is derived from the mounted features.
13
12
  import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
14
13
  import { join, resolve } from "node:path";
15
14
  import { Project, SyntaxKind } from "ts-morph";
@@ -16,6 +16,7 @@ export type ScaffoldTestSetupFactory = (input: {
16
16
  readonly appName: string;
17
17
  readonly frameworkVersion: string;
18
18
  }) => ScaffoldTestSetup;
19
+ export declare const SCAFFOLD_BIOME_VERSION = "2.5.7";
19
20
  export type ScaffoldAppOptions = {
20
21
  /** kebab-case app name (e.g. "my-shop"). Becomes package-name + folder. */
21
22
  readonly name: string;