@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/cli.js CHANGED
@@ -2066,23 +2066,23 @@ function isValidModuleName(value) {
2066
2066
  var projectSlugSchema = import_zod.z.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
2067
2067
  var moduleKindSchema = import_zod.z.enum(["feature", "library", "contract"]);
2068
2068
  var endpointMethodSchema = import_zod.z.enum(ENDPOINT_METHODS);
2069
- function endpointPathValidationError(path3) {
2070
- if (!path3.startsWith("/api/")) {
2069
+ function endpointPathValidationError(path5) {
2070
+ if (!path5.startsWith("/api/")) {
2071
2071
  return 'must begin with "/api/"';
2072
2072
  }
2073
- if (path3.endsWith("/")) {
2073
+ if (path5.endsWith("/")) {
2074
2074
  return "must not end with a trailing slash";
2075
2075
  }
2076
- if (path3.includes("?") || path3.includes("#")) {
2076
+ if (path5.includes("?") || path5.includes("#")) {
2077
2077
  return "must not include query or hash";
2078
2078
  }
2079
- if (path3.includes("\\")) {
2079
+ if (path5.includes("\\")) {
2080
2080
  return "must not include backslashes";
2081
2081
  }
2082
- if (path3.includes("%")) {
2082
+ if (path5.includes("%")) {
2083
2083
  return "must not include percent-encoded segments";
2084
2084
  }
2085
- const parts = path3.split("/");
2085
+ const parts = path5.split("/");
2086
2086
  if (parts.length < 3 || parts[0] !== "" || parts[1] !== "api") {
2087
2087
  return 'must begin with "/api/"';
2088
2088
  }
@@ -2132,12 +2132,12 @@ function endpointPathValidationError(path3) {
2132
2132
  }
2133
2133
  return null;
2134
2134
  }
2135
- function normalizeEndpointPathForCollision(path3) {
2136
- const error = endpointPathValidationError(path3);
2135
+ function normalizeEndpointPathForCollision(path5) {
2136
+ const error = endpointPathValidationError(path5);
2137
2137
  if (error != null) {
2138
2138
  throw new Error(`invalid endpoint path: ${error}`);
2139
2139
  }
2140
- return path3.split("/").map((segment, index) => {
2140
+ return path5.split("/").map((segment, index) => {
2141
2141
  if (index < 2) {
2142
2142
  return segment;
2143
2143
  }
@@ -2150,8 +2150,8 @@ function normalizeEndpointPathForCollision(path3) {
2150
2150
  return segment;
2151
2151
  }).join("/");
2152
2152
  }
2153
- var endpointPathSchema = import_zod.z.string().superRefine((path3, ctx) => {
2154
- const error = endpointPathValidationError(path3);
2153
+ var endpointPathSchema = import_zod.z.string().superRefine((path5, ctx) => {
2154
+ const error = endpointPathValidationError(path5);
2155
2155
  if (error != null) {
2156
2156
  ctx.addIssue({ code: import_zod.z.ZodIssueCode.custom, message: error });
2157
2157
  }
@@ -2398,16 +2398,16 @@ var moduleManifestSchema = import_zod.z.object({
2398
2398
  }
2399
2399
  }
2400
2400
  const seenWorkflows = /* @__PURE__ */ new Map();
2401
- const noteWorkflow = (name, path3) => {
2401
+ const noteWorkflow = (name, path5) => {
2402
2402
  const existing = seenWorkflows.get(name);
2403
2403
  if (existing != null) {
2404
2404
  ctx.addIssue({
2405
2405
  code: import_zod.z.ZodIssueCode.custom,
2406
2406
  message: `duplicate workflow name "${name}" (also at ${existing})`,
2407
- path: path3
2407
+ path: path5
2408
2408
  });
2409
2409
  } else {
2410
- seenWorkflows.set(name, path3.join("."));
2410
+ seenWorkflows.set(name, path5.join("."));
2411
2411
  }
2412
2412
  };
2413
2413
  for (let i = 0; i < manifest.workflows.length; i += 1) {
@@ -2419,7 +2419,7 @@ var moduleManifestSchema = import_zod.z.object({
2419
2419
  }
2420
2420
  }
2421
2421
  const seenEndpointKeys = /* @__PURE__ */ new Map();
2422
- const noteEndpoint = (claim, path3) => {
2422
+ const noteEndpoint = (claim, path5) => {
2423
2423
  let key;
2424
2424
  try {
2425
2425
  key = normalizeEndpointPathForCollision(claim.path);
@@ -2431,10 +2431,10 @@ var moduleManifestSchema = import_zod.z.object({
2431
2431
  ctx.addIssue({
2432
2432
  code: import_zod.z.ZodIssueCode.custom,
2433
2433
  message: `duplicate normalized endpoint path "${key}" (also at ${existing})`,
2434
- path: path3
2434
+ path: path5
2435
2435
  });
2436
2436
  } else {
2437
- seenEndpointKeys.set(key, path3.join("."));
2437
+ seenEndpointKeys.set(key, path5.join("."));
2438
2438
  }
2439
2439
  };
2440
2440
  for (let i = 0; i < manifest.endpoints.length; i += 1) {
@@ -2535,8 +2535,8 @@ var moduleManifestSchema = import_zod.z.object({
2535
2535
  }).transform(({ version: _legacyVersion, ...manifest }) => manifest);
2536
2536
  function formatZodErrors(error) {
2537
2537
  return error.issues.map((issue) => {
2538
- const path3 = issue.path.length > 0 ? issue.path.join(".") : "root";
2539
- return `${path3}: ${issue.message}`;
2538
+ const path5 = issue.path.length > 0 ? issue.path.join(".") : "root";
2539
+ return `${path5}: ${issue.message}`;
2540
2540
  });
2541
2541
  }
2542
2542
  function parseModuleManifest(raw) {
@@ -2936,16 +2936,16 @@ var collectedE2eFactsSchema = import_zod3.z.object({
2936
2936
  function validateRouteExportEvidenceCongruence(input) {
2937
2937
  const entrySet = new Set(input.routeEntries);
2938
2938
  const exportPaths = input.routePageExports.map((e) => e.nextRelativePath);
2939
- for (const path3 of exportPaths) {
2940
- if (!entrySet.has(path3)) {
2941
- input.onIssue(`routePageExports path "${path3}" is not listed in routeEntries`);
2939
+ for (const path5 of exportPaths) {
2940
+ if (!entrySet.has(path5)) {
2941
+ input.onIssue(`routePageExports path "${path5}" is not listed in routeEntries`);
2942
2942
  }
2943
2943
  }
2944
2944
  if (input.routeCollectionErrors.length === 0) {
2945
2945
  const exportSet = new Set(exportPaths);
2946
- for (const path3 of input.routeEntries) {
2947
- if (!exportSet.has(path3)) {
2948
- input.onIssue(`routeEntries path "${path3}" is missing from routePageExports`);
2946
+ for (const path5 of input.routeEntries) {
2947
+ if (!exportSet.has(path5)) {
2948
+ input.onIssue(`routeEntries path "${path5}" is missing from routePageExports`);
2949
2949
  }
2950
2950
  }
2951
2951
  }
@@ -3901,12 +3901,13 @@ var MODULES_GEN_PATH = `${APP_WEB_PREFIX}src/modules.gen.ts`;
3901
3901
  var APP_EXTENSIONS_PATH = `${APP_WEB_PREFIX}src/lib/modules/app-extensions.ts`;
3902
3902
 
3903
3903
  // src/cli.ts
3904
- var import_typescript5 = __toESM(require("typescript"));
3904
+ var import_typescript6 = __toESM(require("typescript"));
3905
+ var import_node_path4 = __toESM(require("path"));
3905
3906
 
3906
3907
  // src/index.ts
3907
3908
  var import_node_child_process2 = require("child_process");
3908
- var import_node_fs2 = __toESM(require("fs"));
3909
- var import_node_path2 = __toESM(require("path"));
3909
+ var import_node_fs3 = __toESM(require("fs"));
3910
+ var import_node_path3 = __toESM(require("path"));
3910
3911
 
3911
3912
  // ../../packages/lib/src/server/module-rail/local-fs-sandbox.ts
3912
3913
  var import_node_child_process = require("child_process");
@@ -4262,13 +4263,13 @@ function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
4262
4263
  function createMessageTree() {
4263
4264
  return /* @__PURE__ */ Object.create(null);
4264
4265
  }
4265
- function assertMessageTree(value, path3) {
4266
+ function assertMessageTree(value, path5) {
4266
4267
  if (typeof value === "string") return;
4267
4268
  if (value === null || Array.isArray(value) || typeof value !== "object") {
4268
- throw new Error(`Invalid message at ${path3}: expected string or nested object`);
4269
+ throw new Error(`Invalid message at ${path5}: expected string or nested object`);
4269
4270
  }
4270
4271
  for (const [key, child] of Object.entries(value)) {
4271
- assertMessageTree(child, `${path3}.${key}`);
4272
+ assertMessageTree(child, `${path5}.${key}`);
4272
4273
  }
4273
4274
  }
4274
4275
  function cloneTree(value) {
@@ -4278,17 +4279,17 @@ function cloneTree(value) {
4278
4279
  }
4279
4280
  return clone;
4280
4281
  }
4281
- function collectLeafPaths(value, path3, out) {
4282
+ function collectLeafPaths(value, path5, out) {
4282
4283
  if (typeof value === "string") {
4283
- out.push(path3);
4284
+ out.push(path5);
4284
4285
  return;
4285
4286
  }
4286
- assertMessageTree(value, path3);
4287
- for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path3}.${key}`, out);
4287
+ assertMessageTree(value, path5);
4288
+ for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path5}.${key}`, out);
4288
4289
  }
4289
- function overrideInto(target, source, owner, path3, missing) {
4290
+ function overrideInto(target, source, owner, path5, missing) {
4290
4291
  for (const [key, value] of Object.entries(source)) {
4291
- const nextPath = path3 ? `${path3}.${key}` : key;
4292
+ const nextPath = path5 ? `${path5}.${key}` : key;
4292
4293
  if (!Object.prototype.hasOwnProperty.call(target, key)) {
4293
4294
  collectLeafPaths(value, nextPath, missing);
4294
4295
  continue;
@@ -4315,9 +4316,9 @@ function overrideInto(target, source, owner, path3, missing) {
4315
4316
  function composeLocaleMessagesForPlan(layers) {
4316
4317
  const result = createMessageTree();
4317
4318
  const leafOwners = /* @__PURE__ */ new Map();
4318
- function mergeInto(target, source, owner, path3) {
4319
+ function mergeInto(target, source, owner, path5) {
4319
4320
  for (const [key, value] of Object.entries(source)) {
4320
- const nextPath = path3 ? `${path3}.${key}` : key;
4321
+ const nextPath = path5 ? `${path5}.${key}` : key;
4321
4322
  const hasExisting = Object.prototype.hasOwnProperty.call(target, key);
4322
4323
  const existing = hasExisting ? target[key] : void 0;
4323
4324
  if (typeof value === "string") {
@@ -4861,8 +4862,8 @@ var METHOD_ORDER = {
4861
4862
  function compareNames2(a, b) {
4862
4863
  return a < b ? -1 : a > b ? 1 : 0;
4863
4864
  }
4864
- function endpointPairKey(method, path3) {
4865
- return `${method} ${path3}`;
4865
+ function endpointPairKey(method, path5) {
4866
+ return `${method} ${path5}`;
4866
4867
  }
4867
4868
  function apiPathToRouteFile(apiPath) {
4868
4869
  if (!apiPath.startsWith("/api/")) {
@@ -4876,11 +4877,11 @@ function routeEntryToAppPageFile(nextRelativePath) {
4876
4877
  }
4877
4878
  var APP_ROUTE_FILE_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
4878
4879
  var API_ROUTE_FILE_SUFFIXES = ["/route.ts", "/route.tsx", "/route.jsx", "/route.js"];
4879
- function appRouteFileToNextRelativePath(path3) {
4880
- const pagePath = appPageFileToNextRelativePath(path3, APP_PACKAGE_DIR);
4880
+ function appRouteFileToNextRelativePath(path5) {
4881
+ const pagePath = appPageFileToNextRelativePath(path5, APP_PACKAGE_DIR);
4881
4882
  if (pagePath != null) return pagePath;
4882
- if (!path3.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
4883
- const relative = path3.slice(APP_ROUTE_FILE_PREFIX.length);
4883
+ if (!path5.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
4884
+ const relative = path5.slice(APP_ROUTE_FILE_PREFIX.length);
4884
4885
  if (!relative.startsWith("api/")) return null;
4885
4886
  const suffix = API_ROUTE_FILE_SUFFIXES.find((candidate) => relative.endsWith(candidate));
4886
4887
  if (!suffix) return null;
@@ -4972,9 +4973,9 @@ function legacyHandlerPropertyMatchesPath(property, method, physicalPath, bindin
4972
4973
  const expectedTokens = knownAliases[`${binding} ${method} ${physicalPath}`] ?? pathTokens;
4973
4974
  return handlerTokens.length === expectedTokens.length && handlerTokens.every((token, index) => token === expectedTokens[index]);
4974
4975
  }
4975
- function parseLegacyApiAdapter(path3, source) {
4976
- const physicalPath = appRouteFileToNextRelativePath(path3);
4977
- if (expectedMarkerKindForPath(path3) !== "endpoint" || physicalPath == null) return null;
4976
+ function parseLegacyApiAdapter(path5, source) {
4977
+ const physicalPath = appRouteFileToNextRelativePath(path5);
4978
+ if (expectedMarkerKindForPath(path5) !== "endpoint" || physicalPath == null) return null;
4978
4979
  const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
4979
4980
  const match = firstLine.match(
4980
4981
  new RegExp(
@@ -4984,7 +4985,7 @@ function parseLegacyApiAdapter(path3, source) {
4984
4985
  if (!match || match[2] !== physicalPath) return null;
4985
4986
  const owner = match[1];
4986
4987
  if (!isValidModuleName(owner)) return null;
4987
- const file = import_typescript4.default.createSourceFile(path3, source, import_typescript4.default.ScriptTarget.Latest, false, import_typescript4.default.ScriptKind.TS);
4988
+ const file = import_typescript4.default.createSourceFile(path5, source, import_typescript4.default.ScriptTarget.Latest, false, import_typescript4.default.ScriptKind.TS);
4988
4989
  if (file.parseDiagnostics.length > 0) {
4989
4990
  return null;
4990
4991
  }
@@ -5026,9 +5027,9 @@ function parseLegacyApiAdapter(path3, source) {
5026
5027
  }
5027
5028
  return handlerBinding != null && methods.size > 0 ? { owner, methods: [...methods] } : null;
5028
5029
  }
5029
- function parseLegacyRouteAdapter(path3, source) {
5030
- const physicalPath = appRouteFileToNextRelativePath(path3);
5031
- if (expectedMarkerKindForPath(path3) !== "route" || physicalPath == null) return null;
5030
+ function parseLegacyRouteAdapter(path5, source) {
5031
+ const physicalPath = appRouteFileToNextRelativePath(path5);
5032
+ if (expectedMarkerKindForPath(path5) !== "route" || physicalPath == null) return null;
5032
5033
  const normalized = source.replace(/\r\n/g, "\n");
5033
5034
  const match = normalized.match(
5034
5035
  new RegExp(
@@ -5040,30 +5041,30 @@ function parseLegacyRouteAdapter(path3, source) {
5040
5041
  const nextRelativePath = match[2];
5041
5042
  return isValidModuleName(owner) && nextRelativePath === physicalPath ? { owner, nextRelativePath } : null;
5042
5043
  }
5043
- function legacyRouteAdapterMatchesStub(path3, source, stub) {
5044
- const legacy = parseLegacyRouteAdapter(path3, source);
5044
+ function legacyRouteAdapterMatchesStub(path5, source, stub) {
5045
+ const legacy = parseLegacyRouteAdapter(path5, source);
5045
5046
  return legacy != null && legacy.owner === stub.owner;
5046
5047
  }
5047
5048
  var API_ROUTE_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/api/`;
5048
5049
  var APP_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
5049
5050
  var PAGE_FILE_SUFFIXES = ["/page.tsx", "/page.ts", "/page.jsx", "/page.js"];
5050
- function expectedMarkerKindForPath(path3) {
5051
- if (path3 === MODULES_GEN_PATH) return "registry";
5052
- if (path3 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
5053
- if (path3 === MODULE_DATASTORES_GEN_PATH) return "datastores";
5054
- if (path3 === MODULE_I18N_GEN_PATH) return "i18n";
5055
- if (path3 === MODULE_INIT_SERVER_GEN_PATH) return "init";
5056
- if (path3 === MODULE_SERVER_GEN_PATH) return "server";
5057
- if (path3.startsWith(API_ROUTE_DIR_PREFIX) && path3.endsWith("/route.ts")) return "endpoint";
5058
- if (path3.startsWith(APP_DIR_PREFIX) && !path3.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path3.endsWith(suffix))) {
5051
+ function expectedMarkerKindForPath(path5) {
5052
+ if (path5 === MODULES_GEN_PATH) return "registry";
5053
+ if (path5 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
5054
+ if (path5 === MODULE_DATASTORES_GEN_PATH) return "datastores";
5055
+ if (path5 === MODULE_I18N_GEN_PATH) return "i18n";
5056
+ if (path5 === MODULE_INIT_SERVER_GEN_PATH) return "init";
5057
+ if (path5 === MODULE_SERVER_GEN_PATH) return "server";
5058
+ if (path5.startsWith(API_ROUTE_DIR_PREFIX) && path5.endsWith("/route.ts")) return "endpoint";
5059
+ if (path5.startsWith(APP_DIR_PREFIX) && !path5.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path5.endsWith(suffix))) {
5059
5060
  return "route";
5060
5061
  }
5061
5062
  return null;
5062
5063
  }
5063
- function markerCongruentWithPath(path3, marker) {
5064
- if (expectedMarkerKindForPath(path3) !== marker.kind) return false;
5064
+ function markerCongruentWithPath(path5, marker) {
5065
+ if (expectedMarkerKindForPath(path5) !== marker.kind) return false;
5065
5066
  if (marker.kind === "route") {
5066
- const derived = appPageFileToNextRelativePath(path3, APP_PACKAGE_DIR);
5067
+ const derived = appPageFileToNextRelativePath(path5, APP_PACKAGE_DIR);
5067
5068
  return derived != null && derived === marker.nextRelativePath;
5068
5069
  }
5069
5070
  return true;
@@ -5334,8 +5335,8 @@ function buildGeneratedModuleRegistry(input) {
5334
5335
  function activeEnhancementPeers(registryModule) {
5335
5336
  return new Set(registryModule.enhancements.filter((e) => e.active).map((e) => e.peer));
5336
5337
  }
5337
- function endpointBucketKey(path3, owner, scope) {
5338
- return `${path3}\0${owner}\0${scopeLabel(scope)}`;
5338
+ function endpointBucketKey(path5, owner, scope) {
5339
+ return `${path5}\0${owner}\0${scopeLabel(scope)}`;
5339
5340
  }
5340
5341
  function planRouteStubs(input) {
5341
5342
  const endpointBuckets = /* @__PURE__ */ new Map();
@@ -5459,7 +5460,7 @@ function planRouteStubs(input) {
5459
5460
  for (const entry of entries) {
5460
5461
  const nextRelativePath = entry.nextRelativePath;
5461
5462
  const destination = normalizeAppRouterDestinationPattern(nextRelativePath);
5462
- const path3 = routeEntryToAppPageFile(nextRelativePath);
5463
+ const path5 = routeEntryToAppPageFile(nextRelativePath);
5463
5464
  const existing = routeDestinationOwners.get(destination);
5464
5465
  if (existing) {
5465
5466
  throw new Error(
@@ -5469,13 +5470,13 @@ function planRouteStubs(input) {
5469
5470
  routeDestinationOwners.set(destination, {
5470
5471
  owner: regMod.name,
5471
5472
  nextRelativePath,
5472
- path: path3
5473
+ path: path5
5473
5474
  });
5474
5475
  const importModule = routeEntryImport(regMod.name, nextRelativePath);
5475
5476
  const analyzed = analyzeRouteEntryPath(nextRelativePath);
5476
5477
  const mount = mounts.find((claim) => routeEntryCoveredByMountClaim(analyzed, claim));
5477
5478
  routeStubs.push({
5478
- path: path3,
5479
+ path: path5,
5479
5480
  owner: regMod.name,
5480
5481
  nextRelativePath,
5481
5482
  importModule,
@@ -5536,16 +5537,16 @@ function planCompositionArtifacts(input) {
5536
5537
  ...stubs.routeStubs.map((stub) => stub.path)
5537
5538
  ]);
5538
5539
  const existingGeneratedPathSet = new Set(input.existingGeneratedPaths);
5539
- const isProvenStaleDeletionCandidate = (path3, content) => {
5540
- if (path3 === MODULES_GEN_PATH) return false;
5541
- if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path3)) return false;
5542
- if (desiredPaths.has(path3)) return false;
5543
- if (!existingGeneratedPathSet.has(path3)) return false;
5544
- const legacy = parseLegacyApiAdapter(path3, content);
5540
+ const isProvenStaleDeletionCandidate = (path5, content) => {
5541
+ if (path5 === MODULES_GEN_PATH) return false;
5542
+ if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path5)) return false;
5543
+ if (desiredPaths.has(path5)) return false;
5544
+ if (!existingGeneratedPathSet.has(path5)) return false;
5545
+ const legacy = parseLegacyApiAdapter(path5, content);
5545
5546
  if (legacy) return true;
5546
- if (parseLegacyRouteAdapter(path3, content)) return true;
5547
+ if (parseLegacyRouteAdapter(path5, content)) return true;
5547
5548
  const marker = parseCompositionArtifactMarker(content);
5548
- return marker != null && markerCongruentWithPath(path3, marker);
5549
+ return marker != null && markerCongruentWithPath(path5, marker);
5549
5550
  };
5550
5551
  const plannedByDestination = /* @__PURE__ */ new Map();
5551
5552
  for (const stub of stubs.routeStubs) {
@@ -5699,13 +5700,13 @@ function planCompositionArtifacts(input) {
5699
5700
  for (const stub of stubs.routeStubs) desired.set(stub.path, stub.content);
5700
5701
  const endpointStubByPath = new Map(stubs.endpointStubs.map((stub) => [stub.path, stub]));
5701
5702
  const routeStubByPath = new Map(stubs.routeStubs.map((stub) => [stub.path, stub]));
5702
- for (const [path3] of desired) {
5703
- const existing = input.existingFiles[path3];
5703
+ for (const [path5] of desired) {
5704
+ const existing = input.existingFiles[path5];
5704
5705
  if (existing == null) continue;
5705
5706
  let marker = parseCompositionArtifactMarker(existing);
5706
- const plannedEndpoint = endpointStubByPath.get(path3);
5707
- const plannedRoute = routeStubByPath.get(path3);
5708
- const legacy = parseLegacyApiAdapter(path3, existing);
5707
+ const plannedEndpoint = endpointStubByPath.get(path5);
5708
+ const plannedRoute = routeStubByPath.get(path5);
5709
+ const legacy = parseLegacyApiAdapter(path5, existing);
5709
5710
  if (legacy && plannedEndpoint) {
5710
5711
  marker = {
5711
5712
  kind: "endpoint",
@@ -5713,66 +5714,66 @@ function planCompositionArtifacts(input) {
5713
5714
  scope: plannedEndpoint.scope
5714
5715
  };
5715
5716
  }
5716
- if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path3, existing, plannedRoute)) {
5717
+ if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path5, existing, plannedRoute)) {
5717
5718
  marker = {
5718
5719
  kind: "route",
5719
5720
  owner: plannedRoute.owner,
5720
5721
  nextRelativePath: plannedRoute.nextRelativePath
5721
5722
  };
5722
5723
  }
5723
- const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path3);
5724
+ const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path5);
5724
5725
  if (!marker) {
5725
5726
  return {
5726
5727
  ok: false,
5727
- 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}`,
5728
- path: path3
5728
+ error: path5 === MODULES_GEN_PATH ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged registry at ${path5}` : isGenRuntimePath ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged generated file at ${path5}` : `[ModuleRail] composition artifacts: refusing to overwrite unmanaged route at ${path5}`,
5729
+ path: path5
5729
5730
  };
5730
5731
  }
5731
- const expectedKind = expectedMarkerKindForPath(path3);
5732
+ const expectedKind = expectedMarkerKindForPath(path5);
5732
5733
  if (marker.kind !== expectedKind) {
5733
5734
  return {
5734
5735
  ok: false,
5735
- 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)`,
5736
- path: path3
5736
+ error: path5 === MODULES_GEN_PATH ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged registry at ${path5} (incongruent marker ownership: found ${marker.kind} marker)` : isGenRuntimePath ? `[ModuleRail] composition artifacts: refusing to overwrite unmanaged generated file at ${path5} (incongruent marker ownership: found ${marker.kind} marker)` : `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path5} (expected ${expectedKind ?? "no"} marker, found ${marker.kind} marker)`,
5737
+ path: path5
5737
5738
  };
5738
5739
  }
5739
5740
  if (marker.kind === "endpoint") {
5740
- const stub = endpointStubByPath.get(path3);
5741
+ const stub = endpointStubByPath.get(path5);
5741
5742
  const ownerMatches = stub != null && marker.owner === stub.owner;
5742
5743
  const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
5743
5744
  if (!ownerMatches || !scopeMatches) {
5744
5745
  return {
5745
5746
  ok: false,
5746
- 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) : "?"})`,
5747
- path: path3,
5747
+ error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path5} (existing owner=${marker.owner} scope=${scopeLabel(marker.scope)} vs planned owner=${stub?.owner ?? "?"} scope=${stub ? scopeLabel(stub.scope) : "?"})`,
5748
+ path: path5,
5748
5749
  owner: stub?.owner
5749
5750
  };
5750
5751
  }
5751
5752
  } else if (marker.kind === "route") {
5752
- const stub = routeStubByPath.get(path3);
5753
+ const stub = routeStubByPath.get(path5);
5753
5754
  const ownerMatches = stub != null && marker.owner === stub.owner;
5754
5755
  const pathMatches = stub != null && marker.nextRelativePath === stub.nextRelativePath;
5755
5756
  if (!ownerMatches || !pathMatches) {
5756
5757
  return {
5757
5758
  ok: false,
5758
- 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 ?? "?"})`,
5759
- path: path3,
5759
+ error: `[ModuleRail] composition artifacts: refusing to overwrite incongruent marker ownership at ${path5} (existing owner=${marker.owner} path=${marker.nextRelativePath} vs planned owner=${stub?.owner ?? "?"} path=${stub?.nextRelativePath ?? "?"})`,
5760
+ path: path5,
5760
5761
  owner: stub?.owner
5761
5762
  };
5762
5763
  }
5763
5764
  }
5764
5765
  }
5765
5766
  const deletes = [];
5766
- for (const path3 of input.existingGeneratedPaths) {
5767
- const existing = input.existingFiles[path3];
5767
+ for (const path5 of input.existingGeneratedPaths) {
5768
+ const existing = input.existingFiles[path5];
5768
5769
  if (existing == null) continue;
5769
- if (!isProvenStaleDeletionCandidate(path3, existing)) {
5770
+ if (!isProvenStaleDeletionCandidate(path5, existing)) {
5770
5771
  continue;
5771
5772
  }
5772
- deletes.push(path3);
5773
+ deletes.push(path5);
5773
5774
  }
5774
5775
  deletes.sort(compareNames2);
5775
- 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));
5776
+ const writes = [...desired.entries()].filter(([path5, content]) => input.existingFiles[path5] !== content).map(([path5, content]) => ({ path: path5, content })).sort((a, b) => compareNames2(a.path, b.path));
5776
5777
  return {
5777
5778
  ok: true,
5778
5779
  plan: {
@@ -5801,8 +5802,16 @@ async function checkoutParsesTypedStores(sandbox) {
5801
5802
  try {
5802
5803
  if (!await sandbox.fileExists(APP_PACKAGE_JSON_PATH)) return false;
5803
5804
  const pkg = JSON.parse(await sandbox.readFile(APP_PACKAGE_JSON_PATH));
5804
- const range = pkg.devDependencies?.[COMPOSE_PACKAGE] ?? pkg.dependencies?.[COMPOSE_PACKAGE];
5805
- if (!range) return false;
5805
+ return composeRangeParsesTypedStores(
5806
+ pkg.devDependencies?.[COMPOSE_PACKAGE] ?? pkg.dependencies?.[COMPOSE_PACKAGE]
5807
+ );
5808
+ } catch {
5809
+ return false;
5810
+ }
5811
+ }
5812
+ function composeRangeParsesTypedStores(range) {
5813
+ if (!range) return false;
5814
+ try {
5806
5815
  const lowest = import_semver.default.minVersion(range);
5807
5816
  return lowest != null && import_semver.default.gte(lowest, TYPED_STORES_MIN_COMPOSE_VERSION);
5808
5817
  } catch {
@@ -6247,11 +6256,11 @@ function countedCompositionContext(context) {
6247
6256
  counts.sandboxExecs += 1;
6248
6257
  return context.sandbox.exec(...args);
6249
6258
  },
6250
- readFile: (path3) => {
6259
+ readFile: (path5) => {
6251
6260
  counts.sandboxReads += 1;
6252
- return context.sandbox.readFile(path3);
6261
+ return context.sandbox.readFile(path5);
6253
6262
  },
6254
- fileExists: (path3) => context.sandbox.fileExists(path3),
6263
+ fileExists: (path5) => context.sandbox.fileExists(path5),
6255
6264
  fetchRemoteRef: (...args) => context.sandbox.fetchRemoteRef(...args),
6256
6265
  addDependencies: (specs) => context.sandbox.addDependencies(specs)
6257
6266
  }
@@ -6463,14 +6472,14 @@ async function discoverModuleRouteEntries(context, moduleName) {
6463
6472
  }
6464
6473
  return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
6465
6474
  }
6466
- async function writeSandboxFileAtomic(target, path3, content) {
6467
- const dir = dirnamePosix(path3);
6468
- const tmp = `${path3}.tmp.${target.runId}`;
6475
+ async function writeSandboxFileAtomic(target, path5, content) {
6476
+ const dir = dirnamePosix(path5);
6477
+ const tmp = `${path5}.tmp.${target.runId}`;
6469
6478
  const b64 = Buffer.from(content, "utf8").toString("base64");
6470
6479
  const cmd = [
6471
6480
  `mkdir -p ${shellQuote(dir)}`,
6472
6481
  `printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
6473
- `mv -f ${shellQuote(tmp)} ${shellQuote(path3)}`
6482
+ `mv -f ${shellQuote(tmp)} ${shellQuote(path5)}`
6474
6483
  ].join(" && ");
6475
6484
  const result = await target.sandbox.exec(cmd, { raiseOnError: false });
6476
6485
  if (result.exitCode !== 0) {
@@ -6485,14 +6494,14 @@ async function writeSandboxFileAtomic(target, path3, content) {
6485
6494
  }
6486
6495
  const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
6487
6496
  throw new Error(
6488
- `[ModuleRail] composition artifacts: failed to write ${redactSecrets(path3)}: ${redactSecrets(detail)}`
6497
+ `[ModuleRail] composition artifacts: failed to write ${redactSecrets(path5)}: ${redactSecrets(detail)}`
6489
6498
  );
6490
6499
  }
6491
6500
  }
6492
6501
  async function collectAbsentWriteParentDirs(context, writePaths) {
6493
6502
  const absent = /* @__PURE__ */ new Set();
6494
- for (const path3 of writePaths) {
6495
- let dir = dirnamePosix(path3);
6503
+ for (const path5 of writePaths) {
6504
+ let dir = dirnamePosix(path5);
6496
6505
  while (dir !== "." && dir !== "") {
6497
6506
  if (absent.has(dir)) {
6498
6507
  dir = dirnamePosix(dir);
@@ -6529,12 +6538,12 @@ async function removeAbsentWriteParentDirsOnRollback(context, dirsDeepestFirst)
6529
6538
  );
6530
6539
  }
6531
6540
  }
6532
- async function deleteSandboxFile(context, path3) {
6533
- const result = await context.sandbox.exec(`rm -f ${shellQuote(path3)}`, { raiseOnError: false });
6541
+ async function deleteSandboxFile(context, path5) {
6542
+ const result = await context.sandbox.exec(`rm -f ${shellQuote(path5)}`, { raiseOnError: false });
6534
6543
  if (result.exitCode !== 0) {
6535
6544
  const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
6536
6545
  throw new Error(
6537
- `[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path3)}: ${redactSecrets(detail)}`
6546
+ `[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path5)}: ${redactSecrets(detail)}`
6538
6547
  );
6539
6548
  }
6540
6549
  }
@@ -6601,37 +6610,37 @@ async function applyCompositionArtifactMutations(context, plan, existingFiles) {
6601
6610
  await writeSandboxFileAtomic(context, write.path, write.content);
6602
6611
  appliedWrites.push({ path: write.path, previous });
6603
6612
  }
6604
- for (const path3 of plan.deletes) {
6605
- const previous = existingFiles[path3];
6613
+ for (const path5 of plan.deletes) {
6614
+ const previous = existingFiles[path5];
6606
6615
  if (previous == null) continue;
6607
- await deleteSandboxFile(context, path3);
6608
- appliedDeletes.push({ path: path3, previous });
6616
+ await deleteSandboxFile(context, path5);
6617
+ appliedDeletes.push({ path: path5, previous });
6609
6618
  }
6610
6619
  } catch (error) {
6611
6620
  console.log(
6612
6621
  `[ModuleRail] runId=${context.runId} composition artifacts mid-apply failure; rolling back writes=${appliedWrites.length} deletes=${appliedDeletes.length} newDirs=${newlyCreatedParentDirs.length}`
6613
6622
  );
6614
- for (const { path: path3, previous } of [...appliedDeletes].reverse()) {
6623
+ for (const { path: path5, previous } of [...appliedDeletes].reverse()) {
6615
6624
  try {
6616
- await writeSandboxFileAtomic(context, path3, previous);
6625
+ await writeSandboxFileAtomic(context, path5, previous);
6617
6626
  } catch (rollbackError) {
6618
6627
  console.log(
6619
- `[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path3)}: ${redactSecrets(
6628
+ `[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path5)}: ${redactSecrets(
6620
6629
  rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
6621
6630
  )}`
6622
6631
  );
6623
6632
  }
6624
6633
  }
6625
- for (const { path: path3, previous } of [...appliedWrites].reverse()) {
6634
+ for (const { path: path5, previous } of [...appliedWrites].reverse()) {
6626
6635
  try {
6627
6636
  if (previous == null) {
6628
- await deleteSandboxFile(context, path3);
6637
+ await deleteSandboxFile(context, path5);
6629
6638
  } else {
6630
- await writeSandboxFileAtomic(context, path3, previous);
6639
+ await writeSandboxFileAtomic(context, path5, previous);
6631
6640
  }
6632
6641
  } catch (rollbackError) {
6633
6642
  console.log(
6634
- `[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path3)}: ${redactSecrets(
6643
+ `[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path5)}: ${redactSecrets(
6635
6644
  rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
6636
6645
  )}`
6637
6646
  );
@@ -6665,39 +6674,39 @@ async function listExistingGeneratedStubPaths(context) {
6665
6674
  COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
6666
6675
  );
6667
6676
  const presentPaths = [];
6668
- for (const path3 of candidates) {
6677
+ for (const path5 of candidates) {
6669
6678
  const presence = assertRealCompositionArtifactFilePresence(
6670
- path3,
6671
- kinds.get(path3) ?? "unreadable"
6679
+ path5,
6680
+ kinds.get(path5) ?? "unreadable"
6672
6681
  );
6673
- if (presence === "file") presentPaths.push(path3);
6682
+ if (presence === "file") presentPaths.push(path5);
6674
6683
  }
6675
6684
  let contents;
6676
6685
  try {
6677
6686
  contents = await readFilesMany(context, presentPaths);
6678
6687
  } catch (error) {
6679
- const path3 = readFailurePath(error);
6688
+ const path5 = readFailurePath(error);
6680
6689
  throw new Error(
6681
- `[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path3)}: ${redactSecrets(
6690
+ `[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path5)}: ${redactSecrets(
6682
6691
  error instanceof Error ? error.message : String(error)
6683
6692
  )}`
6684
6693
  );
6685
6694
  }
6686
6695
  const owned = [];
6687
- for (const path3 of presentPaths) {
6688
- const content = contents.get(path3);
6696
+ for (const path5 of presentPaths) {
6697
+ const content = contents.get(path5);
6689
6698
  const marker = parseCompositionArtifactMarker(content);
6690
- if (parseLegacyApiAdapter(path3, content) || parseLegacyRouteAdapter(path3, content)) {
6691
- owned.push(path3);
6699
+ if (parseLegacyApiAdapter(path5, content) || parseLegacyRouteAdapter(path5, content)) {
6700
+ owned.push(path5);
6692
6701
  continue;
6693
6702
  }
6694
6703
  if (marker == null) continue;
6695
- if (!markerCongruentWithPath(path3, marker)) {
6704
+ if (!markerCongruentWithPath(path5, marker)) {
6696
6705
  throw new Error(
6697
- `[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)`
6706
+ `[ModuleRail] composition artifacts: incongruent marker at ${redactSecrets(path5)} (found ${marker.kind} marker on a path shaped for ${expectedMarkerKindForPath(path5) ?? "no"} artifacts; refusing to reconcile a swapped marker)`
6698
6707
  );
6699
6708
  }
6700
- owned.push(path3);
6709
+ owned.push(path5);
6701
6710
  }
6702
6711
  return owned;
6703
6712
  }
@@ -6830,7 +6839,7 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
6830
6839
  { raiseOnError: false }
6831
6840
  );
6832
6841
  if (result.exitCode !== 0) {
6833
- for (const path3 of chunk) kinds.set(path3, "unreadable");
6842
+ for (const path5 of chunk) kinds.set(path5, "unreadable");
6834
6843
  continue;
6835
6844
  }
6836
6845
  const lines = result.output.split("\n").filter(Boolean);
@@ -6841,31 +6850,31 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
6841
6850
  }
6842
6851
  } else {
6843
6852
  for (const line of lines) {
6844
- const [rawKind, path3] = line.split(" ", 2);
6853
+ const [rawKind, path5] = line.split(" ", 2);
6845
6854
  const kind = rawKind?.replace(/^entry-kind:/, "");
6846
- if (path3 && chunk.includes(path3) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
6847
- kinds.set(path3, kind);
6855
+ if (path5 && chunk.includes(path5) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
6856
+ kinds.set(path5, kind);
6848
6857
  }
6849
6858
  }
6850
6859
  }
6851
- for (const path3 of chunk) {
6852
- if (!kinds.has(path3)) kinds.set(path3, "unreadable");
6860
+ for (const path5 of chunk) {
6861
+ if (!kinds.has(path5)) kinds.set(path5, "unreadable");
6853
6862
  }
6854
6863
  }
6855
6864
  return kinds;
6856
6865
  }
6857
- async function classifySandboxPathPresenceKind(context, path3, probeToken) {
6858
- return (await classifySandboxPathPresenceKindMany(context, [path3], probeToken)).get(path3);
6866
+ async function classifySandboxPathPresenceKind(context, path5, probeToken) {
6867
+ return (await classifySandboxPathPresenceKindMany(context, [path5], probeToken)).get(path5);
6859
6868
  }
6860
6869
  var LOCALE_JSON_KIND_PROBE_TOKEN = "locale-json-kind";
6861
- function assertRealLocaleJsonFilePresence(path3, presence) {
6870
+ function assertRealLocaleJsonFilePresence(path5, presence) {
6862
6871
  if (presence === "file") return;
6863
6872
  if (presence === "absent") {
6864
- throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path3}`);
6873
+ throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path5}`);
6865
6874
  }
6866
- const presenceError = formatConventionalEntryPresenceError(path3, presence);
6875
+ const presenceError = formatConventionalEntryPresenceError(path5, presence);
6867
6876
  throw new Error(
6868
- `[ModuleRail] composition artifacts: ${presenceError ?? `${path3} is not a real locale JSON file`}`
6877
+ `[ModuleRail] composition artifacts: ${presenceError ?? `${path5} is not a real locale JSON file`}`
6869
6878
  );
6870
6879
  }
6871
6880
  function splitTaggedLine(line) {
@@ -6940,11 +6949,11 @@ async function listLocaleBasenamesIfDirMany(context, dirs) {
6940
6949
  }
6941
6950
  async function readFilesMany(context, paths) {
6942
6951
  const sortedPaths = [...paths].sort((a, b) => a.localeCompare(b));
6943
- const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path3) => {
6952
+ const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path5) => {
6944
6953
  try {
6945
- return { path: path3, value: await context.sandbox.readFile(path3) };
6954
+ return { path: path5, value: await context.sandbox.readFile(path5) };
6946
6955
  } catch (error) {
6947
- return { path: path3, error };
6956
+ return { path: path5, error };
6948
6957
  }
6949
6958
  });
6950
6959
  const files = /* @__PURE__ */ new Map();
@@ -6967,58 +6976,58 @@ function readFailurePath(error) {
6967
6976
  var CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN = "conventional-entry-kind";
6968
6977
  var SERVER_REGISTRATIONS_FILE = "server/registrations.ts";
6969
6978
  var COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN = "composition-artifact-kind";
6970
- async function classifyCompositionArtifactPathKind(context, path3) {
6971
- return classifySandboxPathPresenceKind(context, path3, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
6979
+ async function classifyCompositionArtifactPathKind(context, path5) {
6980
+ return classifySandboxPathPresenceKind(context, path5, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
6972
6981
  }
6973
- function assertRealCompositionArtifactFilePresence(path3, presence) {
6982
+ function assertRealCompositionArtifactFilePresence(path5, presence) {
6974
6983
  if (presence === "file") return "file";
6975
6984
  if (presence === "absent") return "absent";
6976
- const presenceError = formatConventionalEntryPresenceError(path3, presence);
6985
+ const presenceError = formatConventionalEntryPresenceError(path5, presence);
6977
6986
  throw new Error(
6978
- `[ModuleRail] composition artifacts: ${presenceError ?? `${path3} is not a real composition artifact file`}`
6987
+ `[ModuleRail] composition artifacts: ${presenceError ?? `${path5} is not a real composition artifact file`}`
6979
6988
  );
6980
6989
  }
6981
- async function readConventionalEntryIfRealFile(path3, kind, presences, files) {
6982
- const presence = presences.get(path3) ?? "unreadable";
6983
- const presenceError = formatConventionalEntryPresenceError(path3, presence);
6990
+ async function readConventionalEntryIfRealFile(path5, kind, presences, files) {
6991
+ const presence = presences.get(path5) ?? "unreadable";
6992
+ const presenceError = formatConventionalEntryPresenceError(path5, presence);
6984
6993
  if (presenceError) {
6985
6994
  throw new Error(`[ModuleRail] composition artifacts: ${presenceError}`);
6986
6995
  }
6987
6996
  if (presence === "absent") {
6988
6997
  return null;
6989
6998
  }
6990
- const source = files.get(path3);
6999
+ const source = files.get(path5);
6991
7000
  switch (kind) {
6992
7001
  case "contributions":
6993
- assertContributionsExport(source, path3);
6994
- assertClientSafeConventionalEntryImports(source, path3, "contributions");
7002
+ assertContributionsExport(source, path5);
7003
+ assertClientSafeConventionalEntryImports(source, path5, "contributions");
6995
7004
  break;
6996
7005
  case "slotCatalogs":
6997
- assertSlotCatalogsExport(source, path3);
6998
- assertClientSafeConventionalEntryImports(source, path3, "slotCatalogs");
7006
+ assertSlotCatalogsExport(source, path5);
7007
+ assertClientSafeConventionalEntryImports(source, path5, "slotCatalogs");
6999
7008
  break;
7000
7009
  case "initializeEnhancement":
7001
- assertInitializeEnhancementExport(source, path3);
7010
+ assertInitializeEnhancementExport(source, path5);
7002
7011
  break;
7003
7012
  }
7004
7013
  return source;
7005
7014
  }
7006
7015
  function hasRealServerRegistrations(modulePath, presences) {
7007
- const path3 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
7008
- return assertRealCompositionArtifactFilePresence(path3, presences.get(path3) ?? "unreadable") === "file";
7016
+ const path5 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
7017
+ return assertRealCompositionArtifactFilePresence(path5, presences.get(path5) ?? "unreadable") === "file";
7009
7018
  }
7010
- async function readJsonObjectFile(path3, files) {
7011
- const raw = files.get(path3);
7019
+ async function readJsonObjectFile(path5, files) {
7020
+ const raw = files.get(path5);
7012
7021
  let parsed;
7013
7022
  try {
7014
7023
  parsed = JSON.parse(raw);
7015
7024
  } catch (error) {
7016
7025
  throw new Error(
7017
- `[ModuleRail] composition artifacts: invalid JSON at ${path3}: ${error instanceof Error ? error.message : String(error)}`
7026
+ `[ModuleRail] composition artifacts: invalid JSON at ${path5}: ${error instanceof Error ? error.message : String(error)}`
7018
7027
  );
7019
7028
  }
7020
7029
  if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
7021
- throw new Error(`[ModuleRail] composition artifacts: ${path3} root must be an object`);
7030
+ throw new Error(`[ModuleRail] composition artifacts: ${path5} root must be an object`);
7022
7031
  }
7023
7032
  return parsed;
7024
7033
  }
@@ -7067,11 +7076,11 @@ async function collectCompositionEntryFacts(context, registry) {
7067
7076
  classifySandboxPathPresenceKindMany(context, localePaths, LOCALE_JSON_KIND_PROBE_TOKEN)
7068
7077
  ]);
7069
7078
  const presentConventionalPaths = [];
7070
- for (const path3 of conventionalPaths) {
7071
- const presence = conventionalPresences.get(path3) ?? "unreadable";
7072
- if (presence === "file") presentConventionalPaths.push(path3);
7079
+ for (const path5 of conventionalPaths) {
7080
+ const presence = conventionalPresences.get(path5) ?? "unreadable";
7081
+ if (presence === "file") presentConventionalPaths.push(path5);
7073
7082
  }
7074
- const presentLocalePaths = localePaths.filter((path3) => localePresences.get(path3) === "file");
7083
+ const presentLocalePaths = localePaths.filter((path5) => localePresences.get(path5) === "file");
7075
7084
  const files = await readFilesMany(context, [...presentConventionalPaths, ...presentLocalePaths]);
7076
7085
  const substrateLocales = [];
7077
7086
  for (const locale of substrateNames) {
@@ -7169,8 +7178,8 @@ async function collectCompositionEntryFacts(context, registry) {
7169
7178
  });
7170
7179
  if (rootLocales.length > 0) {
7171
7180
  for (const locale of rootLocales) {
7172
- const path3 = `${modulePath}/i18n/${locale}.json`;
7173
- assertRealLocaleJsonFilePresence(path3, localePresences.get(path3) ?? "unreadable");
7181
+ const path5 = `${modulePath}/i18n/${locale}.json`;
7182
+ assertRealLocaleJsonFilePresence(path5, localePresences.get(path5) ?? "unreadable");
7174
7183
  }
7175
7184
  const enMessages = await readJsonObjectFile(`${modulePath}/i18n/en.json`, files);
7176
7185
  const localeMessages = { en: enMessages };
@@ -7194,8 +7203,8 @@ async function collectCompositionEntryFacts(context, registry) {
7194
7203
  if (!enh.active || enh.locales.length === 0) continue;
7195
7204
  const peerDir = `${modulePath}/enhancements/${enh.peer}`;
7196
7205
  for (const locale of enh.locales) {
7197
- const path3 = `${peerDir}/i18n/${locale}.json`;
7198
- assertRealLocaleJsonFilePresence(path3, localePresences.get(path3) ?? "unreadable");
7206
+ const path5 = `${peerDir}/i18n/${locale}.json`;
7207
+ assertRealLocaleJsonFilePresence(path5, localePresences.get(path5) ?? "unreadable");
7199
7208
  }
7200
7209
  const enMessages = await readJsonObjectFile(`${peerDir}/i18n/en.json`, files);
7201
7210
  const localeMessages = { en: enMessages };
@@ -7396,16 +7405,16 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7396
7405
  for (const stub of probePlan.plan.routeStubs) candidatePaths.add(stub.path);
7397
7406
  const existingFiles = {};
7398
7407
  const remainingCandidatePaths = [];
7399
- for (const path3 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
7400
- if (Object.prototype.hasOwnProperty.call(existingAppPages, path3)) {
7401
- existingFiles[path3] = existingAppPages[path3];
7408
+ for (const path5 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
7409
+ if (Object.prototype.hasOwnProperty.call(existingAppPages, path5)) {
7410
+ existingFiles[path5] = existingAppPages[path5];
7402
7411
  continue;
7403
7412
  }
7404
- if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path3)) {
7405
- existingFiles[path3] = existingAppRoutes[path3];
7413
+ if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path5)) {
7414
+ existingFiles[path5] = existingAppRoutes[path5];
7406
7415
  continue;
7407
7416
  }
7408
- remainingCandidatePaths.push(path3);
7417
+ remainingCandidatePaths.push(path5);
7409
7418
  }
7410
7419
  const candidateKinds = await classifySandboxPathPresenceKindMany(
7411
7420
  context,
@@ -7413,12 +7422,12 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7413
7422
  COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
7414
7423
  );
7415
7424
  const presentCandidatePaths = [];
7416
- for (const path3 of remainingCandidatePaths) {
7425
+ for (const path5 of remainingCandidatePaths) {
7417
7426
  const presence = assertRealCompositionArtifactFilePresence(
7418
- path3,
7419
- candidateKinds.get(path3) ?? "unreadable"
7427
+ path5,
7428
+ candidateKinds.get(path5) ?? "unreadable"
7420
7429
  );
7421
- if (presence === "file") presentCandidatePaths.push(path3);
7430
+ if (presence === "file") presentCandidatePaths.push(path5);
7422
7431
  }
7423
7432
  try {
7424
7433
  Object.assign(
@@ -7426,9 +7435,9 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7426
7435
  Object.fromEntries(await readFilesMany(context, presentCandidatePaths))
7427
7436
  );
7428
7437
  } catch (error) {
7429
- const path3 = readFailurePath(error);
7438
+ const path5 = readFailurePath(error);
7430
7439
  throw new Error(
7431
- `[ModuleRail] composition artifacts: cannot read ${redactSecrets(path3)} during preflight: ${redactSecrets(
7440
+ `[ModuleRail] composition artifacts: cannot read ${redactSecrets(path5)} during preflight: ${redactSecrets(
7432
7441
  error instanceof Error ? error.message : String(error)
7433
7442
  )}`
7434
7443
  );
@@ -7497,14 +7506,139 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7497
7506
  var HOME_I18N_SUBSTRATE_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/substrate`;
7498
7507
  var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
7499
7508
 
7509
+ // src/module-boundaries.ts
7510
+ var import_node_fs2 = __toESM(require("fs"));
7511
+ var import_node_path2 = __toESM(require("path"));
7512
+ var import_typescript5 = __toESM(require("typescript"));
7513
+ var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
7514
+ var RESOLVE_SUFFIXES = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
7515
+ var DATA_FILE = /\.(?:sql|json)$/;
7516
+ function dataPathsIn(fileName, source) {
7517
+ if (!/\.(?:sql|json)["'`]/.test(source)) return [];
7518
+ const paths = [];
7519
+ const visit = (node) => {
7520
+ if ((import_typescript5.default.isStringLiteralLike(node) || import_typescript5.default.isTemplateTail(node)) && DATA_FILE.test(node.text)) {
7521
+ paths.push(node.text);
7522
+ }
7523
+ import_typescript5.default.forEachChild(node, visit);
7524
+ };
7525
+ visit(import_typescript5.default.createSourceFile(fileName, source, import_typescript5.default.ScriptTarget.Latest, false));
7526
+ return paths;
7527
+ }
7528
+ function scanModule(modulesDir, module2) {
7529
+ const scanned = /* @__PURE__ */ new Map();
7530
+ const moduleDir = import_node_path2.default.join(modulesDir, module2);
7531
+ if (!import_node_fs2.default.existsSync(moduleDir)) return scanned;
7532
+ const files = import_node_fs2.default.readdirSync(moduleDir, { recursive: true, encoding: "utf8" }).filter((relative) => SOURCE_FILE.test(relative) && !relative.includes("node_modules"));
7533
+ for (const relative of files) {
7534
+ const absolute = import_node_path2.default.join(moduleDir, relative);
7535
+ if (!import_node_fs2.default.statSync(absolute).isFile()) continue;
7536
+ const source = import_node_fs2.default.readFileSync(absolute, "utf8");
7537
+ const specifiers = import_typescript5.default.preProcessFile(source, true, true).importedFiles.map((f) => f.fileName);
7538
+ const dataPaths = dataPathsIn(absolute, source);
7539
+ const targets = [...specifiers, ...dataPaths].flatMap((spec) => {
7540
+ if (spec.startsWith("@/modules/"))
7541
+ return [import_node_path2.default.join(modulesDir, spec.slice("@/modules/".length))];
7542
+ if (spec.startsWith("./") || spec.startsWith("../")) {
7543
+ return [import_node_path2.default.resolve(import_node_path2.default.dirname(absolute), spec)];
7544
+ }
7545
+ return [];
7546
+ });
7547
+ scanned.set(absolute, {
7548
+ module: module2,
7549
+ relative: relative.split(import_node_path2.default.sep).join("/"),
7550
+ targets,
7551
+ dataPaths
7552
+ });
7553
+ }
7554
+ return scanned;
7555
+ }
7556
+ function findModuleBoundaryViolations(modulesDir, peersByModule, { pruning = false } = {}) {
7557
+ const files = /* @__PURE__ */ new Map();
7558
+ for (const [module2, peers] of Object.entries(peersByModule)) {
7559
+ if (peers.length === 0) continue;
7560
+ for (const [absolute, file] of scanModule(modulesDir, module2)) files.set(absolute, file);
7561
+ }
7562
+ const exempt = (file, peer) => (pruning ? peersByModule[file.module] ?? [] : [peer]).some(
7563
+ (q) => file.relative.startsWith(`enhancements/${q}/`)
7564
+ );
7565
+ const goneWith = (target, peer) => {
7566
+ const relative = import_node_path2.default.relative(modulesDir, target).split(import_node_path2.default.sep).join("/");
7567
+ return relative === peer || relative.startsWith(`${peer}/`) || new RegExp(`^[^/]+/enhancements/${peer}(/|$)`).test(relative);
7568
+ };
7569
+ const tainted = /* @__PURE__ */ new Map();
7570
+ const queue = [];
7571
+ const taint = (absolute, peer) => {
7572
+ const peers = tainted.get(absolute) ?? /* @__PURE__ */ new Set();
7573
+ if (peers.has(peer)) return;
7574
+ peers.add(peer);
7575
+ tainted.set(absolute, peers);
7576
+ queue.push([absolute, peer]);
7577
+ };
7578
+ const importers = /* @__PURE__ */ new Map();
7579
+ for (const [absolute, file] of files) {
7580
+ for (const target of file.targets) {
7581
+ const hit = RESOLVE_SUFFIXES.map((suffix) => target + suffix).find((c) => files.has(c));
7582
+ if (hit) importers.set(hit, [...importers.get(hit) ?? [], absolute]);
7583
+ }
7584
+ for (const peer of peersByModule[file.module] ?? []) {
7585
+ if (exempt(file, peer)) continue;
7586
+ if (file.targets.some((target) => goneWith(target, peer)) || file.dataPaths.some(
7587
+ (p) => p.startsWith(`modules/${peer}/`) || p.includes(`/modules/${peer}/`)
7588
+ )) {
7589
+ taint(absolute, peer);
7590
+ }
7591
+ }
7592
+ }
7593
+ for (let next = queue.shift(); next; next = queue.shift()) {
7594
+ const [target, peer] = next;
7595
+ for (const importer of importers.get(target) ?? []) {
7596
+ const file = files.get(importer);
7597
+ if ((peersByModule[file.module] ?? []).includes(peer) && !exempt(file, peer)) {
7598
+ taint(importer, peer);
7599
+ }
7600
+ }
7601
+ }
7602
+ const violations = [];
7603
+ for (const [absolute, peers] of tainted) {
7604
+ const file = files.get(absolute);
7605
+ for (const peer of peers) {
7606
+ violations.push({ file: `${file.module}/${file.relative}`, module: file.module, peer });
7607
+ }
7608
+ }
7609
+ return violations.sort((a, b) => a.file.localeCompare(b.file) || a.peer.localeCompare(b.peer));
7610
+ }
7611
+ function findOptionalPeerViolations(modulesDir) {
7612
+ if (!import_node_fs2.default.existsSync(modulesDir)) return [];
7613
+ const manifests = {};
7614
+ for (const name of import_node_fs2.default.readdirSync(modulesDir)) {
7615
+ const manifestPath = import_node_path2.default.join(modulesDir, name, "module.json");
7616
+ if (!import_node_fs2.default.existsSync(manifestPath)) continue;
7617
+ manifests[name] = JSON.parse(import_node_fs2.default.readFileSync(manifestPath, "utf8"));
7618
+ }
7619
+ const features = Object.keys(manifests).filter((name) => manifests[name].kind === "feature");
7620
+ const peersByModule = {};
7621
+ for (const [name, manifest] of Object.entries(manifests)) {
7622
+ const uses = new Set(manifest.uses ?? []);
7623
+ peersByModule[name] = features.filter((peer) => peer !== name && !uses.has(peer));
7624
+ }
7625
+ return findModuleBoundaryViolations(modulesDir, peersByModule);
7626
+ }
7627
+ function formatBoundaryViolations(modulesDirLabel, violations) {
7628
+ const files = new Set(violations.map((v) => v.file)).size;
7629
+ return `${files} Module file(s) import a peer Module that may be absent (not in the Module's \`uses\`, or excluded by --enabled).
7630
+ \`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).
7631
+ ` + violations.map((v) => `${modulesDirLabel}/${v.file}: ${v.peer}`).join("\n");
7632
+ }
7633
+
7500
7634
  // src/index.ts
7501
7635
  var MODULES_DIR2 = MODULES_SANDBOX_DIR;
7502
7636
  function readComposeInputs(cwd) {
7503
- const file = import_node_path2.default.join(cwd, COMPOSE_INPUTS_PATH);
7504
- if (!import_node_fs2.default.existsSync(file)) return { connectedStores: [], installs: {} };
7637
+ const file = import_node_path3.default.join(cwd, COMPOSE_INPUTS_PATH);
7638
+ if (!import_node_fs3.default.existsSync(file)) return { connectedStores: [], installs: {} };
7505
7639
  let raw;
7506
7640
  try {
7507
- raw = JSON.parse(import_node_fs2.default.readFileSync(file, "utf8"));
7641
+ raw = JSON.parse(import_node_fs3.default.readFileSync(file, "utf8"));
7508
7642
  } catch (error) {
7509
7643
  throw new Error(
7510
7644
  `[compose] ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
@@ -7546,18 +7680,18 @@ function stubGitignoreEntries(artifactPaths) {
7546
7680
  function writeStubGitignore(cwd, artifactPaths) {
7547
7681
  const entries = stubGitignoreEntries(artifactPaths);
7548
7682
  const content = [STUB_GITIGNORE_HEADER, ...entries].join("\n") + "\n";
7549
- const file = import_node_path2.default.join(cwd, STUB_GITIGNORE_PATH);
7550
- const existing = import_node_fs2.default.existsSync(file) ? import_node_fs2.default.readFileSync(file, "utf8") : null;
7683
+ const file = import_node_path3.default.join(cwd, STUB_GITIGNORE_PATH);
7684
+ const existing = import_node_fs3.default.existsSync(file) ? import_node_fs3.default.readFileSync(file, "utf8") : null;
7551
7685
  if (existing !== content) {
7552
- import_node_fs2.default.mkdirSync(import_node_path2.default.dirname(file), { recursive: true });
7553
- import_node_fs2.default.writeFileSync(file, content);
7686
+ import_node_fs3.default.mkdirSync(import_node_path3.default.dirname(file), { recursive: true });
7687
+ import_node_fs3.default.writeFileSync(file, content);
7554
7688
  }
7555
7689
  return entries.length;
7556
7690
  }
7557
7691
  function assertStubGitignoreOwned(cwd) {
7558
- const file = import_node_path2.default.join(cwd, STUB_GITIGNORE_PATH);
7559
- if (!import_node_fs2.default.existsSync(file)) return;
7560
- const firstLine = import_node_fs2.default.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
7692
+ const file = import_node_path3.default.join(cwd, STUB_GITIGNORE_PATH);
7693
+ if (!import_node_fs3.default.existsSync(file)) return;
7694
+ const firstLine = import_node_fs3.default.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
7561
7695
  if (firstLine !== STUB_GITIGNORE_HEADER) {
7562
7696
  throw new Error(
7563
7697
  `[compose] refusing to overwrite ${STUB_GITIGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
@@ -7565,8 +7699,8 @@ function assertStubGitignoreOwned(cwd) {
7565
7699
  }
7566
7700
  }
7567
7701
  function assertAppRoot(cwd) {
7568
- const appPath = import_node_path2.default.join(cwd, APP_PACKAGE_JSON);
7569
- if (!import_node_fs2.default.existsSync(appPath)) {
7702
+ const appPath = import_node_path3.default.join(cwd, APP_PACKAGE_JSON);
7703
+ if (!import_node_fs3.default.existsSync(appPath)) {
7570
7704
  throw new Error(
7571
7705
  `[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
7572
7706
  );
@@ -7612,9 +7746,9 @@ ${porcelain.trim()}`
7612
7746
  }
7613
7747
  var APP_SRC_DIR = "apps/web/src";
7614
7748
  function walkSources(dir, skip, out) {
7615
- if (!import_node_fs2.default.existsSync(dir)) return;
7616
- for (const entry of import_node_fs2.default.readdirSync(dir, { withFileTypes: true })) {
7617
- const full = import_node_path2.default.join(dir, entry.name);
7749
+ if (!import_node_fs3.default.existsSync(dir)) return;
7750
+ for (const entry of import_node_fs3.default.readdirSync(dir, { withFileTypes: true })) {
7751
+ const full = import_node_path3.default.join(dir, entry.name);
7618
7752
  if (full === skip) continue;
7619
7753
  if (entry.isDirectory()) walkSources(full, skip, out);
7620
7754
  else if (/\.[cm]?[jt]sx?$/.test(entry.name)) out.push(full);
@@ -7623,13 +7757,13 @@ function walkSources(dir, skip, out) {
7623
7757
  function assertNoAppLayerImporters(cwd, excluded) {
7624
7758
  if (excluded.length === 0) return;
7625
7759
  const files = [];
7626
- walkSources(import_node_path2.default.join(cwd, APP_SRC_DIR), import_node_path2.default.join(cwd, MODULES_DIR2), files);
7760
+ walkSources(import_node_path3.default.join(cwd, APP_SRC_DIR), import_node_path3.default.join(cwd, MODULES_DIR2), files);
7627
7761
  const offenders = [];
7628
7762
  for (const file of files) {
7629
- const source = import_node_fs2.default.readFileSync(file, "utf8");
7763
+ const source = import_node_fs3.default.readFileSync(file, "utf8");
7630
7764
  for (const name of excluded) {
7631
7765
  if (new RegExp(`@/modules/${name}(?=["'\`/])`).test(source)) {
7632
- offenders.push(`${import_node_path2.default.relative(cwd, file)}: ${name}`);
7766
+ offenders.push(`${import_node_path3.default.relative(cwd, file)}: ${name}`);
7633
7767
  }
7634
7768
  }
7635
7769
  }
@@ -7642,8 +7776,8 @@ function assertNoAppLayerImporters(cwd, excluded) {
7642
7776
  }
7643
7777
  }
7644
7778
  function removeDir(absolute) {
7645
- if (!import_node_fs2.default.existsSync(absolute)) return false;
7646
- import_node_fs2.default.rmSync(absolute, { recursive: true, force: true });
7779
+ if (!import_node_fs3.default.existsSync(absolute)) return false;
7780
+ import_node_fs3.default.rmSync(absolute, { recursive: true, force: true });
7647
7781
  return true;
7648
7782
  }
7649
7783
  async function compose(options) {
@@ -7670,6 +7804,16 @@ async function compose(options) {
7670
7804
  dryRun: true
7671
7805
  });
7672
7806
  assertNoAppLayerImporters(options.cwd, preview.excludedModules);
7807
+ const violations = findModuleBoundaryViolations(
7808
+ import_node_path3.default.join(options.cwd, MODULES_DIR2),
7809
+ Object.fromEntries(preview.composedModules.map((name) => [name, preview.excludedModules])),
7810
+ { pruning: true }
7811
+ );
7812
+ if (violations.length > 0) {
7813
+ throw new Error(
7814
+ `[compose] refusing --prune: ${formatBoundaryViolations(MODULES_DIR2, violations)}`
7815
+ );
7816
+ }
7673
7817
  }
7674
7818
  const result = await reconcileCompositionArtifacts(context, {
7675
7819
  enabledModulesRaw: options.enabled,
@@ -7681,11 +7825,11 @@ async function compose(options) {
7681
7825
  let pruned = 0;
7682
7826
  if (options.prune) {
7683
7827
  for (const name of result.excludedModules) {
7684
- if (removeDir(import_node_path2.default.join(options.cwd, MODULES_DIR2, name))) pruned++;
7828
+ if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, name))) pruned++;
7685
7829
  }
7686
7830
  for (const owner of result.composedModules) {
7687
7831
  for (const peer of result.excludedModules) {
7688
- if (removeDir(import_node_path2.default.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
7832
+ if (removeDir(import_node_path3.default.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
7689
7833
  }
7690
7834
  }
7691
7835
  }
@@ -7701,15 +7845,28 @@ async function compose(options) {
7701
7845
 
7702
7846
  // src/cli.ts
7703
7847
  async function main() {
7704
- assertSupportedTypescript(import_typescript5.default.versionMajorMinor);
7848
+ assertSupportedTypescript(import_typescript6.default.versionMajorMinor);
7705
7849
  const { values } = (0, import_node_util.parseArgs)({
7706
7850
  options: {
7707
7851
  enabled: { type: "string" },
7708
7852
  prune: { type: "boolean", default: false },
7709
- cwd: { type: "string" }
7853
+ cwd: { type: "string" },
7854
+ "check-boundaries": { type: "boolean", default: false }
7710
7855
  },
7711
7856
  strict: true
7712
7857
  });
7858
+ if (values["check-boundaries"]) {
7859
+ const violations = findOptionalPeerViolations(
7860
+ import_node_path4.default.join(values.cwd ?? process.cwd(), MODULES_SANDBOX_DIR)
7861
+ );
7862
+ if (violations.length > 0) {
7863
+ throw new Error(
7864
+ `[compose] --check-boundaries: ${formatBoundaryViolations(MODULES_SANDBOX_DIR, violations)}`
7865
+ );
7866
+ }
7867
+ console.log("[compose] --check-boundaries: no Module file reaches a peer outside its `uses`");
7868
+ return;
7869
+ }
7713
7870
  const enabled = values.enabled ?? process.env[ENABLED_MODULES_ENV];
7714
7871
  if (values.prune && !enabled) {
7715
7872
  throw new Error(