@cosmicdrift/kumiko-dev-server 0.369.0 → 0.371.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.
Files changed (44) hide show
  1. package/dist/build.d.ts +1 -1
  2. package/dist/cli.d.ts +3 -3
  3. package/dist/compose-stacks.d.ts +63 -3
  4. package/dist/compose-stacks.js +94 -12
  5. package/dist/host-dispatch-adapter.d.ts +7 -0
  6. package/dist/host-dispatch-adapter.js +31 -0
  7. package/dist/index.d.ts +21 -20
  8. package/dist/index.js +2 -1
  9. package/dist/init-deploy-cli.js +6 -0
  10. package/dist/managed-files/app-scripts.d.ts +13 -0
  11. package/dist/managed-files/app-scripts.js +40 -0
  12. package/dist/managed-files/check.d.ts +34 -0
  13. package/dist/managed-files/check.js +109 -0
  14. package/dist/managed-files/index.d.ts +8 -0
  15. package/dist/managed-files/index.js +8 -0
  16. package/dist/managed-files/jsonc.d.ts +1 -0
  17. package/dist/managed-files/jsonc.js +12 -0
  18. package/dist/managed-files/line-diff.d.ts +2 -0
  19. package/dist/managed-files/line-diff.js +60 -0
  20. package/dist/managed-files/marker.d.ts +17 -0
  21. package/dist/managed-files/marker.js +49 -0
  22. package/dist/managed-files/port-registry.d.ts +17 -0
  23. package/dist/managed-files/port-registry.js +31 -0
  24. package/dist/managed-files/registry.d.ts +51 -0
  25. package/dist/managed-files/registry.js +115 -0
  26. package/dist/managed-files/static-app-config.d.ts +16 -0
  27. package/dist/managed-files/static-app-config.js +99 -0
  28. package/dist/managed-files/sync.d.ts +19 -0
  29. package/dist/managed-files/sync.js +101 -0
  30. package/dist/run-dev-app.d.ts +1 -1
  31. package/dist/run-dev-app.js +7 -1
  32. package/dist/scaffold-app.d.ts +3 -3
  33. package/dist/scaffold-app.js +125 -199
  34. package/dist/scaffold-deploy.d.ts +9 -0
  35. package/dist/scaffold-deploy.js +34 -6
  36. package/dist/scaffold-feature.js +1 -0
  37. package/package.json +18 -9
  38. package/src/changes.json +25 -0
  39. package/templates/deploy/migrate-step.sh.template +11 -6
  40. package/templates/managed/cache-cleanup.yml.template +30 -0
  41. package/templates/managed/ci.yml.template +65 -0
  42. package/templates/managed/husky-pre-push.template +40 -0
  43. package/templates/managed/kumiko-schema.ts.template +12 -0
  44. package/templates/managed/release.yml.template +55 -0
package/dist/build.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { ASSETS_DIR, type BuildManifest, type BuildProdBundleOptions, type BuildResult, buildProdBundle, type ClientEntry, type ClientEntryDeclaration, discoverHtmlTemplate, formatBuildResult, injectAssetTags, readClientEntriesConfig, resolveClientEntries, } from "@cosmicdrift/kumiko-server-runtime/build-prod-bundle";
2
- export { type BuildServerBundleEntry, type BuildServerBundleOptions, type BuildServerBundleResult, buildServerBundle, discoverServerEntry, formatServerBuildResult, readExtraRuntimeExternals, } from "./build-server-bundle.js";
2
+ export { type BuildServerBundleEntry, type BuildServerBundleOptions, type BuildServerBundleResult, buildServerBundle, discoverServerEntry, formatServerBuildResult, readExtraRuntimeExternals, } from "./build-server-bundle.ts";
package/dist/cli.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export { buildProdBundle, buildServerBundle, discoverServerEntry, formatBuildResult, formatServerBuildResult, readClientEntriesConfig, readExtraRuntimeExternals, resolveClientEntries, } from "./build.js";
2
- export { createCrashTracker } from "./crash-tracker.js";
3
- export { runInitDeployCli } from "./init-deploy-cli.js";
1
+ export { buildProdBundle, buildServerBundle, discoverServerEntry, formatBuildResult, formatServerBuildResult, readClientEntriesConfig, readExtraRuntimeExternals, resolveClientEntries, } from "./build.ts";
2
+ export { createCrashTracker } from "./crash-tracker.ts";
3
+ export { runInitDeployCli } from "./init-deploy-cli.ts";
@@ -1,13 +1,20 @@
1
+ import { type AccountSecurityFeatureOptions } from "@cosmicdrift/kumiko-bundled-features/account-security";
1
2
  import { type AuthMfaFeatureOptions } from "@cosmicdrift/kumiko-bundled-features/auth-mfa";
3
+ import { type BillingFoundationOptions } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
4
+ import { type CreateCapOverviewOptions } from "@cosmicdrift/kumiko-bundled-features/cap-overview";
2
5
  import { type LegalPagesOptions } from "@cosmicdrift/kumiko-bundled-features/legal-pages";
6
+ import { type SubscriptionMollieOptions } from "@cosmicdrift/kumiko-bundled-features/subscription-mollie";
7
+ import { type CreateTierEngineOptions } from "@cosmicdrift/kumiko-bundled-features/tier-engine";
3
8
  import { type UserDataRightsOptions } from "@cosmicdrift/kumiko-bundled-features/user-data-rights";
9
+ import { type WaitlistOptions } from "@cosmicdrift/kumiko-bundled-features/waitlist";
4
10
  import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
5
11
  import { type MailBrandSuffixResolver, type MailFooterResolver } from "@cosmicdrift/kumiko-renderer-mail-html";
