@stardeck-customer-apps/compose 0.8.0 → 0.10.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/index.mjs CHANGED
@@ -2020,8 +2020,8 @@ var require_semver2 = __commonJS({
2020
2020
 
2021
2021
  // src/index.ts
2022
2022
  import { spawnSync as spawnSync2 } from "child_process";
2023
- import fs2 from "fs";
2024
- import path2 from "path";
2023
+ import fs3 from "fs";
2024
+ import path3 from "path";
2025
2025
 
2026
2026
  // ../../packages/lib/src/server/module-rail/local-fs-sandbox.ts
2027
2027
  import { spawnSync } from "child_process";
@@ -2080,11 +2080,54 @@ function shellQuote(value) {
2080
2080
  return `'${value.replace(/'/g, `'\\''`)}'`;
2081
2081
  }
2082
2082
 
2083
- // ../../packages/lib/src/shared/module-composition.ts
2084
- import { z as z6 } from "zod";
2083
+ // ../../packages/lib/src/shared/compose-inputs.ts
2084
+ import { z } from "zod";
2085
+ var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
2086
+ var composeInputsSchema = z.strictObject({
2087
+ connectedStores: z.array(
2088
+ z.strictObject({
2089
+ storeId: z.string().min(1),
2090
+ slug: z.string().min(1),
2091
+ // A row with an empty binding key must not fail a deploy: the rail
2092
+ // treats falsy as "no binding key", so normalise rather than reject.
2093
+ bindingKey: z.string().nullish().transform((value) => value || null),
2094
+ accessLevel: z.enum(["read", "write", "admin"]),
2095
+ // Defaulted so a file written before the platform emitted store types
2096
+ // (database rows only) still parses. The platform starts writing
2097
+ // storage rows, with `type` set, once forks run a compose that reads it.
2098
+ type: z.enum(["database", "storage"]).default("database")
2099
+ })
2100
+ ).default([]),
2101
+ installs: z.record(
2102
+ z.string(),
2103
+ z.strictObject({
2104
+ installedVersion: z.string().min(1),
2105
+ datastoreBindings: z.record(z.string(), z.string()).nullish()
2106
+ })
2107
+ ).default({})
2108
+ });
2109
+ var STUB_IGNORE_PATH = "apps/web/src/app/.gitignore";
2110
+ var STUB_IGNORE_HEADER = "# Generated by stardeck-compose \u2014 Module-rail stubs are build outputs; do not edit or commit them.";
2111
+ function escapeStubIgnorePattern(pattern) {
2112
+ const escaped = pattern.replace(/[\\[\]*?]/g, (char) => `\\${char}`);
2113
+ return /^[#!]/.test(escaped) ? `\\${escaped}` : escaped;
2114
+ }
2115
+ function buildStubIgnore(artifactPaths) {
2116
+ const prefix = "apps/web/src/app/";
2117
+ const entries = artifactPaths.filter((artifact) => artifact.startsWith(prefix)).map((artifact) => escapeStubIgnorePattern(`/${artifact.slice(prefix.length)}`)).sort();
2118
+ return [STUB_IGNORE_HEADER, ...entries].join("\n") + "\n";
2119
+ }
2120
+
2121
+ // ../../packages/lib/src/shared/module-enablement.ts
2122
+ function isModuleEnabledByList(raw, name, kindOf) {
2123
+ if (!raw) return true;
2124
+ const kind = kindOf(name);
2125
+ if (kind != null && kind !== "feature") return true;
2126
+ return raw.split(",").includes(name);
2127
+ }
2085
2128
 
2086
2129
  // ../../packages/module-contract/src/module-manifest.ts
2087
- import { z } from "zod";
2130
+ import { z as z2 } from "zod";
2088
2131
 
2089
2132
  // ../../packages/module-contract/src/slug.ts
2090
2133
  function validateSlug(slug) {
@@ -2119,30 +2162,30 @@ var ROUTE_GROUP_REGEX = /^\([a-z0-9-]+\)$/;
2119
2162
  var ENDPOINT_STATIC_SEGMENT_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2120
2163
  var ENDPOINT_PARAM_NAME_REGEX = /^[a-z][a-z0-9_-]*$/;
2121
2164
  var ENDPOINT_METHODS = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
2122
- var moduleNameSchema = z.string().max(40).regex(KEBAB_SLUG_REGEX, "must be a kebab-case slug (lowercase letters, digits, hyphens)");
2165
+ var moduleNameSchema = z2.string().max(40).regex(KEBAB_SLUG_REGEX, "must be a kebab-case slug (lowercase letters, digits, hyphens)");
2123
2166
  function isValidModuleName(value) {
2124
2167
  return moduleNameSchema.safeParse(value).success;
2125
2168
  }
2126
- var projectSlugSchema = z.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
2127
- var moduleKindSchema = z.enum(["feature", "library", "contract"]);
2128
- var endpointMethodSchema = z.enum(ENDPOINT_METHODS);
2129
- function endpointPathValidationError(path3) {
2130
- if (!path3.startsWith("/api/")) {
2169
+ var projectSlugSchema = z2.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
2170
+ var moduleKindSchema = z2.enum(["feature", "library", "contract"]);
2171
+ var endpointMethodSchema = z2.enum(ENDPOINT_METHODS);
2172
+ function endpointPathValidationError(path4) {
2173
+ if (!path4.startsWith("/api/")) {
2131
2174
  return 'must begin with "/api/"';
2132
2175
  }
2133
- if (path3.endsWith("/")) {
2176
+ if (path4.endsWith("/")) {
2134
2177
  return "must not end with a trailing slash";
2135
2178
  }
2136
- if (path3.includes("?") || path3.includes("#")) {
2179
+ if (path4.includes("?") || path4.includes("#")) {
2137
2180
  return "must not include query or hash";
2138
2181
  }
2139
- if (path3.includes("\\")) {
2182
+ if (path4.includes("\\")) {
2140
2183
  return "must not include backslashes";
2141
2184
  }
2142
- if (path3.includes("%")) {
2185
+ if (path4.includes("%")) {
2143
2186
  return "must not include percent-encoded segments";
2144
2187
  }
2145
- const parts = path3.split("/");
2188
+ const parts = path4.split("/");
2146
2189
  if (parts.length < 3 || parts[0] !== "" || parts[1] !== "api") {
2147
2190
  return 'must begin with "/api/"';
2148
2191
  }
@@ -2192,12 +2235,12 @@ function endpointPathValidationError(path3) {
2192
2235
  }
2193
2236
  return null;
2194
2237
  }
2195
- function normalizeEndpointPathForCollision(path3) {
2196
- const error = endpointPathValidationError(path3);
2238
+ function normalizeEndpointPathForCollision(path4) {
2239
+ const error = endpointPathValidationError(path4);
2197
2240
  if (error != null) {
2198
2241
  throw new Error(`invalid endpoint path: ${error}`);
2199
2242
  }
2200
- return path3.split("/").map((segment, index) => {
2243
+ return path4.split("/").map((segment, index) => {
2201
2244
  if (index < 2) {
2202
2245
  return segment;
2203
2246
  }
@@ -2210,21 +2253,21 @@ function normalizeEndpointPathForCollision(path3) {
2210
2253
  return segment;
2211
2254
  }).join("/");
2212
2255
  }
2213
- var endpointPathSchema = z.string().superRefine((path3, ctx) => {
2214
- const error = endpointPathValidationError(path3);
2256
+ var endpointPathSchema = z2.string().superRefine((path4, ctx) => {
2257
+ const error = endpointPathValidationError(path4);
2215
2258
  if (error != null) {
2216
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: error });
2259
+ ctx.addIssue({ code: z2.ZodIssueCode.custom, message: error });
2217
2260
  }
2218
2261
  });
2219
- var endpointClaimSchema = z.object({
2262
+ var endpointClaimSchema = z2.object({
2220
2263
  path: endpointPathSchema,
2221
- methods: z.array(endpointMethodSchema).min(1, "methods must be non-empty").superRefine((methods, ctx) => {
2264
+ methods: z2.array(endpointMethodSchema).min(1, "methods must be non-empty").superRefine((methods, ctx) => {
2222
2265
  const seen = /* @__PURE__ */ new Set();
2223
2266
  for (let i = 0; i < methods.length; i += 1) {
2224
2267
  const method = methods[i];
2225
2268
  if (seen.has(method)) {
2226
2269
  ctx.addIssue({
2227
- code: z.ZodIssueCode.custom,
2270
+ code: z2.ZodIssueCode.custom,
2228
2271
  message: `duplicate method "${method}"`,
2229
2272
  path: [i]
2230
2273
  });
@@ -2261,14 +2304,14 @@ function mountSegmentValidationError(segment) {
2261
2304
  }
2262
2305
  return null;
2263
2306
  }
2264
- var mountSegmentSchema = z.string().superRefine((segment, ctx) => {
2307
+ var mountSegmentSchema = z2.string().superRefine((segment, ctx) => {
2265
2308
  const error = mountSegmentValidationError(segment);
2266
2309
  if (error != null) {
2267
- ctx.addIssue({ code: z.ZodIssueCode.custom, message: error });
2310
+ ctx.addIssue({ code: z2.ZodIssueCode.custom, message: error });
2268
2311
  }
2269
2312
  });
2270
- var mountSchema = z.object({
2271
- group: z.string().regex(ROUTE_GROUP_REGEX, 'must be a Next.js route group like "(admin)"'),
2313
+ var mountSchema = z2.object({
2314
+ group: z2.string().regex(ROUTE_GROUP_REGEX, 'must be a Next.js route group like "(admin)"'),
2272
2315
  segment: mountSegmentSchema
2273
2316
  }).strict();
2274
2317
  function splitMountSegment(segment) {
@@ -2281,21 +2324,21 @@ function splitMountSegment(segment) {
2281
2324
  }
2282
2325
  return segment.split("/");
2283
2326
  }
2284
- var tableNameSchema = z.string().regex(SNAKE_TABLE_REGEX, "must be snake_case");
2327
+ var tableNameSchema = z2.string().regex(SNAKE_TABLE_REGEX, "must be snake_case");
2285
2328
  var NPM_PACKAGE_NAME_REGEX = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
2286
- var npmPackageNameSchema = z.string().max(214).regex(NPM_PACKAGE_NAME_REGEX, "must be a valid npm package name");
2287
- var descriptionSchema = z.string().max(200, "must be at most 200 characters").refine((value) => value.trim().length > 0, "must be non-empty after trimming");
2288
- var crossAppSchema = z.object({
2289
- exposes: z.array(z.unknown()).max(0, "must be empty until cross-app contracts are defined"),
2290
- consumes: z.array(z.unknown()).max(0, "must be empty until cross-app contracts are defined")
2329
+ var npmPackageNameSchema = z2.string().max(214).regex(NPM_PACKAGE_NAME_REGEX, "must be a valid npm package name");
2330
+ var descriptionSchema = z2.string().max(200, "must be at most 200 characters").refine((value) => value.trim().length > 0, "must be non-empty after trimming");
2331
+ var crossAppSchema = z2.object({
2332
+ exposes: z2.array(z2.unknown()).max(0, "must be empty until cross-app contracts are defined"),
2333
+ consumes: z2.array(z2.unknown()).max(0, "must be empty until cross-app contracts are defined")
2291
2334
  }).strict();
2292
- var enhancementWorkflowsSchema = z.record(moduleNameSchema, z.array(z.string().min(1))).default({});
2293
- var enhancementEndpointsSchema = z.record(moduleNameSchema, z.array(endpointClaimSchema)).default({});
2294
- var datastoreRequirementSchema = z.object({
2335
+ var enhancementWorkflowsSchema = z2.record(moduleNameSchema, z2.array(z2.string().min(1))).default({});
2336
+ var enhancementEndpointsSchema = z2.record(moduleNameSchema, z2.array(endpointClaimSchema)).default({});
2337
+ var datastoreRequirementSchema = z2.object({
2295
2338
  /** Logical name, module-local, snake_case. Never a physical store key. */
2296
2339
  id: tableNameSchema,
2297
2340
  /** "app" = this project's app-local store; "org" = an org-level shared store. */
2298
- scope: z.enum(["app", "org"]).default("app"),
2341
+ scope: z2.enum(["app", "org"]).default("app"),
2299
2342
  /**
2300
2343
  * Which kind of connected store satisfies this requirement. A "database"
2301
2344
  * requirement only ever binds to a database store and a "storage" one
@@ -2303,34 +2346,34 @@ var datastoreRequirementSchema = z.object({
2303
2346
  * rail never crosses the two. Defaults to "database", the only kind the
2304
2347
  * rail resolved before this field existed.
2305
2348
  */
2306
- type: z.enum(["database", "storage"]).default("database"),
2349
+ type: z2.enum(["database", "storage"]).default("database"),
2307
2350
  /** true → install fails when no store resolves; false → binding omitted, module degrades. */
2308
- required: z.boolean().default(true),
2351
+ required: z2.boolean().default(true),
2309
2352
  /** Checked against the connected store's accessLevel at install. */
2310
- access: z.enum(["read", "write", "admin"]).default("write")
2353
+ access: z2.enum(["read", "write", "admin"]).default("write")
2311
2354
  }).strict();
2312
- var permissionClaimSchema = z.object({
2355
+ var permissionClaimSchema = z2.object({
2313
2356
  /**
2314
2357
  * Lowercase resource:action with one colon and no underscores. This grammar
2315
2358
  * is deliberately narrower than the platform permission tool's charset.
2316
2359
  */
2317
- key: z.string().regex(/^[a-z0-9][a-z0-9-]*:[a-z0-9][a-z0-9-]*$/, "must be a lowercase resource:action key").max(100, "must be at most 100 characters (platform permission tool limit)"),
2360
+ key: z2.string().regex(/^[a-z0-9][a-z0-9-]*:[a-z0-9][a-z0-9-]*$/, "must be a lowercase resource:action key").max(100, "must be at most 100 characters (platform permission tool limit)"),
2318
2361
  /** Human label for the deployment auth config row. */
2319
2362
  description: descriptionSchema
2320
2363
  }).strict();
2321
- var seedBundleSchema = z.object({
2364
+ var seedBundleSchema = z2.object({
2322
2365
  table: tableNameSchema,
2323
2366
  /** Columns forming the ON CONFLICT key — must appear in every row. */
2324
- conflictTarget: z.array(z.string().min(1)).min(1),
2367
+ conflictTarget: z2.array(z2.string().min(1)).min(1),
2325
2368
  /** Scalar JSON values only (no nested objects/arrays). */
2326
- rows: z.array(z.record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()]))).min(1)
2369
+ rows: z2.array(z2.record(z2.string(), z2.union([z2.string(), z2.number(), z2.boolean(), z2.null()]))).min(1)
2327
2370
  }).strict().superRefine((bundle, ctx) => {
2328
2371
  const rowKeys = bundle.rows.map((row) => Object.keys(row).sort().join("\0"));
2329
2372
  const canonical = rowKeys[0];
2330
2373
  for (let i = 1; i < rowKeys.length; i += 1) {
2331
2374
  if (rowKeys[i] !== canonical) {
2332
2375
  ctx.addIssue({
2333
- code: z.ZodIssueCode.custom,
2376
+ code: z2.ZodIssueCode.custom,
2334
2377
  message: "all rows in a seed bundle must have identical key sets",
2335
2378
  path: ["rows", i]
2336
2379
  });
@@ -2342,7 +2385,7 @@ var seedBundleSchema = z.object({
2342
2385
  for (const col of bundle.conflictTarget) {
2343
2386
  if (!keys.has(col)) {
2344
2387
  ctx.addIssue({
2345
- code: z.ZodIssueCode.custom,
2388
+ code: z2.ZodIssueCode.custom,
2346
2389
  message: `conflictTarget column "${col}" must be present in every row`,
2347
2390
  path: ["rows", i]
2348
2391
  });
@@ -2350,35 +2393,35 @@ var seedBundleSchema = z.object({
2350
2393
  }
2351
2394
  }
2352
2395
  });
2353
- var moduleManifestSchema = z.object({
2396
+ var moduleManifestSchema = z2.object({
2354
2397
  name: moduleNameSchema,
2355
2398
  kind: moduleKindSchema,
2356
2399
  // Release versions are platform-owned in module_versions. Accept this
2357
2400
  // legacy field while existing Module source ages out, but strip it from the
2358
2401
  // parsed contract so no runtime can treat module.json as version authority.
2359
- version: z.unknown().optional(),
2402
+ version: z2.unknown().optional(),
2360
2403
  home: projectSlugSchema,
2361
2404
  /** Machine-checkable purpose line — required, ≤200 code units, non-blank when trimmed. */
2362
2405
  description: descriptionSchema,
2363
- uses: z.array(moduleNameSchema).default([]),
2406
+ uses: z2.array(moduleNameSchema).default([]),
2364
2407
  /**
2365
2408
  * Soft peers: this module ships `enhancements/<peer>/` directories that are
2366
2409
  * composition-pruned — the installer includes them only when the peer is
2367
2410
  * present, so the peer is NOT an install-time requirement. Disjoint from
2368
2411
  * `uses`; entries may name modules absent from the composition.
2369
2412
  */
2370
- enhancements: z.array(moduleNameSchema).default([]),
2371
- tables: z.array(tableNameSchema).default([]),
2372
- mounts: z.array(mountSchema).default([]),
2373
- integration: z.array(z.string()).default([]),
2374
- workflows: z.array(z.string().min(1)).default([]),
2413
+ enhancements: z2.array(moduleNameSchema).default([]),
2414
+ tables: z2.array(tableNameSchema).default([]),
2415
+ mounts: z2.array(mountSchema).default([]),
2416
+ integration: z2.array(z2.string()).default([]),
2417
+ workflows: z2.array(z2.string().min(1)).default([]),
2375
2418
  /**
2376
2419
  * Slice-scoped workflow names keyed by peer. Keys must be ⊆ `enhancements`;
2377
2420
  * names must be unique across root `workflows` and every slice list.
2378
2421
  */
2379
2422
  enhancementWorkflows: enhancementWorkflowsSchema,
2380
2423
  /** Root API endpoint path claims. */
2381
- endpoints: z.array(endpointClaimSchema).default([]),
2424
+ endpoints: z2.array(endpointClaimSchema).default([]),
2382
2425
  /**
2383
2426
  * Slice-scoped endpoint claims keyed by peer. Keys must be ⊆ `enhancements`;
2384
2427
  * normalized paths must be unique across root and every slice.
@@ -2394,38 +2437,38 @@ var moduleManifestSchema = z.object({
2394
2437
  * to a physical connected store and generates module-datastores.gen.ts;
2395
2438
  * unlike seeds, this field drives generated output.
2396
2439
  */
2397
- datastores: z.array(datastoreRequirementSchema).optional(),
2440
+ datastores: z2.array(datastoreRequirementSchema).optional(),
2398
2441
  /**
2399
2442
  * Keys this module's guards enforce. Install registers them in deployment
2400
2443
  * auth config; registration only — declaring is never granting.
2401
2444
  */
2402
- permissions: z.array(permissionClaimSchema).optional(),
2445
+ permissions: z2.array(permissionClaimSchema).optional(),
2403
2446
  /**
2404
2447
  * Optional starter rows for tables this module owns. Replay is
2405
2448
  * `INSERT ... ON CONFLICT (conflictTarget) DO NOTHING` — idempotent by
2406
2449
  * construction; the rail (not this schema) performs the replay.
2407
2450
  */
2408
- seeds: z.array(seedBundleSchema).optional(),
2451
+ seeds: z2.array(seedBundleSchema).optional(),
2409
2452
  /** npm packages the source imports beyond the base app. Names only; the rail installs each at the home app's range. */
2410
- packages: z.array(npmPackageNameSchema).optional(),
2453
+ packages: z2.array(npmPackageNameSchema).optional(),
2411
2454
  /**
2412
2455
  * UI-layer contract. `separable: true` asserts the Module's components/ and
2413
2456
  * routes/ hold presentation only — the Blueprint's
2414
2457
  * `module-boundaries/ui-layer` lint rule enforces it — so a fork may
2415
2458
  * soft-eject them. See docs/module-ui-soft-eject-plan.md.
2416
2459
  */
2417
- ui: z.object({ separable: z.boolean() }).strict().optional()
2460
+ ui: z2.object({ separable: z2.boolean() }).strict().optional()
2418
2461
  }).strict().superRefine((manifest, ctx) => {
2419
2462
  if (manifest.uses.includes(manifest.name)) {
2420
2463
  ctx.addIssue({
2421
- code: z.ZodIssueCode.custom,
2464
+ code: z2.ZodIssueCode.custom,
2422
2465
  message: "a module cannot list itself in uses",
2423
2466
  path: ["uses"]
2424
2467
  });
2425
2468
  }
2426
2469
  if (manifest.enhancements.includes(manifest.name)) {
2427
2470
  ctx.addIssue({
2428
- code: z.ZodIssueCode.custom,
2471
+ code: z2.ZodIssueCode.custom,
2429
2472
  message: "a module cannot list itself in enhancements",
2430
2473
  path: ["enhancements"]
2431
2474
  });
@@ -2433,7 +2476,7 @@ var moduleManifestSchema = z.object({
2433
2476
  const hardAndSoft = manifest.enhancements.filter((peer) => manifest.uses.includes(peer));
2434
2477
  if (hardAndSoft.length > 0) {
2435
2478
  ctx.addIssue({
2436
- code: z.ZodIssueCode.custom,
2479
+ code: z2.ZodIssueCode.custom,
2437
2480
  message: `uses and enhancements must be disjoint (a peer is either a hard dep or a soft one): ${hardAndSoft.join(", ")}`,
2438
2481
  path: ["enhancements"]
2439
2482
  });
@@ -2442,7 +2485,7 @@ var moduleManifestSchema = z.object({
2442
2485
  for (const peer of Object.keys(manifest.enhancementWorkflows)) {
2443
2486
  if (!enhancementPeers.has(peer)) {
2444
2487
  ctx.addIssue({
2445
- code: z.ZodIssueCode.custom,
2488
+ code: z2.ZodIssueCode.custom,
2446
2489
  message: `enhancementWorkflows key "${peer}" must be listed in enhancements`,
2447
2490
  path: ["enhancementWorkflows", peer]
2448
2491
  });
@@ -2451,23 +2494,23 @@ var moduleManifestSchema = z.object({
2451
2494
  for (const peer of Object.keys(manifest.enhancementEndpoints)) {
2452
2495
  if (!enhancementPeers.has(peer)) {
2453
2496
  ctx.addIssue({
2454
- code: z.ZodIssueCode.custom,
2497
+ code: z2.ZodIssueCode.custom,
2455
2498
  message: `enhancementEndpoints key "${peer}" must be listed in enhancements`,
2456
2499
  path: ["enhancementEndpoints", peer]
2457
2500
  });
2458
2501
  }
2459
2502
  }
2460
2503
  const seenWorkflows = /* @__PURE__ */ new Map();
2461
- const noteWorkflow = (name, path3) => {
2504
+ const noteWorkflow = (name, path4) => {
2462
2505
  const existing = seenWorkflows.get(name);
2463
2506
  if (existing != null) {
2464
2507
  ctx.addIssue({
2465
- code: z.ZodIssueCode.custom,
2508
+ code: z2.ZodIssueCode.custom,
2466
2509
  message: `duplicate workflow name "${name}" (also at ${existing})`,
2467
- path: path3
2510
+ path: path4
2468
2511
  });
2469
2512
  } else {
2470
- seenWorkflows.set(name, path3.join("."));
2513
+ seenWorkflows.set(name, path4.join("."));
2471
2514
  }
2472
2515
  };
2473
2516
  for (let i = 0; i < manifest.workflows.length; i += 1) {
@@ -2479,7 +2522,7 @@ var moduleManifestSchema = z.object({
2479
2522
  }
2480
2523
  }
2481
2524
  const seenEndpointKeys = /* @__PURE__ */ new Map();
2482
- const noteEndpoint = (claim, path3) => {
2525
+ const noteEndpoint = (claim, path4) => {
2483
2526
  let key;
2484
2527
  try {
2485
2528
  key = normalizeEndpointPathForCollision(claim.path);
@@ -2489,12 +2532,12 @@ var moduleManifestSchema = z.object({
2489
2532
  const existing = seenEndpointKeys.get(key);
2490
2533
  if (existing != null) {
2491
2534
  ctx.addIssue({
2492
- code: z.ZodIssueCode.custom,
2535
+ code: z2.ZodIssueCode.custom,
2493
2536
  message: `duplicate normalized endpoint path "${key}" (also at ${existing})`,
2494
- path: path3
2537
+ path: path4
2495
2538
  });
2496
2539
  } else {
2497
- seenEndpointKeys.set(key, path3.join("."));
2540
+ seenEndpointKeys.set(key, path4.join("."));
2498
2541
  }
2499
2542
  };
2500
2543
  for (let i = 0; i < manifest.endpoints.length; i += 1) {
@@ -2511,7 +2554,7 @@ var moduleManifestSchema = z.object({
2511
2554
  const id = manifest.datastores[i].id;
2512
2555
  if (seenDatastoreIds.has(id)) {
2513
2556
  ctx.addIssue({
2514
- code: z.ZodIssueCode.custom,
2557
+ code: z2.ZodIssueCode.custom,
2515
2558
  message: `duplicate datastore id "${id}"`,
2516
2559
  path: ["datastores", i, "id"]
2517
2560
  });
@@ -2526,7 +2569,7 @@ var moduleManifestSchema = z.object({
2526
2569
  const key = manifest.permissions[i].key;
2527
2570
  if (seenPermissionKeys.has(key)) {
2528
2571
  ctx.addIssue({
2529
- code: z.ZodIssueCode.custom,
2572
+ code: z2.ZodIssueCode.custom,
2530
2573
  message: `duplicate permission key "${key}"`,
2531
2574
  path: ["permissions", i, "key"]
2532
2575
  });
@@ -2538,35 +2581,35 @@ var moduleManifestSchema = z.object({
2538
2581
  if (manifest.kind === "library") {
2539
2582
  if (manifest.tables.length > 0) {
2540
2583
  ctx.addIssue({
2541
- code: z.ZodIssueCode.custom,
2584
+ code: z2.ZodIssueCode.custom,
2542
2585
  message: "library modules must not declare tables",
2543
2586
  path: ["tables"]
2544
2587
  });
2545
2588
  }
2546
2589
  if (manifest.mounts.length > 0) {
2547
2590
  ctx.addIssue({
2548
- code: z.ZodIssueCode.custom,
2591
+ code: z2.ZodIssueCode.custom,
2549
2592
  message: "library modules must not declare mounts",
2550
2593
  path: ["mounts"]
2551
2594
  });
2552
2595
  }
2553
2596
  if (manifest.seeds != null && manifest.seeds.length > 0) {
2554
2597
  ctx.addIssue({
2555
- code: z.ZodIssueCode.custom,
2598
+ code: z2.ZodIssueCode.custom,
2556
2599
  message: "library modules must not declare seeds",
2557
2600
  path: ["seeds"]
2558
2601
  });
2559
2602
  }
2560
2603
  if (manifest.datastores != null && manifest.datastores.length > 0) {
2561
2604
  ctx.addIssue({
2562
- code: z.ZodIssueCode.custom,
2605
+ code: z2.ZodIssueCode.custom,
2563
2606
  message: "library modules must not declare datastores",
2564
2607
  path: ["datastores"]
2565
2608
  });
2566
2609
  }
2567
2610
  if (manifest.permissions != null && manifest.permissions.length > 0) {
2568
2611
  ctx.addIssue({
2569
- code: z.ZodIssueCode.custom,
2612
+ code: z2.ZodIssueCode.custom,
2570
2613
  message: "library modules must not declare permissions",
2571
2614
  path: ["permissions"]
2572
2615
  });
@@ -2574,7 +2617,7 @@ var moduleManifestSchema = z.object({
2574
2617
  }
2575
2618
  if (manifest.kind === "contract" && manifest.mounts.length > 0) {
2576
2619
  ctx.addIssue({
2577
- code: z.ZodIssueCode.custom,
2620
+ code: z2.ZodIssueCode.custom,
2578
2621
  message: "contract modules must not declare mounts",
2579
2622
  path: ["mounts"]
2580
2623
  });
@@ -2585,7 +2628,7 @@ var moduleManifestSchema = z.object({
2585
2628
  const bundle = manifest.seeds[i];
2586
2629
  if (!ownedTables.has(bundle.table)) {
2587
2630
  ctx.addIssue({
2588
- code: z.ZodIssueCode.custom,
2631
+ code: z2.ZodIssueCode.custom,
2589
2632
  message: `seeds may only target tables listed in tables (got "${bundle.table}")`,
2590
2633
  path: ["seeds", i, "table"]
2591
2634
  });
@@ -2595,8 +2638,8 @@ var moduleManifestSchema = z.object({
2595
2638
  }).transform(({ version: _legacyVersion, ...manifest }) => manifest);
2596
2639
  function formatZodErrors(error) {
2597
2640
  return error.issues.map((issue) => {
2598
- const path3 = issue.path.length > 0 ? issue.path.join(".") : "root";
2599
- return `${path3}: ${issue.message}`;
2641
+ const path4 = issue.path.length > 0 ? issue.path.join(".") : "root";
2642
+ return `${path4}: ${issue.message}`;
2600
2643
  });
2601
2644
  }
2602
2645
  function parseModuleManifest(raw) {
@@ -2636,10 +2679,10 @@ var MANDATORY_MODULE_CHECK_DESCRIPTORS = Object.freeze([
2636
2679
  ]);
2637
2680
 
2638
2681
  // ../../packages/module-contract/src/module-structure.ts
2639
- import { z as z3 } from "zod";
2682
+ import { z as z4 } from "zod";
2640
2683
 
2641
2684
  // ../../packages/module-contract/src/module-e2e-manifest.ts
2642
- import { z as z2 } from "zod";
2685
+ import { z as z3 } from "zod";
2643
2686
  function e2eSpecFileValidationError(file) {
2644
2687
  if (file.length === 0) {
2645
2688
  return "must be a non-empty relative path ending in .spec.ts";
@@ -2667,22 +2710,22 @@ function e2eSpecFileValidationError(file) {
2667
2710
  }
2668
2711
  return null;
2669
2712
  }
2670
- var e2eSpecFileSchema = z2.string().superRefine((file, ctx) => {
2713
+ var e2eSpecFileSchema = z3.string().superRefine((file, ctx) => {
2671
2714
  const error = e2eSpecFileValidationError(file);
2672
2715
  if (error != null) {
2673
- ctx.addIssue({ code: z2.ZodIssueCode.custom, message: error });
2716
+ ctx.addIssue({ code: z3.ZodIssueCode.custom, message: error });
2674
2717
  }
2675
2718
  });
2676
- var e2eSpecSchema = z2.object({
2719
+ var e2eSpecSchema = z3.object({
2677
2720
  file: e2eSpecFileSchema,
2678
- requires: z2.array(moduleNameSchema).min(1, "requires must be non-empty")
2721
+ requires: z3.array(moduleNameSchema).min(1, "requires must be non-empty")
2679
2722
  }).strict().superRefine((spec, ctx) => {
2680
2723
  const seen = /* @__PURE__ */ new Set();
2681
2724
  for (let i = 0; i < spec.requires.length; i += 1) {
2682
2725
  const name = spec.requires[i];
2683
2726
  if (seen.has(name)) {
2684
2727
  ctx.addIssue({
2685
- code: z2.ZodIssueCode.custom,
2728
+ code: z3.ZodIssueCode.custom,
2686
2729
  message: `duplicate requirement "${name}"`,
2687
2730
  path: ["requires", i]
2688
2731
  });
@@ -2691,15 +2734,15 @@ var e2eSpecSchema = z2.object({
2691
2734
  }
2692
2735
  }
2693
2736
  });
2694
- var moduleE2eManifestSchema = z2.object({
2695
- specs: z2.array(e2eSpecSchema).min(1, "specs must contain at least one entry")
2737
+ var moduleE2eManifestSchema = z3.object({
2738
+ specs: z3.array(e2eSpecSchema).min(1, "specs must contain at least one entry")
2696
2739
  }).strict().superRefine((manifest, ctx) => {
2697
2740
  const seenFiles = /* @__PURE__ */ new Set();
2698
2741
  for (let i = 0; i < manifest.specs.length; i += 1) {
2699
2742
  const file = manifest.specs[i].file;
2700
2743
  if (seenFiles.has(file)) {
2701
2744
  ctx.addIssue({
2702
- code: z2.ZodIssueCode.custom,
2745
+ code: z3.ZodIssueCode.custom,
2703
2746
  message: `duplicate file "${file}"`,
2704
2747
  path: ["specs", i, "file"]
2705
2748
  });
@@ -2941,7 +2984,7 @@ function compareCodeUnit(a, b) {
2941
2984
  // ../../packages/module-contract/src/module-structure.ts
2942
2985
  var MODULE_STRUCTURE_JSON_BYTE_CAP = 64 * 1024;
2943
2986
  function uniqueSortedStringArray(label) {
2944
- return z3.array(z3.string()).superRefine((arr, ctx) => {
2987
+ return z4.array(z4.string()).superRefine((arr, ctx) => {
2945
2988
  if (new Set(arr).size !== arr.length) {
2946
2989
  ctx.addIssue({ code: "custom", message: `${label} must be unique` });
2947
2990
  return;
@@ -2957,10 +3000,10 @@ function uniqueSortedStringArray(label) {
2957
3000
  });
2958
3001
  }
2959
3002
  function contributionPermissionsSchema(label) {
2960
- return z3.array(
2961
- z3.object({
2962
- contributionId: z3.string().min(1),
2963
- permission: z3.string().min(1)
3003
+ return z4.array(
3004
+ z4.object({
3005
+ contributionId: z4.string().min(1),
3006
+ permission: z4.string().min(1)
2964
3007
  }).strict()
2965
3008
  ).superRefine((entries, ctx) => {
2966
3009
  const keys = entries.map((entry) => `${entry.contributionId}\0${entry.permission}`);
@@ -2978,17 +3021,17 @@ function contributionPermissionsSchema(label) {
2978
3021
  }
2979
3022
  }).optional();
2980
3023
  }
2981
- var collectedFilePayloadSchema = z3.discriminatedUnion("status", [
2982
- z3.object({ status: z3.literal("ok"), text: z3.string() }).strict(),
2983
- z3.object({ status: z3.literal("missing") }).strict(),
2984
- z3.object({ status: z3.literal("too-large") }).strict(),
2985
- z3.object({ status: z3.literal("unreadable") }).strict()
3024
+ var collectedFilePayloadSchema = z4.discriminatedUnion("status", [
3025
+ z4.object({ status: z4.literal("ok"), text: z4.string() }).strict(),
3026
+ z4.object({ status: z4.literal("missing") }).strict(),
3027
+ z4.object({ status: z4.literal("too-large") }).strict(),
3028
+ z4.object({ status: z4.literal("unreadable") }).strict()
2986
3029
  ]);
2987
- var collectedLocaleFileSchema = z3.object({
2988
- locale: z3.string().min(1),
3030
+ var collectedLocaleFileSchema = z4.object({
3031
+ locale: z4.string().min(1),
2989
3032
  payload: collectedFilePayloadSchema
2990
3033
  }).strict();
2991
- var collectedE2eFactsSchema = z3.object({
3034
+ var collectedE2eFactsSchema = z4.object({
2992
3035
  manifest: collectedFilePayloadSchema,
2993
3036
  specFiles: uniqueSortedStringArray("specFiles"),
2994
3037
  escapingSymlinks: uniqueSortedStringArray("escapingSymlinks")
@@ -2996,34 +3039,34 @@ var collectedE2eFactsSchema = z3.object({
2996
3039
  function validateRouteExportEvidenceCongruence(input) {
2997
3040
  const entrySet = new Set(input.routeEntries);
2998
3041
  const exportPaths = input.routePageExports.map((e) => e.nextRelativePath);
2999
- for (const path3 of exportPaths) {
3000
- if (!entrySet.has(path3)) {
3001
- input.onIssue(`routePageExports path "${path3}" is not listed in routeEntries`);
3042
+ for (const path4 of exportPaths) {
3043
+ if (!entrySet.has(path4)) {
3044
+ input.onIssue(`routePageExports path "${path4}" is not listed in routeEntries`);
3002
3045
  }
3003
3046
  }
3004
3047
  if (input.routeCollectionErrors.length === 0) {
3005
3048
  const exportSet = new Set(exportPaths);
3006
- for (const path3 of input.routeEntries) {
3007
- if (!exportSet.has(path3)) {
3008
- input.onIssue(`routeEntries path "${path3}" is missing from routePageExports`);
3049
+ for (const path4 of input.routeEntries) {
3050
+ if (!exportSet.has(path4)) {
3051
+ input.onIssue(`routeEntries path "${path4}" is missing from routePageExports`);
3009
3052
  }
3010
3053
  }
3011
3054
  }
3012
3055
  }
3013
- var collectedEnhancementFactsSchema = z3.object({
3014
- peer: z3.string().min(1),
3015
- hasContributionsTs: z3.boolean(),
3056
+ var collectedEnhancementFactsSchema = z4.object({
3057
+ peer: z4.string().min(1),
3058
+ hasContributionsTs: z4.boolean(),
3016
3059
  contributionPermissions: contributionPermissionsSchema("enhancement contributionPermissions"),
3017
- hasInitializeServerTs: z3.boolean(),
3060
+ hasInitializeServerTs: z4.boolean(),
3018
3061
  contributionsCollectionErrors: uniqueSortedStringArray(
3019
3062
  "enhancement contributionsCollectionErrors"
3020
3063
  ),
3021
3064
  initializeServerCollectionErrors: uniqueSortedStringArray(
3022
3065
  "enhancement initializeServerCollectionErrors"
3023
3066
  ),
3024
- hasRoutesDir: z3.boolean(),
3067
+ hasRoutesDir: z4.boolean(),
3025
3068
  routeCollectionErrors: uniqueSortedStringArray("enhancement routeCollectionErrors"),
3026
- locales: z3.array(collectedLocaleFileSchema).superRefine((locales, ctx) => {
3069
+ locales: z4.array(collectedLocaleFileSchema).superRefine((locales, ctx) => {
3027
3070
  const names = locales.map((l) => l.locale);
3028
3071
  if (new Set(names).size !== names.length) {
3029
3072
  ctx.addIssue({ code: "custom", message: "enhancement locales must be unique by locale" });
@@ -3038,27 +3081,27 @@ var collectedEnhancementFactsSchema = z3.object({
3038
3081
  }),
3039
3082
  e2e: collectedE2eFactsSchema.nullable()
3040
3083
  }).strict();
3041
- var moduleStructureFactsSchema = z3.object({
3042
- collected: z3.literal(true),
3043
- dirName: z3.string().min(1),
3044
- hasIndexTs: z3.boolean(),
3045
- hasContributionsTs: z3.boolean(),
3084
+ var moduleStructureFactsSchema = z4.object({
3085
+ collected: z4.literal(true),
3086
+ dirName: z4.string().min(1),
3087
+ hasIndexTs: z4.boolean(),
3088
+ hasContributionsTs: z4.boolean(),
3046
3089
  contributionPermissions: contributionPermissionsSchema("contributionPermissions"),
3047
- hasSlotsTs: z3.boolean(),
3090
+ hasSlotsTs: z4.boolean(),
3048
3091
  contributionsCollectionErrors: uniqueSortedStringArray("contributionsCollectionErrors"),
3049
3092
  slotsCollectionErrors: uniqueSortedStringArray("slotsCollectionErrors"),
3050
- hasTestsDir: z3.boolean(),
3051
- hasDatastoreDir: z3.boolean(),
3052
- datastoreFileCount: z3.number().int().nonnegative(),
3053
- hasServerIndexTs: z3.boolean(),
3054
- hasOtherServerTs: z3.boolean(),
3055
- hasE2eDir: z3.boolean(),
3056
- hasRoutesDir: z3.boolean(),
3093
+ hasTestsDir: z4.boolean(),
3094
+ hasDatastoreDir: z4.boolean(),
3095
+ datastoreFileCount: z4.number().int().nonnegative(),
3096
+ hasServerIndexTs: z4.boolean(),
3097
+ hasOtherServerTs: z4.boolean(),
3098
+ hasE2eDir: z4.boolean(),
3099
+ hasRoutesDir: z4.boolean(),
3057
3100
  routeEntries: uniqueSortedStringArray("routeEntries"),
3058
- routePageExports: z3.array(
3059
- z3.object({
3060
- nextRelativePath: z3.string().min(1),
3061
- hasGenerateMetadata: z3.boolean()
3101
+ routePageExports: z4.array(
3102
+ z4.object({
3103
+ nextRelativePath: z4.string().min(1),
3104
+ hasGenerateMetadata: z4.boolean()
3062
3105
  }).strict()
3063
3106
  ).superRefine((entries, ctx) => {
3064
3107
  const paths = entries.map((e) => e.nextRelativePath);
@@ -3080,7 +3123,7 @@ var moduleStructureFactsSchema = z3.object({
3080
3123
  }
3081
3124
  }),
3082
3125
  routeCollectionErrors: uniqueSortedStringArray("routeCollectionErrors"),
3083
- rootLocales: z3.array(collectedLocaleFileSchema).superRefine((locales, ctx) => {
3126
+ rootLocales: z4.array(collectedLocaleFileSchema).superRefine((locales, ctx) => {
3084
3127
  const names = locales.map((l) => l.locale);
3085
3128
  if (new Set(names).size !== names.length) {
3086
3129
  ctx.addIssue({ code: "custom", message: "rootLocales must be unique by locale" });
@@ -3094,7 +3137,7 @@ var moduleStructureFactsSchema = z3.object({
3094
3137
  }
3095
3138
  }),
3096
3139
  rootE2e: collectedE2eFactsSchema.nullable(),
3097
- enhancements: z3.array(collectedEnhancementFactsSchema).superRefine((enhancements, ctx) => {
3140
+ enhancements: z4.array(collectedEnhancementFactsSchema).superRefine((enhancements, ctx) => {
3098
3141
  const peers = enhancements.map((e) => e.peer);
3099
3142
  if (new Set(peers).size !== peers.length) {
3100
3143
  ctx.addIssue({ code: "custom", message: "enhancement peers must be unique" });
@@ -3115,10 +3158,10 @@ var moduleStructureFactsSchema = z3.object({
3115
3158
  onIssue: (message) => ctx.addIssue({ code: "custom", message })
3116
3159
  });
3117
3160
  });
3118
- var moduleStructureCollectionSchema = z3.object({
3161
+ var moduleStructureCollectionSchema = z4.object({
3119
3162
  homeLocales: uniqueSortedStringArray("homeLocales"),
3120
- modules: z3.array(moduleStructureFactsSchema),
3121
- truncated: z3.boolean().optional(),
3163
+ modules: z4.array(moduleStructureFactsSchema),
3164
+ truncated: z4.boolean().optional(),
3122
3165
  collectionErrors: uniqueSortedStringArray("collectionErrors").optional()
3123
3166
  }).strict().superRefine((collection, ctx) => {
3124
3167
  const dirs = collection.modules.map((m) => m.dirName);
@@ -3135,9 +3178,9 @@ var moduleStructureCollectionSchema = z3.object({
3135
3178
  });
3136
3179
 
3137
3180
  // ../../packages/module-contract/src/locale.ts
3138
- import { z as z4 } from "zod";
3181
+ import { z as z5 } from "zod";
3139
3182
  var LOCALE_BASENAME_REGEX = /^[a-z]{2,3}(-[A-Za-z0-9]+)*$/;
3140
- var localeBasenameSchema = z4.string().max(35).regex(LOCALE_BASENAME_REGEX, "must be a BCP47-like locale tag (e.g. en, th, en-US)");
3183
+ var localeBasenameSchema = z5.string().max(35).regex(LOCALE_BASENAME_REGEX, "must be a BCP47-like locale tag (e.g. en, th, en-US)");
3141
3184
  function isValidLocaleBasename(value) {
3142
3185
  return localeBasenameSchema.safeParse(value).success;
3143
3186
  }
@@ -3540,43 +3583,43 @@ function formatRoutePageExportErrors(nextRelativePath, validation) {
3540
3583
  import ts2 from "typescript";
3541
3584
 
3542
3585
  // ../../packages/module-contract/src/module-registry.ts
3543
- import { z as z5 } from "zod";
3544
- var projectSlugSchema2 = z5.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
3545
- var registryEnhancementSchema = z5.object({
3586
+ import { z as z6 } from "zod";
3587
+ var projectSlugSchema2 = z6.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
3588
+ var registryEnhancementSchema = z6.object({
3546
3589
  peer: moduleNameSchema,
3547
- active: z5.boolean()
3590
+ active: z6.boolean()
3548
3591
  }).strict();
3549
- var registryModuleSchema = z5.object({
3592
+ var registryModuleSchema = z6.object({
3550
3593
  name: moduleNameSchema,
3551
3594
  /** Installed version as recorded by the rail (non-empty string). */
3552
- version: z5.string().min(1),
3595
+ version: z6.string().min(1),
3553
3596
  home: projectSlugSchema2,
3554
3597
  kind: moduleKindSchema,
3555
- mounts: z5.array(mountSchema),
3556
- enhancements: z5.array(registryEnhancementSchema)
3598
+ mounts: z6.array(mountSchema),
3599
+ enhancements: z6.array(registryEnhancementSchema)
3557
3600
  }).strict().superRefine((mod, ctx) => {
3558
3601
  for (let i = 0; i < mod.enhancements.length; i += 1) {
3559
3602
  if (mod.enhancements[i].peer === mod.name) {
3560
3603
  ctx.addIssue({
3561
- code: z5.ZodIssueCode.custom,
3604
+ code: z6.ZodIssueCode.custom,
3562
3605
  message: "enhancement peer must not equal the module's own name",
3563
3606
  path: ["enhancements", i, "peer"]
3564
3607
  });
3565
3608
  }
3566
3609
  }
3567
3610
  });
3568
- var generatedModuleRegistrySchema = z5.object({
3569
- registryVersion: z5.literal(1),
3611
+ var generatedModuleRegistrySchema = z6.object({
3612
+ registryVersion: z6.literal(1),
3570
3613
  /** Rail run id, or `"manual"` for fixtures. */
3571
- generatedBy: z5.string().min(1),
3572
- modules: z5.array(registryModuleSchema)
3614
+ generatedBy: z6.string().min(1),
3615
+ modules: z6.array(registryModuleSchema)
3573
3616
  }).strict().superRefine((registry, ctx) => {
3574
3617
  const seen = /* @__PURE__ */ new Set();
3575
3618
  for (let i = 0; i < registry.modules.length; i += 1) {
3576
3619
  const name = registry.modules[i].name;
3577
3620
  if (seen.has(name)) {
3578
3621
  ctx.addIssue({
3579
- code: z5.ZodIssueCode.custom,
3622
+ code: z6.ZodIssueCode.custom,
3580
3623
  message: `duplicate module name "${name}"`,
3581
3624
  path: ["modules", i, "name"]
3582
3625
  });
@@ -3947,169 +3990,6 @@ function analyzeClientSafeEntryImports(sourceText, fileName) {
3947
3990
  return analyzeClientSafeEntryImportsWithTs(ts3, sourceText, fileName);
3948
3991
  }
3949
3992
 
3950
- // ../../packages/lib/src/shared/module-composition.ts
3951
- var moduleRefSchema = z6.object({
3952
- homeProjectId: z6.string().uuid(),
3953
- moduleName: moduleNameSchema
3954
- });
3955
- var creationSourceBlankSchema = z6.object({
3956
- type: z6.literal("blank")
3957
- });
3958
- var creationSourceBlueprintSchema = z6.object({
3959
- type: z6.literal("blueprint"),
3960
- /** Internal template/fork project id — keep template naming in identifiers. */
3961
- blueprintProjectId: z6.string().uuid(),
3962
- /**
3963
- * `null`/absent means every feature Module. `[]` is refused: the platform
3964
- * serialises it to an empty `NEXT_PUBLIC_STARDECK_ENABLED_MODULES`, which the
3965
- * enablement parser reads back as "all" (`module-enablement.ts`) — the
3966
- * opposite of what an empty selection means.
3967
- */
3968
- enabledModules: z6.array(moduleNameSchema).min(1, "Enable at least one Module").max(100).nullable().optional()
3969
- });
3970
- var creationSourceModulesSchema = z6.object({
3971
- type: z6.literal("modules"),
3972
- requested: z6.array(moduleRefSchema).min(1)
3973
- });
3974
- var creationSourceSchema = z6.discriminatedUnion("type", [
3975
- creationSourceBlankSchema,
3976
- creationSourceBlueprintSchema,
3977
- creationSourceModulesSchema
3978
- ]);
3979
- var moduleCompositionStatusSchema = z6.enum([
3980
- "pending",
3981
- "installing",
3982
- "ready",
3983
- "partial",
3984
- "failed"
3985
- ]);
3986
- var resolvedModuleSnapshotSchema = moduleRefSchema.extend({
3987
- version: z6.string().min(1),
3988
- homeCommitSha: z6.string().min(1)
3989
- });
3990
- var moduleInstallHandoffSchema = z6.object({
3991
- moduleName: z6.string().min(1),
3992
- homeProjectId: z6.string().uuid(),
3993
- installedVersion: z6.string().min(1),
3994
- baselineSha: z6.string().min(1),
3995
- mounts: z6.array(
3996
- z6.object({
3997
- group: z6.string(),
3998
- segment: z6.string()
3999
- })
4000
- ),
4001
- integration: z6.array(z6.string()),
4002
- workflows: z6.array(z6.string()),
4003
- prunedEnhancements: z6.array(z6.string()),
4004
- restoredEnhancements: z6.array(z6.string()).optional(),
4005
- permissions: z6.array(
4006
- z6.object({
4007
- key: z6.string().min(1),
4008
- description: z6.string().min(1)
4009
- })
4010
- ).optional()
4011
- });
4012
- var moduleCompositionRunAttemptSchema = z6.object({
4013
- module: moduleRefSchema,
4014
- runId: z6.string().min(1),
4015
- status: z6.string().min(1),
4016
- schemaApplied: z6.boolean(),
4017
- error: z6.string().optional(),
4018
- /** Prefer full ModuleInstallHandoff; identity fields optional for older rows. */
4019
- handoff: moduleInstallHandoffSchema.partial({ moduleName: true, homeProjectId: true }).optional()
4020
- });
4021
- var initialModuleCompositionSchema = z6.object({
4022
- version: z6.literal(1),
4023
- status: moduleCompositionStatusSchema,
4024
- requested: z6.array(moduleRefSchema).min(1),
4025
- resolved: z6.array(resolvedModuleSnapshotSchema),
4026
- installOrder: z6.array(moduleRefSchema),
4027
- runs: z6.array(moduleCompositionRunAttemptSchema).default([]),
4028
- /**
4029
- * True when any failed/partial attempt applied schema before failing.
4030
- * Never claim transactional rollback across git/schema.
4031
- */
4032
- schemaApplied: z6.boolean().default(false),
4033
- /**
4034
- * Present when retry would silently change pinned home SHAs — caller must
4035
- * reconfirm the new plan rather than continue.
4036
- */
4037
- reconfirmationRequired: z6.object({
4038
- reason: z6.string(),
4039
- previous: z6.array(resolvedModuleSnapshotSchema),
4040
- next: z6.array(resolvedModuleSnapshotSchema)
4041
- }).optional(),
4042
- error: z6.string().optional(),
4043
- /**
4044
- * First-turn system handoff lifecycle. Absent/`pending` means inject when
4045
- * status is ready; `consumed` means a successful agent turn already used it.
4046
- */
4047
- systemHandoffStatus: z6.enum(["pending", "consumed"]).optional(),
4048
- /**
4049
- * CAS ownership token for concurrent retry attempts. Only the attempt that
4050
- * currently owns this value may reset or compensate durable composition state.
4051
- */
4052
- retryAttemptId: z6.string().min(1).optional(),
4053
- /**
4054
- * When the current `installing` attempt was claimed. A setup run that dies
4055
- * with its host (a killed Vercel instance) leaves the composition `installing`
4056
- * forever, and the creating page's poll only stops on ready/partial/failed —
4057
- * so staleness, not a queue's stall detector, is what settles it. Absent on
4058
- * rows claimed before this field existed; treated as stale.
4059
- */
4060
- attemptStartedAt: z6.string().datetime().optional(),
4061
- /** Outside-module files the agent changed during connection work. */
4062
- integrationFiles: z6.array(z6.string()).optional(),
4063
- verificationSummary: z6.string().optional()
4064
- });
4065
- var MODULE_SETUP_ATTEMPT_STALE_MS = 10 * 60 * 1e3;
4066
-
4067
- // ../../packages/lib/src/shared/compose-inputs.ts
4068
- import { z as z7 } from "zod";
4069
- var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
4070
- var composeInputsSchema = z7.strictObject({
4071
- connectedStores: z7.array(
4072
- z7.strictObject({
4073
- storeId: z7.string().min(1),
4074
- slug: z7.string().min(1),
4075
- // A row with an empty binding key must not fail a deploy: the rail
4076
- // treats falsy as "no binding key", so normalise rather than reject.
4077
- bindingKey: z7.string().nullish().transform((value) => value || null),
4078
- accessLevel: z7.enum(["read", "write", "admin"]),
4079
- // Defaulted so a file written before the platform emitted store types
4080
- // (database rows only) still parses. The platform starts writing
4081
- // storage rows, with `type` set, once forks run a compose that reads it.
4082
- type: z7.enum(["database", "storage"]).default("database")
4083
- })
4084
- ).default([]),
4085
- installs: z7.record(
4086
- z7.string(),
4087
- z7.strictObject({
4088
- installedVersion: z7.string().min(1),
4089
- datastoreBindings: z7.record(z7.string(), z7.string()).nullish()
4090
- })
4091
- ).default({})
4092
- });
4093
- var STUB_IGNORE_PATH = "apps/web/src/app/.gitignore";
4094
- var STUB_IGNORE_HEADER = "# Generated by stardeck-compose \u2014 Module-rail stubs are build outputs; do not edit or commit them.";
4095
- function escapeStubIgnorePattern(pattern) {
4096
- const escaped = pattern.replace(/[\\[\]*?]/g, (char) => `\\${char}`);
4097
- return /^[#!]/.test(escaped) ? `\\${escaped}` : escaped;
4098
- }
4099
- function buildStubIgnore(artifactPaths) {
4100
- const prefix = "apps/web/src/app/";
4101
- const entries = artifactPaths.filter((artifact) => artifact.startsWith(prefix)).map((artifact) => escapeStubIgnorePattern(`/${artifact.slice(prefix.length)}`)).sort();
4102
- return [STUB_IGNORE_HEADER, ...entries].join("\n") + "\n";
4103
- }
4104
-
4105
- // ../../packages/lib/src/shared/module-enablement.ts
4106
- function isModuleEnabledByList(raw, name, kindOf) {
4107
- if (!raw) return true;
4108
- const kind = kindOf(name);
4109
- if (kind != null && kind !== "feature") return true;
4110
- return raw.split(",").includes(name);
4111
- }
4112
-
4113
3993
  // ../../packages/lib/src/shared/platform-owned-paths.ts
4114
3994
  var APP_WEB_PREFIX = "apps/web/";
4115
3995
  var MODULE_STUB_MARKER = "@stardeck-module-rail-generated";
@@ -4256,13 +4136,13 @@ function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
4256
4136
  function createMessageTree() {
4257
4137
  return /* @__PURE__ */ Object.create(null);
4258
4138
  }
4259
- function assertMessageTree(value, path3) {
4139
+ function assertMessageTree(value, path4) {
4260
4140
  if (typeof value === "string") return;
4261
4141
  if (value === null || Array.isArray(value) || typeof value !== "object") {
4262
- throw new Error(`Invalid message at ${path3}: expected string or nested object`);
4142
+ throw new Error(`Invalid message at ${path4}: expected string or nested object`);
4263
4143
  }
4264
4144
  for (const [key, child] of Object.entries(value)) {
4265
- assertMessageTree(child, `${path3}.${key}`);
4145
+ assertMessageTree(child, `${path4}.${key}`);
4266
4146
  }
4267
4147
  }
4268
4148
  function cloneTree(value) {
@@ -4272,17 +4152,17 @@ function cloneTree(value) {
4272
4152
  }
4273
4153
  return clone;
4274
4154
  }
4275
- function collectLeafPaths(value, path3, out) {
4155
+ function collectLeafPaths(value, path4, out) {
4276
4156
  if (typeof value === "string") {
4277
- out.push(path3);
4157
+ out.push(path4);
4278
4158
  return;
4279
4159
  }
4280
- assertMessageTree(value, path3);
4281
- for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path3}.${key}`, out);
4160
+ assertMessageTree(value, path4);
4161
+ for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path4}.${key}`, out);
4282
4162
  }
4283
- function overrideInto(target, source, owner, path3, missing) {
4163
+ function overrideInto(target, source, owner, path4, missing) {
4284
4164
  for (const [key, value] of Object.entries(source)) {
4285
- const nextPath = path3 ? `${path3}.${key}` : key;
4165
+ const nextPath = path4 ? `${path4}.${key}` : key;
4286
4166
  if (!Object.prototype.hasOwnProperty.call(target, key)) {
4287
4167
  collectLeafPaths(value, nextPath, missing);
4288
4168
  continue;
@@ -4309,9 +4189,9 @@ function overrideInto(target, source, owner, path3, missing) {
4309
4189
  function composeLocaleMessagesForPlan(layers) {
4310
4190
  const result = createMessageTree();
4311
4191
  const leafOwners = /* @__PURE__ */ new Map();
4312
- function mergeInto(target, source, owner, path3) {
4192
+ function mergeInto(target, source, owner, path4) {
4313
4193
  for (const [key, value] of Object.entries(source)) {
4314
- const nextPath = path3 ? `${path3}.${key}` : key;
4194
+ const nextPath = path4 ? `${path4}.${key}` : key;
4315
4195
  const hasExisting = Object.prototype.hasOwnProperty.call(target, key);
4316
4196
  const existing = hasExisting ? target[key] : void 0;
4317
4197
  if (typeof value === "string") {
@@ -4855,8 +4735,8 @@ var METHOD_ORDER = {
4855
4735
  function compareNames2(a, b) {
4856
4736
  return a < b ? -1 : a > b ? 1 : 0;
4857
4737
  }
4858
- function endpointPairKey(method, path3) {
4859
- return `${method} ${path3}`;
4738
+ function endpointPairKey(method, path4) {
4739
+ return `${method} ${path4}`;
4860
4740
  }
4861
4741
  function apiPathToRouteFile(apiPath) {
4862
4742
  if (!apiPath.startsWith("/api/")) {
@@ -4870,11 +4750,11 @@ function routeEntryToAppPageFile(nextRelativePath) {
4870
4750
  }
4871
4751
  var APP_ROUTE_FILE_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
4872
4752
  var API_ROUTE_FILE_SUFFIXES = ["/route.ts", "/route.tsx", "/route.jsx", "/route.js"];
4873
- function appRouteFileToNextRelativePath(path3) {
4874
- const pagePath = appPageFileToNextRelativePath(path3, APP_PACKAGE_DIR);
4753
+ function appRouteFileToNextRelativePath(path4) {
4754
+ const pagePath = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
4875
4755
  if (pagePath != null) return pagePath;
4876
- if (!path3.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
4877
- const relative = path3.slice(APP_ROUTE_FILE_PREFIX.length);
4756
+ if (!path4.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
4757
+ const relative = path4.slice(APP_ROUTE_FILE_PREFIX.length);
4878
4758
  if (!relative.startsWith("api/")) return null;
4879
4759
  const suffix = API_ROUTE_FILE_SUFFIXES.find((candidate) => relative.endsWith(candidate));
4880
4760
  if (!suffix) return null;
@@ -4966,9 +4846,9 @@ function legacyHandlerPropertyMatchesPath(property, method, physicalPath, bindin
4966
4846
  const expectedTokens = knownAliases[`${binding} ${method} ${physicalPath}`] ?? pathTokens;
4967
4847
  return handlerTokens.length === expectedTokens.length && handlerTokens.every((token, index) => token === expectedTokens[index]);
4968
4848
  }
4969
- function parseLegacyApiAdapter(path3, source) {
4970
- const physicalPath = appRouteFileToNextRelativePath(path3);
4971
- if (expectedMarkerKindForPath(path3) !== "endpoint" || physicalPath == null) return null;
4849
+ function parseLegacyApiAdapter(path4, source) {
4850
+ const physicalPath = appRouteFileToNextRelativePath(path4);
4851
+ if (expectedMarkerKindForPath(path4) !== "endpoint" || physicalPath == null) return null;
4972
4852
  const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
4973
4853
  const match = firstLine.match(
4974
4854
  new RegExp(
@@ -4978,7 +4858,7 @@ function parseLegacyApiAdapter(path3, source) {
4978
4858
  if (!match || match[2] !== physicalPath) return null;
4979
4859
  const owner = match[1];
4980
4860
  if (!isValidModuleName(owner)) return null;
4981
- const file = ts4.createSourceFile(path3, source, ts4.ScriptTarget.Latest, false, ts4.ScriptKind.TS);
4861
+ const file = ts4.createSourceFile(path4, source, ts4.ScriptTarget.Latest, false, ts4.ScriptKind.TS);
4982
4862
  if (file.parseDiagnostics.length > 0) {
4983
4863
  return null;
4984
4864
  }
@@ -5020,9 +4900,9 @@ function parseLegacyApiAdapter(path3, source) {
5020
4900
  }
5021
4901
  return handlerBinding != null && methods.size > 0 ? { owner, methods: [...methods] } : null;
5022
4902
  }
5023
- function parseLegacyRouteAdapter(path3, source) {
5024
- const physicalPath = appRouteFileToNextRelativePath(path3);
5025
- if (expectedMarkerKindForPath(path3) !== "route" || physicalPath == null) return null;
4903
+ function parseLegacyRouteAdapter(path4, source) {
4904
+ const physicalPath = appRouteFileToNextRelativePath(path4);
4905
+ if (expectedMarkerKindForPath(path4) !== "route" || physicalPath == null) return null;
5026
4906
  const normalized = source.replace(/\r\n/g, "\n");
5027
4907
  const match = normalized.match(
5028
4908
  new RegExp(
@@ -5034,30 +4914,30 @@ function parseLegacyRouteAdapter(path3, source) {
5034
4914
  const nextRelativePath = match[2];
5035
4915
  return isValidModuleName(owner) && nextRelativePath === physicalPath ? { owner, nextRelativePath } : null;
5036
4916
  }
5037
- function legacyRouteAdapterMatchesStub(path3, source, stub) {
5038
- const legacy = parseLegacyRouteAdapter(path3, source);
4917
+ function legacyRouteAdapterMatchesStub(path4, source, stub) {
4918
+ const legacy = parseLegacyRouteAdapter(path4, source);
5039
4919
  return legacy != null && legacy.owner === stub.owner;
5040
4920
  }
5041
4921
  var API_ROUTE_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/api/`;
5042
4922
  var APP_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
5043
4923
  var PAGE_FILE_SUFFIXES = ["/page.tsx", "/page.ts", "/page.jsx", "/page.js"];
5044
- function expectedMarkerKindForPath(path3) {
5045
- if (path3 === MODULES_GEN_PATH) return "registry";
5046
- if (path3 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
5047
- if (path3 === MODULE_DATASTORES_GEN_PATH) return "datastores";
5048
- if (path3 === MODULE_I18N_GEN_PATH) return "i18n";
5049
- if (path3 === MODULE_INIT_SERVER_GEN_PATH) return "init";
5050
- if (path3 === MODULE_SERVER_GEN_PATH) return "server";
5051
- if (path3.startsWith(API_ROUTE_DIR_PREFIX) && path3.endsWith("/route.ts")) return "endpoint";
5052
- if (path3.startsWith(APP_DIR_PREFIX) && !path3.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path3.endsWith(suffix))) {
4924
+ function expectedMarkerKindForPath(path4) {
4925
+ if (path4 === MODULES_GEN_PATH) return "registry";
4926
+ if (path4 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
4927
+ if (path4 === MODULE_DATASTORES_GEN_PATH) return "datastores";
4928
+ if (path4 === MODULE_I18N_GEN_PATH) return "i18n";
4929
+ if (path4 === MODULE_INIT_SERVER_GEN_PATH) return "init";
4930
+ if (path4 === MODULE_SERVER_GEN_PATH) return "server";
4931
+ if (path4.startsWith(API_ROUTE_DIR_PREFIX) && path4.endsWith("/route.ts")) return "endpoint";
4932
+ if (path4.startsWith(APP_DIR_PREFIX) && !path4.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path4.endsWith(suffix))) {
5053
4933
  return "route";
5054
4934
  }
5055
4935
  return null;
5056
4936
  }
5057
- function markerCongruentWithPath(path3, marker) {
5058
- if (expectedMarkerKindForPath(path3) !== marker.kind) return false;
4937
+ function markerCongruentWithPath(path4, marker) {
4938
+ if (expectedMarkerKindForPath(path4) !== marker.kind) return false;
5059
4939
  if (marker.kind === "route") {
5060
- const derived = appPageFileToNextRelativePath(path3, APP_PACKAGE_DIR);
4940
+ const derived = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
5061
4941
  return derived != null && derived === marker.nextRelativePath;
5062
4942
  }
5063
4943
  return true;
@@ -5328,8 +5208,8 @@ function buildGeneratedModuleRegistry(input) {
5328
5208
  function activeEnhancementPeers(registryModule) {
5329
5209
  return new Set(registryModule.enhancements.filter((e) => e.active).map((e) => e.peer));
5330
5210
  }
5331
- function endpointBucketKey(path3, owner, scope) {
5332
- return `${path3}\0${owner}\0${scopeLabel(scope)}`;
5211
+ function endpointBucketKey(path4, owner, scope) {
5212
+ return `${path4}\0${owner}\0${scopeLabel(scope)}`;
5333
5213
  }
5334
5214
  function planRouteStubs(input) {
5335
5215
  const endpointBuckets = /* @__PURE__ */ new Map();
@@ -5453,7 +5333,7 @@ function planRouteStubs(input) {
5453
5333
  for (const entry of entries) {
5454
5334
  const nextRelativePath = entry.nextRelativePath;
5455
5335
  const destination = normalizeAppRouterDestinationPattern(nextRelativePath);
5456
- const path3 = routeEntryToAppPageFile(nextRelativePath);
5336
+ const path4 = routeEntryToAppPageFile(nextRelativePath);
5457
5337
  const existing = routeDestinationOwners.get(destination);
5458
5338
  if (existing) {
5459
5339
  throw new Error(
@@ -5463,13 +5343,13 @@ function planRouteStubs(input) {
5463
5343
  routeDestinationOwners.set(destination, {
5464
5344
  owner: regMod.name,
5465
5345
  nextRelativePath,
5466
- path: path3
5346
+ path: path4
5467
5347
  });
5468
5348
  const importModule = routeEntryImport(regMod.name, nextRelativePath);
5469
5349
  const analyzed = analyzeRouteEntryPath(nextRelativePath);
5470
5350
  const mount = mounts.find((claim) => routeEntryCoveredByMountClaim(analyzed, claim));
5471
5351
  routeStubs.push({
5472
- path: path3,
5352
+ path: path4,
5473
5353
  owner: regMod.name,
5474
5354
  nextRelativePath,
5475
5355
  importModule,
@@ -5530,16 +5410,16 @@ function planCompositionArtifacts(input) {
5530
5410
  ...stubs.routeStubs.map((stub) => stub.path)
5531
5411
  ]);
5532
5412
  const existingGeneratedPathSet = new Set(input.existingGeneratedPaths);
5533
- const isProvenStaleDeletionCandidate = (path3, content) => {
5534
- if (path3 === MODULES_GEN_PATH) return false;
5535
- if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path3)) return false;
5536
- if (desiredPaths.has(path3)) return false;
5537
- if (!existingGeneratedPathSet.has(path3)) return false;
5538
- const legacy = parseLegacyApiAdapter(path3, content);
5413
+ const isProvenStaleDeletionCandidate = (path4, content) => {
5414
+ if (path4 === MODULES_GEN_PATH) return false;
5415
+ if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path4)) return false;
5416
+ if (desiredPaths.has(path4)) return false;
5417
+ if (!existingGeneratedPathSet.has(path4)) return false;
5418
+ const legacy = parseLegacyApiAdapter(path4, content);
5539
5419
  if (legacy) return true;
5540
- if (parseLegacyRouteAdapter(path3, content)) return true;
5420
+ if (parseLegacyRouteAdapter(path4, content)) return true;
5541
5421
  const marker = parseCompositionArtifactMarker(content);
5542
- return marker != null && markerCongruentWithPath(path3, marker);
5422
+ return marker != null && markerCongruentWithPath(path4, marker);
5543
5423
  };
5544
5424
  const plannedByDestination = /* @__PURE__ */ new Map();
5545
5425
  for (const stub of stubs.routeStubs) {
@@ -5693,13 +5573,13 @@ function planCompositionArtifacts(input) {
5693
5573
  for (const stub of stubs.routeStubs) desired.set(stub.path, stub.content);
5694
5574
  const endpointStubByPath = new Map(stubs.endpointStubs.map((stub) => [stub.path, stub]));
5695
5575
  const routeStubByPath = new Map(stubs.routeStubs.map((stub) => [stub.path, stub]));
5696
- for (const [path3] of desired) {
5697
- const existing = input.existingFiles[path3];
5576
+ for (const [path4] of desired) {
5577
+ const existing = input.existingFiles[path4];
5698
5578
  if (existing == null) continue;
5699
5579
  let marker = parseCompositionArtifactMarker(existing);
5700
- const plannedEndpoint = endpointStubByPath.get(path3);
5701
- const plannedRoute = routeStubByPath.get(path3);
5702
- const legacy = parseLegacyApiAdapter(path3, existing);
5580
+ const plannedEndpoint = endpointStubByPath.get(path4);
5581
+ const plannedRoute = routeStubByPath.get(path4);
5582
+ const legacy = parseLegacyApiAdapter(path4, existing);
5703
5583
  if (legacy && plannedEndpoint) {
5704
5584
  marker = {
5705
5585
  kind: "endpoint",
@@ -5707,66 +5587,66 @@ function planCompositionArtifacts(input) {
5707
5587
  scope: plannedEndpoint.scope
5708
5588
  };
5709
5589
  }
5710
- if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path3, existing, plannedRoute)) {
5590
+ if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path4, existing, plannedRoute)) {
5711
5591
  marker = {
5712
5592
  kind: "route",
5713
5593
  owner: plannedRoute.owner,
5714
5594
  nextRelativePath: plannedRoute.nextRelativePath
5715
5595
  };
5716
5596
  }
5717
- const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path3);
5597
+ const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path4);
5718
5598
  if (!marker) {
5719
5599
  return {
5720
5600
  ok: false,
5721
- error: path3 === MODULES_GEN_PATH ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged registry at ${path3}` : isGenRuntimePath ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged generated file at ${path3}` : `[ModuleRail] composition artifacts: refusing to overwrite unmanaged route at ${path3}`,
5722
- path: path3
5601
+ error: path4 === MODULES_GEN_PATH ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged registry at ${path4}` : isGenRuntimePath ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged generated file at ${path4}` : `[ModuleRail] composition artifacts: refusing to overwrite unmanaged route at ${path4}`,
5602
+ path: path4
5723
5603
  };
5724
5604
  }
5725
- const expectedKind = expectedMarkerKindForPath(path3);
5605
+ const expectedKind = expectedMarkerKindForPath(path4);
5726
5606
  if (marker.kind !== expectedKind) {
5727
5607
  return {
5728
5608
  ok: false,
5729
- error: path3 === MODULES_GEN_PATH ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged registry at ${path3} (incongruent marker ownership: found ${marker.kind} marker)` : isGenRuntimePath ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged generated file at ${path3} (incongruent marker ownership: found ${marker.kind} marker)` : `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path3} (expected ${expectedKind ?? "no"} marker, found ${marker.kind} marker)`,
5730
- path: path3
5609
+ error: path4 === MODULES_GEN_PATH ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged registry at ${path4} (incongruent marker ownership: found ${marker.kind} marker)` : isGenRuntimePath ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged generated file at ${path4} (incongruent marker ownership: found ${marker.kind} marker)` : `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path4} (expected ${expectedKind ?? "no"} marker, found ${marker.kind} marker)`,
5610
+ path: path4
5731
5611
  };
5732
5612
  }
5733
5613
  if (marker.kind === "endpoint") {
5734
- const stub = endpointStubByPath.get(path3);
5614
+ const stub = endpointStubByPath.get(path4);
5735
5615
  const ownerMatches = stub != null && marker.owner === stub.owner;
5736
5616
  const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
5737
5617
  if (!ownerMatches || !scopeMatches) {
5738
5618
  return {
5739
5619
  ok: false,
5740
- error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path3} (existing owner=${marker.owner} scope=${scopeLabel(marker.scope)} vs planned owner=${stub?.owner ?? "?"} scope=${stub ? scopeLabel(stub.scope) : "?"})`,
5741
- path: path3,
5620
+ error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path4} (existing owner=${marker.owner} scope=${scopeLabel(marker.scope)} vs planned owner=${stub?.owner ?? "?"} scope=${stub ? scopeLabel(stub.scope) : "?"})`,
5621
+ path: path4,
5742
5622
  owner: stub?.owner
5743
5623
  };
5744
5624
  }
5745
5625
  } else if (marker.kind === "route") {
5746
- const stub = routeStubByPath.get(path3);
5626
+ const stub = routeStubByPath.get(path4);
5747
5627
  const ownerMatches = stub != null && marker.owner === stub.owner;
5748
5628
  const pathMatches = stub != null && marker.nextRelativePath === stub.nextRelativePath;
5749
5629
  if (!ownerMatches || !pathMatches) {
5750
5630
  return {
5751
5631
  ok: false,
5752
- error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path3} (existing owner=${marker.owner} path=${marker.nextRelativePath} vs planned owner=${stub?.owner ?? "?"} path=${stub?.nextRelativePath ?? "?"})`,
5753
- path: path3,
5632
+ error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path4} (existing owner=${marker.owner} path=${marker.nextRelativePath} vs planned owner=${stub?.owner ?? "?"} path=${stub?.nextRelativePath ?? "?"})`,
5633
+ path: path4,
5754
5634
  owner: stub?.owner
5755
5635
  };
5756
5636
  }
5757
5637
  }
5758
5638
  }
5759
5639
  const deletes = [];
5760
- for (const path3 of input.existingGeneratedPaths) {
5761
- const existing = input.existingFiles[path3];
5640
+ for (const path4 of input.existingGeneratedPaths) {
5641
+ const existing = input.existingFiles[path4];
5762
5642
  if (existing == null) continue;
5763
- if (!isProvenStaleDeletionCandidate(path3, existing)) {
5643
+ if (!isProvenStaleDeletionCandidate(path4, existing)) {
5764
5644
  continue;
5765
5645
  }
5766
- deletes.push(path3);
5646
+ deletes.push(path4);
5767
5647
  }
5768
5648
  deletes.sort(compareNames2);
5769
- const writes = [...desired.entries()].filter(([path3, content]) => input.existingFiles[path3] !== content).map(([path3, content]) => ({ path: path3, content })).sort((a, b) => compareNames2(a.path, b.path));
5649
+ const writes = [...desired.entries()].filter(([path4, content]) => input.existingFiles[path4] !== content).map(([path4, content]) => ({ path: path4, content })).sort((a, b) => compareNames2(a.path, b.path));
5770
5650
  return {
5771
5651
  ok: true,
5772
5652
  plan: {
@@ -5795,8 +5675,16 @@ async function checkoutParsesTypedStores(sandbox) {
5795
5675
  try {
5796
5676
  if (!await sandbox.fileExists(APP_PACKAGE_JSON_PATH)) return false;
5797
5677
  const pkg = JSON.parse(await sandbox.readFile(APP_PACKAGE_JSON_PATH));
5798
- const range = pkg.devDependencies?.[COMPOSE_PACKAGE] ?? pkg.dependencies?.[COMPOSE_PACKAGE];
5799
- if (!range) return false;
5678
+ return composeRangeParsesTypedStores(
5679
+ pkg.devDependencies?.[COMPOSE_PACKAGE] ?? pkg.dependencies?.[COMPOSE_PACKAGE]
5680
+ );
5681
+ } catch {
5682
+ return false;
5683
+ }
5684
+ }
5685
+ function composeRangeParsesTypedStores(range) {
5686
+ if (!range) return false;
5687
+ try {
5800
5688
  const lowest = import_semver.default.minVersion(range);
5801
5689
  return lowest != null && import_semver.default.gte(lowest, TYPED_STORES_MIN_COMPOSE_VERSION);
5802
5690
  } catch {
@@ -6028,6 +5916,50 @@ function rejectPseudoModuleEntries(entries, parentDir, label) {
6028
5916
  return dirs;
6029
5917
  }
6030
5918
 
5919
+ // ../../packages/lib/src/shared/module-composition.ts
5920
+ import { z as z7 } from "zod";
5921
+ var creationSourceBlankSchema = z7.object({
5922
+ type: z7.literal("blank")
5923
+ });
5924
+ var creationSourceBlueprintSchema = z7.object({
5925
+ type: z7.literal("blueprint"),
5926
+ /** Internal template/fork project id — keep template naming in identifiers. */
5927
+ blueprintProjectId: z7.string().uuid(),
5928
+ /**
5929
+ * `null`/absent means every feature Module. `[]` is refused: the platform
5930
+ * serialises it to an empty `NEXT_PUBLIC_STARDECK_ENABLED_MODULES`, which the
5931
+ * enablement parser reads back as "all" (`module-enablement.ts`) — the
5932
+ * opposite of what an empty selection means.
5933
+ */
5934
+ enabledModules: z7.array(moduleNameSchema).min(1, "Enable at least one Module").max(100).nullable().optional()
5935
+ });
5936
+ var creationSourceSchema = z7.discriminatedUnion("type", [
5937
+ creationSourceBlankSchema,
5938
+ creationSourceBlueprintSchema
5939
+ ]);
5940
+ var moduleInstallHandoffSchema = z7.object({
5941
+ moduleName: z7.string().min(1),
5942
+ homeProjectId: z7.string().uuid(),
5943
+ installedVersion: z7.string().min(1),
5944
+ baselineSha: z7.string().min(1),
5945
+ mounts: z7.array(
5946
+ z7.object({
5947
+ group: z7.string(),
5948
+ segment: z7.string()
5949
+ })
5950
+ ),
5951
+ integration: z7.array(z7.string()),
5952
+ workflows: z7.array(z7.string()),
5953
+ prunedEnhancements: z7.array(z7.string()),
5954
+ restoredEnhancements: z7.array(z7.string()).optional(),
5955
+ permissions: z7.array(
5956
+ z7.object({
5957
+ key: z7.string().min(1),
5958
+ description: z7.string().min(1)
5959
+ })
5960
+ ).optional()
5961
+ });
5962
+
6031
5963
  // ../../packages/lib/src/shared/schema-ops.ts
6032
5964
  import { z as z8 } from "zod";
6033
5965
  var columnSpecSchema = z8.object({
@@ -6241,11 +6173,11 @@ function countedCompositionContext(context) {
6241
6173
  counts.sandboxExecs += 1;
6242
6174
  return context.sandbox.exec(...args);
6243
6175
  },
6244
- readFile: (path3) => {
6176
+ readFile: (path4) => {
6245
6177
  counts.sandboxReads += 1;
6246
- return context.sandbox.readFile(path3);
6178
+ return context.sandbox.readFile(path4);
6247
6179
  },
6248
- fileExists: (path3) => context.sandbox.fileExists(path3),
6180
+ fileExists: (path4) => context.sandbox.fileExists(path4),
6249
6181
  fetchRemoteRef: (...args) => context.sandbox.fetchRemoteRef(...args),
6250
6182
  addDependencies: (specs) => context.sandbox.addDependencies(specs)
6251
6183
  }
@@ -6457,14 +6389,14 @@ async function discoverModuleRouteEntries(context, moduleName) {
6457
6389
  }
6458
6390
  return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
6459
6391
  }
6460
- async function writeSandboxFileAtomic(target, path3, content) {
6461
- const dir = dirnamePosix(path3);
6462
- const tmp = `${path3}.tmp.${target.runId}`;
6392
+ async function writeSandboxFileAtomic(target, path4, content) {
6393
+ const dir = dirnamePosix(path4);
6394
+ const tmp = `${path4}.tmp.${target.runId}`;
6463
6395
  const b64 = Buffer.from(content, "utf8").toString("base64");
6464
6396
  const cmd = [
6465
6397
  `mkdir -p ${shellQuote(dir)}`,
6466
6398
  `printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
6467
- `mv -f ${shellQuote(tmp)} ${shellQuote(path3)}`
6399
+ `mv -f ${shellQuote(tmp)} ${shellQuote(path4)}`
6468
6400
  ].join(" && ");
6469
6401
  const result = await target.sandbox.exec(cmd, { raiseOnError: false });
6470
6402
  if (result.exitCode !== 0) {
@@ -6479,14 +6411,14 @@ async function writeSandboxFileAtomic(target, path3, content) {
6479
6411
  }
6480
6412
  const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
6481
6413
  throw new Error(
6482
- `[ModuleRail] composition artifacts: failed to write ${redactSecrets(path3)}: ${redactSecrets(detail)}`
6414
+ `[ModuleRail] composition artifacts: failed to write ${redactSecrets(path4)}: ${redactSecrets(detail)}`
6483
6415
  );
6484
6416
  }
6485
6417
  }
6486
6418
  async function collectAbsentWriteParentDirs(context, writePaths) {
6487
6419
  const absent = /* @__PURE__ */ new Set();
6488
- for (const path3 of writePaths) {
6489
- let dir = dirnamePosix(path3);
6420
+ for (const path4 of writePaths) {
6421
+ let dir = dirnamePosix(path4);
6490
6422
  while (dir !== "." && dir !== "") {
6491
6423
  if (absent.has(dir)) {
6492
6424
  dir = dirnamePosix(dir);
@@ -6523,12 +6455,12 @@ async function removeAbsentWriteParentDirsOnRollback(context, dirsDeepestFirst)
6523
6455
  );
6524
6456
  }
6525
6457
  }
6526
- async function deleteSandboxFile(context, path3) {
6527
- const result = await context.sandbox.exec(`rm -f ${shellQuote(path3)}`, { raiseOnError: false });
6458
+ async function deleteSandboxFile(context, path4) {
6459
+ const result = await context.sandbox.exec(`rm -f ${shellQuote(path4)}`, { raiseOnError: false });
6528
6460
  if (result.exitCode !== 0) {
6529
6461
  const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
6530
6462
  throw new Error(
6531
- `[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path3)}: ${redactSecrets(detail)}`
6463
+ `[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path4)}: ${redactSecrets(detail)}`
6532
6464
  );
6533
6465
  }
6534
6466
  }
@@ -6595,37 +6527,37 @@ async function applyCompositionArtifactMutations(context, plan, existingFiles) {
6595
6527
  await writeSandboxFileAtomic(context, write.path, write.content);
6596
6528
  appliedWrites.push({ path: write.path, previous });
6597
6529
  }
6598
- for (const path3 of plan.deletes) {
6599
- const previous = existingFiles[path3];
6530
+ for (const path4 of plan.deletes) {
6531
+ const previous = existingFiles[path4];
6600
6532
  if (previous == null) continue;
6601
- await deleteSandboxFile(context, path3);
6602
- appliedDeletes.push({ path: path3, previous });
6533
+ await deleteSandboxFile(context, path4);
6534
+ appliedDeletes.push({ path: path4, previous });
6603
6535
  }
6604
6536
  } catch (error) {
6605
6537
  console.log(
6606
6538
  `[ModuleRail] runId=${context.runId} composition artifacts mid-apply failure; rolling back writes=${appliedWrites.length} deletes=${appliedDeletes.length} newDirs=${newlyCreatedParentDirs.length}`
6607
6539
  );
6608
- for (const { path: path3, previous } of [...appliedDeletes].reverse()) {
6540
+ for (const { path: path4, previous } of [...appliedDeletes].reverse()) {
6609
6541
  try {
6610
- await writeSandboxFileAtomic(context, path3, previous);
6542
+ await writeSandboxFileAtomic(context, path4, previous);
6611
6543
  } catch (rollbackError) {
6612
6544
  console.log(
6613
- `[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path3)}: ${redactSecrets(
6545
+ `[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path4)}: ${redactSecrets(
6614
6546
  rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
6615
6547
  )}`
6616
6548
  );
6617
6549
  }
6618
6550
  }
6619
- for (const { path: path3, previous } of [...appliedWrites].reverse()) {
6551
+ for (const { path: path4, previous } of [...appliedWrites].reverse()) {
6620
6552
  try {
6621
6553
  if (previous == null) {
6622
- await deleteSandboxFile(context, path3);
6554
+ await deleteSandboxFile(context, path4);
6623
6555
  } else {
6624
- await writeSandboxFileAtomic(context, path3, previous);
6556
+ await writeSandboxFileAtomic(context, path4, previous);
6625
6557
  }
6626
6558
  } catch (rollbackError) {
6627
6559
  console.log(
6628
- `[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path3)}: ${redactSecrets(
6560
+ `[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path4)}: ${redactSecrets(
6629
6561
  rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
6630
6562
  )}`
6631
6563
  );
@@ -6659,39 +6591,39 @@ async function listExistingGeneratedStubPaths(context) {
6659
6591
  COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
6660
6592
  );
6661
6593
  const presentPaths = [];
6662
- for (const path3 of candidates) {
6594
+ for (const path4 of candidates) {
6663
6595
  const presence = assertRealCompositionArtifactFilePresence(
6664
- path3,
6665
- kinds.get(path3) ?? "unreadable"
6596
+ path4,
6597
+ kinds.get(path4) ?? "unreadable"
6666
6598
  );
6667
- if (presence === "file") presentPaths.push(path3);
6599
+ if (presence === "file") presentPaths.push(path4);
6668
6600
  }
6669
6601
  let contents;
6670
6602
  try {
6671
6603
  contents = await readFilesMany(context, presentPaths);
6672
6604
  } catch (error) {
6673
- const path3 = readFailurePath(error);
6605
+ const path4 = readFailurePath(error);
6674
6606
  throw new Error(
6675
- `[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path3)}: ${redactSecrets(
6607
+ `[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path4)}: ${redactSecrets(
6676
6608
  error instanceof Error ? error.message : String(error)
6677
6609
  )}`
6678
6610
  );
6679
6611
  }
6680
6612
  const owned = [];
6681
- for (const path3 of presentPaths) {
6682
- const content = contents.get(path3);
6613
+ for (const path4 of presentPaths) {
6614
+ const content = contents.get(path4);
6683
6615
  const marker = parseCompositionArtifactMarker(content);
6684
- if (parseLegacyApiAdapter(path3, content) || parseLegacyRouteAdapter(path3, content)) {
6685
- owned.push(path3);
6616
+ if (parseLegacyApiAdapter(path4, content) || parseLegacyRouteAdapter(path4, content)) {
6617
+ owned.push(path4);
6686
6618
  continue;
6687
6619
  }
6688
6620
  if (marker == null) continue;
6689
- if (!markerCongruentWithPath(path3, marker)) {
6621
+ if (!markerCongruentWithPath(path4, marker)) {
6690
6622
  throw new Error(
6691
- `[ModuleRail] composition artifacts: incongruent marker at ${redactSecrets(path3)} (found ${marker.kind} marker on a path shaped for ${expectedMarkerKindForPath(path3) ?? "no"} artifacts; refusing to reconcile a swapped marker)`
6623
+ `[ModuleRail] composition artifacts: incongruent marker at ${redactSecrets(path4)} (found ${marker.kind} marker on a path shaped for ${expectedMarkerKindForPath(path4) ?? "no"} artifacts; refusing to reconcile a swapped marker)`
6692
6624
  );
6693
6625
  }
6694
- owned.push(path3);
6626
+ owned.push(path4);
6695
6627
  }
6696
6628
  return owned;
6697
6629
  }
@@ -6824,7 +6756,7 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
6824
6756
  { raiseOnError: false }
6825
6757
  );
6826
6758
  if (result.exitCode !== 0) {
6827
- for (const path3 of chunk) kinds.set(path3, "unreadable");
6759
+ for (const path4 of chunk) kinds.set(path4, "unreadable");
6828
6760
  continue;
6829
6761
  }
6830
6762
  const lines = result.output.split("\n").filter(Boolean);
@@ -6835,31 +6767,31 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
6835
6767
  }
6836
6768
  } else {
6837
6769
  for (const line of lines) {
6838
- const [rawKind, path3] = line.split(" ", 2);
6770
+ const [rawKind, path4] = line.split(" ", 2);
6839
6771
  const kind = rawKind?.replace(/^entry-kind:/, "");
6840
- if (path3 && chunk.includes(path3) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
6841
- kinds.set(path3, kind);
6772
+ if (path4 && chunk.includes(path4) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
6773
+ kinds.set(path4, kind);
6842
6774
  }
6843
6775
  }
6844
6776
  }
6845
- for (const path3 of chunk) {
6846
- if (!kinds.has(path3)) kinds.set(path3, "unreadable");
6777
+ for (const path4 of chunk) {
6778
+ if (!kinds.has(path4)) kinds.set(path4, "unreadable");
6847
6779
  }
6848
6780
  }
6849
6781
  return kinds;
6850
6782
  }
6851
- async function classifySandboxPathPresenceKind(context, path3, probeToken) {
6852
- return (await classifySandboxPathPresenceKindMany(context, [path3], probeToken)).get(path3);
6783
+ async function classifySandboxPathPresenceKind(context, path4, probeToken) {
6784
+ return (await classifySandboxPathPresenceKindMany(context, [path4], probeToken)).get(path4);
6853
6785
  }
6854
6786
  var LOCALE_JSON_KIND_PROBE_TOKEN = "locale-json-kind";
6855
- function assertRealLocaleJsonFilePresence(path3, presence) {
6787
+ function assertRealLocaleJsonFilePresence(path4, presence) {
6856
6788
  if (presence === "file") return;
6857
6789
  if (presence === "absent") {
6858
- throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path3}`);
6790
+ throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path4}`);
6859
6791
  }
6860
- const presenceError = formatConventionalEntryPresenceError(path3, presence);
6792
+ const presenceError = formatConventionalEntryPresenceError(path4, presence);
6861
6793
  throw new Error(
6862
- `[ModuleRail] composition artifacts: ${presenceError ?? `${path3} is not a real locale JSON file`}`
6794
+ `[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real locale JSON file`}`
6863
6795
  );
6864
6796
  }
6865
6797
  function splitTaggedLine(line) {
@@ -6934,11 +6866,11 @@ async function listLocaleBasenamesIfDirMany(context, dirs) {
6934
6866
  }
6935
6867
  async function readFilesMany(context, paths) {
6936
6868
  const sortedPaths = [...paths].sort((a, b) => a.localeCompare(b));
6937
- const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path3) => {
6869
+ const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path4) => {
6938
6870
  try {
6939
- return { path: path3, value: await context.sandbox.readFile(path3) };
6871
+ return { path: path4, value: await context.sandbox.readFile(path4) };
6940
6872
  } catch (error) {
6941
- return { path: path3, error };
6873
+ return { path: path4, error };
6942
6874
  }
6943
6875
  });
6944
6876
  const files = /* @__PURE__ */ new Map();
@@ -6961,58 +6893,58 @@ function readFailurePath(error) {
6961
6893
  var CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN = "conventional-entry-kind";
6962
6894
  var SERVER_REGISTRATIONS_FILE = "server/registrations.ts";
6963
6895
  var COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN = "composition-artifact-kind";
6964
- async function classifyCompositionArtifactPathKind(context, path3) {
6965
- return classifySandboxPathPresenceKind(context, path3, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
6896
+ async function classifyCompositionArtifactPathKind(context, path4) {
6897
+ return classifySandboxPathPresenceKind(context, path4, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
6966
6898
  }
6967
- function assertRealCompositionArtifactFilePresence(path3, presence) {
6899
+ function assertRealCompositionArtifactFilePresence(path4, presence) {
6968
6900
  if (presence === "file") return "file";
6969
6901
  if (presence === "absent") return "absent";
6970
- const presenceError = formatConventionalEntryPresenceError(path3, presence);
6902
+ const presenceError = formatConventionalEntryPresenceError(path4, presence);
6971
6903
  throw new Error(
6972
- `[ModuleRail] composition artifacts: ${presenceError ?? `${path3} is not a real composition artifact file`}`
6904
+ `[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real composition artifact file`}`
6973
6905
  );
6974
6906
  }
6975
- async function readConventionalEntryIfRealFile(path3, kind, presences, files) {
6976
- const presence = presences.get(path3) ?? "unreadable";
6977
- const presenceError = formatConventionalEntryPresenceError(path3, presence);
6907
+ async function readConventionalEntryIfRealFile(path4, kind, presences, files) {
6908
+ const presence = presences.get(path4) ?? "unreadable";
6909
+ const presenceError = formatConventionalEntryPresenceError(path4, presence);
6978
6910
  if (presenceError) {
6979
6911
  throw new Error(`[ModuleRail] composition artifacts: ${presenceError}`);
6980
6912
  }
6981
6913
  if (presence === "absent") {
6982
6914
  return null;
6983
6915
  }
6984
- const source = files.get(path3);
6916
+ const source = files.get(path4);
6985
6917
  switch (kind) {
6986
6918
  case "contributions":
6987
- assertContributionsExport(source, path3);
6988
- assertClientSafeConventionalEntryImports(source, path3, "contributions");
6919
+ assertContributionsExport(source, path4);
6920
+ assertClientSafeConventionalEntryImports(source, path4, "contributions");
6989
6921
  break;
6990
6922
  case "slotCatalogs":
6991
- assertSlotCatalogsExport(source, path3);
6992
- assertClientSafeConventionalEntryImports(source, path3, "slotCatalogs");
6923
+ assertSlotCatalogsExport(source, path4);
6924
+ assertClientSafeConventionalEntryImports(source, path4, "slotCatalogs");
6993
6925
  break;
6994
6926
  case "initializeEnhancement":
6995
- assertInitializeEnhancementExport(source, path3);
6927
+ assertInitializeEnhancementExport(source, path4);
6996
6928
  break;
6997
6929
  }
6998
6930
  return source;
6999
6931
  }
7000
6932
  function hasRealServerRegistrations(modulePath, presences) {
7001
- const path3 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
7002
- return assertRealCompositionArtifactFilePresence(path3, presences.get(path3) ?? "unreadable") === "file";
6933
+ const path4 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
6934
+ return assertRealCompositionArtifactFilePresence(path4, presences.get(path4) ?? "unreadable") === "file";
7003
6935
  }
7004
- async function readJsonObjectFile(path3, files) {
7005
- const raw = files.get(path3);
6936
+ async function readJsonObjectFile(path4, files) {
6937
+ const raw = files.get(path4);
7006
6938
  let parsed;
7007
6939
  try {
7008
6940
  parsed = JSON.parse(raw);
7009
6941
  } catch (error) {
7010
6942
  throw new Error(
7011
- `[ModuleRail] composition artifacts: invalid JSON at ${path3}: ${error instanceof Error ? error.message : String(error)}`
6943
+ `[ModuleRail] composition artifacts: invalid JSON at ${path4}: ${error instanceof Error ? error.message : String(error)}`
7012
6944
  );
7013
6945
  }
7014
6946
  if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
7015
- throw new Error(`[ModuleRail] composition artifacts: ${path3} root must be an object`);
6947
+ throw new Error(`[ModuleRail] composition artifacts: ${path4} root must be an object`);
7016
6948
  }
7017
6949
  return parsed;
7018
6950
  }
@@ -7061,11 +6993,11 @@ async function collectCompositionEntryFacts(context, registry) {
7061
6993
  classifySandboxPathPresenceKindMany(context, localePaths, LOCALE_JSON_KIND_PROBE_TOKEN)
7062
6994
  ]);
7063
6995
  const presentConventionalPaths = [];
7064
- for (const path3 of conventionalPaths) {
7065
- const presence = conventionalPresences.get(path3) ?? "unreadable";
7066
- if (presence === "file") presentConventionalPaths.push(path3);
6996
+ for (const path4 of conventionalPaths) {
6997
+ const presence = conventionalPresences.get(path4) ?? "unreadable";
6998
+ if (presence === "file") presentConventionalPaths.push(path4);
7067
6999
  }
7068
- const presentLocalePaths = localePaths.filter((path3) => localePresences.get(path3) === "file");
7000
+ const presentLocalePaths = localePaths.filter((path4) => localePresences.get(path4) === "file");
7069
7001
  const files = await readFilesMany(context, [...presentConventionalPaths, ...presentLocalePaths]);
7070
7002
  const substrateLocales = [];
7071
7003
  for (const locale of substrateNames) {
@@ -7163,8 +7095,8 @@ async function collectCompositionEntryFacts(context, registry) {
7163
7095
  });
7164
7096
  if (rootLocales.length > 0) {
7165
7097
  for (const locale of rootLocales) {
7166
- const path3 = `${modulePath}/i18n/${locale}.json`;
7167
- assertRealLocaleJsonFilePresence(path3, localePresences.get(path3) ?? "unreadable");
7098
+ const path4 = `${modulePath}/i18n/${locale}.json`;
7099
+ assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
7168
7100
  }
7169
7101
  const enMessages = await readJsonObjectFile(`${modulePath}/i18n/en.json`, files);
7170
7102
  const localeMessages = { en: enMessages };
@@ -7188,8 +7120,8 @@ async function collectCompositionEntryFacts(context, registry) {
7188
7120
  if (!enh.active || enh.locales.length === 0) continue;
7189
7121
  const peerDir = `${modulePath}/enhancements/${enh.peer}`;
7190
7122
  for (const locale of enh.locales) {
7191
- const path3 = `${peerDir}/i18n/${locale}.json`;
7192
- assertRealLocaleJsonFilePresence(path3, localePresences.get(path3) ?? "unreadable");
7123
+ const path4 = `${peerDir}/i18n/${locale}.json`;
7124
+ assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
7193
7125
  }
7194
7126
  const enMessages = await readJsonObjectFile(`${peerDir}/i18n/en.json`, files);
7195
7127
  const localeMessages = { en: enMessages };
@@ -7390,16 +7322,16 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7390
7322
  for (const stub of probePlan.plan.routeStubs) candidatePaths.add(stub.path);
7391
7323
  const existingFiles = {};
7392
7324
  const remainingCandidatePaths = [];
7393
- for (const path3 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
7394
- if (Object.prototype.hasOwnProperty.call(existingAppPages, path3)) {
7395
- existingFiles[path3] = existingAppPages[path3];
7325
+ for (const path4 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
7326
+ if (Object.prototype.hasOwnProperty.call(existingAppPages, path4)) {
7327
+ existingFiles[path4] = existingAppPages[path4];
7396
7328
  continue;
7397
7329
  }
7398
- if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path3)) {
7399
- existingFiles[path3] = existingAppRoutes[path3];
7330
+ if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path4)) {
7331
+ existingFiles[path4] = existingAppRoutes[path4];
7400
7332
  continue;
7401
7333
  }
7402
- remainingCandidatePaths.push(path3);
7334
+ remainingCandidatePaths.push(path4);
7403
7335
  }
7404
7336
  const candidateKinds = await classifySandboxPathPresenceKindMany(
7405
7337
  context,
@@ -7407,12 +7339,12 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7407
7339
  COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
7408
7340
  );
7409
7341
  const presentCandidatePaths = [];
7410
- for (const path3 of remainingCandidatePaths) {
7342
+ for (const path4 of remainingCandidatePaths) {
7411
7343
  const presence = assertRealCompositionArtifactFilePresence(
7412
- path3,
7413
- candidateKinds.get(path3) ?? "unreadable"
7344
+ path4,
7345
+ candidateKinds.get(path4) ?? "unreadable"
7414
7346
  );
7415
- if (presence === "file") presentCandidatePaths.push(path3);
7347
+ if (presence === "file") presentCandidatePaths.push(path4);
7416
7348
  }
7417
7349
  try {
7418
7350
  Object.assign(
@@ -7420,9 +7352,9 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7420
7352
  Object.fromEntries(await readFilesMany(context, presentCandidatePaths))
7421
7353
  );
7422
7354
  } catch (error) {
7423
- const path3 = readFailurePath(error);
7355
+ const path4 = readFailurePath(error);
7424
7356
  throw new Error(
7425
- `[ModuleRail] composition artifacts: cannot read ${redactSecrets(path3)} during preflight: ${redactSecrets(
7357
+ `[ModuleRail] composition artifacts: cannot read ${redactSecrets(path4)} during preflight: ${redactSecrets(
7426
7358
  error instanceof Error ? error.message : String(error)
7427
7359
  )}`
7428
7360
  );
@@ -7491,14 +7423,142 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7491
7423
  var HOME_I18N_SUBSTRATE_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/substrate`;
7492
7424
  var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
7493
7425
 
7426
+ // src/index.ts
7427
+ import ts6 from "typescript";
7428
+
7429
+ // src/module-boundaries.ts
7430
+ import fs2 from "fs";
7431
+ import path2 from "path";
7432
+ import ts5 from "typescript";
7433
+ var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
7434
+ var RESOLVE_SUFFIXES = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
7435
+ var DATA_FILE = /\.(?:sql|json)$/;
7436
+ function dataPathsIn(fileName, source) {
7437
+ if (!/\.(?:sql|json)["'`]/.test(source)) return [];
7438
+ const paths = [];
7439
+ const visit = (node) => {
7440
+ if ((ts5.isStringLiteralLike(node) || ts5.isTemplateTail(node)) && DATA_FILE.test(node.text)) {
7441
+ paths.push(node.text);
7442
+ }
7443
+ ts5.forEachChild(node, visit);
7444
+ };
7445
+ visit(ts5.createSourceFile(fileName, source, ts5.ScriptTarget.Latest, false));
7446
+ return paths;
7447
+ }
7448
+ function scanModule(modulesDir, module) {
7449
+ const scanned = /* @__PURE__ */ new Map();
7450
+ const moduleDir = path2.join(modulesDir, module);
7451
+ if (!fs2.existsSync(moduleDir)) return scanned;
7452
+ const files = fs2.readdirSync(moduleDir, { recursive: true, encoding: "utf8" }).filter((relative) => SOURCE_FILE.test(relative) && !relative.includes("node_modules"));
7453
+ for (const relative of files) {
7454
+ const absolute = path2.join(moduleDir, relative);
7455
+ if (!fs2.statSync(absolute).isFile()) continue;
7456
+ const source = fs2.readFileSync(absolute, "utf8");
7457
+ const specifiers = ts5.preProcessFile(source, true, true).importedFiles.map((f) => f.fileName);
7458
+ const dataPaths = dataPathsIn(absolute, source);
7459
+ const targets = [...specifiers, ...dataPaths].flatMap((spec) => {
7460
+ if (spec.startsWith("@/modules/"))
7461
+ return [path2.join(modulesDir, spec.slice("@/modules/".length))];
7462
+ if (spec.startsWith("./") || spec.startsWith("../")) {
7463
+ return [path2.resolve(path2.dirname(absolute), spec)];
7464
+ }
7465
+ return [];
7466
+ });
7467
+ scanned.set(absolute, {
7468
+ module,
7469
+ relative: relative.split(path2.sep).join("/"),
7470
+ targets,
7471
+ dataPaths
7472
+ });
7473
+ }
7474
+ return scanned;
7475
+ }
7476
+ function findModuleBoundaryViolations(modulesDir, peersByModule, { pruning = false } = {}) {
7477
+ const files = /* @__PURE__ */ new Map();
7478
+ for (const [module, peers] of Object.entries(peersByModule)) {
7479
+ if (peers.length === 0) continue;
7480
+ for (const [absolute, file] of scanModule(modulesDir, module)) files.set(absolute, file);
7481
+ }
7482
+ const exempt = (file, peer) => (pruning ? peersByModule[file.module] ?? [] : [peer]).some(
7483
+ (q) => file.relative.startsWith(`enhancements/${q}/`)
7484
+ );
7485
+ const goneWith = (target, peer) => {
7486
+ const relative = path2.relative(modulesDir, target).split(path2.sep).join("/");
7487
+ return relative === peer || relative.startsWith(`${peer}/`) || new RegExp(`^[^/]+/enhancements/${peer}(/|$)`).test(relative);
7488
+ };
7489
+ const tainted = /* @__PURE__ */ new Map();
7490
+ const queue = [];
7491
+ const taint = (absolute, peer) => {
7492
+ const peers = tainted.get(absolute) ?? /* @__PURE__ */ new Set();
7493
+ if (peers.has(peer)) return;
7494
+ peers.add(peer);
7495
+ tainted.set(absolute, peers);
7496
+ queue.push([absolute, peer]);
7497
+ };
7498
+ const importers = /* @__PURE__ */ new Map();
7499
+ for (const [absolute, file] of files) {
7500
+ for (const target of file.targets) {
7501
+ const hit = RESOLVE_SUFFIXES.map((suffix) => target + suffix).find((c) => files.has(c));
7502
+ if (hit) importers.set(hit, [...importers.get(hit) ?? [], absolute]);
7503
+ }
7504
+ for (const peer of peersByModule[file.module] ?? []) {
7505
+ if (exempt(file, peer)) continue;
7506
+ if (file.targets.some((target) => goneWith(target, peer)) || file.dataPaths.some(
7507
+ (p) => p.startsWith(`modules/${peer}/`) || p.includes(`/modules/${peer}/`)
7508
+ )) {
7509
+ taint(absolute, peer);
7510
+ }
7511
+ }
7512
+ }
7513
+ for (let next = queue.shift(); next; next = queue.shift()) {
7514
+ const [target, peer] = next;
7515
+ for (const importer of importers.get(target) ?? []) {
7516
+ const file = files.get(importer);
7517
+ if ((peersByModule[file.module] ?? []).includes(peer) && !exempt(file, peer)) {
7518
+ taint(importer, peer);
7519
+ }
7520
+ }
7521
+ }
7522
+ const violations = [];
7523
+ for (const [absolute, peers] of tainted) {
7524
+ const file = files.get(absolute);
7525
+ for (const peer of peers) {
7526
+ violations.push({ file: `${file.module}/${file.relative}`, module: file.module, peer });
7527
+ }
7528
+ }
7529
+ return violations.sort((a, b) => a.file.localeCompare(b.file) || a.peer.localeCompare(b.peer));
7530
+ }
7531
+ function findOptionalPeerViolations(modulesDir) {
7532
+ if (!fs2.existsSync(modulesDir)) return [];
7533
+ const manifests = {};
7534
+ for (const name of fs2.readdirSync(modulesDir)) {
7535
+ const manifestPath = path2.join(modulesDir, name, "module.json");
7536
+ if (!fs2.existsSync(manifestPath)) continue;
7537
+ manifests[name] = JSON.parse(fs2.readFileSync(manifestPath, "utf8"));
7538
+ }
7539
+ const features = Object.keys(manifests).filter((name) => manifests[name].kind === "feature");
7540
+ const peersByModule = {};
7541
+ for (const [name, manifest] of Object.entries(manifests)) {
7542
+ const uses = new Set(manifest.uses ?? []);
7543
+ peersByModule[name] = features.filter((peer) => peer !== name && !uses.has(peer));
7544
+ }
7545
+ return findModuleBoundaryViolations(modulesDir, peersByModule);
7546
+ }
7547
+ function formatBoundaryViolations(modulesDirLabel, violations) {
7548
+ const files = new Set(violations.map((v) => v.file)).size;
7549
+ return `${files} Module file(s) import a peer Module that may be absent (not in the Module's \`uses\`, or excluded by --enabled).
7550
+ \`next build\` type-checks them after the peer is gone. For each line, move the file into ${modulesDirLabel}/<module>/enhancements/<peer>/ or declare <peer> in the module's module.json \`uses\` (and enable it).
7551
+ ` + violations.map((v) => `${modulesDirLabel}/${v.file}: ${v.peer}`).join("\n");
7552
+ }
7553
+
7494
7554
  // src/index.ts
7495
7555
  var MODULES_DIR2 = MODULES_SANDBOX_DIR;
7496
7556
  function readComposeInputs(cwd) {
7497
- const file = path2.join(cwd, COMPOSE_INPUTS_PATH);
7498
- if (!fs2.existsSync(file)) return { connectedStores: [], installs: {} };
7557
+ const file = path3.join(cwd, COMPOSE_INPUTS_PATH);
7558
+ if (!fs3.existsSync(file)) return { connectedStores: [], installs: {} };
7499
7559
  let raw;
7500
7560
  try {
7501
- raw = JSON.parse(fs2.readFileSync(file, "utf8"));
7561
+ raw = JSON.parse(fs3.readFileSync(file, "utf8"));
7502
7562
  } catch (error) {
7503
7563
  throw new Error(
7504
7564
  `[compose] ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
@@ -7540,18 +7600,18 @@ function stubGitignoreEntries(artifactPaths) {
7540
7600
  function writeStubGitignore(cwd, artifactPaths) {
7541
7601
  const entries = stubGitignoreEntries(artifactPaths);
7542
7602
  const content = [STUB_GITIGNORE_HEADER, ...entries].join("\n") + "\n";
7543
- const file = path2.join(cwd, STUB_GITIGNORE_PATH);
7544
- const existing = fs2.existsSync(file) ? fs2.readFileSync(file, "utf8") : null;
7603
+ const file = path3.join(cwd, STUB_GITIGNORE_PATH);
7604
+ const existing = fs3.existsSync(file) ? fs3.readFileSync(file, "utf8") : null;
7545
7605
  if (existing !== content) {
7546
- fs2.mkdirSync(path2.dirname(file), { recursive: true });
7547
- fs2.writeFileSync(file, content);
7606
+ fs3.mkdirSync(path3.dirname(file), { recursive: true });
7607
+ fs3.writeFileSync(file, content);
7548
7608
  }
7549
7609
  return entries.length;
7550
7610
  }
7551
7611
  function assertStubGitignoreOwned(cwd) {
7552
- const file = path2.join(cwd, STUB_GITIGNORE_PATH);
7553
- if (!fs2.existsSync(file)) return;
7554
- const firstLine = fs2.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
7612
+ const file = path3.join(cwd, STUB_GITIGNORE_PATH);
7613
+ if (!fs3.existsSync(file)) return;
7614
+ const firstLine = fs3.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
7555
7615
  if (firstLine !== STUB_GITIGNORE_HEADER) {
7556
7616
  throw new Error(
7557
7617
  `[compose] refusing to overwrite ${STUB_GITIGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
@@ -7559,8 +7619,8 @@ function assertStubGitignoreOwned(cwd) {
7559
7619
  }
7560
7620
  }
7561
7621
  function assertAppRoot(cwd) {
7562
- const appPath = path2.join(cwd, APP_PACKAGE_JSON);
7563
- if (!fs2.existsSync(appPath)) {
7622
+ const appPath = path3.join(cwd, APP_PACKAGE_JSON);
7623
+ if (!fs3.existsSync(appPath)) {
7564
7624
  throw new Error(
7565
7625
  `[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
7566
7626
  );
@@ -7606,9 +7666,9 @@ ${porcelain.trim()}`
7606
7666
  }
7607
7667
  var APP_SRC_DIR = "apps/web/src";
7608
7668
  function walkSources(dir, skip, out) {
7609
- if (!fs2.existsSync(dir)) return;
7610
- for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
7611
- const full = path2.join(dir, entry.name);
7669
+ if (!fs3.existsSync(dir)) return;
7670
+ for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
7671
+ const full = path3.join(dir, entry.name);
7612
7672
  if (full === skip) continue;
7613
7673
  if (entry.isDirectory()) walkSources(full, skip, out);
7614
7674
  else if (/\.[cm]?[jt]sx?$/.test(entry.name)) out.push(full);
@@ -7617,13 +7677,13 @@ function walkSources(dir, skip, out) {
7617
7677
  function assertNoAppLayerImporters(cwd, excluded) {
7618
7678
  if (excluded.length === 0) return;
7619
7679
  const files = [];
7620
- walkSources(path2.join(cwd, APP_SRC_DIR), path2.join(cwd, MODULES_DIR2), files);
7680
+ walkSources(path3.join(cwd, APP_SRC_DIR), path3.join(cwd, MODULES_DIR2), files);
7621
7681
  const offenders = [];
7622
7682
  for (const file of files) {
7623
- const source = fs2.readFileSync(file, "utf8");
7683
+ const source = fs3.readFileSync(file, "utf8");
7624
7684
  for (const name of excluded) {
7625
7685
  if (new RegExp(`@/modules/${name}(?=["'\`/])`).test(source)) {
7626
- offenders.push(`${path2.relative(cwd, file)}: ${name}`);
7686
+ offenders.push(`${path3.relative(cwd, file)}: ${name}`);
7627
7687
  }
7628
7688
  }
7629
7689
  }
@@ -7635,9 +7695,44 @@ function assertNoAppLayerImporters(cwd, excluded) {
7635
7695
  );
7636
7696
  }
7637
7697
  }
7698
+ var TSCONFIG_PATH = "apps/web/tsconfig.json";
7699
+ var BUILD_TSCONFIG_PATH = "apps/web/tsconfig.build.gen.json";
7700
+ var BUILD_TSCONFIG_HEADER = "// Generated by stardeck-compose \u2014 the tsconfig `next build` type-checks. Do not edit or commit.";
7701
+ function writeBuildTsconfig(cwd, excluded) {
7702
+ const baseFile = path3.join(cwd, TSCONFIG_PATH);
7703
+ if (!fs3.existsSync(path3.join(cwd, MODULES_DIR2)) || !fs3.existsSync(baseFile)) return;
7704
+ const base = ts6.readConfigFile(baseFile, ts6.sys.readFile);
7705
+ if (base.error) {
7706
+ throw new Error(
7707
+ `[compose] cannot read ${TSCONFIG_PATH}: ${ts6.flattenDiagnosticMessageText(base.error.messageText, "\n")}`
7708
+ );
7709
+ }
7710
+ const baseExclude = base.config?.exclude ?? ["node_modules"];
7711
+ const modulesDir = path3.posix.relative("apps/web", MODULES_DIR2);
7712
+ const config = {
7713
+ extends: "./tsconfig.json",
7714
+ exclude: [
7715
+ ...baseExclude,
7716
+ ...excluded.flatMap((name) => [
7717
+ `${modulesDir}/${name}`,
7718
+ `${modulesDir}/*/enhancements/${name}`
7719
+ ])
7720
+ ]
7721
+ };
7722
+ const content = `${BUILD_TSCONFIG_HEADER}
7723
+ ${JSON.stringify(config, null, 2)}
7724
+ `;
7725
+ const file = path3.join(cwd, BUILD_TSCONFIG_PATH);
7726
+ if (!fs3.existsSync(file) || fs3.readFileSync(file, "utf8") !== content) {
7727
+ fs3.writeFileSync(file, content);
7728
+ }
7729
+ console.log(
7730
+ `[compose] ${BUILD_TSCONFIG_PATH} excludes ${excluded.length} Module(s): ${excluded.join(",") || "none"}`
7731
+ );
7732
+ }
7638
7733
  function removeDir(absolute) {
7639
- if (!fs2.existsSync(absolute)) return false;
7640
- fs2.rmSync(absolute, { recursive: true, force: true });
7734
+ if (!fs3.existsSync(absolute)) return false;
7735
+ fs3.rmSync(absolute, { recursive: true, force: true });
7641
7736
  return true;
7642
7737
  }
7643
7738
  async function compose(options) {
@@ -7664,6 +7759,16 @@ async function compose(options) {
7664
7759
  dryRun: true
7665
7760
  });
7666
7761
  assertNoAppLayerImporters(options.cwd, preview.excludedModules);
7762
+ const violations = findModuleBoundaryViolations(
7763
+ path3.join(options.cwd, MODULES_DIR2),
7764
+ Object.fromEntries(preview.composedModules.map((name) => [name, preview.excludedModules])),
7765
+ { pruning: true }
7766
+ );
7767
+ if (violations.length > 0) {
7768
+ throw new Error(
7769
+ `[compose] refusing --prune: ${formatBoundaryViolations(MODULES_DIR2, violations)}`
7770
+ );
7771
+ }
7667
7772
  }
7668
7773
  const result = await reconcileCompositionArtifacts(context, {
7669
7774
  enabledModulesRaw: options.enabled,
@@ -7672,14 +7777,15 @@ async function compose(options) {
7672
7777
  skipComposeInputs: true
7673
7778
  });
7674
7779
  const gitignoreEntries = options.enabled ? null : writeStubGitignore(options.cwd, result.artifactPaths);
7780
+ writeBuildTsconfig(options.cwd, result.excludedModules);
7675
7781
  let pruned = 0;
7676
7782
  if (options.prune) {
7677
7783
  for (const name of result.excludedModules) {
7678
- if (removeDir(path2.join(options.cwd, MODULES_DIR2, name))) pruned++;
7784
+ if (removeDir(path3.join(options.cwd, MODULES_DIR2, name))) pruned++;
7679
7785
  }
7680
7786
  for (const owner of result.composedModules) {
7681
7787
  for (const peer of result.excludedModules) {
7682
- if (removeDir(path2.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
7788
+ if (removeDir(path3.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
7683
7789
  }
7684
7790
  }
7685
7791
  }
@@ -7693,9 +7799,13 @@ async function compose(options) {
7693
7799
  };
7694
7800
  }
7695
7801
  export {
7802
+ BUILD_TSCONFIG_PATH,
7696
7803
  COMPOSE_INPUTS_PATH,
7697
7804
  assertSupportedTypescript,
7698
7805
  compose,
7699
7806
  escapeGitignore,
7807
+ findModuleBoundaryViolations,
7808
+ findOptionalPeerViolations,
7809
+ formatBoundaryViolations,
7700
7810
  readComposeInputs
7701
7811
  };