@cosmicdrift/kumiko-dev-server 0.374.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/compose-stacks.d.ts +3 -0
- package/dist/compose-stacks.js +11 -4
- package/dist/create-kumiko-server.d.ts +4 -2
- package/dist/create-kumiko-server.js +23 -25
- package/dist/managed-files/app-jobs.d.ts +4 -0
- package/dist/managed-files/app-jobs.js +68 -0
- package/dist/managed-files/check.d.ts +2 -0
- package/dist/managed-files/check.js +4 -2
- package/dist/managed-files/index.d.ts +2 -4
- package/dist/managed-files/index.js +2 -4
- package/dist/managed-files/registry.d.ts +7 -1
- package/dist/managed-files/registry.js +37 -5
- package/dist/managed-files/sync.js +10 -1
- package/dist/run-dev-app.d.ts +26 -10
- package/dist/run-dev-app.js +108 -37
- package/dist/scaffold-app-feature.js +1 -1
- package/dist/scaffold-app.d.ts +3 -0
- package/dist/scaffold-app.js +6 -7
- package/dist/scaffold-demo-tasks.js +1 -1
- package/dist/scaffold-deploy.d.ts +6 -0
- package/dist/scaffold-deploy.js +16 -1
- package/dist/testing.d.ts +17 -0
- package/dist/testing.js +46 -0
- package/package.json +19 -10
- package/src/changes.json +18 -0
- package/templates/managed/ci-public.yml.template +134 -0
- package/templates/managed/ci.yml.template +9 -6
- package/templates/managed/husky-pre-push.template +1 -7
- package/templates/managed/kumiko-schema.ts.template +16 -1
- package/templates/managed/release.yml.template +17 -1
- package/dist/managed-files/port-registry.d.ts +0 -17
- package/dist/managed-files/port-registry.js +0 -31
- package/dist/managed-files/static-app-config.d.ts +0 -16
- package/dist/managed-files/static-app-config.js +0 -99
package/dist/run-dev-app.js
CHANGED
|
@@ -23,15 +23,17 @@ import { createInMemoryLoginRateLimiter, parseTrustedProxyHopsEnv, TRUSTED_PROXY
|
|
|
23
23
|
import { configureBlindIndexKey, configurePiiSubjectKms, } from "@cosmicdrift/kumiko-framework/crypto";
|
|
24
24
|
import { configureEntityFieldEncryption } from "@cosmicdrift/kumiko-framework/db";
|
|
25
25
|
import { collectWriteHandlerQns, createRegistry, FILE_STORAGE_PROVIDER_BOOT_SENTINEL, FILE_STORAGE_PROVIDER_ENV, findTierResolverUsage, requireTierResolverPlugin, validateAppCustomScreenWriteQns, validateBoot, } from "@cosmicdrift/kumiko-framework/engine";
|
|
26
|
+
import { parseEnv } from "@cosmicdrift/kumiko-framework/env";
|
|
26
27
|
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
27
28
|
import { warnIfNonUtcServerTimeZone } from "@cosmicdrift/kumiko-framework/time";
|
|
29
|
+
import { assertStackBootRequirements, isSeedFnList, rejectRemovedEffectiveFeatures, resolveBootOptionWiring, withBootOptionEnvBlocks, } from "@cosmicdrift/kumiko-server-runtime";
|
|
28
30
|
import { applyBootSeeds } from "@cosmicdrift/kumiko-server-runtime/boot/apply-boot-seeds";
|
|
29
31
|
import { resolveBootCrypto } from "@cosmicdrift/kumiko-server-runtime/boot/boot-crypto";
|
|
30
32
|
import { buildComposeAuthOptions, composeFeatures, } from "@cosmicdrift/kumiko-server-runtime/compose-features";
|
|
31
33
|
import { assertPiiBootInvariants } from "@cosmicdrift/kumiko-server-runtime/pii-boot-gate";
|
|
32
34
|
import { assertSessionBootInvariants } from "@cosmicdrift/kumiko-server-runtime/session-boot-gate";
|
|
33
35
|
import { createKumikoServer, logInfo, } from "./create-kumiko-server.js";
|
|
34
|
-
import { captureEphemeralDevPiiKms, resolveDevPiiKms } from "./resolve-dev-pii-kms.js";
|
|
36
|
+
import { captureEphemeralDevPiiKms, resolveDevPiiKms, } from "./resolve-dev-pii-kms.js";
|
|
35
37
|
import { renderWelcomeBanner } from "./welcome-banner.js";
|
|
36
38
|
import { addConfigAccessorFactory, buildBootExtraContext, loggerFromExtraContext, requireEnv, resolveAuthMail, resolveUnsubscribeWiring, } from "@cosmicdrift/kumiko-server-runtime/run-prod-app";
|
|
37
39
|
export async function runDevApp(options) {
|
|
@@ -39,12 +41,52 @@ export async function runDevApp(options) {
|
|
|
39
41
|
// source of truth — auch runProdApp und der per-app drizzle-Schema-
|
|
40
42
|
// Generator nutzen denselben Helper, damit Migration und Runtime nie
|
|
41
43
|
// auseinanderdriften können).
|
|
42
|
-
const
|
|
44
|
+
const rawEnvSource = options.envSource ?? process.env;
|
|
45
|
+
const seedsConfig = options.seeds !== undefined && !isSeedFnList(options.seeds) ? options.seeds : undefined;
|
|
46
|
+
const stackSeeds = options.seeds === undefined
|
|
47
|
+
? []
|
|
48
|
+
: isSeedFnList(options.seeds)
|
|
49
|
+
? options.seeds
|
|
50
|
+
: (options.seeds.run ?? []);
|
|
51
|
+
if (options.envSchema) {
|
|
52
|
+
// Free dev seeds take the test stack; the env blocks only read the declarative part.
|
|
53
|
+
const { seeds: _devSeeds, kms: devKms, ...bootFields } = options;
|
|
54
|
+
const composed = withBootOptionEnvBlocks(options.envSchema, {
|
|
55
|
+
...bootFields,
|
|
56
|
+
...(devKms && { kms: devKms }),
|
|
57
|
+
...(seedsConfig && { seeds: { ...seedsConfig, run: [] } }),
|
|
58
|
+
features: options.features,
|
|
59
|
+
});
|
|
60
|
+
parseEnv(composed.schema, rawEnvSource, { sources: composed.sources });
|
|
61
|
+
}
|
|
62
|
+
rejectRemovedEffectiveFeatures(options);
|
|
63
|
+
const wiring = await resolveBootOptionWiring({
|
|
64
|
+
features: options.features,
|
|
65
|
+
...(options.mail && { mail: options.mail }),
|
|
66
|
+
...(options.fileStorage && { fileStorage: options.fileStorage }),
|
|
67
|
+
...(options.kmsMode !== undefined && { kmsMode: options.kmsMode }),
|
|
68
|
+
...(options.billing && { billing: options.billing }),
|
|
69
|
+
...(options.featureToggles && { featureToggles: true }),
|
|
70
|
+
...(options.demoMode && { demoMode: options.demoMode }),
|
|
71
|
+
...(seedsConfig && { seeds: { ...seedsConfig, run: [] } }),
|
|
72
|
+
...(options.kms && { kms: options.kms }),
|
|
73
|
+
...(options.blindIndexKey !== undefined && { blindIndexKey: options.blindIndexKey }),
|
|
74
|
+
...(options.auth && { auth: options.auth }),
|
|
75
|
+
}, rawEnvSource);
|
|
76
|
+
const envSource = Object.keys(wiring.envDefaults).length > 0
|
|
77
|
+
? { ...wiring.envDefaults, ...rawEnvSource }
|
|
78
|
+
: rawEnvSource;
|
|
79
|
+
assertStackBootRequirements({
|
|
80
|
+
features: options.features,
|
|
81
|
+
...(options.featureToggles && { featureToggles: true }),
|
|
82
|
+
}, ["featureToggles"]);
|
|
43
83
|
// auth.mail → normalisiert in die expliziten Flow-Felder (symmetrisch zu
|
|
44
84
|
// runProdApp). hmacSecret = JWT_SECRET-env (fail-fast, symmetrisch zu
|
|
45
85
|
// runProdApp). Ab hier IMMER effectiveAuth statt options.auth.
|
|
46
86
|
const effectiveAuth = options.auth
|
|
47
|
-
? resolveAuthMail(options.auth, requireEnv("JWT_SECRET", envSource, "runDevApp"), envSource
|
|
87
|
+
? resolveAuthMail(options.auth, requireEnv("JWT_SECRET", envSource, "runDevApp"), envSource, {
|
|
88
|
+
mailConfigured: wiring.mailConfigured,
|
|
89
|
+
})
|
|
48
90
|
: undefined;
|
|
49
91
|
const composeAuthOptions = buildComposeAuthOptions(effectiveAuth);
|
|
50
92
|
const { unsubscribeLinks, unsubscribeRoutes } = resolveUnsubscribeWiring({
|
|
@@ -54,10 +96,10 @@ export async function runDevApp(options) {
|
|
|
54
96
|
envSource,
|
|
55
97
|
warn: (message) => createFallbackLogger("runDevApp").warn(message),
|
|
56
98
|
});
|
|
57
|
-
const extraRoutes = unsubscribeRoutes.length === 0
|
|
99
|
+
const extraRoutes = unsubscribeRoutes.length === 0 && wiring.extraRoutes.length === 0
|
|
58
100
|
? options.extraRoutes
|
|
59
|
-
: [...(options.extraRoutes ?? []), ...unsubscribeRoutes];
|
|
60
|
-
const features = composeFeatures(options.features, {
|
|
101
|
+
: [...(options.extraRoutes ?? []), ...unsubscribeRoutes, ...wiring.extraRoutes];
|
|
102
|
+
const features = composeFeatures([...options.features, ...wiring.features], {
|
|
61
103
|
includeBundled: !!effectiveAuth,
|
|
62
104
|
...(composeAuthOptions && { authOptions: composeAuthOptions }),
|
|
63
105
|
});
|
|
@@ -65,13 +107,19 @@ export async function runDevApp(options) {
|
|
|
65
107
|
// FILE_STORAGE_PROVIDER boot gate. Passed via the validateBoot env option
|
|
66
108
|
// instead of mutating process.env, so nothing leaks into later runDevApp
|
|
67
109
|
// calls in the same process.
|
|
68
|
-
const baseBootEnv =
|
|
110
|
+
const baseBootEnv = {
|
|
111
|
+
...wiring.envDefaults,
|
|
112
|
+
...(options.validateBootOptions?.env ?? process.env),
|
|
113
|
+
};
|
|
114
|
+
const hasBootEnvDefaults = Object.keys(wiring.envDefaults).length > 0;
|
|
69
115
|
const bootOptions = options.files !== undefined && baseBootEnv[FILE_STORAGE_PROVIDER_ENV] === undefined
|
|
70
116
|
? {
|
|
71
117
|
...options.validateBootOptions,
|
|
72
118
|
env: { ...baseBootEnv, [FILE_STORAGE_PROVIDER_ENV]: FILE_STORAGE_PROVIDER_BOOT_SENTINEL },
|
|
73
119
|
}
|
|
74
|
-
:
|
|
120
|
+
: hasBootEnvDefaults
|
|
121
|
+
? { ...options.validateBootOptions, env: baseBootEnv }
|
|
122
|
+
: options.validateBootOptions;
|
|
75
123
|
// Boot-Validation als allererstes — vor dem Server. Dieselbe
|
|
76
124
|
// Fehlerklasse (unqualifizierte nav-/handler-QNs, screen-access etc.),
|
|
77
125
|
// die früher nur runProdApp fing und sonst erst den Prod-Pod im
|
|
@@ -87,34 +135,33 @@ export async function runDevApp(options) {
|
|
|
87
135
|
warnIfNonUtcServerTimeZone();
|
|
88
136
|
validateAppCustomScreenWriteQns(process.cwd(), collectWriteHandlerQns(features));
|
|
89
137
|
// Sprint-8a Tier-Composition auto-wire: scan features for a
|
|
90
|
-
// tenantTierResolver-extension
|
|
91
|
-
//
|
|
92
|
-
//
|
|
138
|
+
// tenantTierResolver-extension and wire a late-bound wrapper here; it is
|
|
139
|
+
// filled in onAfterSetup (where stack.db + stack.registry are available).
|
|
140
|
+
// The global operator toggles layer over the same wrapper.
|
|
93
141
|
// App-Author sees nothing — `createTierEngineFeature(opts)` mounts +
|
|
94
142
|
// framework auto-wires.
|
|
95
|
-
const tierResolverUsage =
|
|
143
|
+
const tierResolverUsage = findTierResolverUsage(features);
|
|
96
144
|
const tierResolverHolder = {
|
|
97
145
|
resolver: undefined,
|
|
98
146
|
};
|
|
99
|
-
const finalEffectiveFeatures =
|
|
100
|
-
(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
: undefined);
|
|
147
|
+
const finalEffectiveFeatures = tierResolverUsage || wiring.featureToggles
|
|
148
|
+
? Object.assign((tenantId) => {
|
|
149
|
+
// Defensive: Server starts AFTER onAfterSetup completes, so the
|
|
150
|
+
// resolver is filled before any request comes in. Throwing here
|
|
151
|
+
// means a programming error (boot order) rather than silent
|
|
152
|
+
// "all-features-on" misbehavior.
|
|
153
|
+
if (!tierResolverHolder.resolver) {
|
|
154
|
+
throw new Error("tier-resolver: extension found but resolver not yet built — boot order issue?");
|
|
155
|
+
}
|
|
156
|
+
return tierResolverHolder.resolver(tenantId);
|
|
157
|
+
}, {
|
|
158
|
+
// Late-bind: der echte trialGate hängt erst nach plugin.build() am
|
|
159
|
+
// holder.resolver. Delegieren (nicht kopieren), weil dieses
|
|
160
|
+
// Wrapper-Closure vor build() konstruiert wird. Kein Trial → false.
|
|
161
|
+
trialGate: (tenantId, featureName) => tierResolverHolder.resolver?.trialGate?.(tenantId, featureName) ??
|
|
162
|
+
Promise.resolve(false),
|
|
163
|
+
})
|
|
164
|
+
: undefined;
|
|
118
165
|
// configResolver-default fürs config-feature — im auth-mode immer
|
|
119
166
|
// hinzufügen, im no-auth-mode dem Caller überlassen. Factory-form
|
|
120
167
|
// wird gewrap't damit der spread auf das aufgerufene Result greift,
|
|
@@ -129,10 +176,21 @@ export async function runDevApp(options) {
|
|
|
129
176
|
// Subject-KMS + Blind-Index (symmetrisch zu runProdApp). Dev warnt bei
|
|
130
177
|
// Klartext-PII statt zu failen; kms+lookupable ohne blindIndexKey bricht
|
|
131
178
|
// auch hier (Lookups wären in jedem Modus kaputt).
|
|
132
|
-
const piiKms =
|
|
179
|
+
const piiKms = options.kmsMode !== undefined
|
|
180
|
+
? {
|
|
181
|
+
kms: wiring.kms.kms,
|
|
182
|
+
blindIndexKey: wiring.kms.blindIndexKey,
|
|
183
|
+
generatedBlindIndexKey: false,
|
|
184
|
+
mode: wiring.kms.kms ? "app-kms" : "plaintext",
|
|
185
|
+
}
|
|
186
|
+
: resolveDevPiiKms(options);
|
|
133
187
|
configurePiiSubjectKms(piiKms.kms);
|
|
134
188
|
configureBlindIndexKey(piiKms.blindIndexKey);
|
|
135
189
|
const releaseEphemeralPiiKms = captureEphemeralDevPiiKms(piiKms);
|
|
190
|
+
const releasePiiKms = async () => {
|
|
191
|
+
releaseEphemeralPiiKms();
|
|
192
|
+
await wiring.kms.close();
|
|
193
|
+
};
|
|
136
194
|
assertPiiBootInvariants(features, {
|
|
137
195
|
kms: piiKms.kms,
|
|
138
196
|
blindIndexKey: piiKms.blindIndexKey,
|
|
@@ -260,6 +318,7 @@ export async function runDevApp(options) {
|
|
|
260
318
|
...(extraRoutes !== undefined && { extraRoutes }),
|
|
261
319
|
...(options.wire !== undefined && { wire: options.wire }),
|
|
262
320
|
...(trustedProxyHops !== undefined && { trustedProxyHops }),
|
|
321
|
+
...(options.demoMode && { wrapFetch: wiring.wrapFetch }),
|
|
263
322
|
...(finalEffectiveFeatures !== undefined && {
|
|
264
323
|
effectiveFeatures: finalEffectiveFeatures,
|
|
265
324
|
}),
|
|
@@ -342,6 +401,15 @@ export async function runDevApp(options) {
|
|
|
342
401
|
cacheSync: stack.cacheSync,
|
|
343
402
|
});
|
|
344
403
|
}
|
|
404
|
+
if (wiring.featureToggles) {
|
|
405
|
+
const bound = await wiring.featureToggles.bind({
|
|
406
|
+
db: stack.db,
|
|
407
|
+
registry: stack.registry,
|
|
408
|
+
cacheSync: stack.cacheSync,
|
|
409
|
+
tierResolver: tierResolverHolder.resolver,
|
|
410
|
+
});
|
|
411
|
+
tierResolverHolder.resolver = bound.resolver;
|
|
412
|
+
}
|
|
345
413
|
if (effectiveAuth && stack.registry.getExtensionUsages(EXT_SESSION_STORE).length > 0) {
|
|
346
414
|
// Secure-by-default (#1372): resolve sessionStore provider; bind
|
|
347
415
|
// password-change mass-revoke + MFA revokeAllOthers.
|
|
@@ -374,17 +442,20 @@ export async function runDevApp(options) {
|
|
|
374
442
|
// Runs before user-supplied seed callbacks so those can read /
|
|
375
443
|
// override the deploy-defaults. The helper indirection is what
|
|
376
444
|
// config-seed-boot.integration.ts pins — keep it as a single call.
|
|
445
|
+
for (const seed of wiring.shortcutSeeds) {
|
|
446
|
+
await seed(stack.db);
|
|
447
|
+
}
|
|
377
448
|
await applyBootSeeds({
|
|
378
449
|
registry: stack.registry,
|
|
379
450
|
db: stack.db,
|
|
380
451
|
...(bootCrypto.configCipher && { cipher: bootCrypto.configCipher }),
|
|
381
452
|
});
|
|
382
|
-
for (const seed of
|
|
453
|
+
for (const seed of stackSeeds) {
|
|
383
454
|
await seed(stack);
|
|
384
455
|
}
|
|
385
456
|
},
|
|
386
|
-
}).catch((error) => {
|
|
387
|
-
|
|
457
|
+
}).catch(async (error) => {
|
|
458
|
+
await releasePiiKms().catch(() => { });
|
|
388
459
|
throw error;
|
|
389
460
|
});
|
|
390
461
|
if (options.welcomeBanner) {
|
|
@@ -411,7 +482,7 @@ export async function runDevApp(options) {
|
|
|
411
482
|
await handle.stop();
|
|
412
483
|
}
|
|
413
484
|
finally {
|
|
414
|
-
|
|
485
|
+
await releasePiiKms();
|
|
415
486
|
}
|
|
416
487
|
},
|
|
417
488
|
};
|
|
@@ -427,7 +498,7 @@ export function mergeConfigResolverDefault(ctx, registry, envSource, cipher) {
|
|
|
427
498
|
...(cipher && { cipher }),
|
|
428
499
|
}),
|
|
429
500
|
};
|
|
430
|
-
// ctx.config wird per-Request aus
|
|
501
|
+
// ctx.config wird per-Request aus configAccessorFactory geminted (siehe
|
|
431
502
|
// addConfigAccessorFactory) — sonst bleibt ctx.config undefined und Handler
|
|
432
503
|
// die es lesen (createFileProviderForTenant) werfen. Aus dem EFFEKTIVEN
|
|
433
504
|
// Resolver (Caller-Override gewinnt) gebaut, symmetrisch zu runProdApp.
|
|
@@ -155,7 +155,7 @@ function renderWebIndex(name) {
|
|
|
155
155
|
// createKumikoApp({ clientFeatures: [...] }) einhängen — das passiert
|
|
156
156
|
// NICHT automatisch. Screens: eine Datei pro Screen in diesem Ordner.
|
|
157
157
|
|
|
158
|
-
import type { ClientFeatureDefinition } from "@cosmicdrift/kumiko-renderer
|
|
158
|
+
import type { ClientFeatureDefinition } from "@cosmicdrift/kumiko-renderer/web";
|
|
159
159
|
|
|
160
160
|
export const ${camel}Client: ClientFeatureDefinition = {
|
|
161
161
|
name: "${name}",
|
package/dist/scaffold-app.d.ts
CHANGED
|
@@ -37,6 +37,9 @@ export type ScaffoldAppOptions = {
|
|
|
37
37
|
* @cosmicdrift/kumiko-testing/scaffold. Without it the app gets the managed
|
|
38
38
|
* bunfigs but no example tests and fewer standard scripts. */
|
|
39
39
|
readonly testSetup?: ScaffoldTestSetupFactory;
|
|
40
|
+
/** Infra tag written to `package.json#kumiko.deploy.infraRef`. Unset: the managed
|
|
41
|
+
* workflows call infra at `main` and `kumiko check managed` warns. */
|
|
42
|
+
readonly infraRef?: string;
|
|
40
43
|
};
|
|
41
44
|
export type ScaffoldAppResult = {
|
|
42
45
|
readonly destination: string;
|
package/dist/scaffold-app.js
CHANGED
|
@@ -51,7 +51,7 @@ export async function scaffoldApp(options) {
|
|
|
51
51
|
mkdirSync(join(destination, "src"), { recursive: true });
|
|
52
52
|
mkdirSync(join(destination, "kumiko"), { recursive: true });
|
|
53
53
|
const files = [];
|
|
54
|
-
write(join(destination, "package.json"), renderPackageJson(options.name, version, testSetup));
|
|
54
|
+
write(join(destination, "package.json"), renderPackageJson(options.name, version, testSetup, options.infraRef));
|
|
55
55
|
files.push("package.json");
|
|
56
56
|
write(join(destination, "kumiko.json"), renderKumikoManifest());
|
|
57
57
|
files.push("kumiko.json");
|
|
@@ -137,18 +137,19 @@ function appScripts(testSetup) {
|
|
|
137
137
|
prepare: PREPARE_SCRIPT,
|
|
138
138
|
};
|
|
139
139
|
}
|
|
140
|
-
function renderPackageJson(name, version, testSetup) {
|
|
140
|
+
function renderPackageJson(name, version, testSetup, infraRef) {
|
|
141
141
|
return `${JSON.stringify({
|
|
142
142
|
name,
|
|
143
143
|
version: "0.1.0",
|
|
144
144
|
private: true,
|
|
145
145
|
type: "module",
|
|
146
|
+
...(infraRef === undefined ? {} : { kumiko: { deploy: { infraRef } } }),
|
|
146
147
|
scripts: appScripts(testSetup),
|
|
147
148
|
dependencies: {
|
|
148
149
|
"@cosmicdrift/kumiko-bundled-features": version,
|
|
149
150
|
"@cosmicdrift/kumiko-dev-server": version,
|
|
150
151
|
"@cosmicdrift/kumiko-framework": version,
|
|
151
|
-
"@cosmicdrift/kumiko-renderer
|
|
152
|
+
"@cosmicdrift/kumiko-renderer": version,
|
|
152
153
|
"@cosmicdrift/kumiko-server-runtime": version,
|
|
153
154
|
react: "^19.2.6",
|
|
154
155
|
"react-dom": "^19.2.6",
|
|
@@ -461,7 +462,7 @@ function renderClient(appName) {
|
|
|
461
462
|
},
|
|
462
463
|
{ specifier: "./features/tasks/web", names: ["tasksClient"] },
|
|
463
464
|
{
|
|
464
|
-
specifier: "@cosmicdrift/kumiko-renderer
|
|
465
|
+
specifier: "@cosmicdrift/kumiko-renderer/web",
|
|
465
466
|
names: ["type AppSchema", "createKumikoApp", "DefaultAppShell"],
|
|
466
467
|
},
|
|
467
468
|
{ specifier: "react", names: ["ReactNode"], typeOnly: true },
|
|
@@ -659,7 +660,7 @@ For full docs see https://docs.kumiko.rocks.
|
|
|
659
660
|
}
|
|
660
661
|
function renderStylesCss() {
|
|
661
662
|
return [
|
|
662
|
-
'@import "@cosmicdrift/kumiko-renderer
|
|
663
|
+
'@import "@cosmicdrift/kumiko-renderer/web/styles.css";',
|
|
663
664
|
"",
|
|
664
665
|
'@source "./**/*.{ts,tsx}";',
|
|
665
666
|
"",
|
|
@@ -693,7 +694,6 @@ function renderBinKumiko() {
|
|
|
693
694
|
'import { envSchema } from "./env-schema";',
|
|
694
695
|
"",
|
|
695
696
|
"const [, , cmd, ...rest] = Bun.argv;",
|
|
696
|
-
"// biome-ignore lint/suspicious/noConsole: CLI output is the feature.",
|
|
697
697
|
"const out = { log: (l: string) => console.log(l), err: (l: string) => console.error(l) };",
|
|
698
698
|
"",
|
|
699
699
|
'if (cmd === "schema") {',
|
|
@@ -708,7 +708,6 @@ function renderBinKumiko() {
|
|
|
708
708
|
" process.exit(await runConsumerCli(rest, out));",
|
|
709
709
|
"}",
|
|
710
710
|
"",
|
|
711
|
-
"// biome-ignore lint/suspicious/noConsole: CLI output is the feature.",
|
|
712
711
|
"console.error(",
|
|
713
712
|
` \`\\n Unknown: kumiko \${cmd ?? ""} — the standalone bundle only runs 'kumiko schema' and 'kumiko consumer'.\\n\`,`,
|
|
714
713
|
");",
|
|
@@ -195,7 +195,7 @@ export const tasksTranslations: TranslationsByLocale = {
|
|
|
195
195
|
`;
|
|
196
196
|
}
|
|
197
197
|
export function renderDemoTasksWebIndex() {
|
|
198
|
-
return `import type { ClientFeatureDefinition } from "@cosmicdrift/kumiko-renderer
|
|
198
|
+
return `import type { ClientFeatureDefinition } from "@cosmicdrift/kumiko-renderer/web";
|
|
199
199
|
import { tasksTranslations } from "../i18n";
|
|
200
200
|
|
|
201
201
|
export const tasksClient: ClientFeatureDefinition = {
|
|
@@ -88,9 +88,15 @@ export declare function scaffoldDeploy(options: ScaffoldDeployOptions): Scaffold
|
|
|
88
88
|
/** Read-only: compares the rendered deploy files against what's on disk
|
|
89
89
|
* without writing anything. */
|
|
90
90
|
export declare function checkDeployDrift(options: RenderDeployFilesOptions): CheckDeployDriftResult;
|
|
91
|
+
export declare const DEPLOY_VISIBILITIES: readonly ["private", "public"];
|
|
92
|
+
export type DeployVisibility = (typeof DEPLOY_VISIBILITIES)[number];
|
|
91
93
|
export type KumikoReleaseTarget = {
|
|
92
94
|
readonly namespace: string;
|
|
93
95
|
readonly deployment: string;
|
|
96
|
+
readonly visibility: DeployVisibility;
|
|
97
|
+
/** Unset until the app pins an infra tag; templates render `main` then. */
|
|
98
|
+
readonly infraRef: string | undefined;
|
|
99
|
+
readonly docStatusGuard: boolean;
|
|
94
100
|
};
|
|
95
101
|
/** Release target from `package.json#kumiko.deploy`; throws on an invalid deploy config. */
|
|
96
102
|
export declare function readReleaseTarget(sourceDir: string, appName: string): KumikoReleaseTarget;
|
package/dist/scaffold-deploy.js
CHANGED
|
@@ -147,6 +147,9 @@ const DB_IDENTIFIER_RE = /^[a-zA-Z0-9_][a-zA-Z0-9_-]{0,62}$/;
|
|
|
147
147
|
// Kubernetes object names (RFC 1123 label); they are interpolated into the
|
|
148
148
|
// generated release workflow, so nothing outside this charset may pass.
|
|
149
149
|
const K8S_NAME_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
150
|
+
// The ref lands in `uses: org/infra/...@<ref>` lines of generated workflows.
|
|
151
|
+
const INFRA_REF_RE = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,99}$/;
|
|
152
|
+
export const DEPLOY_VISIBILITIES = ["private", "public"];
|
|
150
153
|
const kumikoDeployConfigSchema = z
|
|
151
154
|
.object({
|
|
152
155
|
dbUser: z.string().regex(DB_IDENTIFIER_RE).optional(),
|
|
@@ -155,6 +158,12 @@ const kumikoDeployConfigSchema = z
|
|
|
155
158
|
namespace: z.string().regex(K8S_NAME_RE).optional(),
|
|
156
159
|
/** Name of the prod Deployment; default = appName. */
|
|
157
160
|
deployment: z.string().regex(K8S_NAME_RE).optional(),
|
|
161
|
+
/** Tag or branch of cosmicdriftgamestudio/infra that ci.yml and release.yml pin their reusable workflows to. */
|
|
162
|
+
infraRef: z.string().regex(INFRA_REF_RE).optional(),
|
|
163
|
+
/** A public repo cannot call the private infra workflows; default "private". */
|
|
164
|
+
visibility: z.enum(DEPLOY_VISIBILITIES).optional(),
|
|
165
|
+
/** `false` turns the doc-status guard off in ci.yml; default on. */
|
|
166
|
+
docStatusGuard: z.boolean().optional(),
|
|
158
167
|
// "directory" is the only behaviour left; the key stays valid so existing
|
|
159
168
|
// package.json files keep scaffolding.
|
|
160
169
|
stackNetwork: z
|
|
@@ -201,7 +210,13 @@ export function readReleaseTarget(sourceDir, appName) {
|
|
|
201
210
|
throw new Error(`scaffoldDeploy: "${value}" cannot serve as the release ${field} (must match ${K8S_NAME_RE}); set package.json#kumiko.deploy.${field}`);
|
|
202
211
|
}
|
|
203
212
|
}
|
|
204
|
-
return {
|
|
213
|
+
return {
|
|
214
|
+
namespace,
|
|
215
|
+
deployment,
|
|
216
|
+
visibility: config.visibility ?? "private",
|
|
217
|
+
infraRef: config.infraRef,
|
|
218
|
+
docStatusGuard: config.docStatusGuard ?? true,
|
|
219
|
+
};
|
|
205
220
|
}
|
|
206
221
|
function resolveDbIdentifier(configured, appName, field, role) {
|
|
207
222
|
const effective = configured ?? appName;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ExtraRouteDefinition } from "@cosmicdrift/kumiko-framework/api";
|
|
2
|
+
import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { type InProcessSeededTenant, type PersistedTenant, type SeedTenantOptions, type SeedWriter } from "@cosmicdrift/kumiko-testing";
|
|
4
|
+
import { type E2eSeedRoutesOptions } from "@cosmicdrift/kumiko-testing/e2e/seed-route";
|
|
5
|
+
import { type TestStack } from "@cosmicdrift/kumiko-testing/stack";
|
|
6
|
+
import { type SetupTestStackFromFeaturesOptions } from "./setup-test-stack-from-features.ts";
|
|
7
|
+
export type SetupAppTestStackOptions = SetupTestStackFromFeaturesOptions & {
|
|
8
|
+
readonly registryTables?: boolean;
|
|
9
|
+
};
|
|
10
|
+
export declare function setupAppTestStack(features: readonly FeatureDefinition[], options?: SetupAppTestStackOptions): Promise<TestStack>;
|
|
11
|
+
export declare function seedTenant(stack: TestStack, options?: SeedTenantOptions): Promise<InProcessSeededTenant>;
|
|
12
|
+
export declare function persistTenantRows(write: SeedWriter, options?: {
|
|
13
|
+
readonly name?: string;
|
|
14
|
+
readonly users?: number;
|
|
15
|
+
readonly admin?: SeedTenantOptions["admin"];
|
|
16
|
+
}): Promise<PersistedTenant>;
|
|
17
|
+
export declare function createE2eSeedRoutes(options?: E2eSeedRoutesOptions): readonly ExtraRouteDefinition[];
|
package/dist/testing.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Ready-made test functions that kumiko-testing cannot hold itself: it has no bundled-features or
|
|
2
|
+
// dev-server dependency, so those pieces are injected here and apps import the bound result.
|
|
3
|
+
import { hashPassword } from "@cosmicdrift/kumiko-bundled-features/auth-email-password";
|
|
4
|
+
import { createMailTransportInMemoryFeature, getInbox, } from "@cosmicdrift/kumiko-bundled-features/mail-transport-inmemory";
|
|
5
|
+
import { TenantHandlers, TenantQueries } from "@cosmicdrift/kumiko-bundled-features/tenant";
|
|
6
|
+
import { UserHandlers } from "@cosmicdrift/kumiko-bundled-features/user";
|
|
7
|
+
import { persistTenantRowsWith, seedTenantWith, } from "@cosmicdrift/kumiko-testing";
|
|
8
|
+
import { createE2eSeedRoutes as createE2eSeedRoutesWith, } from "@cosmicdrift/kumiko-testing/e2e/seed-route";
|
|
9
|
+
import { pushEntityProjectionTables } from "@cosmicdrift/kumiko-testing/stack";
|
|
10
|
+
import { setupTestStackFromFeatures, } from "./setup-test-stack-from-features.js";
|
|
11
|
+
export async function setupAppTestStack(features, options = {}) {
|
|
12
|
+
const { registryTables = true, includeBundled = true, ...stackOptions } = options;
|
|
13
|
+
const stack = await setupTestStackFromFeatures(features, {
|
|
14
|
+
...stackOptions,
|
|
15
|
+
includeBundled,
|
|
16
|
+
entityTables: registryTables,
|
|
17
|
+
});
|
|
18
|
+
if (!registryTables)
|
|
19
|
+
return stack;
|
|
20
|
+
try {
|
|
21
|
+
await pushEntityProjectionTables(stack, stack.registry);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
await stack.cleanup();
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
return stack;
|
|
28
|
+
}
|
|
29
|
+
const seedBundledApi = { hashPassword, UserHandlers, TenantHandlers };
|
|
30
|
+
const seedRouteBundledApi = {
|
|
31
|
+
...seedBundledApi,
|
|
32
|
+
tenantMeQuery: TenantQueries.me,
|
|
33
|
+
mailInbox: {
|
|
34
|
+
featureName: createMailTransportInMemoryFeature().name,
|
|
35
|
+
read: getInbox,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
export function seedTenant(stack, options = {}) {
|
|
39
|
+
return seedTenantWith(stack, options, seedBundledApi);
|
|
40
|
+
}
|
|
41
|
+
export function persistTenantRows(write, options = {}) {
|
|
42
|
+
return persistTenantRowsWith(write, options, seedBundledApi);
|
|
43
|
+
}
|
|
44
|
+
export function createE2eSeedRoutes(options = {}) {
|
|
45
|
+
return createE2eSeedRoutesWith(options, seedRouteBundledApi);
|
|
46
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-dev-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.377.0",
|
|
4
4
|
"description": "Dev-tooling for Kumiko apps: local dev-server bootstrap (runDevApp), scaffolding. Its compose-stacks/env-schema subpaths are consumed at prod boot too — see @cosmicdrift/kumiko-server-runtime for the prod runner itself.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"bunOnlyExports": [
|
|
20
20
|
".",
|
|
21
21
|
"./managed-files",
|
|
22
|
-
"./setup-test-stack-from-features"
|
|
22
|
+
"./setup-test-stack-from-features",
|
|
23
|
+
"./testing"
|
|
23
24
|
]
|
|
24
25
|
},
|
|
25
26
|
"exports": {
|
|
@@ -47,6 +48,10 @@
|
|
|
47
48
|
"types": "./dist/setup-test-stack-from-features.d.ts",
|
|
48
49
|
"default": "./dist/setup-test-stack-from-features.js"
|
|
49
50
|
},
|
|
51
|
+
"./testing": {
|
|
52
|
+
"types": "./dist/testing.d.ts",
|
|
53
|
+
"default": "./dist/testing.js"
|
|
54
|
+
},
|
|
50
55
|
"./schema-apply": {
|
|
51
56
|
"types": "./dist/schema-apply.d.ts",
|
|
52
57
|
"default": "./dist/schema-apply.js"
|
|
@@ -72,14 +77,14 @@
|
|
|
72
77
|
"kumiko-upgrade": "./bin/kumiko-upgrade.ts"
|
|
73
78
|
},
|
|
74
79
|
"dependencies": {
|
|
75
|
-
"@cosmicdrift/kumiko-bundled-features": "0.
|
|
76
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
77
|
-
"@cosmicdrift/kumiko-
|
|
78
|
-
"@cosmicdrift/kumiko-renderer-mail-html": "0.
|
|
79
|
-
"@cosmicdrift/kumiko-
|
|
80
|
-
"@cosmicdrift/kumiko-server-runtime": "0.
|
|
81
|
-
"@cosmicdrift/kumiko-testing": "0.
|
|
82
|
-
"@cosmicdrift/kumiko-types": "0.
|
|
80
|
+
"@cosmicdrift/kumiko-bundled-features": "0.377.0",
|
|
81
|
+
"@cosmicdrift/kumiko-framework": "0.377.0",
|
|
82
|
+
"@cosmicdrift/kumiko-renderer": "0.377.0",
|
|
83
|
+
"@cosmicdrift/kumiko-renderer-mail-html": "0.377.0",
|
|
84
|
+
"@cosmicdrift/kumiko-guards": "0.377.0",
|
|
85
|
+
"@cosmicdrift/kumiko-server-runtime": "0.377.0",
|
|
86
|
+
"@cosmicdrift/kumiko-testing": "0.377.0",
|
|
87
|
+
"@cosmicdrift/kumiko-types": "0.377.0",
|
|
83
88
|
"bun-types": "^1.3.13",
|
|
84
89
|
"ts-morph": "^28.0.0",
|
|
85
90
|
"zod": "^4.4.3"
|
|
@@ -112,6 +117,10 @@
|
|
|
112
117
|
"types": "./dist/setup-test-stack-from-features.d.ts",
|
|
113
118
|
"default": "./dist/setup-test-stack-from-features.js"
|
|
114
119
|
},
|
|
120
|
+
"./testing": {
|
|
121
|
+
"types": "./dist/testing.d.ts",
|
|
122
|
+
"default": "./dist/testing.js"
|
|
123
|
+
},
|
|
115
124
|
"./schema-apply": {
|
|
116
125
|
"types": "./dist/schema-apply.d.ts",
|
|
117
126
|
"default": "./dist/schema-apply.js"
|
package/src/changes.json
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "0.376.0",
|
|
4
|
+
"type": "improvement",
|
|
5
|
+
"title": "composeSaasStack with stripe warns instead of requiring billing",
|
|
6
|
+
"detail": "A stripe stack without the billing option logs a boot warning instead of aborting, and composeSaasStack takes featureToggles to require that option."
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"version": "0.376.0",
|
|
10
|
+
"type": "improvement",
|
|
11
|
+
"title": "Managed workflows take infraRef, visibility and app-* jobs",
|
|
12
|
+
"detail": "ci.yml and release.yml render the infra reusables as @<infraRef> from package.json#kumiko.deploy.infraRef. Without it sync-managed renders @main and kumiko check managed warns; this warning becomes an error in a later release. kumiko.deploy.visibility \"public\" renders a self-contained ci.yml and a release.yml that only prints the gh workflow run hint for the public rollout. Jobs named app-* survive a sync and are not drift. kumiko.deploy.docStatusGuard false renders doc-status-guard false."
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"version": "0.376.0",
|
|
16
|
+
"type": "improvement",
|
|
17
|
+
"title": "./testing subpath with the bound test stack and seed helpers",
|
|
18
|
+
"detail": "setupAppTestStack, seedTenant, persistTenantRows and createE2eSeedRoutes, wired to the bundled user, tenant and auth features."
|
|
19
|
+
},
|
|
2
20
|
{
|
|
3
21
|
"version": "0.373.0",
|
|
4
22
|
"type": "improvement",
|