6
12
  export type FileProviderKind = "inmemory" | "s3" | "s3-env";
7
13
  export type MailTransportKind = "inmemory" | "smtp";
8
14
  export type GdprStackOrder = "retention-first" | "compliance-first";
9
15
  export type FileStackOptions = {
10
- readonly providers: readonly FileProviderKind[];
16
+ /** Without providers the stack needs runProdApp's `fileStorage` option. */
17
+ readonly providers?: readonly FileProviderKind[];
11
18
  readonly includeFilesFeature?: boolean;
12
19
  };
13
20
  export type MailStackOptions = {
@@ -15,16 +22,25 @@ export type MailStackOptions = {
15
22
  };
16
23
  export type PagesStackOptions = {
17
24
  readonly wrapLayout?: LegalPagesOptions["wrapLayout"];
25
+ /** Also serves the terms routes, see LegalPagesOptions.terms. */
26
+ readonly terms?: LegalPagesOptions["terms"];
18
27
  };
19
28
  export type GdprStackOptions = {
20
29
  readonly order?: GdprStackOrder;
21
30
  readonly sessions?: boolean;
22
31
  readonly tenantLifecycle?: boolean;
32
+ /** Adds sessions, user-data-rights and user-profile in the order the
33
+ * self-service privacy center needs; user-data-rights needs `mail`. */
34
+ readonly selfService?: {
35
+ readonly userDataRights?: UserDataRightsOptions;
36
+ };
23
37
  /** Optional tokenVerifier provider(s) (e.g. PAT) when `sessions` is on.
24
38
  * Session-only stacks need none — sessions registers sessionStore, which
25
39
  * satisfies auth-foundation's boot check (#1570). Pass providers when the
26
40
  * app also wants Bearer/PAT auth. */
27
41
  readonly providers?: readonly FeatureDefinition[];
42
+ /** Roles allowed to use the compliance-profiles handlers; default access.admin. */
43
+ readonly access?: readonly string[];
28
44
  };
29
45
  export type UserDataRightsStackOptions = {
30
46
  readonly userDataRights?: UserDataRightsOptions;
@@ -32,6 +48,8 @@ export type UserDataRightsStackOptions = {
32
48
  };
33
49
  export type OpsStackOptions = {
34
50
  readonly delivery?: boolean;
51
+ /** Roles allowed to use the delivery handlers; default access.admin. */
52
+ readonly access?: readonly string[];
35
53
  readonly audit?: boolean;
36
54
  readonly jobs?: boolean;
37
55
  readonly rateLimiting?: boolean;
@@ -56,12 +74,54 @@ export type RendererStackOptions = {
56
74
  readonly mailBrandSuffix?: MailBrandSuffixResolver;
57
75
  };
58
76
  export declare function composeRendererStack(options?: RendererStackOptions): FeatureDefinition[];
77
+ /** Tenant mail transports reach users through channel-email, so the stack needs `mail`. */
59
78
  export declare function composeMailStack(options: MailStackOptions): FeatureDefinition[];
60
- export declare function composeFileStack(options: FileStackOptions): FeatureDefinition[];
61
- /** Retention + compliance (+ optional lifecycle/sessions). UDR/files mount separately — order varies per app. */
79
+ export declare function composeFileStack(options?: FileStackOptions): FeatureDefinition[];
80
+ /** Retention + compliance (+ optional lifecycle/sessions/self-service). Needs an
81
+ * explicit KMS decision (`kmsMode`, or the low-level `kms` / `allowPlaintextPii`).
82
+ * `selfService` mounts user-profile, which requires user-data-rights-defaults and
83
+ * auth-email-password: composeUserDataRightsStack mounts the defaults, the login comes from the `auth` option. */
62
84
  export declare function composeGdprStack(options?: GdprStackOptions): FeatureDefinition[];
63
85
  export declare function composeUserDataRightsStack(options?: UserDataRightsStackOptions): FeatureDefinition[];
64
86
  export declare function composeOpsStack(options?: OpsStackOptions): FeatureDefinition[];
65
87
  /** Identity opt-ins: sessions by default; auth-mfa when `mfa` options given.
66
88
  * Do not also pass `sessions: true` to composeGdprStack — duplicate feature. */
67
89
  export declare function composeIdentityStack(options?: IdentityStackOptions): FeatureDefinition[];
90
+ export type AuthStackOptions = {
91
+ readonly sessions?: boolean;
92
+ readonly mfa?: AuthMfaFeatureOptions;
93
+ /** Self-service screen for MFA and own sessions; mounted when `mfa` and `sessions` are on. */
94
+ readonly accountSecurity?: AccountSecurityFeatureOptions;
95
+ };
96
+ /** auth-foundation + sessions, optional auth-mfa and account-security.
97
+ * auth-email-password stays with runProdApp's `auth` option, which also wires
98
+ * the MFA status checker into the login handler. */
99
+ export declare function composeAuthStack(options?: AuthStackOptions): FeatureDefinition[];
100
+ export type SaasStackBilling = {
101
+ readonly billing: "stripe";
102
+ } | {
103
+ readonly billing: "mollie";
104
+ readonly mollie: SubscriptionMollieOptions;
105
+ };
106
+ export type SaasStackOptions<TCaps extends Readonly<Record<string, unknown>> = Readonly<Record<string, unknown>>> = SaasStackBilling & {
107
+ readonly tiers: CreateTierEngineOptions<TCaps>;
108
+ readonly billingFoundation?: BillingFoundationOptions;
109
+ /** Mounts cap-counter and cap-overview. */
110
+ readonly caps?: CreateCapOverviewOptions;
111
+ /** Mounts waitlist; it needs auth-email-password and user-data-rights. */
112
+ readonly waitlist?: WaitlistOptions;
113
+ };
114
+ /** billing-foundation + tier-engine (+ caps, waitlist). `"stripe"` leaves
115
+ * subscription-stripe and its webhook route to runProdApp's `billing` option;
116
+ * `"mollie"` mounts subscription-mollie, whose webhook route the app adds.
117
+ * billing-foundation requires tenant-lifecycle and compliance-profiles, which
118
+ * composeGdprStack({ tenantLifecycle: true }) mounts. */
119
+ export declare function composeSaasStack<TCaps extends Readonly<Record<string, unknown>>>(options: SaasStackOptions<TCaps>): FeatureDefinition[];
120
+ export type SecurityStackOptions = {
121
+ /** Default true; pass false when another stack already mounts sessions with other options. */
122
+ readonly includeSessions?: boolean;
123
+ };
124
+ /** sessions, crypto-shredding, rate-limiting, audit: the security baseline the
125
+ * production boot warning checks. Relies on runProdApp's default L1/L2 rate
126
+ * limits and an explicit KMS decision. */
127
+ export declare function composeSecurityStack(options?: SecurityStackOptions): FeatureDefinition[];
@@ -5,12 +5,18 @@
5
5
  // (kumiko-framework#2337).
6
6
  //
7
7
  // Composable SaaS stack presets — explicit opt-in blocks for run-config.ts.
8
- // Presets export feature instances only; no implicit PAT scopes, anonymousAccess,
9
- // or tier maps (those stay in bin/server.ts / app run-config).
8
+ // Presets export feature instances only; no implicit PAT scopes or
9
+ // anonymousAccess. A stack that needs a runProdApp boot option tags the
10
+ // feature that depends on it with `bootRequires`; runProdApp names the gap.
11
+ import { createAccountSecurityFeature, } from "@cosmicdrift/kumiko-bundled-features/account-security";
10
12
  import { createAuditFeature } from "@cosmicdrift/kumiko-bundled-features/audit";
11
13
  import { createAuthFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
12
14
  import { createAuthMfaFeature, } from "@cosmicdrift/kumiko-bundled-features/auth-mfa";
15
+ import { createBillingFoundationFeature, } from "@cosmicdrift/kumiko-bundled-features/billing-foundation";
16
+ import { createCapCounterFeature } from "@cosmicdrift/kumiko-bundled-features/cap-counter";
17
+ import { createCapOverviewFeature, } from "@cosmicdrift/kumiko-bundled-features/cap-overview";
13
18
  import { createComplianceProfilesFeature } from "@cosmicdrift/kumiko-bundled-features/compliance-profiles";
19
+ import { createCryptoShreddingFeature } from "@cosmicdrift/kumiko-bundled-features/crypto-shredding";
14
20
  import { createDataRetentionFeature } from "@cosmicdrift/kumiko-bundled-features/data-retention";
15
21
  import { createDeliveryFeature } from "@cosmicdrift/kumiko-bundled-features/delivery";
16
22
  import { createFileFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/file-foundation";
@@ -25,11 +31,18 @@ import { createMailTransportSmtpFeature } from "@cosmicdrift/kumiko-bundled-feat
25
31
  import { createRateLimitingFeature } from "@cosmicdrift/kumiko-bundled-features/rate-limiting";
26
32
  import { createRendererFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/renderer-foundation";
27
33
  import { createSessionsFeature } from "@cosmicdrift/kumiko-bundled-features/sessions";
34
+ import { createSubscriptionMollieFeature, } from "@cosmicdrift/kumiko-bundled-features/subscription-mollie";
28
35
  import { createTemplateResolverFeature } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
29
36
  import { createTenantLifecycleFeature } from "@cosmicdrift/kumiko-bundled-features/tenant-lifecycle";
37
+ import { createTierEngineFeature, } from "@cosmicdrift/kumiko-bundled-features/tier-engine";
30
38
  import { createUserDataRightsFeature, } from "@cosmicdrift/kumiko-bundled-features/user-data-rights";
31
39
  import { createUserDataRightsDefaultsFeature } from "@cosmicdrift/kumiko-bundled-features/user-data-rights-defaults";
40
+ import { createUserProfileFeature } from "@cosmicdrift/kumiko-bundled-features/user-profile";
41
+ import { createWaitlistFeature, } from "@cosmicdrift/kumiko-bundled-features/waitlist";
32
42
  import { createRendererMailHtmlFeature, } from "@cosmicdrift/kumiko-renderer-mail-html";
43
+ function withBootRequires(feature, stack, options) {
44
+ return { ...feature, bootRequires: { stack, options } };
45
+ }
33
46
  export function stackFeatureNames(features) {
34
47
  return features.map((f) => f.name);
35
48
  }
@@ -40,7 +53,10 @@ export function composePagesStack(options = {}) {
40
53
  // r.requires fails the boot. Mounting it here instead would collide with
41
54
  // composeRendererStack (createRegistry rejects duplicate features).
42
55
  return [
43
- createLegalPagesFeature(options.wrapLayout !== undefined ? { wrapLayout: options.wrapLayout } : {}),
56
+ createLegalPagesFeature({
57
+ ...(options.wrapLayout !== undefined && { wrapLayout: options.wrapLayout }),
58
+ ...(options.terms !== undefined && { terms: options.terms }),
59
+ }),
44
60
  ];
45
61
  }
46
62
  export function composeRendererStack(options = {}) {
@@ -53,8 +69,11 @@ export function composeRendererStack(options = {}) {
53
69
  }),
54
70
  ];
55
71
  }
72
+ /** Tenant mail transports reach users through channel-email, so the stack needs `mail`. */
56
73
  export function composeMailStack(options) {
57
- const out = [createMailFoundationFeature()];
74
+ const out = [
75
+ withBootRequires(createMailFoundationFeature(), "composeMailStack", ["mail"]),
76
+ ];
58
77
  for (const transport of options.transports) {
59
78
  if (transport === "inmemory")
60
79
  out.push(createMailTransportInMemoryFeature());
@@ -63,9 +82,15 @@ export function composeMailStack(options) {
63
82
  }
64
83
  return out;
65
84
  }
66
- export function composeFileStack(options) {
67
- const out = [createFileFoundationFeature()];
68
- for (const provider of options.providers) {
85
+ export function composeFileStack(options = {}) {
86
+ const providers = options.providers ?? [];
87
+ const foundation = createFileFoundationFeature();
88
+ const out = [
89
+ providers.length === 0
90
+ ? withBootRequires(foundation, "composeFileStack", ["fileStorage"])
91
+ : foundation,
92
+ ];
93
+ for (const provider of providers) {
69
94
  if (provider === "inmemory")
70
95
  out.push(createFileProviderInMemoryFeature());
71
96
  if (provider === "s3")
@@ -77,11 +102,14 @@ export function composeFileStack(options) {
77
102
  out.push(createFilesFeature());
78
103
  return out;
79
104
  }
80
- /** Retention + compliance (+ optional lifecycle/sessions). UDR/files mount separately — order varies per app. */
105
+ /** Retention + compliance (+ optional lifecycle/sessions/self-service). Needs an
106
+ * explicit KMS decision (`kmsMode`, or the low-level `kms` / `allowPlaintextPii`).
107
+ * `selfService` mounts user-profile, which requires user-data-rights-defaults and
108
+ * auth-email-password: composeUserDataRightsStack mounts the defaults, the login comes from the `auth` option. */
81
109
  export function composeGdprStack(options = {}) {
82
110
  const order = options.order ?? "retention-first";
83
- const retention = createDataRetentionFeature();
84
- const compliance = createComplianceProfilesFeature();
111
+ const retention = withBootRequires(createDataRetentionFeature(), "composeGdprStack", ["kmsMode"]);
112
+ const compliance = createComplianceProfilesFeature(options.access !== undefined ? { access: options.access } : {});
85
113
  const out = order === "compliance-first" ? [compliance, retention] : [retention, compliance];
86
114
  if (options.tenantLifecycle)
87
115
  out.push(createTenantLifecycleFeature());
@@ -90,11 +118,19 @@ export function composeGdprStack(options = {}) {
90
118
  if (options.providers)
91
119
  out.push(...options.providers);
92
120
  }
121
+ if (options.selfService) {
122
+ if (!options.sessions)
123
+ out.push(createSessionsFeature());
124
+ out.push(withBootRequires(createUserDataRightsFeature(options.selfService.userDataRights ?? {}), "composeGdprStack", ["mail"]), createUserProfileFeature());
125
+ }
93
126
  return out;
94
127
  }
95
128
  export function composeUserDataRightsStack(options = {}) {
96
129
  const includeDefaults = options.includeDefaults ?? true;
97
- const out = [createUserDataRightsFeature(options.userDataRights ?? {})];
130
+ // Export-ready and deletion mails go through delivery + channel-email.
131
+ const out = [
132
+ withBootRequires(createUserDataRightsFeature(options.userDataRights ?? {}), "composeUserDataRightsStack", ["mail"]),
133
+ ];
98
134
  if (includeDefaults)
99
135
  out.push(createUserDataRightsDefaultsFeature());
100
136
  return out;
@@ -106,7 +142,7 @@ export function composeOpsStack(options = {}) {
106
142
  const rateLimiting = options.rateLimiting ?? false;
107
143
  const out = [];
108
144
  if (delivery)
109
- out.push(createDeliveryFeature());
145
+ out.push(createDeliveryFeature(options.access !== undefined ? { access: options.access } : {}));
110
146
  if (audit)
111
147
  out.push(createAuditFeature());
112
148
  if (jobs)
@@ -127,3 +163,49 @@ export function composeIdentityStack(options = {}) {
127
163
  out.push(createAuthMfaFeature(options.mfa));
128
164
  return out;
129
165
  }
166
+ /** auth-foundation + sessions, optional auth-mfa and account-security.
167
+ * auth-email-password stays with runProdApp's `auth` option, which also wires
168
+ * the MFA status checker into the login handler. */
169
+ export function composeAuthStack(options = {}) {
170
+ const sessions = options.sessions ?? true;
171
+ const out = composeIdentityStack({
172
+ sessions,
173
+ ...(options.mfa !== undefined && { mfa: options.mfa }),
174
+ });
175
+ if (options.mfa !== undefined && sessions) {
176
+ out.push(createAccountSecurityFeature(options.accountSecurity ?? {}));
177
+ }
178
+ return out;
179
+ }
180
+ /** billing-foundation + tier-engine (+ caps, waitlist). `"stripe"` leaves
181
+ * subscription-stripe and its webhook route to runProdApp's `billing` option;
182
+ * `"mollie"` mounts subscription-mollie, whose webhook route the app adds.
183
+ * billing-foundation requires tenant-lifecycle and compliance-profiles, which
184
+ * composeGdprStack({ tenantLifecycle: true }) mounts. */
185
+ export function composeSaasStack(options) {
186
+ const foundation = createBillingFoundationFeature(options.billingFoundation ?? {});
187
+ const out = [
188
+ options.billing === "stripe"
189
+ ? withBootRequires(foundation, "composeSaasStack", ["billing"])
190
+ : foundation,
191
+ createTierEngineFeature(options.tiers),
192
+ ];
193
+ if (options.billing === "mollie")
194
+ out.push(createSubscriptionMollieFeature(options.mollie));
195
+ if (options.caps)
196
+ out.push(createCapCounterFeature(), createCapOverviewFeature(options.caps));
197
+ if (options.waitlist)
198
+ out.push(createWaitlistFeature(options.waitlist));
199
+ return out;
200
+ }
201
+ /** sessions, crypto-shredding, rate-limiting, audit: the security baseline the
202
+ * production boot warning checks. Relies on runProdApp's default L1/L2 rate
203
+ * limits and an explicit KMS decision. */
204
+ export function composeSecurityStack(options = {}) {
205
+ return [
206
+ ...(options.includeSessions === false ? [] : [createSessionsFeature()]),
207
+ withBootRequires(createCryptoShreddingFeature(), "composeSecurityStack", ["kmsMode"]),
208
+ withBootRequires(createRateLimitingFeature(), "composeSecurityStack", ["rateLimit"]),
209
+ createAuditFeature(),
210
+ ];
211
+ }
@@ -0,0 +1,7 @@
1
+ import type { HostRoutingDispatchFn } from "@cosmicdrift/kumiko-bundled-features/host-routing";
2
+ import type { DevHostDispatch } from "./create-kumiko-server.ts";
3
+ export type ToDevHostDispatchOptions = {
4
+ readonly entries?: Readonly<Record<string, string>>;
5
+ readonly staticDir: string;
6
+ };
7
+ export declare function toDevHostDispatch(dispatch: HostRoutingDispatchFn, options: ToDevHostDispatchOptions): DevHostDispatch;
@@ -0,0 +1,31 @@
1
+ import { join } from "node:path";
2
+ // Runs the prod host-routing decision in the dev server, so dev and prod
3
+ // cannot route differently. Per-host CSP is a prod concern and dropped here.
4
+ export function toDevHostDispatch(dispatch, options) {
5
+ const entries = options.entries ?? {};
6
+ const toDev = (result) => {
7
+ switch (result.kind) {
8
+ case "redirect":
9
+ return result;
10
+ case "html": {
11
+ const entryName = entries[result.file];
12
+ return entryName === undefined
13
+ ? { kind: "static-html", file: join(options.staticDir, result.file) }
14
+ : { kind: "html", entryName };
15
+ }
16
+ case "not-found": {
17
+ if (result.file === undefined)
18
+ return { kind: "not-found" };
19
+ const entryName = entries[result.file];
20
+ return entryName === undefined
21
+ ? { kind: "not-found", file: join(options.staticDir, result.file) }
22
+ : { kind: "not-found", entryName };
23
+ }
24
+ }
25
+ };
26
+ return async (req, deps) => {
27
+ const url = new URL(req.url);
28
+ const host = req.headers.get("host") ?? url.host;
29
+ return toDev(await dispatch({ host, path: url.pathname, search: url.search }, deps));
30
+ };
31
+ }
package/dist/index.d.ts CHANGED
@@ -1,21 +1,22 @@
1
1
  export { type ComposeFeaturesOptions, composeFeatures, } from "@cosmicdrift/kumiko-server-runtime/compose-features";
2
- export { composeFileStack, composeGdprStack, composeIdentityStack, composeMailStack, composeOpsStack, composePagesStack, composeRendererStack, composeUserDataRightsStack, type FileProviderKind, type FileStackOptions, type GdprStackOptions, type GdprStackOrder, type IdentityStackOptions, type MailStackOptions, type MailTransportKind, type OpsStackOptions, type PagesStackOptions, stackFeatureNames, type UserDataRightsStackOptions, } from "./compose-stacks.js";
3
- export { type CreateKumikoServerOptions, createKumikoServer, type KumikoServerHandle, PROD_BUNDLES_ENV, resolveStylesheet, STYLESHEET_WATCH_ENV, } from "./create-kumiko-server.js";
4
- export { type FrameworkCoreEnv, frameworkCoreEnvSchema } from "./env-schema.js";
5
- export type { AuthoringStyle, BuildFewShotCorpusOptions, CorpusWarning, FewShotCorpus, FewShotEntry, } from "./few-shot-corpus.js";
6
- export { buildFewShotCorpus, pathToId } from "./few-shot-corpus.js";
7
- export { resolveFrameworkVersion } from "./framework-version.js";
8
- export type { CliOutput, RunInitDeployCliOptions } from "./init-deploy-cli.js";
9
- export { runInitDeployCli } from "./init-deploy-cli.js";
10
- export type { RunDevAppAuthOptions, RunDevAppOptions, SeedFn } from "./run-dev-app.js";
11
- export { runDevApp } from "./run-dev-app.js";
12
- export type { ScaffoldAppOptions, ScaffoldAppResult, ScaffoldFeatureEntry, } from "./scaffold-app.js";
13
- export { SCAFFOLD_BIOME_VERSION, scaffoldApp } from "./scaffold-app.js";
14
- export type { ScaffoldAppFeatureOptions, ScaffoldAppFeatureResult, } from "./scaffold-app-feature.js";
15
- export { runConfigPathForApp, scaffoldAppFeature } from "./scaffold-app-feature.js";
16
- export type { CheckDeployDriftResult, DeployDriftEntry, RenderDeployFilesOptions, RenderDeployFilesResult, RenderedDeployFile, ScaffoldDeployDetected, ScaffoldDeployOptions, ScaffoldDeployResult, ScaffoldedFile, } from "./scaffold-deploy.js";
17
- export { checkDeployDrift, renderDeployFiles, scaffoldDeploy } from "./scaffold-deploy.js";
18
- export type { ScaffoldFeatureOptions, ScaffoldFeatureResult } from "./scaffold-feature.js";
19
- export { scaffoldFeature } from "./scaffold-feature.js";
20
- export { type SetupTestStackFromFeaturesOptions, setupTestStackFromFeatures, type TestStackPreset, } from "./setup-test-stack-from-features.js";
21
- export { renderWelcomeBanner, type WelcomeBannerInput } from "./welcome-banner.js";
2
+ export { type AuthStackOptions, composeAuthStack, composeFileStack, composeGdprStack, composeIdentityStack, composeMailStack, composeOpsStack, composePagesStack, composeRendererStack, composeSaasStack, composeSecurityStack, composeUserDataRightsStack, type FileProviderKind, type FileStackOptions, type GdprStackOptions, type GdprStackOrder, type IdentityStackOptions, type MailStackOptions, type MailTransportKind, type OpsStackOptions, type PagesStackOptions, type SaasStackBilling, type SaasStackOptions, type SecurityStackOptions, stackFeatureNames, type UserDataRightsStackOptions, } from "./compose-stacks.ts";
3
+ export { type CreateKumikoServerOptions, createKumikoServer, type DevHostDispatch, type DevHostDispatchResult, type KumikoServerHandle, PROD_BUNDLES_ENV, resolveStylesheet, STYLESHEET_WATCH_ENV, } from "./create-kumiko-server.ts";
4
+ export { type FrameworkCoreEnv, frameworkCoreEnvSchema } from "./env-schema.ts";
5
+ export type { AuthoringStyle, BuildFewShotCorpusOptions, CorpusWarning, FewShotCorpus, FewShotEntry, } from "./few-shot-corpus.ts";
6
+ export { buildFewShotCorpus, pathToId } from "./few-shot-corpus.ts";
7
+ export { resolveFrameworkVersion } from "./framework-version.ts";
8
+ export { type ToDevHostDispatchOptions, toDevHostDispatch } from "./host-dispatch-adapter.ts";
9
+ export type { CliOutput, RunInitDeployCliOptions } from "./init-deploy-cli.ts";
10
+ export { runInitDeployCli } from "./init-deploy-cli.ts";
11
+ export type { RunDevAppAuthOptions, RunDevAppOptions, SeedFn } from "./run-dev-app.ts";
12
+ export { runDevApp } from "./run-dev-app.ts";
13
+ export type { ScaffoldAppOptions, ScaffoldAppResult, ScaffoldFeatureEntry, } from "./scaffold-app.ts";
14
+ export { SCAFFOLD_BIOME_VERSION, scaffoldApp } from "./scaffold-app.ts";
15
+ export type { ScaffoldAppFeatureOptions, ScaffoldAppFeatureResult, } from "./scaffold-app-feature.ts";
16
+ export { runConfigPathForApp, scaffoldAppFeature } from "./scaffold-app-feature.ts";
17
+ export type { CheckDeployDriftResult, DeployDriftEntry, RenderDeployFilesOptions, RenderDeployFilesResult, RenderedDeployFile, ScaffoldDeployDetected, ScaffoldDeployOptions, ScaffoldDeployResult, ScaffoldedFile, } from "./scaffold-deploy.ts";
18
+ export { checkDeployDrift, renderDeployFiles, scaffoldDeploy } from "./scaffold-deploy.ts";
19
+ export type { ScaffoldFeatureOptions, ScaffoldFeatureResult } from "./scaffold-feature.ts";
20
+ export { scaffoldFeature } from "./scaffold-feature.ts";
21
+ export { type SetupTestStackFromFeaturesOptions, setupTestStackFromFeatures, type TestStackPreset, } from "./setup-test-stack-from-features.ts";
22
+ export { renderWelcomeBanner, type WelcomeBannerInput } from "./welcome-banner.ts";
package/dist/index.js CHANGED
@@ -17,11 +17,12 @@ export { composeFeatures, } from "@cosmicdrift/kumiko-server-runtime/compose-fea
17
17
  // im Sub-Path-Export `@cosmicdrift/kumiko-dev-server/build`. Damit zieht der Main-Barrel
18
18
  // kein Bun-Toolchain-Bundle mehr in Production-Reads (z.B. wenn drizzle-kit
19
19
  // die App-Config unter Node lädt).
20
- export { composeFileStack, composeGdprStack, composeIdentityStack, composeMailStack, composeOpsStack, composePagesStack, composeRendererStack, composeUserDataRightsStack, stackFeatureNames, } from "./compose-stacks.js";
20
+ export { composeAuthStack, composeFileStack, composeGdprStack, composeIdentityStack, composeMailStack, composeOpsStack, composePagesStack, composeRendererStack, composeSaasStack, composeSecurityStack, composeUserDataRightsStack, stackFeatureNames, } from "./compose-stacks.js";
21
21
  export { createKumikoServer, PROD_BUNDLES_ENV, resolveStylesheet, STYLESHEET_WATCH_ENV, } from "./create-kumiko-server.js";
22
22
  export { frameworkCoreEnvSchema } from "./env-schema.js";
23
23
  export { buildFewShotCorpus, pathToId } from "./few-shot-corpus.js";
24
24
  export { resolveFrameworkVersion } from "./framework-version.js";
25
+ export { toDevHostDispatch } from "./host-dispatch-adapter.js";
25
26
  export { runInitDeployCli } from "./init-deploy-cli.js";
26
27
  export { runDevApp } from "./run-dev-app.js";
27
28
  export { SCAFFOLD_BIOME_VERSION, scaffoldApp } from "./scaffold-app.js";
@@ -8,8 +8,13 @@ import { getFlag, getNumberFlag, getStringFlag, parseArgs, } from "@cosmicdrift/
8
8
  import * as z from "zod";
9
9
  import { checkDeployDrift, scaffoldDeploy } from "./scaffold-deploy.js";
10
10
  const USAGE = "Usage: kumiko-init-deploy --app <name> [--port <n>] [--github-org <org>] [--out <dir>] [--force | --check]";
11
+ // Kept for one release so existing pipelines keep running; deploy/ is now a
12
+ // managed-files group that `kumiko new app` writes and `kumiko sync-managed` updates.
13
+ const DEPRECATION_NOTICE = " ! kumiko-init-deploy is deprecated and will be removed in the next release: deploy/ files are managed files now. Use `kumiko check managed` and `kumiko sync-managed --write`.";
11
14
  const HELP = `${USAGE}
12
15
 
16
+ ${DEPRECATION_NOTICE}
17
+
13
18
  --app <name> app name (default: package.json "name" without its @scope/)
14
19
  --port <n> port the container exposes
15
20
  --github-org <org> GitHub org for the image reference
@@ -87,6 +92,7 @@ export async function runInitDeployCli({ argv, cwd, out, }) {
87
92
  out.log(HELP);
88
93
  return 0;
89
94
  }
95
+ out.err(DEPRECATION_NOTICE);
90
96
  const args = parseArgs(argv);
91
97
  const force = getFlag(args, "force");
92
98
  const check = getFlag(args, "check");
@@ -0,0 +1,13 @@
1
+ export declare const STANDARD_APP_SCRIPTS: readonly ["dev", "build", "start", "typecheck", "lint", "check", "test", "test:unit", "test:integration", "test:integration:ci", "test:dom", "test:real", "e2e", "e2e:real", "screenshots", "prepare"];
2
+ export type StandardAppScript = (typeof STANDARD_APP_SCRIPTS)[number];
3
+ /** Extras every app may carry; they are listed but expected. */
4
+ export declare const KNOWN_EXTRA_APP_SCRIPTS: readonly ["test:verbose", "test:bunfig", "test:lib-coverage", "shots"];
5
+ export type AppScriptsReport = {
6
+ readonly missing: readonly StandardAppScript[];
7
+ readonly knownExtras: readonly string[];
8
+ readonly appExtras: readonly string[];
9
+ };
10
+ export declare function checkAppScripts(scripts: Readonly<Record<string, string>>, options: {
11
+ readonly hasUi: boolean;
12
+ }): AppScriptsReport;
13
+ export declare const PREPARE_SCRIPT = "[ \"$HUSKY\" = \"0\" ] || git config core.hooksPath .husky 2>/dev/null || true";
@@ -0,0 +1,40 @@
1
+ // The package.json scripts every app exposes, so agents and CI call the same
2
+ // names in every repo.
3
+ export const STANDARD_APP_SCRIPTS = [
4
+ "dev",
5
+ "build",
6
+ "start",
7
+ "typecheck",
8
+ "lint",
9
+ "check",
10
+ "test",
11
+ "test:unit",
12
+ "test:integration",
13
+ "test:integration:ci",
14
+ "test:dom",
15
+ "test:real",
16
+ "e2e",
17
+ "e2e:real",
18
+ "screenshots",
19
+ "prepare",
20
+ ];
21
+ /** Extras every app may carry; they are listed but expected. */
22
+ export const KNOWN_EXTRA_APP_SCRIPTS = [
23
+ "test:verbose",
24
+ "test:bunfig",
25
+ "test:lib-coverage",
26
+ "shots",
27
+ ];
28
+ export function checkAppScripts(scripts, options) {
29
+ const required = STANDARD_APP_SCRIPTS.filter((name) => name !== "test:dom" || options.hasUi);
30
+ const standard = new Set(STANDARD_APP_SCRIPTS);
31
+ const known = new Set(KNOWN_EXTRA_APP_SCRIPTS);
32
+ const extras = Object.keys(scripts).filter((name) => !standard.has(name));
33
+ return {
34
+ missing: required.filter((name) => scripts[name] === undefined),
35
+ knownExtras: extras.filter((name) => known.has(name)),
36
+ appExtras: extras.filter((name) => !known.has(name)),
37
+ };
38
+ }
39
+ // The hooks path line every app already carries; HUSKY=0 (CI) skips it.
40
+ export const PREPARE_SCRIPT = '[ "$HUSKY" = "0" ] || git config core.hooksPath .husky 2>/dev/null || true';
@@ -0,0 +1,34 @@
1
+ import { type ManagedAppFacts, type ManagedFile } from "./registry.ts";
2
+ export type ManagedFileState = {
3
+ readonly state: "ok";
4
+ } | {
5
+ readonly state: "missing";
6
+ }
7
+ /** `-` lines: template, `+` lines: repo. */
8
+ | {
9
+ readonly state: "differs";
10
+ readonly diff: string;
11
+ }
12
+ /** Exists without the marker; `diff` is set when the content also differs. */
13
+ | {
14
+ readonly state: "unmarked";
15
+ readonly diff?: string;
16
+ } | {
17
+ readonly state: "local";
18
+ readonly reason: string;
19
+ } | {
20
+ readonly state: "invalid";
21
+ readonly message: string;
22
+ };
23
+ export type ManagedFileReport = ManagedFileState & {
24
+ readonly path: string;
25
+ readonly kind: ManagedFile["kind"];
26
+ };
27
+ export type ManagedCheckResult = {
28
+ /** kumiko.config.ts calls defineAppConfig: drift fails the check from then on. */
29
+ readonly adopted: boolean;
30
+ readonly files: readonly ManagedFileReport[];
31
+ };
32
+ export declare function checkManagedFiles(facts: ManagedAppFacts): ManagedCheckResult;
33
+ /** States that fail `kumiko check managed` once the app has adopted kumiko.config.ts. */
34
+ export declare function isManagedDrift(report: ManagedFileReport): boolean;
@@ -0,0 +1,109 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { mergeBunfig } from "@cosmicdrift/kumiko-testing/bunfig";
4
+ import { parseJsonc } from "./jsonc.js";
5
+ import { renderLineDiff } from "./line-diff.js";
6
+ import { readManagedMarker, stripManagedMarker } from "./marker.js";
7
+ import { BIOME_PRESET, listManagedFiles, renderManagedBunfig, TSCONFIG_PRESET, } from "./registry.js";
8
+ function readIfExists(path) {
9
+ return existsSync(path) ? readFileSync(path, "utf-8") : undefined;
10
+ }
11
+ function compareBodies(expected, actual) {
12
+ return expected === actual ? undefined : renderLineDiff(expected, actual);
13
+ }
14
+ function checkTemplate(file, existing) {
15
+ const marker = readManagedMarker(existing);
16
+ if (marker.kind === "invalid")
17
+ return { state: "invalid", message: marker.message };
18
+ if (marker.kind === "local")
19
+ return { state: "local", reason: marker.reason };
20
+ const diff = compareBodies(stripManagedMarker(file.content), stripManagedMarker(existing));
21
+ if (marker.kind === "unmarked")
22
+ return diff === undefined ? { state: "unmarked" } : { state: "unmarked", diff };
23
+ return diff === undefined ? { state: "ok" } : { state: "differs", diff };
24
+ }
25
+ function checkBunfig(file, existing) {
26
+ const marker = readManagedMarker(existing);
27
+ if (marker.kind === "invalid")
28
+ return { state: "invalid", message: marker.message };
29
+ if (marker.kind === "local")
30
+ return { state: "local", reason: marker.reason };
31
+ const merged = mergeBunfig(renderManagedBunfig(file, existing), existing);
32
+ if (!merged.ok) {
33
+ const keys = merged.unknownKeys.map(({ section, key }) => `[${section}] ${key}`).join(", ");
34
+ return { state: "invalid", message: `keys the generator does not own: ${keys}` };
35
+ }
36
+ const diff = compareBodies(merged.content, existing);
37
+ if (marker.kind === "unmarked")
38
+ return diff === undefined ? { state: "unmarked" } : { state: "unmarked", diff };
39
+ return diff === undefined ? { state: "ok" } : { state: "differs", diff };
40
+ }
41
+ function extendsList(value) {
42
+ if (typeof value !== "object" || value === null || !("extends" in value))
43
+ return [];
44
+ const extendsValue = value.extends;
45
+ return Array.isArray(extendsValue) ? extendsValue : [extendsValue];
46
+ }
47
+ function checkStructural(file, facts, existing) {
48
+ if (file.path === "kumiko.config.ts") {
49
+ switch (facts.config.kind) {
50
+ case "defined":
51
+ return { state: "ok" };
52
+ case "legacy":
53
+ return {
54
+ state: "invalid",
55
+ message: "default-export defineAppConfig({ features, client, ports }) from @cosmicdrift/kumiko-framework/config",
56
+ };
57
+ case "invalid":
58
+ return { state: "invalid", message: facts.config.message };
59
+ case "missing":
60
+ return { state: "missing" };
61
+ }
62
+ }
63
+ const preset = file.path === "biome.json" ? BIOME_PRESET : TSCONFIG_PRESET;
64
+ let parsed;
65
+ try {
66
+ parsed = parseJsonc(existing, file.path);
67
+ }
68
+ catch (error) {
69
+ return {
70
+ state: "invalid",
71
+ message: `cannot parse: ${error instanceof Error ? error.message : String(error)}`,
72
+ };
73
+ }
74
+ return extendsList(parsed).includes(preset)
75
+ ? { state: "ok" }
76
+ : { state: "invalid", message: `must extend "${preset}"; keep app overrides below extends` };
77
+ }
78
+ function checkFile(file, facts) {
79
+ if (file.kind === "structural" && file.path === "kumiko.config.ts") {
80
+ return checkStructural(file, facts, "");
81
+ }
82
+ const existing = readIfExists(join(facts.appRoot, file.path));
83
+ if (existing === undefined)
84
+ return { state: "missing" };
85
+ switch (file.kind) {
86
+ case "template":
87
+ return checkTemplate(file, existing);
88
+ case "bunfig":
89
+ return checkBunfig(file, existing);
90
+ case "structural":
91
+ return checkStructural(file, facts, existing);
92
+ case "seed":
93
+ return { state: "ok" };
94
+ }
95
+ }
96
+ export function checkManagedFiles(facts) {
97
+ return {
98
+ adopted: facts.config.kind === "defined",
99
+ files: listManagedFiles(facts).map((file) => ({
100
+ path: file.path,
101
+ kind: file.kind,
102
+ ...checkFile(file, facts),
103
+ })),
104
+ };
105
+ }
106
+ /** States that fail `kumiko check managed` once the app has adopted kumiko.config.ts. */
107
+ export function isManagedDrift(report) {
108
+ return report.state !== "ok" && report.state !== "local";
109
+ }