@cosmicdrift/kumiko-dev-server 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/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/compose-stacks.d.ts
CHANGED
|
@@ -110,9 +110,12 @@ export type SaasStackOptions<TCaps extends Readonly<Record<string, unknown>> = R
|
|
|
110
110
|
readonly caps?: CreateCapOverviewOptions;
|
|
111
111
|
/** Mounts waitlist; it needs auth-email-password and user-data-rights. */
|
|
112
112
|
readonly waitlist?: WaitlistOptions;
|
|
113
|
+
/** Operator switches over the tiers: the stack then expects runProdApp / runDevApp's `featureToggles` option. */
|
|
114
|
+
readonly featureToggles?: boolean;
|
|
113
115
|
};
|
|
114
116
|
/** billing-foundation + tier-engine (+ caps, waitlist). `"stripe"` leaves
|
|
115
117
|
* subscription-stripe and its webhook route to runProdApp's `billing` option;
|
|
118
|
+
* Without that option a stripe stack only warns at boot.
|
|
116
119
|
* `"mollie"` mounts subscription-mollie, whose webhook route the app adds.
|
|
117
120
|
* billing-foundation requires tenant-lifecycle and compliance-profiles, which
|
|
118
121
|
* composeGdprStack({ tenantLifecycle: true }) mounts. */
|
package/dist/compose-stacks.js
CHANGED
|
@@ -40,8 +40,11 @@ import { createUserDataRightsDefaultsFeature } from "@cosmicdrift/kumiko-bundled
|
|
|
40
40
|
import { createUserProfileFeature } from "@cosmicdrift/kumiko-bundled-features/user-profile";
|
|
41
41
|
import { createWaitlistFeature, } from "@cosmicdrift/kumiko-bundled-features/waitlist";
|
|
42
42
|
import { createRendererMailHtmlFeature, } from "@cosmicdrift/kumiko-renderer-mail-html";
|
|
43
|
-
function withBootRequires(feature, stack, options) {
|
|
44
|
-
return {
|
|
43
|
+
function withBootRequires(feature, stack, options, severity = "error") {
|
|
44
|
+
return {
|
|
45
|
+
...feature,
|
|
46
|
+
bootRequires: { stack, options, ...(severity === "warn" && { severity }) },
|
|
47
|
+
};
|
|
45
48
|
}
|
|
46
49
|
export function stackFeatureNames(features) {
|
|
47
50
|
return features.map((f) => f.name);
|
|
@@ -179,16 +182,20 @@ export function composeAuthStack(options = {}) {
|
|
|
179
182
|
}
|
|
180
183
|
/** billing-foundation + tier-engine (+ caps, waitlist). `"stripe"` leaves
|
|
181
184
|
* subscription-stripe and its webhook route to runProdApp's `billing` option;
|
|
185
|
+
* Without that option a stripe stack only warns at boot.
|
|
182
186
|
* `"mollie"` mounts subscription-mollie, whose webhook route the app adds.
|
|
183
187
|
* billing-foundation requires tenant-lifecycle and compliance-profiles, which
|
|
184
188
|
* composeGdprStack({ tenantLifecycle: true }) mounts. */
|
|
185
189
|
export function composeSaasStack(options) {
|
|
186
190
|
const foundation = createBillingFoundationFeature(options.billingFoundation ?? {});
|
|
191
|
+
const tierEngine = createTierEngineFeature(options.tiers);
|
|
187
192
|
const out = [
|
|
188
193
|
options.billing === "stripe"
|
|
189
|
-
? withBootRequires(foundation, "composeSaasStack", ["billing"])
|
|
194
|
+
? withBootRequires(foundation, "composeSaasStack", ["billing"], "warn")
|
|
190
195
|
: foundation,
|
|
191
|
-
|
|
196
|
+
options.featureToggles
|
|
197
|
+
? withBootRequires(tierEngine, "composeSaasStack", ["featureToggles"])
|
|
198
|
+
: tierEngine,
|
|
192
199
|
];
|
|
193
200
|
if (options.billing === "mollie")
|
|
194
201
|
out.push(createSubscriptionMollieFeature(options.mollie));
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { type AuthRoutesConfig, type ExtraRouteDefinition } from "@cosmicdrift/kumiko-framework/api";
|
|
3
3
|
import { type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
4
|
import type { ErrorDocsConfig } from "@cosmicdrift/kumiko-framework/errors";
|
|
5
|
-
import { type PageHeadResolver, type PageHeadSystemQuery } from "@cosmicdrift/kumiko-headless/apex";
|
|
5
|
+
import { type PageHeadResolver, type PageHeadSystemQuery } from "@cosmicdrift/kumiko-renderer/headless/apex";
|
|
6
6
|
import { type SystemWireDeps } from "@cosmicdrift/kumiko-server-runtime/extra-routes-deps";
|
|
7
7
|
import { type TestStack, type TestStackOptions } from "@cosmicdrift/kumiko-testing/stack";
|
|
8
8
|
type BunServer = typeof Bun extends undefined ? unknown : ReturnType<typeof Bun.serve>;
|
|
@@ -99,7 +99,7 @@ export type CreateKumikoServerOptions = {
|
|
|
99
99
|
* whether the CLI keeps running with `--watch` afterwards.
|
|
100
100
|
*
|
|
101
101
|
* When `undefined` and a client entry is set, the app's own
|
|
102
|
-
* src/styles.css wins, otherwise `@cosmicdrift/kumiko-renderer
|
|
102
|
+
* src/styles.css wins, otherwise `@cosmicdrift/kumiko-renderer/web/styles.css`
|
|
103
103
|
* resolves via package exports, so no sample hardcodes a monorepo-relative path.
|
|
104
104
|
*
|
|
105
105
|
* `stylesheet: false` disables the CSS pipeline. */
|
|
@@ -183,6 +183,8 @@ export type CreateKumikoServerOptions = {
|
|
|
183
183
|
* ships unchanged with status 200 — never a 500. `systemQuery` inside
|
|
184
184
|
* the resolver runs as the anonymous role, same as prod. */
|
|
185
185
|
readonly resolvePageHead?: PageHeadResolver;
|
|
186
|
+
/** Wraps the request handler before it reaches Bun.serve and `handle.fetch`, e.g. the demo read-only gate. */
|
|
187
|
+
readonly wrapFetch?: (handler: (req: Request, socketAddress?: string) => Promise<Response>) => (req: Request, socketAddress?: string) => Promise<Response>;
|
|
186
188
|
};
|
|
187
189
|
export type KumikoServerHandle = {
|
|
188
190
|
/** The fetch handler that routes a Request through the dev-server
|
|
@@ -26,7 +26,7 @@ import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
|
26
26
|
import { rankPreferredLocales } from "@cosmicdrift/kumiko-framework/i18n";
|
|
27
27
|
import { createMetricsForFactory } from "@cosmicdrift/kumiko-framework/observability";
|
|
28
28
|
import { TestUsers } from "@cosmicdrift/kumiko-framework/seeding";
|
|
29
|
-
import { resolveAndInjectPageHead, } from "@cosmicdrift/kumiko-headless/apex";
|
|
29
|
+
import { resolveAndInjectPageHead, } from "@cosmicdrift/kumiko-renderer/headless/apex";
|
|
30
30
|
import { startDevJobRunners } from "@cosmicdrift/kumiko-server-runtime/boot/job-run-logger";
|
|
31
31
|
import { buildBunServeOptions, resolveDerivedMaxRequestBodySize, } from "@cosmicdrift/kumiko-server-runtime/bun-serve-options";
|
|
32
32
|
import { makeDispatchSystemQuery, makeDispatchSystemWrite, } from "@cosmicdrift/kumiko-server-runtime/extra-routes-deps";
|
|
@@ -99,7 +99,7 @@ const RELOAD_SNIPPET = `
|
|
|
99
99
|
// keyboard shrinks only the visual viewport, not the layout viewport that
|
|
100
100
|
// `position: fixed` anchors to — a fixed bottom action bar would stay
|
|
101
101
|
// pinned behind the keyboard instead of above it. iOS Safari ignores the
|
|
102
|
-
// key; renderer
|
|
102
|
+
// key; renderer/web's useKeyboardInset covers it via window.visualViewport.
|
|
103
103
|
export const DEFAULT_HTML = `<!doctype html>
|
|
104
104
|
<html lang="en">
|
|
105
105
|
<head>
|
|
@@ -393,23 +393,20 @@ function expandWatchPatterns(patterns) {
|
|
|
393
393
|
}
|
|
394
394
|
return out;
|
|
395
395
|
}
|
|
396
|
-
//
|
|
397
|
-
// -
|
|
398
|
-
// - false → CSS
|
|
399
|
-
// - undefined + client
|
|
400
|
-
// 1.
|
|
401
|
-
// 2.
|
|
402
|
-
// - undefined +
|
|
396
|
+
// Resolves the path to the Tailwind entry CSS:
|
|
397
|
+
// - explicit string → the resolved absolute path
|
|
398
|
+
// - false → CSS pipeline off (undefined)
|
|
399
|
+
// - undefined + client entries:
|
|
400
|
+
// 1. the app's own src/styles.css (theme override) if present
|
|
401
|
+
// 2. else the default `@cosmicdrift/kumiko-renderer/web/styles.css` via package exports
|
|
402
|
+
// - undefined + no client entry: undefined (no CSS needed)
|
|
403
403
|
//
|
|
404
|
-
//
|
|
405
|
-
//
|
|
406
|
-
//
|
|
407
|
-
//
|
|
408
|
-
// werden ignoriert (DX-Falle).
|
|
404
|
+
// The src/styles.css auto-detection mirrors build-prod-bundle:resolveStylesheetEntry
|
|
405
|
+
// so dev and prod resolve identically. Without it every app would have to set
|
|
406
|
+
// `stylesheet: "./src/styles.css"`, or dev would fall back to the default and
|
|
407
|
+
// ignore the brand tokens.
|
|
409
408
|
//
|
|
410
|
-
// @internal —
|
|
411
|
-
// re-exportiert. Konsumenten gehen ausschließlich über die `stylesheet`-
|
|
412
|
-
// Option der createKumikoServer-Aufrufstelle.
|
|
409
|
+
// @internal — exported for unit tests only, not re-exported from the package index.
|
|
413
410
|
export function resolveStylesheet(options) {
|
|
414
411
|
if (options.stylesheet === false)
|
|
415
412
|
return undefined;
|
|
@@ -419,12 +416,12 @@ export function resolveStylesheet(options) {
|
|
|
419
416
|
(options.clientEntries !== undefined && options.clientEntries.length > 0);
|
|
420
417
|
if (!hasAnyEntry)
|
|
421
418
|
return undefined;
|
|
422
|
-
//
|
|
423
|
-
//
|
|
419
|
+
// The app's own src/styles.css beats the renderer/web default, same as
|
|
420
|
+
// kumiko-build, so local and prod builds match.
|
|
424
421
|
const local = resolve(process.cwd(), "src/styles.css");
|
|
425
422
|
if (existsSync(local))
|
|
426
423
|
return local;
|
|
427
|
-
// Bun.resolveSync folgt Package-Exports — "./styles.css" in renderer
|
|
424
|
+
// Bun.resolveSync folgt Package-Exports — "./styles.css" in renderer/web's
|
|
428
425
|
// package.json. Das Monorepo auflöst direkt auf den Workspace-File, eine
|
|
429
426
|
// installierte Fremd-App auf den node_modules-File. Kein `fileURLToPath`
|
|
430
427
|
// nötig, Bun gibt schon einen absoluten FS-Pfad zurück.
|
|
@@ -435,14 +432,14 @@ export function resolveStylesheet(options) {
|
|
|
435
432
|
return undefined;
|
|
436
433
|
}
|
|
437
434
|
try {
|
|
438
|
-
const resolved = Bun.resolveSync("@cosmicdrift/kumiko-renderer
|
|
435
|
+
const resolved = Bun.resolveSync("@cosmicdrift/kumiko-renderer/web/styles.css", process.cwd());
|
|
439
436
|
if (!canResolveTailwindStylesheet(resolved, { bun: Bun, cwd: process.cwd() })) {
|
|
440
437
|
return undefined;
|
|
441
438
|
}
|
|
442
439
|
return resolved;
|
|
443
440
|
}
|
|
444
441
|
catch (err) {
|
|
445
|
-
logError("[kumiko-server] couldn't auto-resolve @cosmicdrift/kumiko-renderer
|
|
442
|
+
logError("[kumiko-server] couldn't auto-resolve @cosmicdrift/kumiko-renderer/web/styles.css — " +
|
|
446
443
|
"pass `stylesheet: <path>` or `stylesheet: false` explicitly.", err);
|
|
447
444
|
return undefined;
|
|
448
445
|
}
|
|
@@ -556,7 +553,7 @@ export async function createKumikoServer(options) {
|
|
|
556
553
|
// reloads after a bundle rebuild anyway.
|
|
557
554
|
//
|
|
558
555
|
// Without an explicit `stylesheet`, the default resolves through the
|
|
559
|
-
// package exports of `@cosmicdrift/kumiko-renderer
|
|
556
|
+
// package exports of `@cosmicdrift/kumiko-renderer/web`, which works both
|
|
560
557
|
// for a workspace link and for an installed app (node_modules).
|
|
561
558
|
let stylesheetPath;
|
|
562
559
|
let killTailwind;
|
|
@@ -800,9 +797,9 @@ export async function createKumikoServer(options) {
|
|
|
800
797
|
? realpathSync(lexicalPublicDir)
|
|
801
798
|
: lexicalPublicDir;
|
|
802
799
|
// The app's cwd first, this package's own location as fallback (the cwd may
|
|
803
|
-
// not have renderer
|
|
800
|
+
// not have renderer/web installed, e.g. a fixture dir).
|
|
804
801
|
const rendererWebFontsDir = resolveRendererWebFontsDir([process.cwd(), import.meta.dir]);
|
|
805
|
-
const
|
|
802
|
+
const routeFetch = async (req, socketAddress) => {
|
|
806
803
|
const url = new URL(req.url);
|
|
807
804
|
// Specific routes first — assets, reload-SSE, API.
|
|
808
805
|
if (req.method === "GET") {
|
|
@@ -959,6 +956,7 @@ export async function createKumikoServer(options) {
|
|
|
959
956
|
// header-hygiene note).
|
|
960
957
|
return stripNoRouteMatchHeader(await stack.app.fetch(req, socketAddress));
|
|
961
958
|
};
|
|
959
|
+
const handleFetch = options.wrapFetch ? options.wrapFetch(routeFetch) : routeFetch;
|
|
962
960
|
// --- HTTP server (Bun only) ---
|
|
963
961
|
// Under Node/vitest we skip Bun.serve entirely — the handle's
|
|
964
962
|
// .fetch() is the test surface. Real dev runs under Bun, where
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function stripAppJobs(content: string): string;
|
|
2
|
+
export declare function appJobsOf(content: string): string;
|
|
3
|
+
/** Appends the app-owned jobs of `existing` to a freshly rendered workflow. */
|
|
4
|
+
export declare function withAppJobsOf(rendered: string, existing: string): string;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Jobs named `app-<name>` in a managed workflow belong to the app: drift
|
|
2
|
+
// checks ignore them and a sync carries them over.
|
|
3
|
+
const JOBS_HEADER = /^jobs:\s*$/;
|
|
4
|
+
const JOB_START = /^ {2}([A-Za-z0-9_-]+):\s*$/;
|
|
5
|
+
const TOP_LEVEL_KEY = /^[^\s#]/;
|
|
6
|
+
const APP_JOB_PREFIX = "app-";
|
|
7
|
+
function isDetachedComment(line) {
|
|
8
|
+
return /^ {2}#/.test(line);
|
|
9
|
+
}
|
|
10
|
+
function findJobBlocks(lines) {
|
|
11
|
+
const header = lines.findIndex((line) => JOBS_HEADER.test(line));
|
|
12
|
+
if (header === -1)
|
|
13
|
+
return [];
|
|
14
|
+
const starts = [];
|
|
15
|
+
let jobsEnd = lines.length;
|
|
16
|
+
for (let index = header + 1; index < lines.length; index++) {
|
|
17
|
+
const line = lines[index] ?? "";
|
|
18
|
+
if (TOP_LEVEL_KEY.test(line)) {
|
|
19
|
+
jobsEnd = index;
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
const match = JOB_START.exec(line);
|
|
23
|
+
if (match?.[1] !== undefined)
|
|
24
|
+
starts.push({ name: match[1], start: index });
|
|
25
|
+
}
|
|
26
|
+
return starts.map(({ name, start }, position) => {
|
|
27
|
+
let end = starts[position + 1]?.start ?? jobsEnd;
|
|
28
|
+
// Comments and blank lines right above the next job introduce that job.
|
|
29
|
+
while (end > start + 1 && (isDetachedComment(lines[end - 1] ?? "") || lines[end - 1] === "")) {
|
|
30
|
+
end--;
|
|
31
|
+
}
|
|
32
|
+
return { name, start: introStart(lines, start, starts[position - 1]), end };
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function introStart(lines, start, previous) {
|
|
36
|
+
let first = start;
|
|
37
|
+
const floor = previous === undefined ? 0 : previous.start + 1;
|
|
38
|
+
while (first > floor && isDetachedComment(lines[first - 1] ?? ""))
|
|
39
|
+
first--;
|
|
40
|
+
return first;
|
|
41
|
+
}
|
|
42
|
+
function appJobBlocks(lines) {
|
|
43
|
+
return findJobBlocks(lines).filter((block) => block.name.startsWith(APP_JOB_PREFIX));
|
|
44
|
+
}
|
|
45
|
+
export function stripAppJobs(content) {
|
|
46
|
+
const lines = content.split("\n");
|
|
47
|
+
const removed = new Set();
|
|
48
|
+
for (const { start, end } of appJobBlocks(lines)) {
|
|
49
|
+
for (let index = start; index < end; index++)
|
|
50
|
+
removed.add(index);
|
|
51
|
+
if (lines[start - 1] === "")
|
|
52
|
+
removed.add(start - 1);
|
|
53
|
+
}
|
|
54
|
+
return lines.filter((_, index) => !removed.has(index)).join("\n");
|
|
55
|
+
}
|
|
56
|
+
export function appJobsOf(content) {
|
|
57
|
+
const lines = content.split("\n");
|
|
58
|
+
return appJobBlocks(lines)
|
|
59
|
+
.map(({ start, end }) => lines.slice(start, end).join("\n"))
|
|
60
|
+
.join("\n\n");
|
|
61
|
+
}
|
|
62
|
+
/** Appends the app-owned jobs of `existing` to a freshly rendered workflow. */
|
|
63
|
+
export function withAppJobsOf(rendered, existing) {
|
|
64
|
+
const appJobs = appJobsOf(existing);
|
|
65
|
+
if (appJobs === "")
|
|
66
|
+
return rendered;
|
|
67
|
+
return `${rendered.replace(/\n+$/, "")}\n\n${appJobs}\n`;
|
|
68
|
+
}
|
|
@@ -28,6 +28,8 @@ export type ManagedCheckResult = {
|
|
|
28
28
|
/** kumiko.config.ts calls defineAppConfig: drift fails the check from then on. */
|
|
29
29
|
readonly adopted: boolean;
|
|
30
30
|
readonly files: readonly ManagedFileReport[];
|
|
31
|
+
/** Findings that do not fail the check. */
|
|
32
|
+
readonly warnings: readonly string[];
|
|
31
33
|
};
|
|
32
34
|
export declare function checkManagedFiles(facts: ManagedAppFacts): ManagedCheckResult;
|
|
33
35
|
/** States that fail `kumiko check managed` once the app has adopted kumiko.config.ts. */
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { mergeBunfig } from "@cosmicdrift/kumiko-testing/bunfig";
|
|
4
|
+
import { stripAppJobs } from "./app-jobs.js";
|
|
4
5
|
import { parseJsonc } from "./jsonc.js";
|
|
5
6
|
import { renderLineDiff } from "./line-diff.js";
|
|
6
7
|
import { readManagedMarker, stripManagedMarker } from "./marker.js";
|
|
7
|
-
import { BIOME_PRESET, listManagedFiles, renderManagedBunfig, TSCONFIG_PRESET, } from "./registry.js";
|
|
8
|
+
import { BIOME_PRESET, listManagedFiles, listManagedWarnings, renderManagedBunfig, TSCONFIG_PRESET, } from "./registry.js";
|
|
8
9
|
function readIfExists(path) {
|
|
9
10
|
return existsSync(path) ? readFileSync(path, "utf-8") : undefined;
|
|
10
11
|
}
|
|
@@ -17,7 +18,7 @@ function checkTemplate(file, existing) {
|
|
|
17
18
|
return { state: "invalid", message: marker.message };
|
|
18
19
|
if (marker.kind === "local")
|
|
19
20
|
return { state: "local", reason: marker.reason };
|
|
20
|
-
const diff = compareBodies(stripManagedMarker(file.content), stripManagedMarker(existing));
|
|
21
|
+
const diff = compareBodies(stripAppJobs(stripManagedMarker(file.content)), stripAppJobs(stripManagedMarker(existing)));
|
|
21
22
|
if (marker.kind === "unmarked")
|
|
22
23
|
return diff === undefined ? { state: "unmarked" } : { state: "unmarked", diff };
|
|
23
24
|
return diff === undefined ? { state: "ok" } : { state: "differs", diff };
|
|
@@ -101,6 +102,7 @@ export function checkManagedFiles(facts) {
|
|
|
101
102
|
kind: file.kind,
|
|
102
103
|
...checkFile(file, facts),
|
|
103
104
|
})),
|
|
105
|
+
warnings: listManagedWarnings(facts),
|
|
104
106
|
};
|
|
105
107
|
}
|
|
106
108
|
/** States that fail `kumiko check managed` once the app has adopted kumiko.config.ts. */
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
export { listAppPorts } from "@cosmicdrift/kumiko-
|
|
1
|
+
export { findPortCollisions, listAppPorts, type PortClaim, type PortCollision, parseStaticAppConfig, readStaticAppConfig, type ScannedApp, type StaticAppConfig, scanWorkspaceApps, } from "@cosmicdrift/kumiko-guards/repo-manifest/app-ports";
|
|
2
2
|
export { type AppScriptsReport, checkAppScripts, KNOWN_EXTRA_APP_SCRIPTS, PREPARE_SCRIPT, STANDARD_APP_SCRIPTS, type StandardAppScript, } from "./app-scripts.ts";
|
|
3
3
|
export { checkManagedFiles, isManagedDrift, type ManagedCheckResult, type ManagedFileReport, type ManagedFileState, } from "./check.ts";
|
|
4
4
|
export { addManagedMarker, MANAGED_MARKER, readManagedMarker, stripManagedMarker, } from "./marker.ts";
|
|
5
|
-
export {
|
|
6
|
-
export { BIOME_PRESET, listManagedFiles, type ManagedAppFacts, type ManagedAppInput, type ManagedFile, type ManagedTemplateFile, renderBiomePresetJson, renderManagedBunfig, renderManagedTemplates, renderTsconfigPresetJson, resolveManagedAppFacts, TSCONFIG_PRESET, } from "./registry.ts";
|
|
7
|
-
export { parseStaticAppConfig, readStaticAppConfig, type StaticAppConfig, } from "./static-app-config.ts";
|
|
5
|
+
export { BIOME_PRESET, listManagedFiles, listManagedWarnings, type ManagedAppFacts, type ManagedAppInput, type ManagedFile, type ManagedTemplateFile, renderBiomePresetJson, renderManagedBunfig, renderManagedTemplates, renderTsconfigPresetJson, resolveManagedAppFacts, TSCONFIG_PRESET, } from "./registry.ts";
|
|
8
6
|
export { type SyncAction, type SyncManagedOptions, syncManagedFiles } from "./sync.ts";
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
export { listAppPorts } from "@cosmicdrift/kumiko-
|
|
1
|
+
export { findPortCollisions, listAppPorts, parseStaticAppConfig, readStaticAppConfig, scanWorkspaceApps, } from "@cosmicdrift/kumiko-guards/repo-manifest/app-ports";
|
|
2
2
|
export { checkAppScripts, KNOWN_EXTRA_APP_SCRIPTS, PREPARE_SCRIPT, STANDARD_APP_SCRIPTS, } from "./app-scripts.js";
|
|
3
3
|
export { checkManagedFiles, isManagedDrift, } from "./check.js";
|
|
4
4
|
export { addManagedMarker, MANAGED_MARKER, readManagedMarker, stripManagedMarker, } from "./marker.js";
|
|
5
|
-
export {
|
|
6
|
-
export { BIOME_PRESET, listManagedFiles, renderBiomePresetJson, renderManagedBunfig, renderManagedTemplates, renderTsconfigPresetJson, resolveManagedAppFacts, TSCONFIG_PRESET, } from "./registry.js";
|
|
7
|
-
export { parseStaticAppConfig, readStaticAppConfig, } from "./static-app-config.js";
|
|
5
|
+
export { BIOME_PRESET, listManagedFiles, listManagedWarnings, renderBiomePresetJson, renderManagedBunfig, renderManagedTemplates, renderTsconfigPresetJson, resolveManagedAppFacts, TSCONFIG_PRESET, } from "./registry.js";
|
|
8
6
|
export { syncManagedFiles } from "./sync.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type StaticAppConfig } from "
|
|
1
|
+
import { type StaticAppConfig } from "@cosmicdrift/kumiko-guards/repo-manifest/app-ports";
|
|
2
|
+
import { type KumikoReleaseTarget } from "../scaffold-deploy.ts";
|
|
2
3
|
export declare const BIOME_PRESET = "@cosmicdrift/kumiko-framework/biome";
|
|
3
4
|
export declare const TSCONFIG_PRESET = "@cosmicdrift/kumiko-framework/tsconfig.app.json";
|
|
4
5
|
export type ManagedAppInput = {
|
|
@@ -13,7 +14,10 @@ export type ManagedAppFacts = {
|
|
|
13
14
|
readonly appName: string;
|
|
14
15
|
readonly hasUi: boolean;
|
|
15
16
|
readonly config: StaticAppConfig;
|
|
17
|
+
readonly deploy: KumikoReleaseTarget;
|
|
16
18
|
};
|
|
19
|
+
/** Ref used in the workflow templates while `kumiko.deploy.infraRef` is unset. */
|
|
20
|
+
export declare const DEFAULT_INFRA_REF = "main";
|
|
17
21
|
/** Byte-compared after the marker lines are stripped. */
|
|
18
22
|
export type ManagedTemplateFile = {
|
|
19
23
|
readonly kind: "template";
|
|
@@ -40,6 +44,8 @@ export type ManagedSeedFile = {
|
|
|
40
44
|
};
|
|
41
45
|
export type ManagedFile = ManagedTemplateFile | ManagedBunfigFile | ManagedStructuralFile | ManagedSeedFile;
|
|
42
46
|
export declare function resolveManagedAppFacts(input: ManagedAppInput): ManagedAppFacts;
|
|
47
|
+
/** Findings that do not fail `kumiko check managed`. */
|
|
48
|
+
export declare function listManagedWarnings(facts: ManagedAppFacts): readonly string[];
|
|
43
49
|
export declare function renderManagedTemplates(facts: ManagedAppFacts): readonly ManagedTemplateFile[];
|
|
44
50
|
export declare function managedBunfigFiles(facts: ManagedAppFacts): readonly ManagedBunfigFile[];
|
|
45
51
|
/** Options are the app's choice; read them from the file so the check only flags real drift. */
|
|
@@ -5,13 +5,15 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { parseJsonOrThrow } from "@cosmicdrift/kumiko-framework/utils";
|
|
8
|
+
import { readStaticAppConfig, } from "@cosmicdrift/kumiko-guards/repo-manifest/app-ports";
|
|
8
9
|
import { BUNFIG_FILES, renderBunfig } from "@cosmicdrift/kumiko-testing/bunfig";
|
|
9
10
|
import { isKebabSegment } from "../kebab.js";
|
|
10
|
-
import { readReleaseTarget, render, renderDeployFiles } from "../scaffold-deploy.js";
|
|
11
|
+
import { readReleaseTarget, render, renderDeployFiles, } from "../scaffold-deploy.js";
|
|
11
12
|
import { addManagedMarker } from "./marker.js";
|
|
12
|
-
import { readStaticAppConfig } from "./static-app-config.js";
|
|
13
13
|
export const BIOME_PRESET = "@cosmicdrift/kumiko-framework/biome";
|
|
14
14
|
export const TSCONFIG_PRESET = "@cosmicdrift/kumiko-framework/tsconfig.app.json";
|
|
15
|
+
/** Ref used in the workflow templates while `kumiko.deploy.infraRef` is unset. */
|
|
16
|
+
export const DEFAULT_INFRA_REF = "main";
|
|
15
17
|
function templatesDir() {
|
|
16
18
|
return join(dirname(fileURLToPath(import.meta.url)), "..", "..", "templates", "managed");
|
|
17
19
|
}
|
|
@@ -39,8 +41,18 @@ export function resolveManagedAppFacts(input) {
|
|
|
39
41
|
appName,
|
|
40
42
|
hasUi: input.uiRoots.length > 0,
|
|
41
43
|
config: readStaticAppConfig(input.appRoot),
|
|
44
|
+
deploy: readReleaseTarget(input.appRoot, appName),
|
|
42
45
|
};
|
|
43
46
|
}
|
|
47
|
+
/** Findings that do not fail `kumiko check managed`. */
|
|
48
|
+
export function listManagedWarnings(facts) {
|
|
49
|
+
if (facts.deploy.visibility === "private" && facts.deploy.infraRef === undefined) {
|
|
50
|
+
return [
|
|
51
|
+
`package.json#kumiko.deploy.infraRef is not set: ci.yml and release.yml call the infra workflows at @${DEFAULT_INFRA_REF}. Set it to an infra tag; a missing value will fail this check in a later release.`,
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
44
56
|
function marked(path, body, style, executable = false) {
|
|
45
57
|
return { kind: "template", path, content: addManagedMarker(body, style), executable };
|
|
46
58
|
}
|
|
@@ -48,7 +60,28 @@ function renderCiWorkflow(facts) {
|
|
|
48
60
|
const infra = facts.config.kind === "defined" ? facts.config.ports.infra : undefined;
|
|
49
61
|
const minio = infra?.s3 !== undefined;
|
|
50
62
|
const meilisearch = infra?.meilisearch !== undefined;
|
|
51
|
-
|
|
63
|
+
if (facts.deploy.visibility === "public") {
|
|
64
|
+
return render(readTemplate("ci-public.yml.template"), { appName: facts.appName }, { uiGuards: facts.hasUi, meilisearch });
|
|
65
|
+
}
|
|
66
|
+
return render(readTemplate("ci.yml.template"), {
|
|
67
|
+
appName: facts.appName,
|
|
68
|
+
uiGuards: String(facts.hasUi),
|
|
69
|
+
infraRef: facts.deploy.infraRef ?? DEFAULT_INFRA_REF,
|
|
70
|
+
}, {
|
|
71
|
+
minio,
|
|
72
|
+
meilisearch,
|
|
73
|
+
e2eInputs: minio || meilisearch,
|
|
74
|
+
docStatusGuardOff: !facts.deploy.docStatusGuard,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function renderReleaseWorkflow(facts) {
|
|
78
|
+
const { namespace, deployment, visibility, infraRef } = facts.deploy;
|
|
79
|
+
return render(readTemplate("release.yml.template"), {
|
|
80
|
+
appName: facts.appName,
|
|
81
|
+
namespace,
|
|
82
|
+
deployment,
|
|
83
|
+
infraRef: infraRef ?? DEFAULT_INFRA_REF,
|
|
84
|
+
}, { privateDeploy: visibility === "private", publicRollout: visibility === "public" });
|
|
52
85
|
}
|
|
53
86
|
export function renderManagedTemplates(facts) {
|
|
54
87
|
const deploy = renderDeployFiles({
|
|
@@ -56,12 +89,11 @@ export function renderManagedTemplates(facts) {
|
|
|
56
89
|
destination: facts.appRoot,
|
|
57
90
|
ensuredRegistryConfigFiles: [BUNFIG_FILES.unit],
|
|
58
91
|
});
|
|
59
|
-
const target = readReleaseTarget(facts.appRoot, facts.appName);
|
|
60
92
|
return [
|
|
61
93
|
...deploy.files.map((file) => marked(`deploy/${file.output}`, file.content, "#", file.output.endsWith(".sh"))),
|
|
62
94
|
marked(".husky/pre-push", readTemplate("husky-pre-push.template"), "#", true),
|
|
63
95
|
marked(".github/workflows/cache-cleanup.yml", readTemplate("cache-cleanup.yml.template"), "#"),
|
|
64
|
-
marked(".github/workflows/release.yml",
|
|
96
|
+
marked(".github/workflows/release.yml", renderReleaseWorkflow(facts), "#"),
|
|
65
97
|
marked(".github/workflows/ci.yml", renderCiWorkflow(facts), "#"),
|
|
66
98
|
marked("kumiko/schema.ts", readTemplate("kumiko-schema.ts.template"), "//"),
|
|
67
99
|
];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, relative, sep } from "node:path";
|
|
3
3
|
import { mergeBunfig } from "@cosmicdrift/kumiko-testing/bunfig";
|
|
4
|
+
import { stripAppJobs, withAppJobsOf } from "./app-jobs.js";
|
|
4
5
|
import { readManagedMarker } from "./marker.js";
|
|
5
6
|
import { listManagedFiles, renderBiomePresetJson, renderManagedBunfig, renderTsconfigPresetJson, } from "./registry.js";
|
|
6
7
|
// A symlinked .github or deploy/ would let the sync write outside the app.
|
|
@@ -24,10 +25,18 @@ function isBrokenSymlink(path) {
|
|
|
24
25
|
return false;
|
|
25
26
|
}
|
|
26
27
|
}
|
|
28
|
+
// App jobs stay where the app put them as long as the rest matches the template.
|
|
29
|
+
function templateContent(rendered, existing) {
|
|
30
|
+
if (existing === undefined)
|
|
31
|
+
return rendered;
|
|
32
|
+
return stripAppJobs(existing) === stripAppJobs(rendered)
|
|
33
|
+
? existing
|
|
34
|
+
: withAppJobsOf(rendered, existing);
|
|
35
|
+
}
|
|
27
36
|
function plannedContent(file, existing) {
|
|
28
37
|
switch (file.kind) {
|
|
29
38
|
case "template":
|
|
30
|
-
return { content: file.content, executable: file.executable };
|
|
39
|
+
return { content: templateContent(file.content, existing), executable: file.executable };
|
|
31
40
|
case "bunfig": {
|
|
32
41
|
const generated = renderManagedBunfig(file, existing);
|
|
33
42
|
if (existing === undefined)
|
package/dist/run-dev-app.d.ts
CHANGED
|
@@ -2,9 +2,11 @@ import { type SeedAdminOptions } from "@cosmicdrift/kumiko-bundled-features/auth
|
|
|
2
2
|
import type { PostAuthLandingResolver } from "@cosmicdrift/kumiko-framework/api";
|
|
3
3
|
import { type KmsAdapter } from "@cosmicdrift/kumiko-framework/crypto";
|
|
4
4
|
import { type FeatureDefinition, type Registry, type ValidateBootOptions } from "@cosmicdrift/kumiko-framework/engine";
|
|
5
|
+
import { type ComposedEnvSchema } from "@cosmicdrift/kumiko-framework/env";
|
|
5
6
|
import type { ErrorDocsConfig } from "@cosmicdrift/kumiko-framework/errors";
|
|
6
7
|
import type { EnvelopeCipher, MasterKeyProvider } from "@cosmicdrift/kumiko-framework/secrets";
|
|
7
|
-
import type { PageHeadResolver } from "@cosmicdrift/kumiko-headless/apex";
|
|
8
|
+
import type { PageHeadResolver } from "@cosmicdrift/kumiko-renderer/headless/apex";
|
|
9
|
+
import { type BillingBootOption, type DemoModeOption, type FileStorageBootOption, type KmsMode, type MailBootOption, type ProdSeedsConfig } from "@cosmicdrift/kumiko-server-runtime";
|
|
8
10
|
import type { TestStack } from "@cosmicdrift/kumiko-testing/stack";
|
|
9
11
|
import { type CreateKumikoServerOptions, type KumikoServerHandle } from "./create-kumiko-server.ts";
|
|
10
12
|
export type { AccountLockoutSetup, AccountUnlockSetup, AuthMailOptions, EmailVerificationSetup, InviteSetup, PasswordResetSetup, SignupSetup, } from "@cosmicdrift/kumiko-server-runtime/run-prod-app";
|
|
@@ -65,6 +67,10 @@ export type RunDevAppAuthOptions = {
|
|
|
65
67
|
/** Hook for app-specific seeding (demo data, fixtures). Runs after the
|
|
66
68
|
* admin (when auth is active) in declared order. */
|
|
67
69
|
export type SeedFn = (stack: TestStack) => Promise<void>;
|
|
70
|
+
/** Dev counterpart of `ProdSeedsConfig`: the declarative shortcuts plus free seeds that get the test stack. */
|
|
71
|
+
export type RunDevAppSeedsConfig = Omit<ProdSeedsConfig, "run"> & {
|
|
72
|
+
readonly run?: readonly SeedFn[];
|
|
73
|
+
};
|
|
68
74
|
export type RunDevAppOptions = {
|
|
69
75
|
/** App-spezifische Features. Im auth-mode werden config/user/tenant/
|
|
70
76
|
* auth-email-password automatisch dazu gemischt — KEIN doppeltes
|
|
@@ -88,7 +94,7 @@ export type RunDevAppOptions = {
|
|
|
88
94
|
* through `runDevApp` show the same resolved head prod does. See
|
|
89
95
|
* `CreateKumikoServerOptions.resolvePageHead` for the full contract. */
|
|
90
96
|
readonly resolvePageHead?: PageHeadResolver;
|
|
91
|
-
/** CSS-Entry. Default: package-export `@cosmicdrift/kumiko-renderer
|
|
97
|
+
/** CSS-Entry. Default: package-export `@cosmicdrift/kumiko-renderer/web/styles.css`
|
|
92
98
|
* wenn ein client-Entry gesetzt ist. `false` deaktiviert die CSS-Pipeline. */
|
|
93
99
|
readonly stylesheet?: string | false;
|
|
94
100
|
/** Tailwind `--watch` process on/off, see `CreateKumikoServerOptions.stylesheetWatch`. */
|
|
@@ -109,7 +115,7 @@ export type RunDevAppOptions = {
|
|
|
109
115
|
readonly auth?: RunDevAppAuthOptions;
|
|
110
116
|
/** Eigene Seed-Funktionen, laufen nach dem Admin (wenn auth) in
|
|
111
117
|
* Array-Reihenfolge. */
|
|
112
|
-
readonly seeds?: readonly SeedFn[];
|
|
118
|
+
readonly seeds?: readonly SeedFn[] | RunDevAppSeedsConfig;
|
|
113
119
|
/** Extra-AppContext-Keys. `templateResolver` (immer) + `secrets` (wenn das
|
|
114
120
|
* secrets-Feature gemountet ist) + `configResolver` (auth-mode) werden
|
|
115
121
|
* automatisch ergänzt — App-Werte gewinnen. Symmetrisch zu runProdApp. */
|
|
@@ -154,13 +160,23 @@ export type RunDevAppOptions = {
|
|
|
154
160
|
/** Hook for app-wired co-running components — symmetric to
|
|
155
161
|
* runProdApp.wire (kumiko-framework#3050). */
|
|
156
162
|
readonly wire?: CreateKumikoServerOptions["wire"];
|
|
157
|
-
/**
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
*
|
|
163
|
-
readonly
|
|
163
|
+
/** Same as `RunProdAppOptions["mail"]`: mounts `channel-email` and wires the auth mail flows. */
|
|
164
|
+
readonly mail?: MailBootOption;
|
|
165
|
+
/** Same as `RunProdAppOptions["fileStorage"]`. */
|
|
166
|
+
readonly fileStorage?: FileStorageBootOption;
|
|
167
|
+
/** Same as `RunProdAppOptions["kmsMode"]`; replaces the dev KMS default and cannot be combined
|
|
168
|
+
* with `kms` / `blindIndexKey`. */
|
|
169
|
+
readonly kmsMode?: KmsMode;
|
|
170
|
+
/** Same as `RunProdAppOptions["billing"]`. */
|
|
171
|
+
readonly billing?: BillingBootOption;
|
|
172
|
+
/** Same as `RunProdAppOptions["featureToggles"]`: global operator switches over the tier resolver. */
|
|
173
|
+
readonly featureToggles?: boolean;
|
|
174
|
+
/** Same as `RunProdAppOptions["demoMode"]`. */
|
|
175
|
+
readonly demoMode?: DemoModeOption;
|
|
176
|
+
/** Composed env schema (see `RunProdAppOptions["envSchema"]`). When set, the env is validated
|
|
177
|
+
* against it plus the blocks the boot options above need, and a violation throws a
|
|
178
|
+
* `KumikoBootError`. Dev does not run the dry-run modes. */
|
|
179
|
+
readonly envSchema?: ComposedEnvSchema;
|
|
164
180
|
/** Print a first-run banner after the server starts (URL, admin login,
|
|
165
181
|
* hot-reload hint, docs link). Default: off — apps that already log
|
|
166
182
|
* their own startup shouldn't get double-printed. The scaffold template
|