@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.js CHANGED
@@ -2027,16 +2027,20 @@ var require_semver2 = __commonJS({
2027
2027
  // src/index.ts
2028
2028
  var index_exports = {};
2029
2029
  __export(index_exports, {
2030
+ BUILD_TSCONFIG_PATH: () => BUILD_TSCONFIG_PATH,
2030
2031
  COMPOSE_INPUTS_PATH: () => COMPOSE_INPUTS_PATH,
2031
2032
  assertSupportedTypescript: () => assertSupportedTypescript,
2032
2033
  compose: () => compose,
2033
2034
  escapeGitignore: () => escapeGitignore,
2035
+ findModuleBoundaryViolations: () => findModuleBoundaryViolations,
2036
+ findOptionalPeerViolations: () => findOptionalPeerViolations,
2037
+ formatBoundaryViolations: () => formatBoundaryViolations,
2034
2038
  readComposeInputs: () => readComposeInputs
2035
2039
  });
2036
2040
  module.exports = __toCommonJS(index_exports);
2037
2041
  var import_node_child_process2 = require("child_process");
2038
- var import_node_fs2 = __toESM(require("fs"));
2039
- var import_node_path2 = __toESM(require("path"));
2042
+ var import_node_fs3 = __toESM(require("fs"));
2043
+ var import_node_path3 = __toESM(require("path"));
2040
2044
 
2041
2045
  // ../../packages/lib/src/server/module-rail/local-fs-sandbox.ts
2042
2046
  var import_node_child_process = require("child_process");
@@ -2095,11 +2099,54 @@ function shellQuote(value) {
2095
2099
  return `'${value.replace(/'/g, `'\\''`)}'`;
2096
2100
  }
2097
2101
 
2098
- // ../../packages/lib/src/shared/module-composition.ts
2099
- var import_zod6 = require("zod");
2102
+ // ../../packages/lib/src/shared/compose-inputs.ts
2103
+ var import_zod = require("zod");
2104
+ var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
2105
+ var composeInputsSchema = import_zod.z.strictObject({
2106
+ connectedStores: import_zod.z.array(
2107
+ import_zod.z.strictObject({
2108
+ storeId: import_zod.z.string().min(1),
2109
+ slug: import_zod.z.string().min(1),
2110
+ // A row with an empty binding key must not fail a deploy: the rail
2111
+ // treats falsy as "no binding key", so normalise rather than reject.
2112
+ bindingKey: import_zod.z.string().nullish().transform((value) => value || null),
2113
+ accessLevel: import_zod.z.enum(["read", "write", "admin"]),
2114
+ // Defaulted so a file written before the platform emitted store types
2115
+ // (database rows only) still parses. The platform starts writing
2116
+ // storage rows, with `type` set, once forks run a compose that reads it.
2117
+ type: import_zod.z.enum(["database", "storage"]).default("database")
2118
+ })
2119
+ ).default([]),
2120
+ installs: import_zod.z.record(
2121
+ import_zod.z.string(),
2122
+ import_zod.z.strictObject({
2123
+ installedVersion: import_zod.z.string().min(1),
2124
+ datastoreBindings: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).nullish()
2125
+ })
2126
+ ).default({})
2127
+ });
2128
+ var STUB_IGNORE_PATH = "apps/web/src/app/.gitignore";
2129
+ var STUB_IGNORE_HEADER = "# Generated by stardeck-compose \u2014 Module-rail stubs are build outputs; do not edit or commit them.";
2130
+ function escapeStubIgnorePattern(pattern) {
2131
+ const escaped = pattern.replace(/[\\[\]*?]/g, (char) => `\\${char}`);
2132
+ return /^[#!]/.test(escaped) ? `\\${escaped}` : escaped;
2133
+ }
2134
+ function buildStubIgnore(artifactPaths) {
2135
+ const prefix = "apps/web/src/app/";
2136
+ const entries = artifactPaths.filter((artifact) => artifact.startsWith(prefix)).map((artifact) => escapeStubIgnorePattern(`/${artifact.slice(prefix.length)}`)).sort();
2137
+ return [STUB_IGNORE_HEADER, ...entries].join("\n") + "\n";
2138
+ }
2139
+
2140
+ // ../../packages/lib/src/shared/module-enablement.ts
2141
+ function isModuleEnabledByList(raw, name, kindOf) {
2142
+ if (!raw) return true;
2143
+ const kind = kindOf(name);
2144
+ if (kind != null && kind !== "feature") return true;
2145
+ return raw.split(",").includes(name);
2146
+ }
2100
2147
 
2101
2148
  // ../../packages/module-contract/src/module-manifest.ts
2102
- var import_zod = require("zod");
2149
+ var import_zod2 = require("zod");
2103
2150
 
2104
2151
  // ../../packages/module-contract/src/slug.ts
2105
2152
  function validateSlug(slug) {
@@ -2134,30 +2181,30 @@ var ROUTE_GROUP_REGEX = /^\([a-z0-9-]+\)$/;
2134
2181
  var ENDPOINT_STATIC_SEGMENT_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2135
2182
  var ENDPOINT_PARAM_NAME_REGEX = /^[a-z][a-z0-9_-]*$/;
2136
2183
  var ENDPOINT_METHODS = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
2137
- var moduleNameSchema = import_zod.z.string().max(40).regex(KEBAB_SLUG_REGEX, "must be a kebab-case slug (lowercase letters, digits, hyphens)");
2184
+ var moduleNameSchema = import_zod2.z.string().max(40).regex(KEBAB_SLUG_REGEX, "must be a kebab-case slug (lowercase letters, digits, hyphens)");
2138
2185
  function isValidModuleName(value) {
2139
2186
  return moduleNameSchema.safeParse(value).success;
2140
2187
  }
2141
- var projectSlugSchema = import_zod.z.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
2142
- var moduleKindSchema = import_zod.z.enum(["feature", "library", "contract"]);
2143
- var endpointMethodSchema = import_zod.z.enum(ENDPOINT_METHODS);
2144
- function endpointPathValidationError(path3) {
2145
- if (!path3.startsWith("/api/")) {
2188
+ var projectSlugSchema = import_zod2.z.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
2189
+ var moduleKindSchema = import_zod2.z.enum(["feature", "library", "contract"]);
2190
+ var endpointMethodSchema = import_zod2.z.enum(ENDPOINT_METHODS);
2191
+ function endpointPathValidationError(path4) {
2192
+ if (!path4.startsWith("/api/")) {
2146
2193
  return 'must begin with "/api/"';
2147
2194
  }
2148
- if (path3.endsWith("/")) {
2195
+ if (path4.endsWith("/")) {
2149
2196
  return "must not end with a trailing slash";
2150
2197
  }
2151
- if (path3.includes("?") || path3.includes("#")) {
2198
+ if (path4.includes("?") || path4.includes("#")) {
2152
2199
  return "must not include query or hash";
2153
2200
  }
2154
- if (path3.includes("\\")) {
2201
+ if (path4.includes("\\")) {
2155
2202
  return "must not include backslashes";
2156
2203
  }
2157
- if (path3.includes("%")) {
2204
+ if (path4.includes("%")) {
2158
2205
  return "must not include percent-encoded segments";
2159
2206
  }
2160
- const parts = path3.split("/");
2207
+ const parts = path4.split("/");
2161
2208
  if (parts.length < 3 || parts[0] !== "" || parts[1] !== "api") {
2162
2209
  return 'must begin with "/api/"';
2163
2210
  }
@@ -2207,12 +2254,12 @@ function endpointPathValidationError(path3) {
2207
2254
  }
2208
2255
  return null;
2209
2256
  }
2210
- function normalizeEndpointPathForCollision(path3) {
2211
- const error = endpointPathValidationError(path3);
2257
+ function normalizeEndpointPathForCollision(path4) {
2258
+ const error = endpointPathValidationError(path4);
2212
2259
  if (error != null) {
2213
2260
  throw new Error(`invalid endpoint path: ${error}`);
2214
2261
  }
2215
- return path3.split("/").map((segment, index) => {
2262
+ return path4.split("/").map((segment, index) => {
2216
2263
  if (index < 2) {
2217
2264
  return segment;
2218
2265
  }
@@ -2225,21 +2272,21 @@ function normalizeEndpointPathForCollision(path3) {
2225
2272
  return segment;
2226
2273
  }).join("/");
2227
2274
  }
2228
- var endpointPathSchema = import_zod.z.string().superRefine((path3, ctx) => {
2229
- const error = endpointPathValidationError(path3);
2275
+ var endpointPathSchema = import_zod2.z.string().superRefine((path4, ctx) => {
2276
+ const error = endpointPathValidationError(path4);
2230
2277
  if (error != null) {
2231
- ctx.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: error });
2278
+ ctx.addIssue({ code: import_zod2.z.ZodIssueCode.custom, message: error });
2232
2279
  }
2233
2280
  });
2234
- var endpointClaimSchema = import_zod.z.object({
2281
+ var endpointClaimSchema = import_zod2.z.object({
2235
2282
  path: endpointPathSchema,
2236
- methods: import_zod.z.array(endpointMethodSchema).min(1, "methods must be non-empty").superRefine((methods, ctx) => {
2283
+ methods: import_zod2.z.array(endpointMethodSchema).min(1, "methods must be non-empty").superRefine((methods, ctx) => {
2237
2284
  const seen = /* @__PURE__ */ new Set();
2238
2285
  for (let i = 0; i < methods.length; i += 1) {
2239
2286
  const method = methods[i];
2240
2287
  if (seen.has(method)) {
2241
2288
  ctx.addIssue({
2242
- code: import_zod.z.ZodIssueCode.custom,
2289
+ code: import_zod2.z.ZodIssueCode.custom,
2243
2290
  message: `duplicate method "${method}"`,
2244
2291
  path: [i]
2245
2292
  });
@@ -2276,14 +2323,14 @@ function mountSegmentValidationError(segment) {
2276
2323
  }
2277
2324
  return null;
2278
2325
  }
2279
- var mountSegmentSchema = import_zod.z.string().superRefine((segment, ctx) => {
2326
+ var mountSegmentSchema = import_zod2.z.string().superRefine((segment, ctx) => {
2280
2327
  const error = mountSegmentValidationError(segment);
2281
2328
  if (error != null) {
2282
- ctx.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: error });
2329
+ ctx.addIssue({ code: import_zod2.z.ZodIssueCode.custom, message: error });
2283
2330
  }
2284
2331
  });
2285
- var mountSchema = import_zod.z.object({
2286
- group: import_zod.z.string().regex(ROUTE_GROUP_REGEX, 'must be a Next.js route group like "(admin)"'),
2332
+ var mountSchema = import_zod2.z.object({
2333
+ group: import_zod2.z.string().regex(ROUTE_GROUP_REGEX, 'must be a Next.js route group like "(admin)"'),
2287
2334
  segment: mountSegmentSchema
2288
2335
  }).strict();
2289
2336
  function splitMountSegment(segment) {
@@ -2296,21 +2343,21 @@ function splitMountSegment(segment) {
2296
2343
  }
2297
2344
  return segment.split("/");
2298
2345
  }
2299
- var tableNameSchema = import_zod.z.string().regex(SNAKE_TABLE_REGEX, "must be snake_case");
2346
+ var tableNameSchema = import_zod2.z.string().regex(SNAKE_TABLE_REGEX, "must be snake_case");
2300
2347
  var NPM_PACKAGE_NAME_REGEX = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
2301
- var npmPackageNameSchema = import_zod.z.string().max(214).regex(NPM_PACKAGE_NAME_REGEX, "must be a valid npm package name");
2302
- var descriptionSchema = import_zod.z.string().max(200, "must be at most 200 characters").refine((value) => value.trim().length > 0, "must be non-empty after trimming");
2303
- var crossAppSchema = import_zod.z.object({
2304
- exposes: import_zod.z.array(import_zod.z.unknown()).max(0, "must be empty until cross-app contracts are defined"),
2305
- consumes: import_zod.z.array(import_zod.z.unknown()).max(0, "must be empty until cross-app contracts are defined")
2348
+ var npmPackageNameSchema = import_zod2.z.string().max(214).regex(NPM_PACKAGE_NAME_REGEX, "must be a valid npm package name");
2349
+ var descriptionSchema = import_zod2.z.string().max(200, "must be at most 200 characters").refine((value) => value.trim().length > 0, "must be non-empty after trimming");
2350
+ var crossAppSchema = import_zod2.z.object({
2351
+ exposes: import_zod2.z.array(import_zod2.z.unknown()).max(0, "must be empty until cross-app contracts are defined"),
2352
+ consumes: import_zod2.z.array(import_zod2.z.unknown()).max(0, "must be empty until cross-app contracts are defined")
2306
2353
  }).strict();
2307
- var enhancementWorkflowsSchema = import_zod.z.record(moduleNameSchema, import_zod.z.array(import_zod.z.string().min(1))).default({});
2308
- var enhancementEndpointsSchema = import_zod.z.record(moduleNameSchema, import_zod.z.array(endpointClaimSchema)).default({});
2309
- var datastoreRequirementSchema = import_zod.z.object({
2354
+ var enhancementWorkflowsSchema = import_zod2.z.record(moduleNameSchema, import_zod2.z.array(import_zod2.z.string().min(1))).default({});
2355
+ var enhancementEndpointsSchema = import_zod2.z.record(moduleNameSchema, import_zod2.z.array(endpointClaimSchema)).default({});
2356
+ var datastoreRequirementSchema = import_zod2.z.object({
2310
2357
  /** Logical name, module-local, snake_case. Never a physical store key. */
2311
2358
  id: tableNameSchema,
2312
2359
  /** "app" = this project's app-local store; "org" = an org-level shared store. */
2313
- scope: import_zod.z.enum(["app", "org"]).default("app"),
2360
+ scope: import_zod2.z.enum(["app", "org"]).default("app"),
2314
2361
  /**
2315
2362
  * Which kind of connected store satisfies this requirement. A "database"
2316
2363
  * requirement only ever binds to a database store and a "storage" one
@@ -2318,34 +2365,34 @@ var datastoreRequirementSchema = import_zod.z.object({
2318
2365
  * rail never crosses the two. Defaults to "database", the only kind the
2319
2366
  * rail resolved before this field existed.
2320
2367
  */
2321
- type: import_zod.z.enum(["database", "storage"]).default("database"),
2368
+ type: import_zod2.z.enum(["database", "storage"]).default("database"),
2322
2369
  /** true → install fails when no store resolves; false → binding omitted, module degrades. */
2323
- required: import_zod.z.boolean().default(true),
2370
+ required: import_zod2.z.boolean().default(true),
2324
2371
  /** Checked against the connected store's accessLevel at install. */
2325
- access: import_zod.z.enum(["read", "write", "admin"]).default("write")
2372
+ access: import_zod2.z.enum(["read", "write", "admin"]).default("write")
2326
2373
  }).strict();
2327
- var permissionClaimSchema = import_zod.z.object({
2374
+ var permissionClaimSchema = import_zod2.z.object({
2328
2375
  /**
2329
2376
  * Lowercase resource:action with one colon and no underscores. This grammar
2330
2377
  * is deliberately narrower than the platform permission tool's charset.
2331
2378
  */
2332
- key: import_zod.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)"),
2379
+ key: import_zod2.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)"),
2333
2380
  /** Human label for the deployment auth config row. */
2334
2381
  description: descriptionSchema
2335
2382
  }).strict();
2336
- var seedBundleSchema = import_zod.z.object({
2383
+ var seedBundleSchema = import_zod2.z.object({
2337
2384
  table: tableNameSchema,
2338
2385
  /** Columns forming the ON CONFLICT key — must appear in every row. */
2339
- conflictTarget: import_zod.z.array(import_zod.z.string().min(1)).min(1),
2386
+ conflictTarget: import_zod2.z.array(import_zod2.z.string().min(1)).min(1),
2340
2387
  /** Scalar JSON values only (no nested objects/arrays). */
2341
- rows: import_zod.z.array(import_zod.z.record(import_zod.z.string(), import_zod.z.union([import_zod.z.string(), import_zod.z.number(), import_zod.z.boolean(), import_zod.z.null()]))).min(1)
2388
+ rows: import_zod2.z.array(import_zod2.z.record(import_zod2.z.string(), import_zod2.z.union([import_zod2.z.string(), import_zod2.z.number(), import_zod2.z.boolean(), import_zod2.z.null()]))).min(1)
2342
2389
  }).strict().superRefine((bundle, ctx) => {
2343
2390
  const rowKeys = bundle.rows.map((row) => Object.keys(row).sort().join("\0"));
2344
2391
  const canonical = rowKeys[0];
2345
2392
  for (let i = 1; i < rowKeys.length; i += 1) {
2346
2393
  if (rowKeys[i] !== canonical) {
2347
2394
  ctx.addIssue({
2348
- code: import_zod.z.ZodIssueCode.custom,
2395
+ code: import_zod2.z.ZodIssueCode.custom,
2349
2396
  message: "all rows in a seed bundle must have identical key sets",
2350
2397
  path: ["rows", i]
2351
2398
  });
@@ -2357,7 +2404,7 @@ var seedBundleSchema = import_zod.z.object({
2357
2404
  for (const col of bundle.conflictTarget) {
2358
2405
  if (!keys.has(col)) {
2359
2406
  ctx.addIssue({
2360
- code: import_zod.z.ZodIssueCode.custom,
2407
+ code: import_zod2.z.ZodIssueCode.custom,
2361
2408
  message: `conflictTarget column "${col}" must be present in every row`,
2362
2409
  path: ["rows", i]
2363
2410
  });
@@ -2365,35 +2412,35 @@ var seedBundleSchema = import_zod.z.object({
2365
2412
  }
2366
2413
  }
2367
2414
  });
2368
- var moduleManifestSchema = import_zod.z.object({
2415
+ var moduleManifestSchema = import_zod2.z.object({
2369
2416
  name: moduleNameSchema,
2370
2417
  kind: moduleKindSchema,
2371
2418
  // Release versions are platform-owned in module_versions. Accept this
2372
2419
  // legacy field while existing Module source ages out, but strip it from the
2373
2420
  // parsed contract so no runtime can treat module.json as version authority.
2374
- version: import_zod.z.unknown().optional(),
2421
+ version: import_zod2.z.unknown().optional(),
2375
2422
  home: projectSlugSchema,
2376
2423
  /** Machine-checkable purpose line — required, ≤200 code units, non-blank when trimmed. */
2377
2424
  description: descriptionSchema,
2378
- uses: import_zod.z.array(moduleNameSchema).default([]),
2425
+ uses: import_zod2.z.array(moduleNameSchema).default([]),
2379
2426
  /**
2380
2427
  * Soft peers: this module ships `enhancements/<peer>/` directories that are
2381
2428
  * composition-pruned — the installer includes them only when the peer is
2382
2429
  * present, so the peer is NOT an install-time requirement. Disjoint from
2383
2430
  * `uses`; entries may name modules absent from the composition.
2384
2431
  */
2385
- enhancements: import_zod.z.array(moduleNameSchema).default([]),
2386
- tables: import_zod.z.array(tableNameSchema).default([]),
2387
- mounts: import_zod.z.array(mountSchema).default([]),
2388
- integration: import_zod.z.array(import_zod.z.string()).default([]),
2389
- workflows: import_zod.z.array(import_zod.z.string().min(1)).default([]),
2432
+ enhancements: import_zod2.z.array(moduleNameSchema).default([]),
2433
+ tables: import_zod2.z.array(tableNameSchema).default([]),
2434
+ mounts: import_zod2.z.array(mountSchema).default([]),
2435
+ integration: import_zod2.z.array(import_zod2.z.string()).default([]),
2436
+ workflows: import_zod2.z.array(import_zod2.z.string().min(1)).default([]),
2390
2437
  /**
2391
2438
  * Slice-scoped workflow names keyed by peer. Keys must be ⊆ `enhancements`;
2392
2439
  * names must be unique across root `workflows` and every slice list.
2393
2440
  */
2394
2441
  enhancementWorkflows: enhancementWorkflowsSchema,
2395
2442
  /** Root API endpoint path claims. */
2396
- endpoints: import_zod.z.array(endpointClaimSchema).default([]),
2443
+ endpoints: import_zod2.z.array(endpointClaimSchema).default([]),
2397
2444
  /**
2398
2445
  * Slice-scoped endpoint claims keyed by peer. Keys must be ⊆ `enhancements`;
2399
2446
  * normalized paths must be unique across root and every slice.
@@ -2409,38 +2456,38 @@ var moduleManifestSchema = import_zod.z.object({
2409
2456
  * to a physical connected store and generates module-datastores.gen.ts;
2410
2457
  * unlike seeds, this field drives generated output.
2411
2458
  */
2412
- datastores: import_zod.z.array(datastoreRequirementSchema).optional(),
2459
+ datastores: import_zod2.z.array(datastoreRequirementSchema).optional(),
2413
2460
  /**
2414
2461
  * Keys this module's guards enforce. Install registers them in deployment
2415
2462
  * auth config; registration only — declaring is never granting.
2416
2463
  */
2417
- permissions: import_zod.z.array(permissionClaimSchema).optional(),
2464
+ permissions: import_zod2.z.array(permissionClaimSchema).optional(),
2418
2465
  /**
2419
2466
  * Optional starter rows for tables this module owns. Replay is
2420
2467
  * `INSERT ... ON CONFLICT (conflictTarget) DO NOTHING` — idempotent by
2421
2468
  * construction; the rail (not this schema) performs the replay.
2422
2469
  */
2423
- seeds: import_zod.z.array(seedBundleSchema).optional(),
2470
+ seeds: import_zod2.z.array(seedBundleSchema).optional(),
2424
2471
  /** npm packages the source imports beyond the base app. Names only; the rail installs each at the home app's range. */
2425
- packages: import_zod.z.array(npmPackageNameSchema).optional(),
2472
+ packages: import_zod2.z.array(npmPackageNameSchema).optional(),
2426
2473
  /**
2427
2474
  * UI-layer contract. `separable: true` asserts the Module's components/ and
2428
2475
  * routes/ hold presentation only — the Blueprint's
2429
2476
  * `module-boundaries/ui-layer` lint rule enforces it — so a fork may
2430
2477
  * soft-eject them. See docs/module-ui-soft-eject-plan.md.
2431
2478
  */
2432
- ui: import_zod.z.object({ separable: import_zod.z.boolean() }).strict().optional()
2479
+ ui: import_zod2.z.object({ separable: import_zod2.z.boolean() }).strict().optional()
2433
2480
  }).strict().superRefine((manifest, ctx) => {
2434
2481
  if (manifest.uses.includes(manifest.name)) {
2435
2482
  ctx.addIssue({
2436
- code: import_zod.z.ZodIssueCode.custom,
2483
+ code: import_zod2.z.ZodIssueCode.custom,
2437
2484
  message: "a module cannot list itself in uses",
2438
2485
  path: ["uses"]
2439
2486
  });
2440
2487
  }
2441
2488
  if (manifest.enhancements.includes(manifest.name)) {
2442
2489
  ctx.addIssue({
2443
- code: import_zod.z.ZodIssueCode.custom,
2490
+ code: import_zod2.z.ZodIssueCode.custom,
2444
2491
  message: "a module cannot list itself in enhancements",
2445
2492
  path: ["enhancements"]
2446
2493
  });
@@ -2448,7 +2495,7 @@ var moduleManifestSchema = import_zod.z.object({
2448
2495
  const hardAndSoft = manifest.enhancements.filter((peer) => manifest.uses.includes(peer));
2449
2496
  if (hardAndSoft.length > 0) {
2450
2497
  ctx.addIssue({
2451
- code: import_zod.z.ZodIssueCode.custom,
2498
+ code: import_zod2.z.ZodIssueCode.custom,
2452
2499
  message: `uses and enhancements must be disjoint (a peer is either a hard dep or a soft one): ${hardAndSoft.join(", ")}`,
2453
2500
  path: ["enhancements"]
2454
2501
  });
@@ -2457,7 +2504,7 @@ var moduleManifestSchema = import_zod.z.object({
2457
2504
  for (const peer of Object.keys(manifest.enhancementWorkflows)) {
2458
2505
  if (!enhancementPeers.has(peer)) {
2459
2506
  ctx.addIssue({
2460
- code: import_zod.z.ZodIssueCode.custom,
2507
+ code: import_zod2.z.ZodIssueCode.custom,
2461
2508
  message: `enhancementWorkflows key "${peer}" must be listed in enhancements`,
2462
2509
  path: ["enhancementWorkflows", peer]
2463
2510
  });
@@ -2466,23 +2513,23 @@ var moduleManifestSchema = import_zod.z.object({
2466
2513
  for (const peer of Object.keys(manifest.enhancementEndpoints)) {
2467
2514
  if (!enhancementPeers.has(peer)) {
2468
2515
  ctx.addIssue({
2469
- code: import_zod.z.ZodIssueCode.custom,
2516
+ code: import_zod2.z.ZodIssueCode.custom,
2470
2517
  message: `enhancementEndpoints key "${peer}" must be listed in enhancements`,
2471
2518
  path: ["enhancementEndpoints", peer]
2472
2519
  });
2473
2520
  }
2474
2521
  }
2475
2522
  const seenWorkflows = /* @__PURE__ */ new Map();
2476
- const noteWorkflow = (name, path3) => {
2523
+ const noteWorkflow = (name, path4) => {
2477
2524
  const existing = seenWorkflows.get(name);
2478
2525
  if (existing != null) {
2479
2526
  ctx.addIssue({
2480
- code: import_zod.z.ZodIssueCode.custom,
2527
+ code: import_zod2.z.ZodIssueCode.custom,
2481
2528
  message: `duplicate workflow name "${name}" (also at ${existing})`,
2482
- path: path3
2529
+ path: path4
2483
2530
  });
2484
2531
  } else {
2485
- seenWorkflows.set(name, path3.join("."));
2532
+ seenWorkflows.set(name, path4.join("."));
2486
2533
  }
2487
2534
  };
2488
2535
  for (let i = 0; i < manifest.workflows.length; i += 1) {
@@ -2494,7 +2541,7 @@ var moduleManifestSchema = import_zod.z.object({
2494
2541
  }
2495
2542
  }
2496
2543
  const seenEndpointKeys = /* @__PURE__ */ new Map();
2497
- const noteEndpoint = (claim, path3) => {
2544
+ const noteEndpoint = (claim, path4) => {
2498
2545
  let key;
2499
2546
  try {
2500
2547
  key = normalizeEndpointPathForCollision(claim.path);
@@ -2504,12 +2551,12 @@ var moduleManifestSchema = import_zod.z.object({
2504
2551
  const existing = seenEndpointKeys.get(key);
2505
2552
  if (existing != null) {
2506
2553
  ctx.addIssue({
2507
- code: import_zod.z.ZodIssueCode.custom,
2554
+ code: import_zod2.z.ZodIssueCode.custom,
2508
2555
  message: `duplicate normalized endpoint path "${key}" (also at ${existing})`,
2509
- path: path3
2556
+ path: path4
2510
2557
  });
2511
2558
  } else {
2512
- seenEndpointKeys.set(key, path3.join("."));
2559
+ seenEndpointKeys.set(key, path4.join("."));
2513
2560
  }
2514
2561
  };
2515
2562
  for (let i = 0; i < manifest.endpoints.length; i += 1) {
@@ -2526,7 +2573,7 @@ var moduleManifestSchema = import_zod.z.object({
2526
2573
  const id = manifest.datastores[i].id;
2527
2574
  if (seenDatastoreIds.has(id)) {
2528
2575
  ctx.addIssue({
2529
- code: import_zod.z.ZodIssueCode.custom,
2576
+ code: import_zod2.z.ZodIssueCode.custom,
2530
2577
  message: `duplicate datastore id "${id}"`,
2531
2578
  path: ["datastores", i, "id"]
2532
2579
  });
@@ -2541,7 +2588,7 @@ var moduleManifestSchema = import_zod.z.object({
2541
2588
  const key = manifest.permissions[i].key;
2542
2589
  if (seenPermissionKeys.has(key)) {
2543
2590
  ctx.addIssue({
2544
- code: import_zod.z.ZodIssueCode.custom,
2591
+ code: import_zod2.z.ZodIssueCode.custom,
2545
2592
  message: `duplicate permission key "${key}"`,
2546
2593
  path: ["permissions", i, "key"]
2547
2594
  });
@@ -2553,35 +2600,35 @@ var moduleManifestSchema = import_zod.z.object({
2553
2600
  if (manifest.kind === "library") {
2554
2601
  if (manifest.tables.length > 0) {
2555
2602
  ctx.addIssue({
2556
- code: import_zod.z.ZodIssueCode.custom,
2603
+ code: import_zod2.z.ZodIssueCode.custom,
2557
2604
  message: "library modules must not declare tables",
2558
2605
  path: ["tables"]
2559
2606
  });
2560
2607
  }
2561
2608
  if (manifest.mounts.length > 0) {
2562
2609
  ctx.addIssue({
2563
- code: import_zod.z.ZodIssueCode.custom,
2610
+ code: import_zod2.z.ZodIssueCode.custom,
2564
2611
  message: "library modules must not declare mounts",
2565
2612
  path: ["mounts"]
2566
2613
  });
2567
2614
  }
2568
2615
  if (manifest.seeds != null && manifest.seeds.length > 0) {
2569
2616
  ctx.addIssue({
2570
- code: import_zod.z.ZodIssueCode.custom,
2617
+ code: import_zod2.z.ZodIssueCode.custom,
2571
2618
  message: "library modules must not declare seeds",
2572
2619
  path: ["seeds"]
2573
2620
  });
2574
2621
  }
2575
2622
  if (manifest.datastores != null && manifest.datastores.length > 0) {
2576
2623
  ctx.addIssue({
2577
- code: import_zod.z.ZodIssueCode.custom,
2624
+ code: import_zod2.z.ZodIssueCode.custom,
2578
2625
  message: "library modules must not declare datastores",
2579
2626
  path: ["datastores"]
2580
2627
  });
2581
2628
  }
2582
2629
  if (manifest.permissions != null && manifest.permissions.length > 0) {
2583
2630
  ctx.addIssue({
2584
- code: import_zod.z.ZodIssueCode.custom,
2631
+ code: import_zod2.z.ZodIssueCode.custom,
2585
2632
  message: "library modules must not declare permissions",
2586
2633
  path: ["permissions"]
2587
2634
  });
@@ -2589,7 +2636,7 @@ var moduleManifestSchema = import_zod.z.object({
2589
2636
  }
2590
2637
  if (manifest.kind === "contract" && manifest.mounts.length > 0) {
2591
2638
  ctx.addIssue({
2592
- code: import_zod.z.ZodIssueCode.custom,
2639
+ code: import_zod2.z.ZodIssueCode.custom,
2593
2640
  message: "contract modules must not declare mounts",
2594
2641
  path: ["mounts"]
2595
2642
  });
@@ -2600,7 +2647,7 @@ var moduleManifestSchema = import_zod.z.object({
2600
2647
  const bundle = manifest.seeds[i];
2601
2648
  if (!ownedTables.has(bundle.table)) {
2602
2649
  ctx.addIssue({
2603
- code: import_zod.z.ZodIssueCode.custom,
2650
+ code: import_zod2.z.ZodIssueCode.custom,
2604
2651
  message: `seeds may only target tables listed in tables (got "${bundle.table}")`,
2605
2652
  path: ["seeds", i, "table"]
2606
2653
  });
@@ -2610,8 +2657,8 @@ var moduleManifestSchema = import_zod.z.object({
2610
2657
  }).transform(({ version: _legacyVersion, ...manifest }) => manifest);
2611
2658
  function formatZodErrors(error) {
2612
2659
  return error.issues.map((issue) => {
2613
- const path3 = issue.path.length > 0 ? issue.path.join(".") : "root";
2614
- return `${path3}: ${issue.message}`;
2660
+ const path4 = issue.path.length > 0 ? issue.path.join(".") : "root";
2661
+ return `${path4}: ${issue.message}`;
2615
2662
  });
2616
2663
  }
2617
2664
  function parseModuleManifest(raw) {
@@ -2651,10 +2698,10 @@ var MANDATORY_MODULE_CHECK_DESCRIPTORS = Object.freeze([
2651
2698
  ]);
2652
2699
 
2653
2700
  // ../../packages/module-contract/src/module-structure.ts
2654
- var import_zod3 = require("zod");
2701
+ var import_zod4 = require("zod");
2655
2702
 
2656
2703
  // ../../packages/module-contract/src/module-e2e-manifest.ts
2657
- var import_zod2 = require("zod");
2704
+ var import_zod3 = require("zod");
2658
2705
  function e2eSpecFileValidationError(file) {
2659
2706
  if (file.length === 0) {
2660
2707
  return "must be a non-empty relative path ending in .spec.ts";
@@ -2682,22 +2729,22 @@ function e2eSpecFileValidationError(file) {
2682
2729
  }
2683
2730
  return null;
2684
2731
  }
2685
- var e2eSpecFileSchema = import_zod2.z.string().superRefine((file, ctx) => {
2732
+ var e2eSpecFileSchema = import_zod3.z.string().superRefine((file, ctx) => {
2686
2733
  const error = e2eSpecFileValidationError(file);
2687
2734
  if (error != null) {
2688
- ctx.addIssue({ code: import_zod2.z.ZodIssueCode.custom, message: error });
2735
+ ctx.addIssue({ code: import_zod3.z.ZodIssueCode.custom, message: error });
2689
2736
  }
2690
2737
  });
2691
- var e2eSpecSchema = import_zod2.z.object({
2738
+ var e2eSpecSchema = import_zod3.z.object({
2692
2739
  file: e2eSpecFileSchema,
2693
- requires: import_zod2.z.array(moduleNameSchema).min(1, "requires must be non-empty")
2740
+ requires: import_zod3.z.array(moduleNameSchema).min(1, "requires must be non-empty")
2694
2741
  }).strict().superRefine((spec, ctx) => {
2695
2742
  const seen = /* @__PURE__ */ new Set();
2696
2743
  for (let i = 0; i < spec.requires.length; i += 1) {
2697
2744
  const name = spec.requires[i];
2698
2745
  if (seen.has(name)) {
2699
2746
  ctx.addIssue({
2700
- code: import_zod2.z.ZodIssueCode.custom,
2747
+ code: import_zod3.z.ZodIssueCode.custom,
2701
2748
  message: `duplicate requirement "${name}"`,
2702
2749
  path: ["requires", i]
2703
2750
  });
@@ -2706,15 +2753,15 @@ var e2eSpecSchema = import_zod2.z.object({
2706
2753
  }
2707
2754
  }
2708
2755
  });
2709
- var moduleE2eManifestSchema = import_zod2.z.object({
2710
- specs: import_zod2.z.array(e2eSpecSchema).min(1, "specs must contain at least one entry")
2756
+ var moduleE2eManifestSchema = import_zod3.z.object({
2757
+ specs: import_zod3.z.array(e2eSpecSchema).min(1, "specs must contain at least one entry")
2711
2758
  }).strict().superRefine((manifest, ctx) => {
2712
2759
  const seenFiles = /* @__PURE__ */ new Set();
2713
2760
  for (let i = 0; i < manifest.specs.length; i += 1) {
2714
2761
  const file = manifest.specs[i].file;
2715
2762
  if (seenFiles.has(file)) {
2716
2763
  ctx.addIssue({
2717
- code: import_zod2.z.ZodIssueCode.custom,
2764
+ code: import_zod3.z.ZodIssueCode.custom,
2718
2765
  message: `duplicate file "${file}"`,
2719
2766
  path: ["specs", i, "file"]
2720
2767
  });
@@ -2956,7 +3003,7 @@ function compareCodeUnit(a, b) {
2956
3003
  // ../../packages/module-contract/src/module-structure.ts
2957
3004
  var MODULE_STRUCTURE_JSON_BYTE_CAP = 64 * 1024;
2958
3005
  function uniqueSortedStringArray(label) {
2959
- return import_zod3.z.array(import_zod3.z.string()).superRefine((arr, ctx) => {
3006
+ return import_zod4.z.array(import_zod4.z.string()).superRefine((arr, ctx) => {
2960
3007
  if (new Set(arr).size !== arr.length) {
2961
3008
  ctx.addIssue({ code: "custom", message: `${label} must be unique` });
2962
3009
  return;
@@ -2972,10 +3019,10 @@ function uniqueSortedStringArray(label) {
2972
3019
  });
2973
3020
  }
2974
3021
  function contributionPermissionsSchema(label) {
2975
- return import_zod3.z.array(
2976
- import_zod3.z.object({
2977
- contributionId: import_zod3.z.string().min(1),
2978
- permission: import_zod3.z.string().min(1)
3022
+ return import_zod4.z.array(
3023
+ import_zod4.z.object({
3024
+ contributionId: import_zod4.z.string().min(1),
3025
+ permission: import_zod4.z.string().min(1)
2979
3026
  }).strict()
2980
3027
  ).superRefine((entries, ctx) => {
2981
3028
  const keys = entries.map((entry) => `${entry.contributionId}\0${entry.permission}`);
@@ -2993,17 +3040,17 @@ function contributionPermissionsSchema(label) {
2993
3040
  }
2994
3041
  }).optional();
2995
3042
  }
2996
- var collectedFilePayloadSchema = import_zod3.z.discriminatedUnion("status", [
2997
- import_zod3.z.object({ status: import_zod3.z.literal("ok"), text: import_zod3.z.string() }).strict(),
2998
- import_zod3.z.object({ status: import_zod3.z.literal("missing") }).strict(),
2999
- import_zod3.z.object({ status: import_zod3.z.literal("too-large") }).strict(),
3000
- import_zod3.z.object({ status: import_zod3.z.literal("unreadable") }).strict()
3043
+ var collectedFilePayloadSchema = import_zod4.z.discriminatedUnion("status", [
3044
+ import_zod4.z.object({ status: import_zod4.z.literal("ok"), text: import_zod4.z.string() }).strict(),
3045
+ import_zod4.z.object({ status: import_zod4.z.literal("missing") }).strict(),
3046
+ import_zod4.z.object({ status: import_zod4.z.literal("too-large") }).strict(),
3047
+ import_zod4.z.object({ status: import_zod4.z.literal("unreadable") }).strict()
3001
3048
  ]);
3002
- var collectedLocaleFileSchema = import_zod3.z.object({
3003
- locale: import_zod3.z.string().min(1),
3049
+ var collectedLocaleFileSchema = import_zod4.z.object({
3050
+ locale: import_zod4.z.string().min(1),
3004
3051
  payload: collectedFilePayloadSchema
3005
3052
  }).strict();
3006
- var collectedE2eFactsSchema = import_zod3.z.object({
3053
+ var collectedE2eFactsSchema = import_zod4.z.object({
3007
3054
  manifest: collectedFilePayloadSchema,
3008
3055
  specFiles: uniqueSortedStringArray("specFiles"),
3009
3056
  escapingSymlinks: uniqueSortedStringArray("escapingSymlinks")
@@ -3011,34 +3058,34 @@ var collectedE2eFactsSchema = import_zod3.z.object({
3011
3058
  function validateRouteExportEvidenceCongruence(input) {
3012
3059
  const entrySet = new Set(input.routeEntries);
3013
3060
  const exportPaths = input.routePageExports.map((e) => e.nextRelativePath);
3014
- for (const path3 of exportPaths) {
3015
- if (!entrySet.has(path3)) {
3016
- input.onIssue(`routePageExports path "${path3}" is not listed in routeEntries`);
3061
+ for (const path4 of exportPaths) {
3062
+ if (!entrySet.has(path4)) {
3063
+ input.onIssue(`routePageExports path "${path4}" is not listed in routeEntries`);
3017
3064
  }
3018
3065
  }
3019
3066
  if (input.routeCollectionErrors.length === 0) {
3020
3067
  const exportSet = new Set(exportPaths);
3021
- for (const path3 of input.routeEntries) {
3022
- if (!exportSet.has(path3)) {
3023
- input.onIssue(`routeEntries path "${path3}" is missing from routePageExports`);
3068
+ for (const path4 of input.routeEntries) {
3069
+ if (!exportSet.has(path4)) {
3070
+ input.onIssue(`routeEntries path "${path4}" is missing from routePageExports`);
3024
3071
  }
3025
3072
  }
3026
3073
  }
3027
3074
  }
3028
- var collectedEnhancementFactsSchema = import_zod3.z.object({
3029
- peer: import_zod3.z.string().min(1),
3030
- hasContributionsTs: import_zod3.z.boolean(),
3075
+ var collectedEnhancementFactsSchema = import_zod4.z.object({
3076
+ peer: import_zod4.z.string().min(1),
3077
+ hasContributionsTs: import_zod4.z.boolean(),
3031
3078
  contributionPermissions: contributionPermissionsSchema("enhancement contributionPermissions"),
3032
- hasInitializeServerTs: import_zod3.z.boolean(),
3079
+ hasInitializeServerTs: import_zod4.z.boolean(),
3033
3080
  contributionsCollectionErrors: uniqueSortedStringArray(
3034
3081
  "enhancement contributionsCollectionErrors"
3035
3082
  ),
3036
3083
  initializeServerCollectionErrors: uniqueSortedStringArray(
3037
3084
  "enhancement initializeServerCollectionErrors"
3038
3085
  ),
3039
- hasRoutesDir: import_zod3.z.boolean(),
3086
+ hasRoutesDir: import_zod4.z.boolean(),
3040
3087
  routeCollectionErrors: uniqueSortedStringArray("enhancement routeCollectionErrors"),
3041
- locales: import_zod3.z.array(collectedLocaleFileSchema).superRefine((locales, ctx) => {
3088
+ locales: import_zod4.z.array(collectedLocaleFileSchema).superRefine((locales, ctx) => {
3042
3089
  const names = locales.map((l) => l.locale);
3043
3090
  if (new Set(names).size !== names.length) {
3044
3091
  ctx.addIssue({ code: "custom", message: "enhancement locales must be unique by locale" });
@@ -3053,27 +3100,27 @@ var collectedEnhancementFactsSchema = import_zod3.z.object({
3053
3100
  }),
3054
3101
  e2e: collectedE2eFactsSchema.nullable()
3055
3102
  }).strict();
3056
- var moduleStructureFactsSchema = import_zod3.z.object({
3057
- collected: import_zod3.z.literal(true),
3058
- dirName: import_zod3.z.string().min(1),
3059
- hasIndexTs: import_zod3.z.boolean(),
3060
- hasContributionsTs: import_zod3.z.boolean(),
3103
+ var moduleStructureFactsSchema = import_zod4.z.object({
3104
+ collected: import_zod4.z.literal(true),
3105
+ dirName: import_zod4.z.string().min(1),
3106
+ hasIndexTs: import_zod4.z.boolean(),
3107
+ hasContributionsTs: import_zod4.z.boolean(),
3061
3108
  contributionPermissions: contributionPermissionsSchema("contributionPermissions"),
3062
- hasSlotsTs: import_zod3.z.boolean(),
3109
+ hasSlotsTs: import_zod4.z.boolean(),
3063
3110
  contributionsCollectionErrors: uniqueSortedStringArray("contributionsCollectionErrors"),
3064
3111
  slotsCollectionErrors: uniqueSortedStringArray("slotsCollectionErrors"),
3065
- hasTestsDir: import_zod3.z.boolean(),
3066
- hasDatastoreDir: import_zod3.z.boolean(),
3067
- datastoreFileCount: import_zod3.z.number().int().nonnegative(),
3068
- hasServerIndexTs: import_zod3.z.boolean(),
3069
- hasOtherServerTs: import_zod3.z.boolean(),
3070
- hasE2eDir: import_zod3.z.boolean(),
3071
- hasRoutesDir: import_zod3.z.boolean(),
3112
+ hasTestsDir: import_zod4.z.boolean(),
3113
+ hasDatastoreDir: import_zod4.z.boolean(),
3114
+ datastoreFileCount: import_zod4.z.number().int().nonnegative(),
3115
+ hasServerIndexTs: import_zod4.z.boolean(),
3116
+ hasOtherServerTs: import_zod4.z.boolean(),
3117
+ hasE2eDir: import_zod4.z.boolean(),
3118
+ hasRoutesDir: import_zod4.z.boolean(),
3072
3119
  routeEntries: uniqueSortedStringArray("routeEntries"),
3073
- routePageExports: import_zod3.z.array(
3074
- import_zod3.z.object({
3075
- nextRelativePath: import_zod3.z.string().min(1),
3076
- hasGenerateMetadata: import_zod3.z.boolean()
3120
+ routePageExports: import_zod4.z.array(
3121
+ import_zod4.z.object({
3122
+ nextRelativePath: import_zod4.z.string().min(1),
3123
+ hasGenerateMetadata: import_zod4.z.boolean()
3077
3124
  }).strict()
3078
3125
  ).superRefine((entries, ctx) => {
3079
3126
  const paths = entries.map((e) => e.nextRelativePath);
@@ -3095,7 +3142,7 @@ var moduleStructureFactsSchema = import_zod3.z.object({
3095
3142
  }
3096
3143
  }),
3097
3144
  routeCollectionErrors: uniqueSortedStringArray("routeCollectionErrors"),
3098
- rootLocales: import_zod3.z.array(collectedLocaleFileSchema).superRefine((locales, ctx) => {
3145
+ rootLocales: import_zod4.z.array(collectedLocaleFileSchema).superRefine((locales, ctx) => {
3099
3146
  const names = locales.map((l) => l.locale);
3100
3147
  if (new Set(names).size !== names.length) {
3101
3148
  ctx.addIssue({ code: "custom", message: "rootLocales must be unique by locale" });
@@ -3109,7 +3156,7 @@ var moduleStructureFactsSchema = import_zod3.z.object({
3109
3156
  }
3110
3157
  }),
3111
3158
  rootE2e: collectedE2eFactsSchema.nullable(),
3112
- enhancements: import_zod3.z.array(collectedEnhancementFactsSchema).superRefine((enhancements, ctx) => {
3159
+ enhancements: import_zod4.z.array(collectedEnhancementFactsSchema).superRefine((enhancements, ctx) => {
3113
3160
  const peers = enhancements.map((e) => e.peer);
3114
3161
  if (new Set(peers).size !== peers.length) {
3115
3162
  ctx.addIssue({ code: "custom", message: "enhancement peers must be unique" });
@@ -3130,10 +3177,10 @@ var moduleStructureFactsSchema = import_zod3.z.object({
3130
3177
  onIssue: (message) => ctx.addIssue({ code: "custom", message })
3131
3178
  });
3132
3179
  });
3133
- var moduleStructureCollectionSchema = import_zod3.z.object({
3180
+ var moduleStructureCollectionSchema = import_zod4.z.object({
3134
3181
  homeLocales: uniqueSortedStringArray("homeLocales"),
3135
- modules: import_zod3.z.array(moduleStructureFactsSchema),
3136
- truncated: import_zod3.z.boolean().optional(),
3182
+ modules: import_zod4.z.array(moduleStructureFactsSchema),
3183
+ truncated: import_zod4.z.boolean().optional(),
3137
3184
  collectionErrors: uniqueSortedStringArray("collectionErrors").optional()
3138
3185
  }).strict().superRefine((collection, ctx) => {
3139
3186
  const dirs = collection.modules.map((m) => m.dirName);
@@ -3150,9 +3197,9 @@ var moduleStructureCollectionSchema = import_zod3.z.object({
3150
3197
  });
3151
3198
 
3152
3199
  // ../../packages/module-contract/src/locale.ts
3153
- var import_zod4 = require("zod");
3200
+ var import_zod5 = require("zod");
3154
3201
  var LOCALE_BASENAME_REGEX = /^[a-z]{2,3}(-[A-Za-z0-9]+)*$/;
3155
- var localeBasenameSchema = import_zod4.z.string().max(35).regex(LOCALE_BASENAME_REGEX, "must be a BCP47-like locale tag (e.g. en, th, en-US)");
3202
+ var localeBasenameSchema = import_zod5.z.string().max(35).regex(LOCALE_BASENAME_REGEX, "must be a BCP47-like locale tag (e.g. en, th, en-US)");
3156
3203
  function isValidLocaleBasename(value) {
3157
3204
  return localeBasenameSchema.safeParse(value).success;
3158
3205
  }
@@ -3555,43 +3602,43 @@ function formatRoutePageExportErrors(nextRelativePath, validation) {
3555
3602
  var import_typescript2 = __toESM(require("typescript"), 1);
3556
3603
 
3557
3604
  // ../../packages/module-contract/src/module-registry.ts
3558
- var import_zod5 = require("zod");
3559
- var projectSlugSchema2 = import_zod5.z.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
3560
- var registryEnhancementSchema = import_zod5.z.object({
3605
+ var import_zod6 = require("zod");
3606
+ var projectSlugSchema2 = import_zod6.z.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
3607
+ var registryEnhancementSchema = import_zod6.z.object({
3561
3608
  peer: moduleNameSchema,
3562
- active: import_zod5.z.boolean()
3609
+ active: import_zod6.z.boolean()
3563
3610
  }).strict();
3564
- var registryModuleSchema = import_zod5.z.object({
3611
+ var registryModuleSchema = import_zod6.z.object({
3565
3612
  name: moduleNameSchema,
3566
3613
  /** Installed version as recorded by the rail (non-empty string). */
3567
- version: import_zod5.z.string().min(1),
3614
+ version: import_zod6.z.string().min(1),
3568
3615
  home: projectSlugSchema2,
3569
3616
  kind: moduleKindSchema,
3570
- mounts: import_zod5.z.array(mountSchema),
3571
- enhancements: import_zod5.z.array(registryEnhancementSchema)
3617
+ mounts: import_zod6.z.array(mountSchema),
3618
+ enhancements: import_zod6.z.array(registryEnhancementSchema)
3572
3619
  }).strict().superRefine((mod, ctx) => {
3573
3620
  for (let i = 0; i < mod.enhancements.length; i += 1) {
3574
3621
  if (mod.enhancements[i].peer === mod.name) {
3575
3622
  ctx.addIssue({
3576
- code: import_zod5.z.ZodIssueCode.custom,
3623
+ code: import_zod6.z.ZodIssueCode.custom,
3577
3624
  message: "enhancement peer must not equal the module's own name",
3578
3625
  path: ["enhancements", i, "peer"]
3579
3626
  });
3580
3627
  }
3581
3628
  }
3582
3629
  });
3583
- var generatedModuleRegistrySchema = import_zod5.z.object({
3584
- registryVersion: import_zod5.z.literal(1),
3630
+ var generatedModuleRegistrySchema = import_zod6.z.object({
3631
+ registryVersion: import_zod6.z.literal(1),
3585
3632
  /** Rail run id, or `"manual"` for fixtures. */
3586
- generatedBy: import_zod5.z.string().min(1),
3587
- modules: import_zod5.z.array(registryModuleSchema)
3633
+ generatedBy: import_zod6.z.string().min(1),
3634
+ modules: import_zod6.z.array(registryModuleSchema)
3588
3635
  }).strict().superRefine((registry, ctx) => {
3589
3636
  const seen = /* @__PURE__ */ new Set();
3590
3637
  for (let i = 0; i < registry.modules.length; i += 1) {
3591
3638
  const name = registry.modules[i].name;
3592
3639
  if (seen.has(name)) {
3593
3640
  ctx.addIssue({
3594
- code: import_zod5.z.ZodIssueCode.custom,
3641
+ code: import_zod6.z.ZodIssueCode.custom,
3595
3642
  message: `duplicate module name "${name}"`,
3596
3643
  path: ["modules", i, "name"]
3597
3644
  });
@@ -3962,169 +4009,6 @@ function analyzeClientSafeEntryImports(sourceText, fileName) {
3962
4009
  return analyzeClientSafeEntryImportsWithTs(import_typescript3.default, sourceText, fileName);
3963
4010
  }
3964
4011
 
3965
- // ../../packages/lib/src/shared/module-composition.ts
3966
- var moduleRefSchema = import_zod6.z.object({
3967
- homeProjectId: import_zod6.z.string().uuid(),
3968
- moduleName: moduleNameSchema
3969
- });
3970
- var creationSourceBlankSchema = import_zod6.z.object({
3971
- type: import_zod6.z.literal("blank")
3972
- });
3973
- var creationSourceBlueprintSchema = import_zod6.z.object({
3974
- type: import_zod6.z.literal("blueprint"),
3975
- /** Internal template/fork project id — keep template naming in identifiers. */
3976
- blueprintProjectId: import_zod6.z.string().uuid(),
3977
- /**
3978
- * `null`/absent means every feature Module. `[]` is refused: the platform
3979
- * serialises it to an empty `NEXT_PUBLIC_STARDECK_ENABLED_MODULES`, which the
3980
- * enablement parser reads back as "all" (`module-enablement.ts`) — the
3981
- * opposite of what an empty selection means.
3982
- */
3983
- enabledModules: import_zod6.z.array(moduleNameSchema).min(1, "Enable at least one Module").max(100).nullable().optional()
3984
- });
3985
- var creationSourceModulesSchema = import_zod6.z.object({
3986
- type: import_zod6.z.literal("modules"),
3987
- requested: import_zod6.z.array(moduleRefSchema).min(1)
3988
- });
3989
- var creationSourceSchema = import_zod6.z.discriminatedUnion("type", [
3990
- creationSourceBlankSchema,
3991
- creationSourceBlueprintSchema,
3992
- creationSourceModulesSchema
3993
- ]);
3994
- var moduleCompositionStatusSchema = import_zod6.z.enum([
3995
- "pending",
3996
- "installing",
3997
- "ready",
3998
- "partial",
3999
- "failed"
4000
- ]);
4001
- var resolvedModuleSnapshotSchema = moduleRefSchema.extend({
4002
- version: import_zod6.z.string().min(1),
4003
- homeCommitSha: import_zod6.z.string().min(1)
4004
- });
4005
- var moduleInstallHandoffSchema = import_zod6.z.object({
4006
- moduleName: import_zod6.z.string().min(1),
4007
- homeProjectId: import_zod6.z.string().uuid(),
4008
- installedVersion: import_zod6.z.string().min(1),
4009
- baselineSha: import_zod6.z.string().min(1),
4010
- mounts: import_zod6.z.array(
4011
- import_zod6.z.object({
4012
- group: import_zod6.z.string(),
4013
- segment: import_zod6.z.string()
4014
- })
4015
- ),
4016
- integration: import_zod6.z.array(import_zod6.z.string()),
4017
- workflows: import_zod6.z.array(import_zod6.z.string()),
4018
- prunedEnhancements: import_zod6.z.array(import_zod6.z.string()),
4019
- restoredEnhancements: import_zod6.z.array(import_zod6.z.string()).optional(),
4020
- permissions: import_zod6.z.array(
4021
- import_zod6.z.object({
4022
- key: import_zod6.z.string().min(1),
4023
- description: import_zod6.z.string().min(1)
4024
- })
4025
- ).optional()
4026
- });
4027
- var moduleCompositionRunAttemptSchema = import_zod6.z.object({
4028
- module: moduleRefSchema,
4029
- runId: import_zod6.z.string().min(1),
4030
- status: import_zod6.z.string().min(1),
4031
- schemaApplied: import_zod6.z.boolean(),
4032
- error: import_zod6.z.string().optional(),
4033
- /** Prefer full ModuleInstallHandoff; identity fields optional for older rows. */
4034
- handoff: moduleInstallHandoffSchema.partial({ moduleName: true, homeProjectId: true }).optional()
4035
- });
4036
- var initialModuleCompositionSchema = import_zod6.z.object({
4037
- version: import_zod6.z.literal(1),
4038
- status: moduleCompositionStatusSchema,
4039
- requested: import_zod6.z.array(moduleRefSchema).min(1),
4040
- resolved: import_zod6.z.array(resolvedModuleSnapshotSchema),
4041
- installOrder: import_zod6.z.array(moduleRefSchema),
4042
- runs: import_zod6.z.array(moduleCompositionRunAttemptSchema).default([]),
4043
- /**
4044
- * True when any failed/partial attempt applied schema before failing.
4045
- * Never claim transactional rollback across git/schema.
4046
- */
4047
- schemaApplied: import_zod6.z.boolean().default(false),
4048
- /**
4049
- * Present when retry would silently change pinned home SHAs — caller must
4050
- * reconfirm the new plan rather than continue.
4051
- */
4052
- reconfirmationRequired: import_zod6.z.object({
4053
- reason: import_zod6.z.string(),
4054
- previous: import_zod6.z.array(resolvedModuleSnapshotSchema),
4055
- next: import_zod6.z.array(resolvedModuleSnapshotSchema)
4056
- }).optional(),
4057
- error: import_zod6.z.string().optional(),
4058
- /**
4059
- * First-turn system handoff lifecycle. Absent/`pending` means inject when
4060
- * status is ready; `consumed` means a successful agent turn already used it.
4061
- */
4062
- systemHandoffStatus: import_zod6.z.enum(["pending", "consumed"]).optional(),
4063
- /**
4064
- * CAS ownership token for concurrent retry attempts. Only the attempt that
4065
- * currently owns this value may reset or compensate durable composition state.
4066
- */
4067
- retryAttemptId: import_zod6.z.string().min(1).optional(),
4068
- /**
4069
- * When the current `installing` attempt was claimed. A setup run that dies
4070
- * with its host (a killed Vercel instance) leaves the composition `installing`
4071
- * forever, and the creating page's poll only stops on ready/partial/failed —
4072
- * so staleness, not a queue's stall detector, is what settles it. Absent on
4073
- * rows claimed before this field existed; treated as stale.
4074
- */
4075
- attemptStartedAt: import_zod6.z.string().datetime().optional(),
4076
- /** Outside-module files the agent changed during connection work. */
4077
- integrationFiles: import_zod6.z.array(import_zod6.z.string()).optional(),
4078
- verificationSummary: import_zod6.z.string().optional()
4079
- });
4080
- var MODULE_SETUP_ATTEMPT_STALE_MS = 10 * 60 * 1e3;
4081
-
4082
- // ../../packages/lib/src/shared/compose-inputs.ts
4083
- var import_zod7 = require("zod");
4084
- var COMPOSE_INPUTS_PATH = "apps/web/stardeck.compose.json";
4085
- var composeInputsSchema = import_zod7.z.strictObject({
4086
- connectedStores: import_zod7.z.array(
4087
- import_zod7.z.strictObject({
4088
- storeId: import_zod7.z.string().min(1),
4089
- slug: import_zod7.z.string().min(1),
4090
- // A row with an empty binding key must not fail a deploy: the rail
4091
- // treats falsy as "no binding key", so normalise rather than reject.
4092
- bindingKey: import_zod7.z.string().nullish().transform((value) => value || null),
4093
- accessLevel: import_zod7.z.enum(["read", "write", "admin"]),
4094
- // Defaulted so a file written before the platform emitted store types
4095
- // (database rows only) still parses. The platform starts writing
4096
- // storage rows, with `type` set, once forks run a compose that reads it.
4097
- type: import_zod7.z.enum(["database", "storage"]).default("database")
4098
- })
4099
- ).default([]),
4100
- installs: import_zod7.z.record(
4101
- import_zod7.z.string(),
4102
- import_zod7.z.strictObject({
4103
- installedVersion: import_zod7.z.string().min(1),
4104
- datastoreBindings: import_zod7.z.record(import_zod7.z.string(), import_zod7.z.string()).nullish()
4105
- })
4106
- ).default({})
4107
- });
4108
- var STUB_IGNORE_PATH = "apps/web/src/app/.gitignore";
4109
- var STUB_IGNORE_HEADER = "# Generated by stardeck-compose \u2014 Module-rail stubs are build outputs; do not edit or commit them.";
4110
- function escapeStubIgnorePattern(pattern) {
4111
- const escaped = pattern.replace(/[\\[\]*?]/g, (char) => `\\${char}`);
4112
- return /^[#!]/.test(escaped) ? `\\${escaped}` : escaped;
4113
- }
4114
- function buildStubIgnore(artifactPaths) {
4115
- const prefix = "apps/web/src/app/";
4116
- const entries = artifactPaths.filter((artifact) => artifact.startsWith(prefix)).map((artifact) => escapeStubIgnorePattern(`/${artifact.slice(prefix.length)}`)).sort();
4117
- return [STUB_IGNORE_HEADER, ...entries].join("\n") + "\n";
4118
- }
4119
-
4120
- // ../../packages/lib/src/shared/module-enablement.ts
4121
- function isModuleEnabledByList(raw, name, kindOf) {
4122
- if (!raw) return true;
4123
- const kind = kindOf(name);
4124
- if (kind != null && kind !== "feature") return true;
4125
- return raw.split(",").includes(name);
4126
- }
4127
-
4128
4012
  // ../../packages/lib/src/shared/platform-owned-paths.ts
4129
4013
  var APP_WEB_PREFIX = "apps/web/";
4130
4014
  var MODULE_STUB_MARKER = "@stardeck-module-rail-generated";
@@ -4271,13 +4155,13 @@ function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
4271
4155
  function createMessageTree() {
4272
4156
  return /* @__PURE__ */ Object.create(null);
4273
4157
  }
4274
- function assertMessageTree(value, path3) {
4158
+ function assertMessageTree(value, path4) {
4275
4159
  if (typeof value === "string") return;
4276
4160
  if (value === null || Array.isArray(value) || typeof value !== "object") {
4277
- throw new Error(`Invalid message at ${path3}: expected string or nested object`);
4161
+ throw new Error(`Invalid message at ${path4}: expected string or nested object`);
4278
4162
  }
4279
4163
  for (const [key, child] of Object.entries(value)) {
4280
- assertMessageTree(child, `${path3}.${key}`);
4164
+ assertMessageTree(child, `${path4}.${key}`);
4281
4165
  }
4282
4166
  }
4283
4167
  function cloneTree(value) {
@@ -4287,17 +4171,17 @@ function cloneTree(value) {
4287
4171
  }
4288
4172
  return clone;
4289
4173
  }
4290
- function collectLeafPaths(value, path3, out) {
4174
+ function collectLeafPaths(value, path4, out) {
4291
4175
  if (typeof value === "string") {
4292
- out.push(path3);
4176
+ out.push(path4);
4293
4177
  return;
4294
4178
  }
4295
- assertMessageTree(value, path3);
4296
- for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path3}.${key}`, out);
4179
+ assertMessageTree(value, path4);
4180
+ for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path4}.${key}`, out);
4297
4181
  }
4298
- function overrideInto(target, source, owner, path3, missing) {
4182
+ function overrideInto(target, source, owner, path4, missing) {
4299
4183
  for (const [key, value] of Object.entries(source)) {
4300
- const nextPath = path3 ? `${path3}.${key}` : key;
4184
+ const nextPath = path4 ? `${path4}.${key}` : key;
4301
4185
  if (!Object.prototype.hasOwnProperty.call(target, key)) {
4302
4186
  collectLeafPaths(value, nextPath, missing);
4303
4187
  continue;
@@ -4324,9 +4208,9 @@ function overrideInto(target, source, owner, path3, missing) {
4324
4208
  function composeLocaleMessagesForPlan(layers) {
4325
4209
  const result = createMessageTree();
4326
4210
  const leafOwners = /* @__PURE__ */ new Map();
4327
- function mergeInto(target, source, owner, path3) {
4211
+ function mergeInto(target, source, owner, path4) {
4328
4212
  for (const [key, value] of Object.entries(source)) {
4329
- const nextPath = path3 ? `${path3}.${key}` : key;
4213
+ const nextPath = path4 ? `${path4}.${key}` : key;
4330
4214
  const hasExisting = Object.prototype.hasOwnProperty.call(target, key);
4331
4215
  const existing = hasExisting ? target[key] : void 0;
4332
4216
  if (typeof value === "string") {
@@ -4870,8 +4754,8 @@ var METHOD_ORDER = {
4870
4754
  function compareNames2(a, b) {
4871
4755
  return a < b ? -1 : a > b ? 1 : 0;
4872
4756
  }
4873
- function endpointPairKey(method, path3) {
4874
- return `${method} ${path3}`;
4757
+ function endpointPairKey(method, path4) {
4758
+ return `${method} ${path4}`;
4875
4759
  }
4876
4760
  function apiPathToRouteFile(apiPath) {
4877
4761
  if (!apiPath.startsWith("/api/")) {
@@ -4885,11 +4769,11 @@ function routeEntryToAppPageFile(nextRelativePath) {
4885
4769
  }
4886
4770
  var APP_ROUTE_FILE_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
4887
4771
  var API_ROUTE_FILE_SUFFIXES = ["/route.ts", "/route.tsx", "/route.jsx", "/route.js"];
4888
- function appRouteFileToNextRelativePath(path3) {
4889
- const pagePath = appPageFileToNextRelativePath(path3, APP_PACKAGE_DIR);
4772
+ function appRouteFileToNextRelativePath(path4) {
4773
+ const pagePath = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
4890
4774
  if (pagePath != null) return pagePath;
4891
- if (!path3.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
4892
- const relative = path3.slice(APP_ROUTE_FILE_PREFIX.length);
4775
+ if (!path4.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
4776
+ const relative = path4.slice(APP_ROUTE_FILE_PREFIX.length);
4893
4777
  if (!relative.startsWith("api/")) return null;
4894
4778
  const suffix = API_ROUTE_FILE_SUFFIXES.find((candidate) => relative.endsWith(candidate));
4895
4779
  if (!suffix) return null;
@@ -4981,9 +4865,9 @@ function legacyHandlerPropertyMatchesPath(property, method, physicalPath, bindin
4981
4865
  const expectedTokens = knownAliases[`${binding} ${method} ${physicalPath}`] ?? pathTokens;
4982
4866
  return handlerTokens.length === expectedTokens.length && handlerTokens.every((token, index) => token === expectedTokens[index]);
4983
4867
  }
4984
- function parseLegacyApiAdapter(path3, source) {
4985
- const physicalPath = appRouteFileToNextRelativePath(path3);
4986
- if (expectedMarkerKindForPath(path3) !== "endpoint" || physicalPath == null) return null;
4868
+ function parseLegacyApiAdapter(path4, source) {
4869
+ const physicalPath = appRouteFileToNextRelativePath(path4);
4870
+ if (expectedMarkerKindForPath(path4) !== "endpoint" || physicalPath == null) return null;
4987
4871
  const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
4988
4872
  const match = firstLine.match(
4989
4873
  new RegExp(
@@ -4993,7 +4877,7 @@ function parseLegacyApiAdapter(path3, source) {
4993
4877
  if (!match || match[2] !== physicalPath) return null;
4994
4878
  const owner = match[1];
4995
4879
  if (!isValidModuleName(owner)) return null;
4996
- const file = import_typescript4.default.createSourceFile(path3, source, import_typescript4.default.ScriptTarget.Latest, false, import_typescript4.default.ScriptKind.TS);
4880
+ const file = import_typescript4.default.createSourceFile(path4, source, import_typescript4.default.ScriptTarget.Latest, false, import_typescript4.default.ScriptKind.TS);
4997
4881
  if (file.parseDiagnostics.length > 0) {
4998
4882
  return null;
4999
4883
  }
@@ -5035,9 +4919,9 @@ function parseLegacyApiAdapter(path3, source) {
5035
4919
  }
5036
4920
  return handlerBinding != null && methods.size > 0 ? { owner, methods: [...methods] } : null;
5037
4921
  }
5038
- function parseLegacyRouteAdapter(path3, source) {
5039
- const physicalPath = appRouteFileToNextRelativePath(path3);
5040
- if (expectedMarkerKindForPath(path3) !== "route" || physicalPath == null) return null;
4922
+ function parseLegacyRouteAdapter(path4, source) {
4923
+ const physicalPath = appRouteFileToNextRelativePath(path4);
4924
+ if (expectedMarkerKindForPath(path4) !== "route" || physicalPath == null) return null;
5041
4925
  const normalized = source.replace(/\r\n/g, "\n");
5042
4926
  const match = normalized.match(
5043
4927
  new RegExp(
@@ -5049,30 +4933,30 @@ function parseLegacyRouteAdapter(path3, source) {
5049
4933
  const nextRelativePath = match[2];
5050
4934
  return isValidModuleName(owner) && nextRelativePath === physicalPath ? { owner, nextRelativePath } : null;
5051
4935
  }
5052
- function legacyRouteAdapterMatchesStub(path3, source, stub) {
5053
- const legacy = parseLegacyRouteAdapter(path3, source);
4936
+ function legacyRouteAdapterMatchesStub(path4, source, stub) {
4937
+ const legacy = parseLegacyRouteAdapter(path4, source);
5054
4938
  return legacy != null && legacy.owner === stub.owner;
5055
4939
  }
5056
4940
  var API_ROUTE_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/api/`;
5057
4941
  var APP_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
5058
4942
  var PAGE_FILE_SUFFIXES = ["/page.tsx", "/page.ts", "/page.jsx", "/page.js"];
5059
- function expectedMarkerKindForPath(path3) {
5060
- if (path3 === MODULES_GEN_PATH) return "registry";
5061
- if (path3 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
5062
- if (path3 === MODULE_DATASTORES_GEN_PATH) return "datastores";
5063
- if (path3 === MODULE_I18N_GEN_PATH) return "i18n";
5064
- if (path3 === MODULE_INIT_SERVER_GEN_PATH) return "init";
5065
- if (path3 === MODULE_SERVER_GEN_PATH) return "server";
5066
- if (path3.startsWith(API_ROUTE_DIR_PREFIX) && path3.endsWith("/route.ts")) return "endpoint";
5067
- if (path3.startsWith(APP_DIR_PREFIX) && !path3.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path3.endsWith(suffix))) {
4943
+ function expectedMarkerKindForPath(path4) {
4944
+ if (path4 === MODULES_GEN_PATH) return "registry";
4945
+ if (path4 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
4946
+ if (path4 === MODULE_DATASTORES_GEN_PATH) return "datastores";
4947
+ if (path4 === MODULE_I18N_GEN_PATH) return "i18n";
4948
+ if (path4 === MODULE_INIT_SERVER_GEN_PATH) return "init";
4949
+ if (path4 === MODULE_SERVER_GEN_PATH) return "server";
4950
+ if (path4.startsWith(API_ROUTE_DIR_PREFIX) && path4.endsWith("/route.ts")) return "endpoint";
4951
+ if (path4.startsWith(APP_DIR_PREFIX) && !path4.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path4.endsWith(suffix))) {
5068
4952
  return "route";
5069
4953
  }
5070
4954
  return null;
5071
4955
  }
5072
- function markerCongruentWithPath(path3, marker) {
5073
- if (expectedMarkerKindForPath(path3) !== marker.kind) return false;
4956
+ function markerCongruentWithPath(path4, marker) {
4957
+ if (expectedMarkerKindForPath(path4) !== marker.kind) return false;
5074
4958
  if (marker.kind === "route") {
5075
- const derived = appPageFileToNextRelativePath(path3, APP_PACKAGE_DIR);
4959
+ const derived = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
5076
4960
  return derived != null && derived === marker.nextRelativePath;
5077
4961
  }
5078
4962
  return true;
@@ -5343,8 +5227,8 @@ function buildGeneratedModuleRegistry(input) {
5343
5227
  function activeEnhancementPeers(registryModule) {
5344
5228
  return new Set(registryModule.enhancements.filter((e) => e.active).map((e) => e.peer));
5345
5229
  }
5346
- function endpointBucketKey(path3, owner, scope) {
5347
- return `${path3}\0${owner}\0${scopeLabel(scope)}`;
5230
+ function endpointBucketKey(path4, owner, scope) {
5231
+ return `${path4}\0${owner}\0${scopeLabel(scope)}`;
5348
5232
  }
5349
5233
  function planRouteStubs(input) {
5350
5234
  const endpointBuckets = /* @__PURE__ */ new Map();
@@ -5468,7 +5352,7 @@ function planRouteStubs(input) {
5468
5352
  for (const entry of entries) {
5469
5353
  const nextRelativePath = entry.nextRelativePath;
5470
5354
  const destination = normalizeAppRouterDestinationPattern(nextRelativePath);
5471
- const path3 = routeEntryToAppPageFile(nextRelativePath);
5355
+ const path4 = routeEntryToAppPageFile(nextRelativePath);
5472
5356
  const existing = routeDestinationOwners.get(destination);
5473
5357
  if (existing) {
5474
5358
  throw new Error(
@@ -5478,13 +5362,13 @@ function planRouteStubs(input) {
5478
5362
  routeDestinationOwners.set(destination, {
5479
5363
  owner: regMod.name,
5480
5364
  nextRelativePath,
5481
- path: path3
5365
+ path: path4
5482
5366
  });
5483
5367
  const importModule = routeEntryImport(regMod.name, nextRelativePath);
5484
5368
  const analyzed = analyzeRouteEntryPath(nextRelativePath);
5485
5369
  const mount = mounts.find((claim) => routeEntryCoveredByMountClaim(analyzed, claim));
5486
5370
  routeStubs.push({
5487
- path: path3,
5371
+ path: path4,
5488
5372
  owner: regMod.name,
5489
5373
  nextRelativePath,
5490
5374
  importModule,
@@ -5545,16 +5429,16 @@ function planCompositionArtifacts(input) {
5545
5429
  ...stubs.routeStubs.map((stub) => stub.path)
5546
5430
  ]);
5547
5431
  const existingGeneratedPathSet = new Set(input.existingGeneratedPaths);
5548
- const isProvenStaleDeletionCandidate = (path3, content) => {
5549
- if (path3 === MODULES_GEN_PATH) return false;
5550
- if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path3)) return false;
5551
- if (desiredPaths.has(path3)) return false;
5552
- if (!existingGeneratedPathSet.has(path3)) return false;
5553
- const legacy = parseLegacyApiAdapter(path3, content);
5432
+ const isProvenStaleDeletionCandidate = (path4, content) => {
5433
+ if (path4 === MODULES_GEN_PATH) return false;
5434
+ if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path4)) return false;
5435
+ if (desiredPaths.has(path4)) return false;
5436
+ if (!existingGeneratedPathSet.has(path4)) return false;
5437
+ const legacy = parseLegacyApiAdapter(path4, content);
5554
5438
  if (legacy) return true;
5555
- if (parseLegacyRouteAdapter(path3, content)) return true;
5439
+ if (parseLegacyRouteAdapter(path4, content)) return true;
5556
5440
  const marker = parseCompositionArtifactMarker(content);
5557
- return marker != null && markerCongruentWithPath(path3, marker);
5441
+ return marker != null && markerCongruentWithPath(path4, marker);
5558
5442
  };
5559
5443
  const plannedByDestination = /* @__PURE__ */ new Map();
5560
5444
  for (const stub of stubs.routeStubs) {
@@ -5708,13 +5592,13 @@ function planCompositionArtifacts(input) {
5708
5592
  for (const stub of stubs.routeStubs) desired.set(stub.path, stub.content);
5709
5593
  const endpointStubByPath = new Map(stubs.endpointStubs.map((stub) => [stub.path, stub]));
5710
5594
  const routeStubByPath = new Map(stubs.routeStubs.map((stub) => [stub.path, stub]));
5711
- for (const [path3] of desired) {
5712
- const existing = input.existingFiles[path3];
5595
+ for (const [path4] of desired) {
5596
+ const existing = input.existingFiles[path4];
5713
5597
  if (existing == null) continue;
5714
5598
  let marker = parseCompositionArtifactMarker(existing);
5715
- const plannedEndpoint = endpointStubByPath.get(path3);
5716
- const plannedRoute = routeStubByPath.get(path3);
5717
- const legacy = parseLegacyApiAdapter(path3, existing);
5599
+ const plannedEndpoint = endpointStubByPath.get(path4);
5600
+ const plannedRoute = routeStubByPath.get(path4);
5601
+ const legacy = parseLegacyApiAdapter(path4, existing);
5718
5602
  if (legacy && plannedEndpoint) {
5719
5603
  marker = {
5720
5604
  kind: "endpoint",
@@ -5722,66 +5606,66 @@ function planCompositionArtifacts(input) {
5722
5606
  scope: plannedEndpoint.scope
5723
5607
  };
5724
5608
  }
5725
- if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path3, existing, plannedRoute)) {
5609
+ if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path4, existing, plannedRoute)) {
5726
5610
  marker = {
5727
5611
  kind: "route",
5728
5612
  owner: plannedRoute.owner,
5729
5613
  nextRelativePath: plannedRoute.nextRelativePath
5730
5614
  };
5731
5615
  }
5732
- const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path3);
5616
+ const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path4);
5733
5617
  if (!marker) {
5734
5618
  return {
5735
5619
  ok: false,
5736
- 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}`,
5737
- path: path3
5620
+ 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}`,
5621
+ path: path4
5738
5622
  };
5739
5623
  }
5740
- const expectedKind = expectedMarkerKindForPath(path3);
5624
+ const expectedKind = expectedMarkerKindForPath(path4);
5741
5625
  if (marker.kind !== expectedKind) {
5742
5626
  return {
5743
5627
  ok: false,
5744
- 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)`,
5745
- path: path3
5628
+ 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)`,
5629
+ path: path4
5746
5630
  };
5747
5631
  }
5748
5632
  if (marker.kind === "endpoint") {
5749
- const stub = endpointStubByPath.get(path3);
5633
+ const stub = endpointStubByPath.get(path4);
5750
5634
  const ownerMatches = stub != null && marker.owner === stub.owner;
5751
5635
  const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
5752
5636
  if (!ownerMatches || !scopeMatches) {
5753
5637
  return {
5754
5638
  ok: false,
5755
- 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) : "?"})`,
5756
- path: path3,
5639
+ 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) : "?"})`,
5640
+ path: path4,
5757
5641
  owner: stub?.owner
5758
5642
  };
5759
5643
  }
5760
5644
  } else if (marker.kind === "route") {
5761
- const stub = routeStubByPath.get(path3);
5645
+ const stub = routeStubByPath.get(path4);
5762
5646
  const ownerMatches = stub != null && marker.owner === stub.owner;
5763
5647
  const pathMatches = stub != null && marker.nextRelativePath === stub.nextRelativePath;
5764
5648
  if (!ownerMatches || !pathMatches) {
5765
5649
  return {
5766
5650
  ok: false,
5767
- 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 ?? "?"})`,
5768
- path: path3,
5651
+ 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 ?? "?"})`,
5652
+ path: path4,
5769
5653
  owner: stub?.owner
5770
5654
  };
5771
5655
  }
5772
5656
  }
5773
5657
  }
5774
5658
  const deletes = [];
5775
- for (const path3 of input.existingGeneratedPaths) {
5776
- const existing = input.existingFiles[path3];
5659
+ for (const path4 of input.existingGeneratedPaths) {
5660
+ const existing = input.existingFiles[path4];
5777
5661
  if (existing == null) continue;
5778
- if (!isProvenStaleDeletionCandidate(path3, existing)) {
5662
+ if (!isProvenStaleDeletionCandidate(path4, existing)) {
5779
5663
  continue;
5780
5664
  }
5781
- deletes.push(path3);
5665
+ deletes.push(path4);
5782
5666
  }
5783
5667
  deletes.sort(compareNames2);
5784
- 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));
5668
+ 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));
5785
5669
  return {
5786
5670
  ok: true,
5787
5671
  plan: {
@@ -5810,8 +5694,16 @@ async function checkoutParsesTypedStores(sandbox) {
5810
5694
  try {
5811
5695
  if (!await sandbox.fileExists(APP_PACKAGE_JSON_PATH)) return false;
5812
5696
  const pkg = JSON.parse(await sandbox.readFile(APP_PACKAGE_JSON_PATH));
5813
- const range = pkg.devDependencies?.[COMPOSE_PACKAGE] ?? pkg.dependencies?.[COMPOSE_PACKAGE];
5814
- if (!range) return false;
5697
+ return composeRangeParsesTypedStores(
5698
+ pkg.devDependencies?.[COMPOSE_PACKAGE] ?? pkg.dependencies?.[COMPOSE_PACKAGE]
5699
+ );
5700
+ } catch {
5701
+ return false;
5702
+ }
5703
+ }
5704
+ function composeRangeParsesTypedStores(range) {
5705
+ if (!range) return false;
5706
+ try {
5815
5707
  const lowest = import_semver.default.minVersion(range);
5816
5708
  return lowest != null && import_semver.default.gte(lowest, TYPED_STORES_MIN_COMPOSE_VERSION);
5817
5709
  } catch {
@@ -6043,6 +5935,50 @@ function rejectPseudoModuleEntries(entries, parentDir, label) {
6043
5935
  return dirs;
6044
5936
  }
6045
5937
 
5938
+ // ../../packages/lib/src/shared/module-composition.ts
5939
+ var import_zod7 = require("zod");
5940
+ var creationSourceBlankSchema = import_zod7.z.object({
5941
+ type: import_zod7.z.literal("blank")
5942
+ });
5943
+ var creationSourceBlueprintSchema = import_zod7.z.object({
5944
+ type: import_zod7.z.literal("blueprint"),
5945
+ /** Internal template/fork project id — keep template naming in identifiers. */
5946
+ blueprintProjectId: import_zod7.z.string().uuid(),
5947
+ /**
5948
+ * `null`/absent means every feature Module. `[]` is refused: the platform
5949
+ * serialises it to an empty `NEXT_PUBLIC_STARDECK_ENABLED_MODULES`, which the
5950
+ * enablement parser reads back as "all" (`module-enablement.ts`) — the
5951
+ * opposite of what an empty selection means.
5952
+ */
5953
+ enabledModules: import_zod7.z.array(moduleNameSchema).min(1, "Enable at least one Module").max(100).nullable().optional()
5954
+ });
5955
+ var creationSourceSchema = import_zod7.z.discriminatedUnion("type", [
5956
+ creationSourceBlankSchema,
5957
+ creationSourceBlueprintSchema
5958
+ ]);
5959
+ var moduleInstallHandoffSchema = import_zod7.z.object({
5960
+ moduleName: import_zod7.z.string().min(1),
5961
+ homeProjectId: import_zod7.z.string().uuid(),
5962
+ installedVersion: import_zod7.z.string().min(1),
5963
+ baselineSha: import_zod7.z.string().min(1),
5964
+ mounts: import_zod7.z.array(
5965
+ import_zod7.z.object({
5966
+ group: import_zod7.z.string(),
5967
+ segment: import_zod7.z.string()
5968
+ })
5969
+ ),
5970
+ integration: import_zod7.z.array(import_zod7.z.string()),
5971
+ workflows: import_zod7.z.array(import_zod7.z.string()),
5972
+ prunedEnhancements: import_zod7.z.array(import_zod7.z.string()),
5973
+ restoredEnhancements: import_zod7.z.array(import_zod7.z.string()).optional(),
5974
+ permissions: import_zod7.z.array(
5975
+ import_zod7.z.object({
5976
+ key: import_zod7.z.string().min(1),
5977
+ description: import_zod7.z.string().min(1)
5978
+ })
5979
+ ).optional()
5980
+ });
5981
+
6046
5982
  // ../../packages/lib/src/shared/schema-ops.ts
6047
5983
  var import_zod8 = require("zod");
6048
5984
  var columnSpecSchema = import_zod8.z.object({
@@ -6256,11 +6192,11 @@ function countedCompositionContext(context) {
6256
6192
  counts.sandboxExecs += 1;
6257
6193
  return context.sandbox.exec(...args);
6258
6194
  },
6259
- readFile: (path3) => {
6195
+ readFile: (path4) => {
6260
6196
  counts.sandboxReads += 1;
6261
- return context.sandbox.readFile(path3);
6197
+ return context.sandbox.readFile(path4);
6262
6198
  },
6263
- fileExists: (path3) => context.sandbox.fileExists(path3),
6199
+ fileExists: (path4) => context.sandbox.fileExists(path4),
6264
6200
  fetchRemoteRef: (...args) => context.sandbox.fetchRemoteRef(...args),
6265
6201
  addDependencies: (specs) => context.sandbox.addDependencies(specs)
6266
6202
  }
@@ -6472,14 +6408,14 @@ async function discoverModuleRouteEntries(context, moduleName) {
6472
6408
  }
6473
6409
  return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
6474
6410
  }
6475
- async function writeSandboxFileAtomic(target, path3, content) {
6476
- const dir = dirnamePosix(path3);
6477
- const tmp = `${path3}.tmp.${target.runId}`;
6411
+ async function writeSandboxFileAtomic(target, path4, content) {
6412
+ const dir = dirnamePosix(path4);
6413
+ const tmp = `${path4}.tmp.${target.runId}`;
6478
6414
  const b64 = Buffer.from(content, "utf8").toString("base64");
6479
6415
  const cmd = [
6480
6416
  `mkdir -p ${shellQuote(dir)}`,
6481
6417
  `printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
6482
- `mv -f ${shellQuote(tmp)} ${shellQuote(path3)}`
6418
+ `mv -f ${shellQuote(tmp)} ${shellQuote(path4)}`
6483
6419
  ].join(" && ");
6484
6420
  const result = await target.sandbox.exec(cmd, { raiseOnError: false });
6485
6421
  if (result.exitCode !== 0) {
@@ -6494,14 +6430,14 @@ async function writeSandboxFileAtomic(target, path3, content) {
6494
6430
  }
6495
6431
  const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
6496
6432
  throw new Error(
6497
- `[ModuleRail] composition artifacts: failed to write ${redactSecrets(path3)}: ${redactSecrets(detail)}`
6433
+ `[ModuleRail] composition artifacts: failed to write ${redactSecrets(path4)}: ${redactSecrets(detail)}`
6498
6434
  );
6499
6435
  }
6500
6436
  }
6501
6437
  async function collectAbsentWriteParentDirs(context, writePaths) {
6502
6438
  const absent = /* @__PURE__ */ new Set();
6503
- for (const path3 of writePaths) {
6504
- let dir = dirnamePosix(path3);
6439
+ for (const path4 of writePaths) {
6440
+ let dir = dirnamePosix(path4);
6505
6441
  while (dir !== "." && dir !== "") {
6506
6442
  if (absent.has(dir)) {
6507
6443
  dir = dirnamePosix(dir);
@@ -6538,12 +6474,12 @@ async function removeAbsentWriteParentDirsOnRollback(context, dirsDeepestFirst)
6538
6474
  );
6539
6475
  }
6540
6476
  }
6541
- async function deleteSandboxFile(context, path3) {
6542
- const result = await context.sandbox.exec(`rm -f ${shellQuote(path3)}`, { raiseOnError: false });
6477
+ async function deleteSandboxFile(context, path4) {
6478
+ const result = await context.sandbox.exec(`rm -f ${shellQuote(path4)}`, { raiseOnError: false });
6543
6479
  if (result.exitCode !== 0) {
6544
6480
  const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
6545
6481
  throw new Error(
6546
- `[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path3)}: ${redactSecrets(detail)}`
6482
+ `[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path4)}: ${redactSecrets(detail)}`
6547
6483
  );
6548
6484
  }
6549
6485
  }
@@ -6610,37 +6546,37 @@ async function applyCompositionArtifactMutations(context, plan, existingFiles) {
6610
6546
  await writeSandboxFileAtomic(context, write.path, write.content);
6611
6547
  appliedWrites.push({ path: write.path, previous });
6612
6548
  }
6613
- for (const path3 of plan.deletes) {
6614
- const previous = existingFiles[path3];
6549
+ for (const path4 of plan.deletes) {
6550
+ const previous = existingFiles[path4];
6615
6551
  if (previous == null) continue;
6616
- await deleteSandboxFile(context, path3);
6617
- appliedDeletes.push({ path: path3, previous });
6552
+ await deleteSandboxFile(context, path4);
6553
+ appliedDeletes.push({ path: path4, previous });
6618
6554
  }
6619
6555
  } catch (error) {
6620
6556
  console.log(
6621
6557
  `[ModuleRail] runId=${context.runId} composition artifacts mid-apply failure; rolling back writes=${appliedWrites.length} deletes=${appliedDeletes.length} newDirs=${newlyCreatedParentDirs.length}`
6622
6558
  );
6623
- for (const { path: path3, previous } of [...appliedDeletes].reverse()) {
6559
+ for (const { path: path4, previous } of [...appliedDeletes].reverse()) {
6624
6560
  try {
6625
- await writeSandboxFileAtomic(context, path3, previous);
6561
+ await writeSandboxFileAtomic(context, path4, previous);
6626
6562
  } catch (rollbackError) {
6627
6563
  console.log(
6628
- `[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path3)}: ${redactSecrets(
6564
+ `[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path4)}: ${redactSecrets(
6629
6565
  rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
6630
6566
  )}`
6631
6567
  );
6632
6568
  }
6633
6569
  }
6634
- for (const { path: path3, previous } of [...appliedWrites].reverse()) {
6570
+ for (const { path: path4, previous } of [...appliedWrites].reverse()) {
6635
6571
  try {
6636
6572
  if (previous == null) {
6637
- await deleteSandboxFile(context, path3);
6573
+ await deleteSandboxFile(context, path4);
6638
6574
  } else {
6639
- await writeSandboxFileAtomic(context, path3, previous);
6575
+ await writeSandboxFileAtomic(context, path4, previous);
6640
6576
  }
6641
6577
  } catch (rollbackError) {
6642
6578
  console.log(
6643
- `[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path3)}: ${redactSecrets(
6579
+ `[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path4)}: ${redactSecrets(
6644
6580
  rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
6645
6581
  )}`
6646
6582
  );
@@ -6674,39 +6610,39 @@ async function listExistingGeneratedStubPaths(context) {
6674
6610
  COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
6675
6611
  );
6676
6612
  const presentPaths = [];
6677
- for (const path3 of candidates) {
6613
+ for (const path4 of candidates) {
6678
6614
  const presence = assertRealCompositionArtifactFilePresence(
6679
- path3,
6680
- kinds.get(path3) ?? "unreadable"
6615
+ path4,
6616
+ kinds.get(path4) ?? "unreadable"
6681
6617
  );
6682
- if (presence === "file") presentPaths.push(path3);
6618
+ if (presence === "file") presentPaths.push(path4);
6683
6619
  }
6684
6620
  let contents;
6685
6621
  try {
6686
6622
  contents = await readFilesMany(context, presentPaths);
6687
6623
  } catch (error) {
6688
- const path3 = readFailurePath(error);
6624
+ const path4 = readFailurePath(error);
6689
6625
  throw new Error(
6690
- `[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path3)}: ${redactSecrets(
6626
+ `[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path4)}: ${redactSecrets(
6691
6627
  error instanceof Error ? error.message : String(error)
6692
6628
  )}`
6693
6629
  );
6694
6630
  }
6695
6631
  const owned = [];
6696
- for (const path3 of presentPaths) {
6697
- const content = contents.get(path3);
6632
+ for (const path4 of presentPaths) {
6633
+ const content = contents.get(path4);
6698
6634
  const marker = parseCompositionArtifactMarker(content);
6699
- if (parseLegacyApiAdapter(path3, content) || parseLegacyRouteAdapter(path3, content)) {
6700
- owned.push(path3);
6635
+ if (parseLegacyApiAdapter(path4, content) || parseLegacyRouteAdapter(path4, content)) {
6636
+ owned.push(path4);
6701
6637
  continue;
6702
6638
  }
6703
6639
  if (marker == null) continue;
6704
- if (!markerCongruentWithPath(path3, marker)) {
6640
+ if (!markerCongruentWithPath(path4, marker)) {
6705
6641
  throw new Error(
6706
- `[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)`
6642
+ `[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)`
6707
6643
  );
6708
6644
  }
6709
- owned.push(path3);
6645
+ owned.push(path4);
6710
6646
  }
6711
6647
  return owned;
6712
6648
  }
@@ -6839,7 +6775,7 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
6839
6775
  { raiseOnError: false }
6840
6776
  );
6841
6777
  if (result.exitCode !== 0) {
6842
- for (const path3 of chunk) kinds.set(path3, "unreadable");
6778
+ for (const path4 of chunk) kinds.set(path4, "unreadable");
6843
6779
  continue;
6844
6780
  }
6845
6781
  const lines = result.output.split("\n").filter(Boolean);
@@ -6850,31 +6786,31 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
6850
6786
  }
6851
6787
  } else {
6852
6788
  for (const line of lines) {
6853
- const [rawKind, path3] = line.split(" ", 2);
6789
+ const [rawKind, path4] = line.split(" ", 2);
6854
6790
  const kind = rawKind?.replace(/^entry-kind:/, "");
6855
- if (path3 && chunk.includes(path3) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
6856
- kinds.set(path3, kind);
6791
+ if (path4 && chunk.includes(path4) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
6792
+ kinds.set(path4, kind);
6857
6793
  }
6858
6794
  }
6859
6795
  }
6860
- for (const path3 of chunk) {
6861
- if (!kinds.has(path3)) kinds.set(path3, "unreadable");
6796
+ for (const path4 of chunk) {
6797
+ if (!kinds.has(path4)) kinds.set(path4, "unreadable");
6862
6798
  }
6863
6799
  }
6864
6800
  return kinds;
6865
6801
  }
6866
- async function classifySandboxPathPresenceKind(context, path3, probeToken) {
6867
- return (await classifySandboxPathPresenceKindMany(context, [path3], probeToken)).get(path3);
6802
+ async function classifySandboxPathPresenceKind(context, path4, probeToken) {
6803
+ return (await classifySandboxPathPresenceKindMany(context, [path4], probeToken)).get(path4);
6868
6804
  }
6869
6805
  var LOCALE_JSON_KIND_PROBE_TOKEN = "locale-json-kind";
6870
- function assertRealLocaleJsonFilePresence(path3, presence) {
6806
+ function assertRealLocaleJsonFilePresence(path4, presence) {
6871
6807
  if (presence === "file") return;
6872
6808
  if (presence === "absent") {
6873
- throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path3}`);
6809
+ throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path4}`);
6874
6810
  }
6875
- const presenceError = formatConventionalEntryPresenceError(path3, presence);
6811
+ const presenceError = formatConventionalEntryPresenceError(path4, presence);
6876
6812
  throw new Error(
6877
- `[ModuleRail] composition artifacts: ${presenceError ?? `${path3} is not a real locale JSON file`}`
6813
+ `[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real locale JSON file`}`
6878
6814
  );
6879
6815
  }
6880
6816
  function splitTaggedLine(line) {
@@ -6949,11 +6885,11 @@ async function listLocaleBasenamesIfDirMany(context, dirs) {
6949
6885
  }
6950
6886
  async function readFilesMany(context, paths) {
6951
6887
  const sortedPaths = [...paths].sort((a, b) => a.localeCompare(b));
6952
- const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path3) => {
6888
+ const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path4) => {
6953
6889
  try {
6954
- return { path: path3, value: await context.sandbox.readFile(path3) };
6890
+ return { path: path4, value: await context.sandbox.readFile(path4) };
6955
6891
  } catch (error) {
6956
- return { path: path3, error };
6892
+ return { path: path4, error };
6957
6893
  }
6958
6894
  });
6959
6895
  const files = /* @__PURE__ */ new Map();
@@ -6976,58 +6912,58 @@ function readFailurePath(error) {
6976
6912
  var CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN = "conventional-entry-kind";
6977
6913
  var SERVER_REGISTRATIONS_FILE = "server/registrations.ts";
6978
6914
  var COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN = "composition-artifact-kind";
6979
- async function classifyCompositionArtifactPathKind(context, path3) {
6980
- return classifySandboxPathPresenceKind(context, path3, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
6915
+ async function classifyCompositionArtifactPathKind(context, path4) {
6916
+ return classifySandboxPathPresenceKind(context, path4, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
6981
6917
  }
6982
- function assertRealCompositionArtifactFilePresence(path3, presence) {
6918
+ function assertRealCompositionArtifactFilePresence(path4, presence) {
6983
6919
  if (presence === "file") return "file";
6984
6920
  if (presence === "absent") return "absent";
6985
- const presenceError = formatConventionalEntryPresenceError(path3, presence);
6921
+ const presenceError = formatConventionalEntryPresenceError(path4, presence);
6986
6922
  throw new Error(
6987
- `[ModuleRail] composition artifacts: ${presenceError ?? `${path3} is not a real composition artifact file`}`
6923
+ `[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real composition artifact file`}`
6988
6924
  );
6989
6925
  }
6990
- async function readConventionalEntryIfRealFile(path3, kind, presences, files) {
6991
- const presence = presences.get(path3) ?? "unreadable";
6992
- const presenceError = formatConventionalEntryPresenceError(path3, presence);
6926
+ async function readConventionalEntryIfRealFile(path4, kind, presences, files) {
6927
+ const presence = presences.get(path4) ?? "unreadable";
6928
+ const presenceError = formatConventionalEntryPresenceError(path4, presence);
6993
6929
  if (presenceError) {
6994
6930
  throw new Error(`[ModuleRail] composition artifacts: ${presenceError}`);
6995
6931
  }
6996
6932
  if (presence === "absent") {
6997
6933
  return null;
6998
6934
  }
6999
- const source = files.get(path3);
6935
+ const source = files.get(path4);
7000
6936
  switch (kind) {
7001
6937
  case "contributions":
7002
- assertContributionsExport(source, path3);
7003
- assertClientSafeConventionalEntryImports(source, path3, "contributions");
6938
+ assertContributionsExport(source, path4);
6939
+ assertClientSafeConventionalEntryImports(source, path4, "contributions");
7004
6940
  break;
7005
6941
  case "slotCatalogs":
7006
- assertSlotCatalogsExport(source, path3);
7007
- assertClientSafeConventionalEntryImports(source, path3, "slotCatalogs");
6942
+ assertSlotCatalogsExport(source, path4);
6943
+ assertClientSafeConventionalEntryImports(source, path4, "slotCatalogs");
7008
6944
  break;
7009
6945
  case "initializeEnhancement":
7010
- assertInitializeEnhancementExport(source, path3);
6946
+ assertInitializeEnhancementExport(source, path4);
7011
6947
  break;
7012
6948
  }
7013
6949
  return source;
7014
6950
  }
7015
6951
  function hasRealServerRegistrations(modulePath, presences) {
7016
- const path3 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
7017
- return assertRealCompositionArtifactFilePresence(path3, presences.get(path3) ?? "unreadable") === "file";
6952
+ const path4 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
6953
+ return assertRealCompositionArtifactFilePresence(path4, presences.get(path4) ?? "unreadable") === "file";
7018
6954
  }
7019
- async function readJsonObjectFile(path3, files) {
7020
- const raw = files.get(path3);
6955
+ async function readJsonObjectFile(path4, files) {
6956
+ const raw = files.get(path4);
7021
6957
  let parsed;
7022
6958
  try {
7023
6959
  parsed = JSON.parse(raw);
7024
6960
  } catch (error) {
7025
6961
  throw new Error(
7026
- `[ModuleRail] composition artifacts: invalid JSON at ${path3}: ${error instanceof Error ? error.message : String(error)}`
6962
+ `[ModuleRail] composition artifacts: invalid JSON at ${path4}: ${error instanceof Error ? error.message : String(error)}`
7027
6963
  );
7028
6964
  }
7029
6965
  if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
7030
- throw new Error(`[ModuleRail] composition artifacts: ${path3} root must be an object`);
6966
+ throw new Error(`[ModuleRail] composition artifacts: ${path4} root must be an object`);
7031
6967
  }
7032
6968
  return parsed;
7033
6969
  }
@@ -7076,11 +7012,11 @@ async function collectCompositionEntryFacts(context, registry) {
7076
7012
  classifySandboxPathPresenceKindMany(context, localePaths, LOCALE_JSON_KIND_PROBE_TOKEN)
7077
7013
  ]);
7078
7014
  const presentConventionalPaths = [];
7079
- for (const path3 of conventionalPaths) {
7080
- const presence = conventionalPresences.get(path3) ?? "unreadable";
7081
- if (presence === "file") presentConventionalPaths.push(path3);
7015
+ for (const path4 of conventionalPaths) {
7016
+ const presence = conventionalPresences.get(path4) ?? "unreadable";
7017
+ if (presence === "file") presentConventionalPaths.push(path4);
7082
7018
  }
7083
- const presentLocalePaths = localePaths.filter((path3) => localePresences.get(path3) === "file");
7019
+ const presentLocalePaths = localePaths.filter((path4) => localePresences.get(path4) === "file");
7084
7020
  const files = await readFilesMany(context, [...presentConventionalPaths, ...presentLocalePaths]);
7085
7021
  const substrateLocales = [];
7086
7022
  for (const locale of substrateNames) {
@@ -7178,8 +7114,8 @@ async function collectCompositionEntryFacts(context, registry) {
7178
7114
  });
7179
7115
  if (rootLocales.length > 0) {
7180
7116
  for (const locale of rootLocales) {
7181
- const path3 = `${modulePath}/i18n/${locale}.json`;
7182
- assertRealLocaleJsonFilePresence(path3, localePresences.get(path3) ?? "unreadable");
7117
+ const path4 = `${modulePath}/i18n/${locale}.json`;
7118
+ assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
7183
7119
  }
7184
7120
  const enMessages = await readJsonObjectFile(`${modulePath}/i18n/en.json`, files);
7185
7121
  const localeMessages = { en: enMessages };
@@ -7203,8 +7139,8 @@ async function collectCompositionEntryFacts(context, registry) {
7203
7139
  if (!enh.active || enh.locales.length === 0) continue;
7204
7140
  const peerDir = `${modulePath}/enhancements/${enh.peer}`;
7205
7141
  for (const locale of enh.locales) {
7206
- const path3 = `${peerDir}/i18n/${locale}.json`;
7207
- assertRealLocaleJsonFilePresence(path3, localePresences.get(path3) ?? "unreadable");
7142
+ const path4 = `${peerDir}/i18n/${locale}.json`;
7143
+ assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
7208
7144
  }
7209
7145
  const enMessages = await readJsonObjectFile(`${peerDir}/i18n/en.json`, files);
7210
7146
  const localeMessages = { en: enMessages };
@@ -7405,16 +7341,16 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7405
7341
  for (const stub of probePlan.plan.routeStubs) candidatePaths.add(stub.path);
7406
7342
  const existingFiles = {};
7407
7343
  const remainingCandidatePaths = [];
7408
- for (const path3 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
7409
- if (Object.prototype.hasOwnProperty.call(existingAppPages, path3)) {
7410
- existingFiles[path3] = existingAppPages[path3];
7344
+ for (const path4 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
7345
+ if (Object.prototype.hasOwnProperty.call(existingAppPages, path4)) {
7346
+ existingFiles[path4] = existingAppPages[path4];
7411
7347
  continue;
7412
7348
  }
7413
- if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path3)) {
7414
- existingFiles[path3] = existingAppRoutes[path3];
7349
+ if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path4)) {
7350
+ existingFiles[path4] = existingAppRoutes[path4];
7415
7351
  continue;
7416
7352
  }
7417
- remainingCandidatePaths.push(path3);
7353
+ remainingCandidatePaths.push(path4);
7418
7354
  }
7419
7355
  const candidateKinds = await classifySandboxPathPresenceKindMany(
7420
7356
  context,
@@ -7422,12 +7358,12 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7422
7358
  COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
7423
7359
  );
7424
7360
  const presentCandidatePaths = [];
7425
- for (const path3 of remainingCandidatePaths) {
7361
+ for (const path4 of remainingCandidatePaths) {
7426
7362
  const presence = assertRealCompositionArtifactFilePresence(
7427
- path3,
7428
- candidateKinds.get(path3) ?? "unreadable"
7363
+ path4,
7364
+ candidateKinds.get(path4) ?? "unreadable"
7429
7365
  );
7430
- if (presence === "file") presentCandidatePaths.push(path3);
7366
+ if (presence === "file") presentCandidatePaths.push(path4);
7431
7367
  }
7432
7368
  try {
7433
7369
  Object.assign(
@@ -7435,9 +7371,9 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7435
7371
  Object.fromEntries(await readFilesMany(context, presentCandidatePaths))
7436
7372
  );
7437
7373
  } catch (error) {
7438
- const path3 = readFailurePath(error);
7374
+ const path4 = readFailurePath(error);
7439
7375
  throw new Error(
7440
- `[ModuleRail] composition artifacts: cannot read ${redactSecrets(path3)} during preflight: ${redactSecrets(
7376
+ `[ModuleRail] composition artifacts: cannot read ${redactSecrets(path4)} during preflight: ${redactSecrets(
7441
7377
  error instanceof Error ? error.message : String(error)
7442
7378
  )}`
7443
7379
  );
@@ -7506,14 +7442,142 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7506
7442
  var HOME_I18N_SUBSTRATE_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/substrate`;
7507
7443
  var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
7508
7444
 
7445
+ // src/index.ts
7446
+ var import_typescript6 = __toESM(require("typescript"));
7447
+
7448
+ // src/module-boundaries.ts
7449
+ var import_node_fs2 = __toESM(require("fs"));
7450
+ var import_node_path2 = __toESM(require("path"));
7451
+ var import_typescript5 = __toESM(require("typescript"));
7452
+ var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
7453
+ var RESOLVE_SUFFIXES = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
7454
+ var DATA_FILE = /\.(?:sql|json)$/;
7455
+ function dataPathsIn(fileName, source) {
7456
+ if (!/\.(?:sql|json)["'`]/.test(source)) return [];
7457
+ const paths = [];
7458
+ const visit = (node) => {
7459
+ if ((import_typescript5.default.isStringLiteralLike(node) || import_typescript5.default.isTemplateTail(node)) && DATA_FILE.test(node.text)) {
7460
+ paths.push(node.text);
7461
+ }
7462
+ import_typescript5.default.forEachChild(node, visit);
7463
+ };
7464
+ visit(import_typescript5.default.createSourceFile(fileName, source, import_typescript5.default.ScriptTarget.Latest, false));
7465
+ return paths;
7466
+ }
7467
+ function scanModule(modulesDir, module2) {
7468
+ const scanned = /* @__PURE__ */ new Map();
7469
+ const moduleDir = import_node_path2.default.join(modulesDir, module2);
7470
+ if (!import_node_fs2.default.existsSync(moduleDir)) return scanned;
7471
+ const files = import_node_fs2.default.readdirSync(moduleDir, { recursive: true, encoding: "utf8" }).filter((relative) => SOURCE_FILE.test(relative) && !relative.includes("node_modules"));
7472
+ for (const relative of files) {
7473
+ const absolute = import_node_path2.default.join(moduleDir, relative);
7474
+ if (!import_node_fs2.default.statSync(absolute).isFile()) continue;
7475
+ const source = import_node_fs2.default.readFileSync(absolute, "utf8");
7476
+ const specifiers = import_typescript5.default.preProcessFile(source, true, true).importedFiles.map((f) => f.fileName);
7477
+ const dataPaths = dataPathsIn(absolute, source);
7478
+ const targets = [...specifiers, ...dataPaths].flatMap((spec) => {
7479
+ if (spec.startsWith("@/modules/"))
7480
+ return [import_node_path2.default.join(modulesDir, spec.slice("@/modules/".length))];
7481
+ if (spec.startsWith("./") || spec.startsWith("../")) {
7482
+ return [import_node_path2.default.resolve(import_node_path2.default.dirname(absolute), spec)];
7483
+ }
7484
+ return [];
7485
+ });
7486
+ scanned.set(absolute, {
7487
+ module: module2,
7488
+ relative: relative.split(import_node_path2.default.sep).join("/"),
7489
+ targets,
7490
+ dataPaths
7491
+ });
7492
+ }
7493
+ return scanned;
7494
+ }
7495
+ function findModuleBoundaryViolations(modulesDir, peersByModule, { pruning = false } = {}) {
7496
+ const files = /* @__PURE__ */ new Map();
7497
+ for (const [module2, peers] of Object.entries(peersByModule)) {
7498
+ if (peers.length === 0) continue;
7499
+ for (const [absolute, file] of scanModule(modulesDir, module2)) files.set(absolute, file);
7500
+ }
7501
+ const exempt = (file, peer) => (pruning ? peersByModule[file.module] ?? [] : [peer]).some(
7502
+ (q) => file.relative.startsWith(`enhancements/${q}/`)
7503
+ );
7504
+ const goneWith = (target, peer) => {
7505
+ const relative = import_node_path2.default.relative(modulesDir, target).split(import_node_path2.default.sep).join("/");
7506
+ return relative === peer || relative.startsWith(`${peer}/`) || new RegExp(`^[^/]+/enhancements/${peer}(/|$)`).test(relative);
7507
+ };
7508
+ const tainted = /* @__PURE__ */ new Map();
7509
+ const queue = [];
7510
+ const taint = (absolute, peer) => {
7511
+ const peers = tainted.get(absolute) ?? /* @__PURE__ */ new Set();
7512
+ if (peers.has(peer)) return;
7513
+ peers.add(peer);
7514
+ tainted.set(absolute, peers);
7515
+ queue.push([absolute, peer]);
7516
+ };
7517
+ const importers = /* @__PURE__ */ new Map();
7518
+ for (const [absolute, file] of files) {
7519
+ for (const target of file.targets) {
7520
+ const hit = RESOLVE_SUFFIXES.map((suffix) => target + suffix).find((c) => files.has(c));
7521
+ if (hit) importers.set(hit, [...importers.get(hit) ?? [], absolute]);
7522
+ }
7523
+ for (const peer of peersByModule[file.module] ?? []) {
7524
+ if (exempt(file, peer)) continue;
7525
+ if (file.targets.some((target) => goneWith(target, peer)) || file.dataPaths.some(
7526
+ (p) => p.startsWith(`modules/${peer}/`) || p.includes(`/modules/${peer}/`)
7527
+ )) {
7528
+ taint(absolute, peer);
7529
+ }
7530
+ }
7531
+ }
7532
+ for (let next = queue.shift(); next; next = queue.shift()) {
7533
+ const [target, peer] = next;
7534
+ for (const importer of importers.get(target) ?? []) {
7535
+ const file = files.get(importer);
7536
+ if ((peersByModule[file.module] ?? []).includes(peer) && !exempt(file, peer)) {
7537
+ taint(importer, peer);
7538
+ }
7539
+ }
7540
+ }
7541
+ const violations = [];
7542
+ for (const [absolute, peers] of tainted) {
7543
+ const file = files.get(absolute);
7544
+ for (const peer of peers) {
7545
+ violations.push({ file: `${file.module}/${file.relative}`, module: file.module, peer });
7546
+ }
7547
+ }
7548
+ return violations.sort((a, b) => a.file.localeCompare(b.file) || a.peer.localeCompare(b.peer));
7549
+ }
7550
+ function findOptionalPeerViolations(modulesDir) {
7551
+ if (!import_node_fs2.default.existsSync(modulesDir)) return [];
7552
+ const manifests = {};
7553
+ for (const name of import_node_fs2.default.readdirSync(modulesDir)) {
7554
+ const manifestPath = import_node_path2.default.join(modulesDir, name, "module.json");
7555
+ if (!import_node_fs2.default.existsSync(manifestPath)) continue;
7556
+ manifests[name] = JSON.parse(import_node_fs2.default.readFileSync(manifestPath, "utf8"));
7557
+ }
7558
+ const features = Object.keys(manifests).filter((name) => manifests[name].kind === "feature");
7559
+ const peersByModule = {};
7560
+ for (const [name, manifest] of Object.entries(manifests)) {
7561
+ const uses = new Set(manifest.uses ?? []);
7562
+ peersByModule[name] = features.filter((peer) => peer !== name && !uses.has(peer));
7563
+ }
7564
+ return findModuleBoundaryViolations(modulesDir, peersByModule);
7565
+ }
7566
+ function formatBoundaryViolations(modulesDirLabel, violations) {
7567
+ const files = new Set(violations.map((v) => v.file)).size;
7568
+ return `${files} Module file(s) import a peer Module that may be absent (not in the Module's \`uses\`, or excluded by --enabled).
7569
+ \`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).
7570
+ ` + violations.map((v) => `${modulesDirLabel}/${v.file}: ${v.peer}`).join("\n");
7571
+ }
7572
+
7509
7573
  // src/index.ts
7510
7574
  var MODULES_DIR2 = MODULES_SANDBOX_DIR;
7511
7575
  function readComposeInputs(cwd) {
7512
- const file = import_node_path2.default.join(cwd, COMPOSE_INPUTS_PATH);
7513
- if (!import_node_fs2.default.existsSync(file)) return { connectedStores: [], installs: {} };
7576
+ const file = import_node_path3.default.join(cwd, COMPOSE_INPUTS_PATH);
7577
+ if (!import_node_fs3.default.existsSync(file)) return { connectedStores: [], installs: {} };
7514
7578
  let raw;
7515
7579
  try {
7516
- raw = JSON.parse(import_node_fs2.default.readFileSync(file, "utf8"));
7580
+ raw = JSON.parse(import_node_fs3.default.readFileSync(file, "utf8"));
7517
7581
  } catch (error) {
7518
7582
  throw new Error(
7519
7583
  `[compose] ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
@@ -7555,18 +7619,18 @@ function stubGitignoreEntries(artifactPaths) {
7555
7619
  function writeStubGitignore(cwd, artifactPaths) {
7556
7620
  const entries = stubGitignoreEntries(artifactPaths);
7557
7621
  const content = [STUB_GITIGNORE_HEADER, ...entries].join("\n") + "\n";
7558
- const file = import_node_path2.default.join(cwd, STUB_GITIGNORE_PATH);
7559
- const existing = import_node_fs2.default.existsSync(file) ? import_node_fs2.default.readFileSync(file, "utf8") : null;
7622
+ const file = import_node_path3.default.join(cwd, STUB_GITIGNORE_PATH);
7623
+ const existing = import_node_fs3.default.existsSync(file) ? import_node_fs3.default.readFileSync(file, "utf8") : null;
7560
7624
  if (existing !== content) {
7561
- import_node_fs2.default.mkdirSync(import_node_path2.default.dirname(file), { recursive: true });
7562
- import_node_fs2.default.writeFileSync(file, content);
7625
+ import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(file), { recursive: true });
7626
+ import_node_fs3.default.writeFileSync(file, content);
7563
7627
  }
7564
7628
  return entries.length;
7565
7629
  }
7566
7630
  function assertStubGitignoreOwned(cwd) {
7567
- const file = import_node_path2.default.join(cwd, STUB_GITIGNORE_PATH);
7568
- if (!import_node_fs2.default.existsSync(file)) return;
7569
- const firstLine = import_node_fs2.default.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
7631
+ const file = import_node_path3.default.join(cwd, STUB_GITIGNORE_PATH);
7632
+ if (!import_node_fs3.default.existsSync(file)) return;
7633
+ const firstLine = import_node_fs3.default.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
7570
7634
  if (firstLine !== STUB_GITIGNORE_HEADER) {
7571
7635
  throw new Error(
7572
7636
  `[compose] refusing to overwrite ${STUB_GITIGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
@@ -7574,8 +7638,8 @@ function assertStubGitignoreOwned(cwd) {
7574
7638
  }
7575
7639
  }
7576
7640
  function assertAppRoot(cwd) {
7577
- const appPath = import_node_path2.default.join(cwd, APP_PACKAGE_JSON);
7578
- if (!import_node_fs2.default.existsSync(appPath)) {
7641
+ const appPath = import_node_path3.default.join(cwd, APP_PACKAGE_JSON);
7642
+ if (!import_node_fs3.default.existsSync(appPath)) {
7579
7643
  throw new Error(
7580
7644
  `[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
7581
7645
  );
@@ -7621,9 +7685,9 @@ ${porcelain.trim()}`
7621
7685
  }
7622
7686
  var APP_SRC_DIR = "apps/web/src";
7623
7687
  function walkSources(dir, skip, out) {
7624
- if (!import_node_fs2.default.existsSync(dir)) return;
7625
- for (const entry of import_node_fs2.default.readdirSync(dir, { withFileTypes: true })) {
7626
- const full = import_node_path2.default.join(dir, entry.name);
7688
+ if (!import_node_fs3.default.existsSync(dir)) return;
7689
+ for (const entry of import_node_fs3.default.readdirSync(dir, { withFileTypes: true })) {
7690
+ const full = import_node_path3.default.join(dir, entry.name);
7627
7691
  if (full === skip) continue;
7628
7692
  if (entry.isDirectory()) walkSources(full, skip, out);
7629
7693
  else if (/\.[cm]?[jt]sx?$/.test(entry.name)) out.push(full);
@@ -7632,13 +7696,13 @@ function walkSources(dir, skip, out) {
7632
7696
  function assertNoAppLayerImporters(cwd, excluded) {
7633
7697
  if (excluded.length === 0) return;
7634
7698
  const files = [];
7635
- walkSources(import_node_path2.default.join(cwd, APP_SRC_DIR), import_node_path2.default.join(cwd, MODULES_DIR2), files);
7699
+ walkSources(import_node_path3.default.join(cwd, APP_SRC_DIR), import_node_path3.default.join(cwd, MODULES_DIR2), files);
7636
7700
  const offenders = [];
7637
7701
  for (const file of files) {
7638
- const source = import_node_fs2.default.readFileSync(file, "utf8");
7702
+ const source = import_node_fs3.default.readFileSync(file, "utf8");
7639
7703
  for (const name of excluded) {
7640
7704
  if (new RegExp(`@/modules/${name}(?=["'\`/])`).test(source)) {
7641
- offenders.push(`${import_node_path2.default.relative(cwd, file)}: ${name}`);
7705
+ offenders.push(`${import_node_path3.default.relative(cwd, file)}: ${name}`);
7642
7706
  }
7643
7707
  }
7644
7708
  }
@@ -7650,9 +7714,44 @@ function assertNoAppLayerImporters(cwd, excluded) {
7650
7714
  );
7651
7715
  }
7652
7716
  }
7717
+ var TSCONFIG_PATH = "apps/web/tsconfig.json";
7718
+ var BUILD_TSCONFIG_PATH = "apps/web/tsconfig.build.gen.json";
7719
+ var BUILD_TSCONFIG_HEADER = "// Generated by stardeck-compose \u2014 the tsconfig `next build` type-checks. Do not edit or commit.";
7720
+ function writeBuildTsconfig(cwd, excluded) {
7721
+ const baseFile = import_node_path3.default.join(cwd, TSCONFIG_PATH);
7722
+ if (!import_node_fs3.default.existsSync(import_node_path3.default.join(cwd, MODULES_DIR2)) || !import_node_fs3.default.existsSync(baseFile)) return;
7723
+ const base = import_typescript6.default.readConfigFile(baseFile, import_typescript6.default.sys.readFile);
7724
+ if (base.error) {
7725
+ throw new Error(
7726
+ `[compose] cannot read ${TSCONFIG_PATH}: ${import_typescript6.default.flattenDiagnosticMessageText(base.error.messageText, "\n")}`
7727
+ );
7728
+ }
7729
+ const baseExclude = base.config?.exclude ?? ["node_modules"];
7730
+ const modulesDir = import_node_path3.default.posix.relative("apps/web", MODULES_DIR2);
7731
+ const config = {
7732
+ extends: "./tsconfig.json",
7733
+ exclude: [
7734
+ ...baseExclude,
7735
+ ...excluded.flatMap((name) => [
7736
+ `${modulesDir}/${name}`,
7737
+ `${modulesDir}/*/enhancements/${name}`
7738
+ ])
7739
+ ]
7740
+ };
7741
+ const content = `${BUILD_TSCONFIG_HEADER}
7742
+ ${JSON.stringify(config, null, 2)}
7743
+ `;
7744
+ const file = import_node_path3.default.join(cwd, BUILD_TSCONFIG_PATH);
7745
+ if (!import_node_fs3.default.existsSync(file) || import_node_fs3.default.readFileSync(file, "utf8") !== content) {
7746
+ import_node_fs3.default.writeFileSync(file, content);
7747
+ }
7748
+ console.log(
7749
+ `[compose] ${BUILD_TSCONFIG_PATH} excludes ${excluded.length} Module(s): ${excluded.join(",") || "none"}`
7750
+ );
7751
+ }
7653
7752
  function removeDir(absolute) {
7654
- if (!import_node_fs2.default.existsSync(absolute)) return false;
7655
- import_node_fs2.default.rmSync(absolute, { recursive: true, force: true });
7753
+ if (!import_node_fs3.default.existsSync(absolute)) return false;
7754
+ import_node_fs3.default.rmSync(absolute, { recursive: true, force: true });
7656
7755
  return true;
7657
7756
  }
7658
7757
  async function compose(options) {
@@ -7679,6 +7778,16 @@ async function compose(options) {
7679
7778
  dryRun: true
7680
7779
  });
7681
7780
  assertNoAppLayerImporters(options.cwd, preview.excludedModules);
7781
+ const violations = findModuleBoundaryViolations(
7782
+ import_node_path3.default.join(options.cwd, MODULES_DIR2),
7783
+ Object.fromEntries(preview.composedModules.map((name) => [name, preview.excludedModules])),
7784
+ { pruning: true }
7785
+ );
7786
+ if (violations.length > 0) {
7787
+ throw new Error(
7788
+ `[compose] refusing --prune: ${formatBoundaryViolations(MODULES_DIR2, violations)}`
7789
+ );
7790
+ }
7682
7791
  }
7683
7792
  const result = await reconcileCompositionArtifacts(context, {
7684
7793
  enabledModulesRaw: options.enabled,
@@ -7687,14 +7796,15 @@ async function compose(options) {
7687
7796
  skipComposeInputs: true
7688
7797
  });
7689
7798
  const gitignoreEntries = options.enabled ? null : writeStubGitignore(options.cwd, result.artifactPaths);
7799
+ writeBuildTsconfig(options.cwd, result.excludedModules);
7690
7800
  let pruned = 0;
7691
7801
  if (options.prune) {
7692
7802
  for (const name of result.excludedModules) {
7693
- if (removeDir(import_node_path2.default.join(options.cwd, MODULES_DIR2, name))) pruned++;
7803
+ if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, name))) pruned++;
7694
7804
  }
7695
7805
  for (const owner of result.composedModules) {
7696
7806
  for (const peer of result.excludedModules) {
7697
- if (removeDir(import_node_path2.default.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
7807
+ if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
7698
7808
  }
7699
7809
  }
7700
7810
  }
@@ -7709,9 +7819,13 @@ async function compose(options) {
7709
7819
  }
7710
7820
  // Annotate the CommonJS export names for ESM import in node:
7711
7821
  0 && (module.exports = {
7822
+ BUILD_TSCONFIG_PATH,
7712
7823
  COMPOSE_INPUTS_PATH,
7713
7824
  assertSupportedTypescript,
7714
7825
  compose,
7715
7826
  escapeGitignore,
7827
+ findModuleBoundaryViolations,
7828
+ findOptionalPeerViolations,
7829
+ formatBoundaryViolations,
7716
7830
  readComposeInputs
7717
7831
  });