@stardeck-customer-apps/compose 0.8.0 → 0.9.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
@@ -2031,12 +2031,15 @@ __export(index_exports, {
2031
2031
  assertSupportedTypescript: () => assertSupportedTypescript,
2032
2032
  compose: () => compose,
2033
2033
  escapeGitignore: () => escapeGitignore,
2034
+ findModuleBoundaryViolations: () => findModuleBoundaryViolations,
2035
+ findOptionalPeerViolations: () => findOptionalPeerViolations,
2036
+ formatBoundaryViolations: () => formatBoundaryViolations,
2034
2037
  readComposeInputs: () => readComposeInputs
2035
2038
  });
2036
2039
  module.exports = __toCommonJS(index_exports);
2037
2040
  var import_node_child_process2 = require("child_process");
2038
- var import_node_fs2 = __toESM(require("fs"));
2039
- var import_node_path2 = __toESM(require("path"));
2041
+ var import_node_fs3 = __toESM(require("fs"));
2042
+ var import_node_path3 = __toESM(require("path"));
2040
2043
 
2041
2044
  // ../../packages/lib/src/server/module-rail/local-fs-sandbox.ts
2042
2045
  var import_node_child_process = require("child_process");
@@ -2141,23 +2144,23 @@ function isValidModuleName(value) {
2141
2144
  var projectSlugSchema = import_zod.z.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
2142
2145
  var moduleKindSchema = import_zod.z.enum(["feature", "library", "contract"]);
2143
2146
  var endpointMethodSchema = import_zod.z.enum(ENDPOINT_METHODS);
2144
- function endpointPathValidationError(path3) {
2145
- if (!path3.startsWith("/api/")) {
2147
+ function endpointPathValidationError(path4) {
2148
+ if (!path4.startsWith("/api/")) {
2146
2149
  return 'must begin with "/api/"';
2147
2150
  }
2148
- if (path3.endsWith("/")) {
2151
+ if (path4.endsWith("/")) {
2149
2152
  return "must not end with a trailing slash";
2150
2153
  }
2151
- if (path3.includes("?") || path3.includes("#")) {
2154
+ if (path4.includes("?") || path4.includes("#")) {
2152
2155
  return "must not include query or hash";
2153
2156
  }
2154
- if (path3.includes("\\")) {
2157
+ if (path4.includes("\\")) {
2155
2158
  return "must not include backslashes";
2156
2159
  }
2157
- if (path3.includes("%")) {
2160
+ if (path4.includes("%")) {
2158
2161
  return "must not include percent-encoded segments";
2159
2162
  }
2160
- const parts = path3.split("/");
2163
+ const parts = path4.split("/");
2161
2164
  if (parts.length < 3 || parts[0] !== "" || parts[1] !== "api") {
2162
2165
  return 'must begin with "/api/"';
2163
2166
  }
@@ -2207,12 +2210,12 @@ function endpointPathValidationError(path3) {
2207
2210
  }
2208
2211
  return null;
2209
2212
  }
2210
- function normalizeEndpointPathForCollision(path3) {
2211
- const error = endpointPathValidationError(path3);
2213
+ function normalizeEndpointPathForCollision(path4) {
2214
+ const error = endpointPathValidationError(path4);
2212
2215
  if (error != null) {
2213
2216
  throw new Error(`invalid endpoint path: ${error}`);
2214
2217
  }
2215
- return path3.split("/").map((segment, index) => {
2218
+ return path4.split("/").map((segment, index) => {
2216
2219
  if (index < 2) {
2217
2220
  return segment;
2218
2221
  }
@@ -2225,8 +2228,8 @@ function normalizeEndpointPathForCollision(path3) {
2225
2228
  return segment;
2226
2229
  }).join("/");
2227
2230
  }
2228
- var endpointPathSchema = import_zod.z.string().superRefine((path3, ctx) => {
2229
- const error = endpointPathValidationError(path3);
2231
+ var endpointPathSchema = import_zod.z.string().superRefine((path4, ctx) => {
2232
+ const error = endpointPathValidationError(path4);
2230
2233
  if (error != null) {
2231
2234
  ctx.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: error });
2232
2235
  }
@@ -2473,16 +2476,16 @@ var moduleManifestSchema = import_zod.z.object({
2473
2476
  }
2474
2477
  }
2475
2478
  const seenWorkflows = /* @__PURE__ */ new Map();
2476
- const noteWorkflow = (name, path3) => {
2479
+ const noteWorkflow = (name, path4) => {
2477
2480
  const existing = seenWorkflows.get(name);
2478
2481
  if (existing != null) {
2479
2482
  ctx.addIssue({
2480
2483
  code: import_zod.z.ZodIssueCode.custom,
2481
2484
  message: `duplicate workflow name "${name}" (also at ${existing})`,
2482
- path: path3
2485
+ path: path4
2483
2486
  });
2484
2487
  } else {
2485
- seenWorkflows.set(name, path3.join("."));
2488
+ seenWorkflows.set(name, path4.join("."));
2486
2489
  }
2487
2490
  };
2488
2491
  for (let i = 0; i < manifest.workflows.length; i += 1) {
@@ -2494,7 +2497,7 @@ var moduleManifestSchema = import_zod.z.object({
2494
2497
  }
2495
2498
  }
2496
2499
  const seenEndpointKeys = /* @__PURE__ */ new Map();
2497
- const noteEndpoint = (claim, path3) => {
2500
+ const noteEndpoint = (claim, path4) => {
2498
2501
  let key;
2499
2502
  try {
2500
2503
  key = normalizeEndpointPathForCollision(claim.path);
@@ -2506,10 +2509,10 @@ var moduleManifestSchema = import_zod.z.object({
2506
2509
  ctx.addIssue({
2507
2510
  code: import_zod.z.ZodIssueCode.custom,
2508
2511
  message: `duplicate normalized endpoint path "${key}" (also at ${existing})`,
2509
- path: path3
2512
+ path: path4
2510
2513
  });
2511
2514
  } else {
2512
- seenEndpointKeys.set(key, path3.join("."));
2515
+ seenEndpointKeys.set(key, path4.join("."));
2513
2516
  }
2514
2517
  };
2515
2518
  for (let i = 0; i < manifest.endpoints.length; i += 1) {
@@ -2610,8 +2613,8 @@ var moduleManifestSchema = import_zod.z.object({
2610
2613
  }).transform(({ version: _legacyVersion, ...manifest }) => manifest);
2611
2614
  function formatZodErrors(error) {
2612
2615
  return error.issues.map((issue) => {
2613
- const path3 = issue.path.length > 0 ? issue.path.join(".") : "root";
2614
- return `${path3}: ${issue.message}`;
2616
+ const path4 = issue.path.length > 0 ? issue.path.join(".") : "root";
2617
+ return `${path4}: ${issue.message}`;
2615
2618
  });
2616
2619
  }
2617
2620
  function parseModuleManifest(raw) {
@@ -3011,16 +3014,16 @@ var collectedE2eFactsSchema = import_zod3.z.object({
3011
3014
  function validateRouteExportEvidenceCongruence(input) {
3012
3015
  const entrySet = new Set(input.routeEntries);
3013
3016
  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`);
3017
+ for (const path4 of exportPaths) {
3018
+ if (!entrySet.has(path4)) {
3019
+ input.onIssue(`routePageExports path "${path4}" is not listed in routeEntries`);
3017
3020
  }
3018
3021
  }
3019
3022
  if (input.routeCollectionErrors.length === 0) {
3020
3023
  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`);
3024
+ for (const path4 of input.routeEntries) {
3025
+ if (!exportSet.has(path4)) {
3026
+ input.onIssue(`routeEntries path "${path4}" is missing from routePageExports`);
3024
3027
  }
3025
3028
  }
3026
3029
  }
@@ -4271,13 +4274,13 @@ function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
4271
4274
  function createMessageTree() {
4272
4275
  return /* @__PURE__ */ Object.create(null);
4273
4276
  }
4274
- function assertMessageTree(value, path3) {
4277
+ function assertMessageTree(value, path4) {
4275
4278
  if (typeof value === "string") return;
4276
4279
  if (value === null || Array.isArray(value) || typeof value !== "object") {
4277
- throw new Error(`Invalid message at ${path3}: expected string or nested object`);
4280
+ throw new Error(`Invalid message at ${path4}: expected string or nested object`);
4278
4281
  }
4279
4282
  for (const [key, child] of Object.entries(value)) {
4280
- assertMessageTree(child, `${path3}.${key}`);
4283
+ assertMessageTree(child, `${path4}.${key}`);
4281
4284
  }
4282
4285
  }
4283
4286
  function cloneTree(value) {
@@ -4287,17 +4290,17 @@ function cloneTree(value) {
4287
4290
  }
4288
4291
  return clone;
4289
4292
  }
4290
- function collectLeafPaths(value, path3, out) {
4293
+ function collectLeafPaths(value, path4, out) {
4291
4294
  if (typeof value === "string") {
4292
- out.push(path3);
4295
+ out.push(path4);
4293
4296
  return;
4294
4297
  }
4295
- assertMessageTree(value, path3);
4296
- for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path3}.${key}`, out);
4298
+ assertMessageTree(value, path4);
4299
+ for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path4}.${key}`, out);
4297
4300
  }
4298
- function overrideInto(target, source, owner, path3, missing) {
4301
+ function overrideInto(target, source, owner, path4, missing) {
4299
4302
  for (const [key, value] of Object.entries(source)) {
4300
- const nextPath = path3 ? `${path3}.${key}` : key;
4303
+ const nextPath = path4 ? `${path4}.${key}` : key;
4301
4304
  if (!Object.prototype.hasOwnProperty.call(target, key)) {
4302
4305
  collectLeafPaths(value, nextPath, missing);
4303
4306
  continue;
@@ -4324,9 +4327,9 @@ function overrideInto(target, source, owner, path3, missing) {
4324
4327
  function composeLocaleMessagesForPlan(layers) {
4325
4328
  const result = createMessageTree();
4326
4329
  const leafOwners = /* @__PURE__ */ new Map();
4327
- function mergeInto(target, source, owner, path3) {
4330
+ function mergeInto(target, source, owner, path4) {
4328
4331
  for (const [key, value] of Object.entries(source)) {
4329
- const nextPath = path3 ? `${path3}.${key}` : key;
4332
+ const nextPath = path4 ? `${path4}.${key}` : key;
4330
4333
  const hasExisting = Object.prototype.hasOwnProperty.call(target, key);
4331
4334
  const existing = hasExisting ? target[key] : void 0;
4332
4335
  if (typeof value === "string") {
@@ -4870,8 +4873,8 @@ var METHOD_ORDER = {
4870
4873
  function compareNames2(a, b) {
4871
4874
  return a < b ? -1 : a > b ? 1 : 0;
4872
4875
  }
4873
- function endpointPairKey(method, path3) {
4874
- return `${method} ${path3}`;
4876
+ function endpointPairKey(method, path4) {
4877
+ return `${method} ${path4}`;
4875
4878
  }
4876
4879
  function apiPathToRouteFile(apiPath) {
4877
4880
  if (!apiPath.startsWith("/api/")) {
@@ -4885,11 +4888,11 @@ function routeEntryToAppPageFile(nextRelativePath) {
4885
4888
  }
4886
4889
  var APP_ROUTE_FILE_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
4887
4890
  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);
4891
+ function appRouteFileToNextRelativePath(path4) {
4892
+ const pagePath = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
4890
4893
  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);
4894
+ if (!path4.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
4895
+ const relative = path4.slice(APP_ROUTE_FILE_PREFIX.length);
4893
4896
  if (!relative.startsWith("api/")) return null;
4894
4897
  const suffix = API_ROUTE_FILE_SUFFIXES.find((candidate) => relative.endsWith(candidate));
4895
4898
  if (!suffix) return null;
@@ -4981,9 +4984,9 @@ function legacyHandlerPropertyMatchesPath(property, method, physicalPath, bindin
4981
4984
  const expectedTokens = knownAliases[`${binding} ${method} ${physicalPath}`] ?? pathTokens;
4982
4985
  return handlerTokens.length === expectedTokens.length && handlerTokens.every((token, index) => token === expectedTokens[index]);
4983
4986
  }
4984
- function parseLegacyApiAdapter(path3, source) {
4985
- const physicalPath = appRouteFileToNextRelativePath(path3);
4986
- if (expectedMarkerKindForPath(path3) !== "endpoint" || physicalPath == null) return null;
4987
+ function parseLegacyApiAdapter(path4, source) {
4988
+ const physicalPath = appRouteFileToNextRelativePath(path4);
4989
+ if (expectedMarkerKindForPath(path4) !== "endpoint" || physicalPath == null) return null;
4987
4990
  const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
4988
4991
  const match = firstLine.match(
4989
4992
  new RegExp(
@@ -4993,7 +4996,7 @@ function parseLegacyApiAdapter(path3, source) {
4993
4996
  if (!match || match[2] !== physicalPath) return null;
4994
4997
  const owner = match[1];
4995
4998
  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);
4999
+ const file = import_typescript4.default.createSourceFile(path4, source, import_typescript4.default.ScriptTarget.Latest, false, import_typescript4.default.ScriptKind.TS);
4997
5000
  if (file.parseDiagnostics.length > 0) {
4998
5001
  return null;
4999
5002
  }
@@ -5035,9 +5038,9 @@ function parseLegacyApiAdapter(path3, source) {
5035
5038
  }
5036
5039
  return handlerBinding != null && methods.size > 0 ? { owner, methods: [...methods] } : null;
5037
5040
  }
5038
- function parseLegacyRouteAdapter(path3, source) {
5039
- const physicalPath = appRouteFileToNextRelativePath(path3);
5040
- if (expectedMarkerKindForPath(path3) !== "route" || physicalPath == null) return null;
5041
+ function parseLegacyRouteAdapter(path4, source) {
5042
+ const physicalPath = appRouteFileToNextRelativePath(path4);
5043
+ if (expectedMarkerKindForPath(path4) !== "route" || physicalPath == null) return null;
5041
5044
  const normalized = source.replace(/\r\n/g, "\n");
5042
5045
  const match = normalized.match(
5043
5046
  new RegExp(
@@ -5049,30 +5052,30 @@ function parseLegacyRouteAdapter(path3, source) {
5049
5052
  const nextRelativePath = match[2];
5050
5053
  return isValidModuleName(owner) && nextRelativePath === physicalPath ? { owner, nextRelativePath } : null;
5051
5054
  }
5052
- function legacyRouteAdapterMatchesStub(path3, source, stub) {
5053
- const legacy = parseLegacyRouteAdapter(path3, source);
5055
+ function legacyRouteAdapterMatchesStub(path4, source, stub) {
5056
+ const legacy = parseLegacyRouteAdapter(path4, source);
5054
5057
  return legacy != null && legacy.owner === stub.owner;
5055
5058
  }
5056
5059
  var API_ROUTE_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/api/`;
5057
5060
  var APP_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
5058
5061
  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))) {
5062
+ function expectedMarkerKindForPath(path4) {
5063
+ if (path4 === MODULES_GEN_PATH) return "registry";
5064
+ if (path4 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
5065
+ if (path4 === MODULE_DATASTORES_GEN_PATH) return "datastores";
5066
+ if (path4 === MODULE_I18N_GEN_PATH) return "i18n";
5067
+ if (path4 === MODULE_INIT_SERVER_GEN_PATH) return "init";
5068
+ if (path4 === MODULE_SERVER_GEN_PATH) return "server";
5069
+ if (path4.startsWith(API_ROUTE_DIR_PREFIX) && path4.endsWith("/route.ts")) return "endpoint";
5070
+ if (path4.startsWith(APP_DIR_PREFIX) && !path4.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path4.endsWith(suffix))) {
5068
5071
  return "route";
5069
5072
  }
5070
5073
  return null;
5071
5074
  }
5072
- function markerCongruentWithPath(path3, marker) {
5073
- if (expectedMarkerKindForPath(path3) !== marker.kind) return false;
5075
+ function markerCongruentWithPath(path4, marker) {
5076
+ if (expectedMarkerKindForPath(path4) !== marker.kind) return false;
5074
5077
  if (marker.kind === "route") {
5075
- const derived = appPageFileToNextRelativePath(path3, APP_PACKAGE_DIR);
5078
+ const derived = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
5076
5079
  return derived != null && derived === marker.nextRelativePath;
5077
5080
  }
5078
5081
  return true;
@@ -5343,8 +5346,8 @@ function buildGeneratedModuleRegistry(input) {
5343
5346
  function activeEnhancementPeers(registryModule) {
5344
5347
  return new Set(registryModule.enhancements.filter((e) => e.active).map((e) => e.peer));
5345
5348
  }
5346
- function endpointBucketKey(path3, owner, scope) {
5347
- return `${path3}\0${owner}\0${scopeLabel(scope)}`;
5349
+ function endpointBucketKey(path4, owner, scope) {
5350
+ return `${path4}\0${owner}\0${scopeLabel(scope)}`;
5348
5351
  }
5349
5352
  function planRouteStubs(input) {
5350
5353
  const endpointBuckets = /* @__PURE__ */ new Map();
@@ -5468,7 +5471,7 @@ function planRouteStubs(input) {
5468
5471
  for (const entry of entries) {
5469
5472
  const nextRelativePath = entry.nextRelativePath;
5470
5473
  const destination = normalizeAppRouterDestinationPattern(nextRelativePath);
5471
- const path3 = routeEntryToAppPageFile(nextRelativePath);
5474
+ const path4 = routeEntryToAppPageFile(nextRelativePath);
5472
5475
  const existing = routeDestinationOwners.get(destination);
5473
5476
  if (existing) {
5474
5477
  throw new Error(
@@ -5478,13 +5481,13 @@ function planRouteStubs(input) {
5478
5481
  routeDestinationOwners.set(destination, {
5479
5482
  owner: regMod.name,
5480
5483
  nextRelativePath,
5481
- path: path3
5484
+ path: path4
5482
5485
  });
5483
5486
  const importModule = routeEntryImport(regMod.name, nextRelativePath);
5484
5487
  const analyzed = analyzeRouteEntryPath(nextRelativePath);
5485
5488
  const mount = mounts.find((claim) => routeEntryCoveredByMountClaim(analyzed, claim));
5486
5489
  routeStubs.push({
5487
- path: path3,
5490
+ path: path4,
5488
5491
  owner: regMod.name,
5489
5492
  nextRelativePath,
5490
5493
  importModule,
@@ -5545,16 +5548,16 @@ function planCompositionArtifacts(input) {
5545
5548
  ...stubs.routeStubs.map((stub) => stub.path)
5546
5549
  ]);
5547
5550
  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);
5551
+ const isProvenStaleDeletionCandidate = (path4, content) => {
5552
+ if (path4 === MODULES_GEN_PATH) return false;
5553
+ if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path4)) return false;
5554
+ if (desiredPaths.has(path4)) return false;
5555
+ if (!existingGeneratedPathSet.has(path4)) return false;
5556
+ const legacy = parseLegacyApiAdapter(path4, content);
5554
5557
  if (legacy) return true;
5555
- if (parseLegacyRouteAdapter(path3, content)) return true;
5558
+ if (parseLegacyRouteAdapter(path4, content)) return true;
5556
5559
  const marker = parseCompositionArtifactMarker(content);
5557
- return marker != null && markerCongruentWithPath(path3, marker);
5560
+ return marker != null && markerCongruentWithPath(path4, marker);
5558
5561
  };
5559
5562
  const plannedByDestination = /* @__PURE__ */ new Map();
5560
5563
  for (const stub of stubs.routeStubs) {
@@ -5708,13 +5711,13 @@ function planCompositionArtifacts(input) {
5708
5711
  for (const stub of stubs.routeStubs) desired.set(stub.path, stub.content);
5709
5712
  const endpointStubByPath = new Map(stubs.endpointStubs.map((stub) => [stub.path, stub]));
5710
5713
  const routeStubByPath = new Map(stubs.routeStubs.map((stub) => [stub.path, stub]));
5711
- for (const [path3] of desired) {
5712
- const existing = input.existingFiles[path3];
5714
+ for (const [path4] of desired) {
5715
+ const existing = input.existingFiles[path4];
5713
5716
  if (existing == null) continue;
5714
5717
  let marker = parseCompositionArtifactMarker(existing);
5715
- const plannedEndpoint = endpointStubByPath.get(path3);
5716
- const plannedRoute = routeStubByPath.get(path3);
5717
- const legacy = parseLegacyApiAdapter(path3, existing);
5718
+ const plannedEndpoint = endpointStubByPath.get(path4);
5719
+ const plannedRoute = routeStubByPath.get(path4);
5720
+ const legacy = parseLegacyApiAdapter(path4, existing);
5718
5721
  if (legacy && plannedEndpoint) {
5719
5722
  marker = {
5720
5723
  kind: "endpoint",
@@ -5722,66 +5725,66 @@ function planCompositionArtifacts(input) {
5722
5725
  scope: plannedEndpoint.scope
5723
5726
  };
5724
5727
  }
5725
- if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path3, existing, plannedRoute)) {
5728
+ if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path4, existing, plannedRoute)) {
5726
5729
  marker = {
5727
5730
  kind: "route",
5728
5731
  owner: plannedRoute.owner,
5729
5732
  nextRelativePath: plannedRoute.nextRelativePath
5730
5733
  };
5731
5734
  }
5732
- const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path3);
5735
+ const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path4);
5733
5736
  if (!marker) {
5734
5737
  return {
5735
5738
  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
5739
+ 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}`,
5740
+ path: path4
5738
5741
  };
5739
5742
  }
5740
- const expectedKind = expectedMarkerKindForPath(path3);
5743
+ const expectedKind = expectedMarkerKindForPath(path4);
5741
5744
  if (marker.kind !== expectedKind) {
5742
5745
  return {
5743
5746
  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
5747
+ 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)`,
5748
+ path: path4
5746
5749
  };
5747
5750
  }
5748
5751
  if (marker.kind === "endpoint") {
5749
- const stub = endpointStubByPath.get(path3);
5752
+ const stub = endpointStubByPath.get(path4);
5750
5753
  const ownerMatches = stub != null && marker.owner === stub.owner;
5751
5754
  const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
5752
5755
  if (!ownerMatches || !scopeMatches) {
5753
5756
  return {
5754
5757
  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,
5758
+ 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) : "?"})`,
5759
+ path: path4,
5757
5760
  owner: stub?.owner
5758
5761
  };
5759
5762
  }
5760
5763
  } else if (marker.kind === "route") {
5761
- const stub = routeStubByPath.get(path3);
5764
+ const stub = routeStubByPath.get(path4);
5762
5765
  const ownerMatches = stub != null && marker.owner === stub.owner;
5763
5766
  const pathMatches = stub != null && marker.nextRelativePath === stub.nextRelativePath;
5764
5767
  if (!ownerMatches || !pathMatches) {
5765
5768
  return {
5766
5769
  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,
5770
+ 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 ?? "?"})`,
5771
+ path: path4,
5769
5772
  owner: stub?.owner
5770
5773
  };
5771
5774
  }
5772
5775
  }
5773
5776
  }
5774
5777
  const deletes = [];
5775
- for (const path3 of input.existingGeneratedPaths) {
5776
- const existing = input.existingFiles[path3];
5778
+ for (const path4 of input.existingGeneratedPaths) {
5779
+ const existing = input.existingFiles[path4];
5777
5780
  if (existing == null) continue;
5778
- if (!isProvenStaleDeletionCandidate(path3, existing)) {
5781
+ if (!isProvenStaleDeletionCandidate(path4, existing)) {
5779
5782
  continue;
5780
5783
  }
5781
- deletes.push(path3);
5784
+ deletes.push(path4);
5782
5785
  }
5783
5786
  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));
5787
+ 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
5788
  return {
5786
5789
  ok: true,
5787
5790
  plan: {
@@ -5810,8 +5813,16 @@ async function checkoutParsesTypedStores(sandbox) {
5810
5813
  try {
5811
5814
  if (!await sandbox.fileExists(APP_PACKAGE_JSON_PATH)) return false;
5812
5815
  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;
5816
+ return composeRangeParsesTypedStores(
5817
+ pkg.devDependencies?.[COMPOSE_PACKAGE] ?? pkg.dependencies?.[COMPOSE_PACKAGE]
5818
+ );
5819
+ } catch {
5820
+ return false;
5821
+ }
5822
+ }
5823
+ function composeRangeParsesTypedStores(range) {
5824
+ if (!range) return false;
5825
+ try {
5815
5826
  const lowest = import_semver.default.minVersion(range);
5816
5827
  return lowest != null && import_semver.default.gte(lowest, TYPED_STORES_MIN_COMPOSE_VERSION);
5817
5828
  } catch {
@@ -6256,11 +6267,11 @@ function countedCompositionContext(context) {
6256
6267
  counts.sandboxExecs += 1;
6257
6268
  return context.sandbox.exec(...args);
6258
6269
  },
6259
- readFile: (path3) => {
6270
+ readFile: (path4) => {
6260
6271
  counts.sandboxReads += 1;
6261
- return context.sandbox.readFile(path3);
6272
+ return context.sandbox.readFile(path4);
6262
6273
  },
6263
- fileExists: (path3) => context.sandbox.fileExists(path3),
6274
+ fileExists: (path4) => context.sandbox.fileExists(path4),
6264
6275
  fetchRemoteRef: (...args) => context.sandbox.fetchRemoteRef(...args),
6265
6276
  addDependencies: (specs) => context.sandbox.addDependencies(specs)
6266
6277
  }
@@ -6472,14 +6483,14 @@ async function discoverModuleRouteEntries(context, moduleName) {
6472
6483
  }
6473
6484
  return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
6474
6485
  }
6475
- async function writeSandboxFileAtomic(target, path3, content) {
6476
- const dir = dirnamePosix(path3);
6477
- const tmp = `${path3}.tmp.${target.runId}`;
6486
+ async function writeSandboxFileAtomic(target, path4, content) {
6487
+ const dir = dirnamePosix(path4);
6488
+ const tmp = `${path4}.tmp.${target.runId}`;
6478
6489
  const b64 = Buffer.from(content, "utf8").toString("base64");
6479
6490
  const cmd = [
6480
6491
  `mkdir -p ${shellQuote(dir)}`,
6481
6492
  `printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
6482
- `mv -f ${shellQuote(tmp)} ${shellQuote(path3)}`
6493
+ `mv -f ${shellQuote(tmp)} ${shellQuote(path4)}`
6483
6494
  ].join(" && ");
6484
6495
  const result = await target.sandbox.exec(cmd, { raiseOnError: false });
6485
6496
  if (result.exitCode !== 0) {
@@ -6494,14 +6505,14 @@ async function writeSandboxFileAtomic(target, path3, content) {
6494
6505
  }
6495
6506
  const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
6496
6507
  throw new Error(
6497
- `[ModuleRail] composition artifacts: failed to write ${redactSecrets(path3)}: ${redactSecrets(detail)}`
6508
+ `[ModuleRail] composition artifacts: failed to write ${redactSecrets(path4)}: ${redactSecrets(detail)}`
6498
6509
  );
6499
6510
  }
6500
6511
  }
6501
6512
  async function collectAbsentWriteParentDirs(context, writePaths) {
6502
6513
  const absent = /* @__PURE__ */ new Set();
6503
- for (const path3 of writePaths) {
6504
- let dir = dirnamePosix(path3);
6514
+ for (const path4 of writePaths) {
6515
+ let dir = dirnamePosix(path4);
6505
6516
  while (dir !== "." && dir !== "") {
6506
6517
  if (absent.has(dir)) {
6507
6518
  dir = dirnamePosix(dir);
@@ -6538,12 +6549,12 @@ async function removeAbsentWriteParentDirsOnRollback(context, dirsDeepestFirst)
6538
6549
  );
6539
6550
  }
6540
6551
  }
6541
- async function deleteSandboxFile(context, path3) {
6542
- const result = await context.sandbox.exec(`rm -f ${shellQuote(path3)}`, { raiseOnError: false });
6552
+ async function deleteSandboxFile(context, path4) {
6553
+ const result = await context.sandbox.exec(`rm -f ${shellQuote(path4)}`, { raiseOnError: false });
6543
6554
  if (result.exitCode !== 0) {
6544
6555
  const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
6545
6556
  throw new Error(
6546
- `[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path3)}: ${redactSecrets(detail)}`
6557
+ `[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path4)}: ${redactSecrets(detail)}`
6547
6558
  );
6548
6559
  }
6549
6560
  }
@@ -6610,37 +6621,37 @@ async function applyCompositionArtifactMutations(context, plan, existingFiles) {
6610
6621
  await writeSandboxFileAtomic(context, write.path, write.content);
6611
6622
  appliedWrites.push({ path: write.path, previous });
6612
6623
  }
6613
- for (const path3 of plan.deletes) {
6614
- const previous = existingFiles[path3];
6624
+ for (const path4 of plan.deletes) {
6625
+ const previous = existingFiles[path4];
6615
6626
  if (previous == null) continue;
6616
- await deleteSandboxFile(context, path3);
6617
- appliedDeletes.push({ path: path3, previous });
6627
+ await deleteSandboxFile(context, path4);
6628
+ appliedDeletes.push({ path: path4, previous });
6618
6629
  }
6619
6630
  } catch (error) {
6620
6631
  console.log(
6621
6632
  `[ModuleRail] runId=${context.runId} composition artifacts mid-apply failure; rolling back writes=${appliedWrites.length} deletes=${appliedDeletes.length} newDirs=${newlyCreatedParentDirs.length}`
6622
6633
  );
6623
- for (const { path: path3, previous } of [...appliedDeletes].reverse()) {
6634
+ for (const { path: path4, previous } of [...appliedDeletes].reverse()) {
6624
6635
  try {
6625
- await writeSandboxFileAtomic(context, path3, previous);
6636
+ await writeSandboxFileAtomic(context, path4, previous);
6626
6637
  } catch (rollbackError) {
6627
6638
  console.log(
6628
- `[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path3)}: ${redactSecrets(
6639
+ `[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path4)}: ${redactSecrets(
6629
6640
  rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
6630
6641
  )}`
6631
6642
  );
6632
6643
  }
6633
6644
  }
6634
- for (const { path: path3, previous } of [...appliedWrites].reverse()) {
6645
+ for (const { path: path4, previous } of [...appliedWrites].reverse()) {
6635
6646
  try {
6636
6647
  if (previous == null) {
6637
- await deleteSandboxFile(context, path3);
6648
+ await deleteSandboxFile(context, path4);
6638
6649
  } else {
6639
- await writeSandboxFileAtomic(context, path3, previous);
6650
+ await writeSandboxFileAtomic(context, path4, previous);
6640
6651
  }
6641
6652
  } catch (rollbackError) {
6642
6653
  console.log(
6643
- `[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path3)}: ${redactSecrets(
6654
+ `[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path4)}: ${redactSecrets(
6644
6655
  rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
6645
6656
  )}`
6646
6657
  );
@@ -6674,39 +6685,39 @@ async function listExistingGeneratedStubPaths(context) {
6674
6685
  COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
6675
6686
  );
6676
6687
  const presentPaths = [];
6677
- for (const path3 of candidates) {
6688
+ for (const path4 of candidates) {
6678
6689
  const presence = assertRealCompositionArtifactFilePresence(
6679
- path3,
6680
- kinds.get(path3) ?? "unreadable"
6690
+ path4,
6691
+ kinds.get(path4) ?? "unreadable"
6681
6692
  );
6682
- if (presence === "file") presentPaths.push(path3);
6693
+ if (presence === "file") presentPaths.push(path4);
6683
6694
  }
6684
6695
  let contents;
6685
6696
  try {
6686
6697
  contents = await readFilesMany(context, presentPaths);
6687
6698
  } catch (error) {
6688
- const path3 = readFailurePath(error);
6699
+ const path4 = readFailurePath(error);
6689
6700
  throw new Error(
6690
- `[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path3)}: ${redactSecrets(
6701
+ `[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path4)}: ${redactSecrets(
6691
6702
  error instanceof Error ? error.message : String(error)
6692
6703
  )}`
6693
6704
  );
6694
6705
  }
6695
6706
  const owned = [];
6696
- for (const path3 of presentPaths) {
6697
- const content = contents.get(path3);
6707
+ for (const path4 of presentPaths) {
6708
+ const content = contents.get(path4);
6698
6709
  const marker = parseCompositionArtifactMarker(content);
6699
- if (parseLegacyApiAdapter(path3, content) || parseLegacyRouteAdapter(path3, content)) {
6700
- owned.push(path3);
6710
+ if (parseLegacyApiAdapter(path4, content) || parseLegacyRouteAdapter(path4, content)) {
6711
+ owned.push(path4);
6701
6712
  continue;
6702
6713
  }
6703
6714
  if (marker == null) continue;
6704
- if (!markerCongruentWithPath(path3, marker)) {
6715
+ if (!markerCongruentWithPath(path4, marker)) {
6705
6716
  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)`
6717
+ `[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
6718
  );
6708
6719
  }
6709
- owned.push(path3);
6720
+ owned.push(path4);
6710
6721
  }
6711
6722
  return owned;
6712
6723
  }
@@ -6839,7 +6850,7 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
6839
6850
  { raiseOnError: false }
6840
6851
  );
6841
6852
  if (result.exitCode !== 0) {
6842
- for (const path3 of chunk) kinds.set(path3, "unreadable");
6853
+ for (const path4 of chunk) kinds.set(path4, "unreadable");
6843
6854
  continue;
6844
6855
  }
6845
6856
  const lines = result.output.split("\n").filter(Boolean);
@@ -6850,31 +6861,31 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
6850
6861
  }
6851
6862
  } else {
6852
6863
  for (const line of lines) {
6853
- const [rawKind, path3] = line.split(" ", 2);
6864
+ const [rawKind, path4] = line.split(" ", 2);
6854
6865
  const kind = rawKind?.replace(/^entry-kind:/, "");
6855
- if (path3 && chunk.includes(path3) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
6856
- kinds.set(path3, kind);
6866
+ if (path4 && chunk.includes(path4) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
6867
+ kinds.set(path4, kind);
6857
6868
  }
6858
6869
  }
6859
6870
  }
6860
- for (const path3 of chunk) {
6861
- if (!kinds.has(path3)) kinds.set(path3, "unreadable");
6871
+ for (const path4 of chunk) {
6872
+ if (!kinds.has(path4)) kinds.set(path4, "unreadable");
6862
6873
  }
6863
6874
  }
6864
6875
  return kinds;
6865
6876
  }
6866
- async function classifySandboxPathPresenceKind(context, path3, probeToken) {
6867
- return (await classifySandboxPathPresenceKindMany(context, [path3], probeToken)).get(path3);
6877
+ async function classifySandboxPathPresenceKind(context, path4, probeToken) {
6878
+ return (await classifySandboxPathPresenceKindMany(context, [path4], probeToken)).get(path4);
6868
6879
  }
6869
6880
  var LOCALE_JSON_KIND_PROBE_TOKEN = "locale-json-kind";
6870
- function assertRealLocaleJsonFilePresence(path3, presence) {
6881
+ function assertRealLocaleJsonFilePresence(path4, presence) {
6871
6882
  if (presence === "file") return;
6872
6883
  if (presence === "absent") {
6873
- throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path3}`);
6884
+ throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path4}`);
6874
6885
  }
6875
- const presenceError = formatConventionalEntryPresenceError(path3, presence);
6886
+ const presenceError = formatConventionalEntryPresenceError(path4, presence);
6876
6887
  throw new Error(
6877
- `[ModuleRail] composition artifacts: ${presenceError ?? `${path3} is not a real locale JSON file`}`
6888
+ `[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real locale JSON file`}`
6878
6889
  );
6879
6890
  }
6880
6891
  function splitTaggedLine(line) {
@@ -6949,11 +6960,11 @@ async function listLocaleBasenamesIfDirMany(context, dirs) {
6949
6960
  }
6950
6961
  async function readFilesMany(context, paths) {
6951
6962
  const sortedPaths = [...paths].sort((a, b) => a.localeCompare(b));
6952
- const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path3) => {
6963
+ const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path4) => {
6953
6964
  try {
6954
- return { path: path3, value: await context.sandbox.readFile(path3) };
6965
+ return { path: path4, value: await context.sandbox.readFile(path4) };
6955
6966
  } catch (error) {
6956
- return { path: path3, error };
6967
+ return { path: path4, error };
6957
6968
  }
6958
6969
  });
6959
6970
  const files = /* @__PURE__ */ new Map();
@@ -6976,58 +6987,58 @@ function readFailurePath(error) {
6976
6987
  var CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN = "conventional-entry-kind";
6977
6988
  var SERVER_REGISTRATIONS_FILE = "server/registrations.ts";
6978
6989
  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);
6990
+ async function classifyCompositionArtifactPathKind(context, path4) {
6991
+ return classifySandboxPathPresenceKind(context, path4, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
6981
6992
  }
6982
- function assertRealCompositionArtifactFilePresence(path3, presence) {
6993
+ function assertRealCompositionArtifactFilePresence(path4, presence) {
6983
6994
  if (presence === "file") return "file";
6984
6995
  if (presence === "absent") return "absent";
6985
- const presenceError = formatConventionalEntryPresenceError(path3, presence);
6996
+ const presenceError = formatConventionalEntryPresenceError(path4, presence);
6986
6997
  throw new Error(
6987
- `[ModuleRail] composition artifacts: ${presenceError ?? `${path3} is not a real composition artifact file`}`
6998
+ `[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real composition artifact file`}`
6988
6999
  );
6989
7000
  }
6990
- async function readConventionalEntryIfRealFile(path3, kind, presences, files) {
6991
- const presence = presences.get(path3) ?? "unreadable";
6992
- const presenceError = formatConventionalEntryPresenceError(path3, presence);
7001
+ async function readConventionalEntryIfRealFile(path4, kind, presences, files) {
7002
+ const presence = presences.get(path4) ?? "unreadable";
7003
+ const presenceError = formatConventionalEntryPresenceError(path4, presence);
6993
7004
  if (presenceError) {
6994
7005
  throw new Error(`[ModuleRail] composition artifacts: ${presenceError}`);
6995
7006
  }
6996
7007
  if (presence === "absent") {
6997
7008
  return null;
6998
7009
  }
6999
- const source = files.get(path3);
7010
+ const source = files.get(path4);
7000
7011
  switch (kind) {
7001
7012
  case "contributions":
7002
- assertContributionsExport(source, path3);
7003
- assertClientSafeConventionalEntryImports(source, path3, "contributions");
7013
+ assertContributionsExport(source, path4);
7014
+ assertClientSafeConventionalEntryImports(source, path4, "contributions");
7004
7015
  break;
7005
7016
  case "slotCatalogs":
7006
- assertSlotCatalogsExport(source, path3);
7007
- assertClientSafeConventionalEntryImports(source, path3, "slotCatalogs");
7017
+ assertSlotCatalogsExport(source, path4);
7018
+ assertClientSafeConventionalEntryImports(source, path4, "slotCatalogs");
7008
7019
  break;
7009
7020
  case "initializeEnhancement":
7010
- assertInitializeEnhancementExport(source, path3);
7021
+ assertInitializeEnhancementExport(source, path4);
7011
7022
  break;
7012
7023
  }
7013
7024
  return source;
7014
7025
  }
7015
7026
  function hasRealServerRegistrations(modulePath, presences) {
7016
- const path3 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
7017
- return assertRealCompositionArtifactFilePresence(path3, presences.get(path3) ?? "unreadable") === "file";
7027
+ const path4 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
7028
+ return assertRealCompositionArtifactFilePresence(path4, presences.get(path4) ?? "unreadable") === "file";
7018
7029
  }
7019
- async function readJsonObjectFile(path3, files) {
7020
- const raw = files.get(path3);
7030
+ async function readJsonObjectFile(path4, files) {
7031
+ const raw = files.get(path4);
7021
7032
  let parsed;
7022
7033
  try {
7023
7034
  parsed = JSON.parse(raw);
7024
7035
  } catch (error) {
7025
7036
  throw new Error(
7026
- `[ModuleRail] composition artifacts: invalid JSON at ${path3}: ${error instanceof Error ? error.message : String(error)}`
7037
+ `[ModuleRail] composition artifacts: invalid JSON at ${path4}: ${error instanceof Error ? error.message : String(error)}`
7027
7038
  );
7028
7039
  }
7029
7040
  if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
7030
- throw new Error(`[ModuleRail] composition artifacts: ${path3} root must be an object`);
7041
+ throw new Error(`[ModuleRail] composition artifacts: ${path4} root must be an object`);
7031
7042
  }
7032
7043
  return parsed;
7033
7044
  }
@@ -7076,11 +7087,11 @@ async function collectCompositionEntryFacts(context, registry) {
7076
7087
  classifySandboxPathPresenceKindMany(context, localePaths, LOCALE_JSON_KIND_PROBE_TOKEN)
7077
7088
  ]);
7078
7089
  const presentConventionalPaths = [];
7079
- for (const path3 of conventionalPaths) {
7080
- const presence = conventionalPresences.get(path3) ?? "unreadable";
7081
- if (presence === "file") presentConventionalPaths.push(path3);
7090
+ for (const path4 of conventionalPaths) {
7091
+ const presence = conventionalPresences.get(path4) ?? "unreadable";
7092
+ if (presence === "file") presentConventionalPaths.push(path4);
7082
7093
  }
7083
- const presentLocalePaths = localePaths.filter((path3) => localePresences.get(path3) === "file");
7094
+ const presentLocalePaths = localePaths.filter((path4) => localePresences.get(path4) === "file");
7084
7095
  const files = await readFilesMany(context, [...presentConventionalPaths, ...presentLocalePaths]);
7085
7096
  const substrateLocales = [];
7086
7097
  for (const locale of substrateNames) {
@@ -7178,8 +7189,8 @@ async function collectCompositionEntryFacts(context, registry) {
7178
7189
  });
7179
7190
  if (rootLocales.length > 0) {
7180
7191
  for (const locale of rootLocales) {
7181
- const path3 = `${modulePath}/i18n/${locale}.json`;
7182
- assertRealLocaleJsonFilePresence(path3, localePresences.get(path3) ?? "unreadable");
7192
+ const path4 = `${modulePath}/i18n/${locale}.json`;
7193
+ assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
7183
7194
  }
7184
7195
  const enMessages = await readJsonObjectFile(`${modulePath}/i18n/en.json`, files);
7185
7196
  const localeMessages = { en: enMessages };
@@ -7203,8 +7214,8 @@ async function collectCompositionEntryFacts(context, registry) {
7203
7214
  if (!enh.active || enh.locales.length === 0) continue;
7204
7215
  const peerDir = `${modulePath}/enhancements/${enh.peer}`;
7205
7216
  for (const locale of enh.locales) {
7206
- const path3 = `${peerDir}/i18n/${locale}.json`;
7207
- assertRealLocaleJsonFilePresence(path3, localePresences.get(path3) ?? "unreadable");
7217
+ const path4 = `${peerDir}/i18n/${locale}.json`;
7218
+ assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
7208
7219
  }
7209
7220
  const enMessages = await readJsonObjectFile(`${peerDir}/i18n/en.json`, files);
7210
7221
  const localeMessages = { en: enMessages };
@@ -7405,16 +7416,16 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7405
7416
  for (const stub of probePlan.plan.routeStubs) candidatePaths.add(stub.path);
7406
7417
  const existingFiles = {};
7407
7418
  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];
7419
+ for (const path4 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
7420
+ if (Object.prototype.hasOwnProperty.call(existingAppPages, path4)) {
7421
+ existingFiles[path4] = existingAppPages[path4];
7411
7422
  continue;
7412
7423
  }
7413
- if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path3)) {
7414
- existingFiles[path3] = existingAppRoutes[path3];
7424
+ if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path4)) {
7425
+ existingFiles[path4] = existingAppRoutes[path4];
7415
7426
  continue;
7416
7427
  }
7417
- remainingCandidatePaths.push(path3);
7428
+ remainingCandidatePaths.push(path4);
7418
7429
  }
7419
7430
  const candidateKinds = await classifySandboxPathPresenceKindMany(
7420
7431
  context,
@@ -7422,12 +7433,12 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7422
7433
  COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
7423
7434
  );
7424
7435
  const presentCandidatePaths = [];
7425
- for (const path3 of remainingCandidatePaths) {
7436
+ for (const path4 of remainingCandidatePaths) {
7426
7437
  const presence = assertRealCompositionArtifactFilePresence(
7427
- path3,
7428
- candidateKinds.get(path3) ?? "unreadable"
7438
+ path4,
7439
+ candidateKinds.get(path4) ?? "unreadable"
7429
7440
  );
7430
- if (presence === "file") presentCandidatePaths.push(path3);
7441
+ if (presence === "file") presentCandidatePaths.push(path4);
7431
7442
  }
7432
7443
  try {
7433
7444
  Object.assign(
@@ -7435,9 +7446,9 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7435
7446
  Object.fromEntries(await readFilesMany(context, presentCandidatePaths))
7436
7447
  );
7437
7448
  } catch (error) {
7438
- const path3 = readFailurePath(error);
7449
+ const path4 = readFailurePath(error);
7439
7450
  throw new Error(
7440
- `[ModuleRail] composition artifacts: cannot read ${redactSecrets(path3)} during preflight: ${redactSecrets(
7451
+ `[ModuleRail] composition artifacts: cannot read ${redactSecrets(path4)} during preflight: ${redactSecrets(
7441
7452
  error instanceof Error ? error.message : String(error)
7442
7453
  )}`
7443
7454
  );
@@ -7506,14 +7517,139 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7506
7517
  var HOME_I18N_SUBSTRATE_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/substrate`;
7507
7518
  var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
7508
7519
 
7520
+ // src/module-boundaries.ts
7521
+ var import_node_fs2 = __toESM(require("fs"));
7522
+ var import_node_path2 = __toESM(require("path"));
7523
+ var import_typescript5 = __toESM(require("typescript"));
7524
+ var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
7525
+ var RESOLVE_SUFFIXES = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
7526
+ var DATA_FILE = /\.(?:sql|json)$/;
7527
+ function dataPathsIn(fileName, source) {
7528
+ if (!/\.(?:sql|json)["'`]/.test(source)) return [];
7529
+ const paths = [];
7530
+ const visit = (node) => {
7531
+ if ((import_typescript5.default.isStringLiteralLike(node) || import_typescript5.default.isTemplateTail(node)) && DATA_FILE.test(node.text)) {
7532
+ paths.push(node.text);
7533
+ }
7534
+ import_typescript5.default.forEachChild(node, visit);
7535
+ };
7536
+ visit(import_typescript5.default.createSourceFile(fileName, source, import_typescript5.default.ScriptTarget.Latest, false));
7537
+ return paths;
7538
+ }
7539
+ function scanModule(modulesDir, module2) {
7540
+ const scanned = /* @__PURE__ */ new Map();
7541
+ const moduleDir = import_node_path2.default.join(modulesDir, module2);
7542
+ if (!import_node_fs2.default.existsSync(moduleDir)) return scanned;
7543
+ const files = import_node_fs2.default.readdirSync(moduleDir, { recursive: true, encoding: "utf8" }).filter((relative) => SOURCE_FILE.test(relative) && !relative.includes("node_modules"));
7544
+ for (const relative of files) {
7545
+ const absolute = import_node_path2.default.join(moduleDir, relative);
7546
+ if (!import_node_fs2.default.statSync(absolute).isFile()) continue;
7547
+ const source = import_node_fs2.default.readFileSync(absolute, "utf8");
7548
+ const specifiers = import_typescript5.default.preProcessFile(source, true, true).importedFiles.map((f) => f.fileName);
7549
+ const dataPaths = dataPathsIn(absolute, source);
7550
+ const targets = [...specifiers, ...dataPaths].flatMap((spec) => {
7551
+ if (spec.startsWith("@/modules/"))
7552
+ return [import_node_path2.default.join(modulesDir, spec.slice("@/modules/".length))];
7553
+ if (spec.startsWith("./") || spec.startsWith("../")) {
7554
+ return [import_node_path2.default.resolve(import_node_path2.default.dirname(absolute), spec)];
7555
+ }
7556
+ return [];
7557
+ });
7558
+ scanned.set(absolute, {
7559
+ module: module2,
7560
+ relative: relative.split(import_node_path2.default.sep).join("/"),
7561
+ targets,
7562
+ dataPaths
7563
+ });
7564
+ }
7565
+ return scanned;
7566
+ }
7567
+ function findModuleBoundaryViolations(modulesDir, peersByModule, { pruning = false } = {}) {
7568
+ const files = /* @__PURE__ */ new Map();
7569
+ for (const [module2, peers] of Object.entries(peersByModule)) {
7570
+ if (peers.length === 0) continue;
7571
+ for (const [absolute, file] of scanModule(modulesDir, module2)) files.set(absolute, file);
7572
+ }
7573
+ const exempt = (file, peer) => (pruning ? peersByModule[file.module] ?? [] : [peer]).some(
7574
+ (q) => file.relative.startsWith(`enhancements/${q}/`)
7575
+ );
7576
+ const goneWith = (target, peer) => {
7577
+ const relative = import_node_path2.default.relative(modulesDir, target).split(import_node_path2.default.sep).join("/");
7578
+ return relative === peer || relative.startsWith(`${peer}/`) || new RegExp(`^[^/]+/enhancements/${peer}(/|$)`).test(relative);
7579
+ };
7580
+ const tainted = /* @__PURE__ */ new Map();
7581
+ const queue = [];
7582
+ const taint = (absolute, peer) => {
7583
+ const peers = tainted.get(absolute) ?? /* @__PURE__ */ new Set();
7584
+ if (peers.has(peer)) return;
7585
+ peers.add(peer);
7586
+ tainted.set(absolute, peers);
7587
+ queue.push([absolute, peer]);
7588
+ };
7589
+ const importers = /* @__PURE__ */ new Map();
7590
+ for (const [absolute, file] of files) {
7591
+ for (const target of file.targets) {
7592
+ const hit = RESOLVE_SUFFIXES.map((suffix) => target + suffix).find((c) => files.has(c));
7593
+ if (hit) importers.set(hit, [...importers.get(hit) ?? [], absolute]);
7594
+ }
7595
+ for (const peer of peersByModule[file.module] ?? []) {
7596
+ if (exempt(file, peer)) continue;
7597
+ if (file.targets.some((target) => goneWith(target, peer)) || file.dataPaths.some(
7598
+ (p) => p.startsWith(`modules/${peer}/`) || p.includes(`/modules/${peer}/`)
7599
+ )) {
7600
+ taint(absolute, peer);
7601
+ }
7602
+ }
7603
+ }
7604
+ for (let next = queue.shift(); next; next = queue.shift()) {
7605
+ const [target, peer] = next;
7606
+ for (const importer of importers.get(target) ?? []) {
7607
+ const file = files.get(importer);
7608
+ if ((peersByModule[file.module] ?? []).includes(peer) && !exempt(file, peer)) {
7609
+ taint(importer, peer);
7610
+ }
7611
+ }
7612
+ }
7613
+ const violations = [];
7614
+ for (const [absolute, peers] of tainted) {
7615
+ const file = files.get(absolute);
7616
+ for (const peer of peers) {
7617
+ violations.push({ file: `${file.module}/${file.relative}`, module: file.module, peer });
7618
+ }
7619
+ }
7620
+ return violations.sort((a, b) => a.file.localeCompare(b.file) || a.peer.localeCompare(b.peer));
7621
+ }
7622
+ function findOptionalPeerViolations(modulesDir) {
7623
+ if (!import_node_fs2.default.existsSync(modulesDir)) return [];
7624
+ const manifests = {};
7625
+ for (const name of import_node_fs2.default.readdirSync(modulesDir)) {
7626
+ const manifestPath = import_node_path2.default.join(modulesDir, name, "module.json");
7627
+ if (!import_node_fs2.default.existsSync(manifestPath)) continue;
7628
+ manifests[name] = JSON.parse(import_node_fs2.default.readFileSync(manifestPath, "utf8"));
7629
+ }
7630
+ const features = Object.keys(manifests).filter((name) => manifests[name].kind === "feature");
7631
+ const peersByModule = {};
7632
+ for (const [name, manifest] of Object.entries(manifests)) {
7633
+ const uses = new Set(manifest.uses ?? []);
7634
+ peersByModule[name] = features.filter((peer) => peer !== name && !uses.has(peer));
7635
+ }
7636
+ return findModuleBoundaryViolations(modulesDir, peersByModule);
7637
+ }
7638
+ function formatBoundaryViolations(modulesDirLabel, violations) {
7639
+ const files = new Set(violations.map((v) => v.file)).size;
7640
+ return `${files} Module file(s) import a peer Module that may be absent (not in the Module's \`uses\`, or excluded by --enabled).
7641
+ \`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).
7642
+ ` + violations.map((v) => `${modulesDirLabel}/${v.file}: ${v.peer}`).join("\n");
7643
+ }
7644
+
7509
7645
  // src/index.ts
7510
7646
  var MODULES_DIR2 = MODULES_SANDBOX_DIR;
7511
7647
  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: {} };
7648
+ const file = import_node_path3.default.join(cwd, COMPOSE_INPUTS_PATH);
7649
+ if (!import_node_fs3.default.existsSync(file)) return { connectedStores: [], installs: {} };
7514
7650
  let raw;
7515
7651
  try {
7516
- raw = JSON.parse(import_node_fs2.default.readFileSync(file, "utf8"));
7652
+ raw = JSON.parse(import_node_fs3.default.readFileSync(file, "utf8"));
7517
7653
  } catch (error) {
7518
7654
  throw new Error(
7519
7655
  `[compose] ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
@@ -7555,18 +7691,18 @@ function stubGitignoreEntries(artifactPaths) {
7555
7691
  function writeStubGitignore(cwd, artifactPaths) {
7556
7692
  const entries = stubGitignoreEntries(artifactPaths);
7557
7693
  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;
7694
+ const file = import_node_path3.default.join(cwd, STUB_GITIGNORE_PATH);
7695
+ const existing = import_node_fs3.default.existsSync(file) ? import_node_fs3.default.readFileSync(file, "utf8") : null;
7560
7696
  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);
7697
+ import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(file), { recursive: true });
7698
+ import_node_fs3.default.writeFileSync(file, content);
7563
7699
  }
7564
7700
  return entries.length;
7565
7701
  }
7566
7702
  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] ?? "";
7703
+ const file = import_node_path3.default.join(cwd, STUB_GITIGNORE_PATH);
7704
+ if (!import_node_fs3.default.existsSync(file)) return;
7705
+ const firstLine = import_node_fs3.default.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
7570
7706
  if (firstLine !== STUB_GITIGNORE_HEADER) {
7571
7707
  throw new Error(
7572
7708
  `[compose] refusing to overwrite ${STUB_GITIGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
@@ -7574,8 +7710,8 @@ function assertStubGitignoreOwned(cwd) {
7574
7710
  }
7575
7711
  }
7576
7712
  function assertAppRoot(cwd) {
7577
- const appPath = import_node_path2.default.join(cwd, APP_PACKAGE_JSON);
7578
- if (!import_node_fs2.default.existsSync(appPath)) {
7713
+ const appPath = import_node_path3.default.join(cwd, APP_PACKAGE_JSON);
7714
+ if (!import_node_fs3.default.existsSync(appPath)) {
7579
7715
  throw new Error(
7580
7716
  `[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
7581
7717
  );
@@ -7621,9 +7757,9 @@ ${porcelain.trim()}`
7621
7757
  }
7622
7758
  var APP_SRC_DIR = "apps/web/src";
7623
7759
  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);
7760
+ if (!import_node_fs3.default.existsSync(dir)) return;
7761
+ for (const entry of import_node_fs3.default.readdirSync(dir, { withFileTypes: true })) {
7762
+ const full = import_node_path3.default.join(dir, entry.name);
7627
7763
  if (full === skip) continue;
7628
7764
  if (entry.isDirectory()) walkSources(full, skip, out);
7629
7765
  else if (/\.[cm]?[jt]sx?$/.test(entry.name)) out.push(full);
@@ -7632,13 +7768,13 @@ function walkSources(dir, skip, out) {
7632
7768
  function assertNoAppLayerImporters(cwd, excluded) {
7633
7769
  if (excluded.length === 0) return;
7634
7770
  const files = [];
7635
- walkSources(import_node_path2.default.join(cwd, APP_SRC_DIR), import_node_path2.default.join(cwd, MODULES_DIR2), files);
7771
+ walkSources(import_node_path3.default.join(cwd, APP_SRC_DIR), import_node_path3.default.join(cwd, MODULES_DIR2), files);
7636
7772
  const offenders = [];
7637
7773
  for (const file of files) {
7638
- const source = import_node_fs2.default.readFileSync(file, "utf8");
7774
+ const source = import_node_fs3.default.readFileSync(file, "utf8");
7639
7775
  for (const name of excluded) {
7640
7776
  if (new RegExp(`@/modules/${name}(?=["'\`/])`).test(source)) {
7641
- offenders.push(`${import_node_path2.default.relative(cwd, file)}: ${name}`);
7777
+ offenders.push(`${import_node_path3.default.relative(cwd, file)}: ${name}`);
7642
7778
  }
7643
7779
  }
7644
7780
  }
@@ -7651,8 +7787,8 @@ function assertNoAppLayerImporters(cwd, excluded) {
7651
7787
  }
7652
7788
  }
7653
7789
  function removeDir(absolute) {
7654
- if (!import_node_fs2.default.existsSync(absolute)) return false;
7655
- import_node_fs2.default.rmSync(absolute, { recursive: true, force: true });
7790
+ if (!import_node_fs3.default.existsSync(absolute)) return false;
7791
+ import_node_fs3.default.rmSync(absolute, { recursive: true, force: true });
7656
7792
  return true;
7657
7793
  }
7658
7794
  async function compose(options) {
@@ -7679,6 +7815,16 @@ async function compose(options) {
7679
7815
  dryRun: true
7680
7816
  });
7681
7817
  assertNoAppLayerImporters(options.cwd, preview.excludedModules);
7818
+ const violations = findModuleBoundaryViolations(
7819
+ import_node_path3.default.join(options.cwd, MODULES_DIR2),
7820
+ Object.fromEntries(preview.composedModules.map((name) => [name, preview.excludedModules])),
7821
+ { pruning: true }
7822
+ );
7823
+ if (violations.length > 0) {
7824
+ throw new Error(
7825
+ `[compose] refusing --prune: ${formatBoundaryViolations(MODULES_DIR2, violations)}`
7826
+ );
7827
+ }
7682
7828
  }
7683
7829
  const result = await reconcileCompositionArtifacts(context, {
7684
7830
  enabledModulesRaw: options.enabled,
@@ -7690,11 +7836,11 @@ async function compose(options) {
7690
7836
  let pruned = 0;
7691
7837
  if (options.prune) {
7692
7838
  for (const name of result.excludedModules) {
7693
- if (removeDir(import_node_path2.default.join(options.cwd, MODULES_DIR2, name))) pruned++;
7839
+ if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, name))) pruned++;
7694
7840
  }
7695
7841
  for (const owner of result.composedModules) {
7696
7842
  for (const peer of result.excludedModules) {
7697
- if (removeDir(import_node_path2.default.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
7843
+ if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
7698
7844
  }
7699
7845
  }
7700
7846
  }
@@ -7713,5 +7859,8 @@ async function compose(options) {
7713
7859
  assertSupportedTypescript,
7714
7860
  compose,
7715
7861
  escapeGitignore,
7862
+ findModuleBoundaryViolations,
7863
+ findOptionalPeerViolations,
7864
+ formatBoundaryViolations,
7716
7865
  readComposeInputs
7717
7866
  });