@stardeck-customer-apps/compose 0.7.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.mjs CHANGED
@@ -2020,8 +2020,8 @@ var require_semver2 = __commonJS({
2020
2020
 
2021
2021
  // src/index.ts
2022
2022
  import { spawnSync as spawnSync2 } from "child_process";
2023
- import fs2 from "fs";
2024
- import path2 from "path";
2023
+ import fs3 from "fs";
2024
+ import path3 from "path";
2025
2025
 
2026
2026
  // ../../packages/lib/src/server/module-rail/local-fs-sandbox.ts
2027
2027
  import { spawnSync } from "child_process";
@@ -2126,23 +2126,23 @@ function isValidModuleName(value) {
2126
2126
  var projectSlugSchema = z.string().refine((value) => validateSlug(value).valid, "must be a valid project slug");
2127
2127
  var moduleKindSchema = z.enum(["feature", "library", "contract"]);
2128
2128
  var endpointMethodSchema = z.enum(ENDPOINT_METHODS);
2129
- function endpointPathValidationError(path3) {
2130
- if (!path3.startsWith("/api/")) {
2129
+ function endpointPathValidationError(path4) {
2130
+ if (!path4.startsWith("/api/")) {
2131
2131
  return 'must begin with "/api/"';
2132
2132
  }
2133
- if (path3.endsWith("/")) {
2133
+ if (path4.endsWith("/")) {
2134
2134
  return "must not end with a trailing slash";
2135
2135
  }
2136
- if (path3.includes("?") || path3.includes("#")) {
2136
+ if (path4.includes("?") || path4.includes("#")) {
2137
2137
  return "must not include query or hash";
2138
2138
  }
2139
- if (path3.includes("\\")) {
2139
+ if (path4.includes("\\")) {
2140
2140
  return "must not include backslashes";
2141
2141
  }
2142
- if (path3.includes("%")) {
2142
+ if (path4.includes("%")) {
2143
2143
  return "must not include percent-encoded segments";
2144
2144
  }
2145
- const parts = path3.split("/");
2145
+ const parts = path4.split("/");
2146
2146
  if (parts.length < 3 || parts[0] !== "" || parts[1] !== "api") {
2147
2147
  return 'must begin with "/api/"';
2148
2148
  }
@@ -2192,12 +2192,12 @@ function endpointPathValidationError(path3) {
2192
2192
  }
2193
2193
  return null;
2194
2194
  }
2195
- function normalizeEndpointPathForCollision(path3) {
2196
- const error = endpointPathValidationError(path3);
2195
+ function normalizeEndpointPathForCollision(path4) {
2196
+ const error = endpointPathValidationError(path4);
2197
2197
  if (error != null) {
2198
2198
  throw new Error(`invalid endpoint path: ${error}`);
2199
2199
  }
2200
- return path3.split("/").map((segment, index) => {
2200
+ return path4.split("/").map((segment, index) => {
2201
2201
  if (index < 2) {
2202
2202
  return segment;
2203
2203
  }
@@ -2210,8 +2210,8 @@ function normalizeEndpointPathForCollision(path3) {
2210
2210
  return segment;
2211
2211
  }).join("/");
2212
2212
  }
2213
- var endpointPathSchema = z.string().superRefine((path3, ctx) => {
2214
- const error = endpointPathValidationError(path3);
2213
+ var endpointPathSchema = z.string().superRefine((path4, ctx) => {
2214
+ const error = endpointPathValidationError(path4);
2215
2215
  if (error != null) {
2216
2216
  ctx.addIssue({ code: z.ZodIssueCode.custom, message: error });
2217
2217
  }
@@ -2458,16 +2458,16 @@ var moduleManifestSchema = z.object({
2458
2458
  }
2459
2459
  }
2460
2460
  const seenWorkflows = /* @__PURE__ */ new Map();
2461
- const noteWorkflow = (name, path3) => {
2461
+ const noteWorkflow = (name, path4) => {
2462
2462
  const existing = seenWorkflows.get(name);
2463
2463
  if (existing != null) {
2464
2464
  ctx.addIssue({
2465
2465
  code: z.ZodIssueCode.custom,
2466
2466
  message: `duplicate workflow name "${name}" (also at ${existing})`,
2467
- path: path3
2467
+ path: path4
2468
2468
  });
2469
2469
  } else {
2470
- seenWorkflows.set(name, path3.join("."));
2470
+ seenWorkflows.set(name, path4.join("."));
2471
2471
  }
2472
2472
  };
2473
2473
  for (let i = 0; i < manifest.workflows.length; i += 1) {
@@ -2479,7 +2479,7 @@ var moduleManifestSchema = z.object({
2479
2479
  }
2480
2480
  }
2481
2481
  const seenEndpointKeys = /* @__PURE__ */ new Map();
2482
- const noteEndpoint = (claim, path3) => {
2482
+ const noteEndpoint = (claim, path4) => {
2483
2483
  let key;
2484
2484
  try {
2485
2485
  key = normalizeEndpointPathForCollision(claim.path);
@@ -2491,10 +2491,10 @@ var moduleManifestSchema = z.object({
2491
2491
  ctx.addIssue({
2492
2492
  code: z.ZodIssueCode.custom,
2493
2493
  message: `duplicate normalized endpoint path "${key}" (also at ${existing})`,
2494
- path: path3
2494
+ path: path4
2495
2495
  });
2496
2496
  } else {
2497
- seenEndpointKeys.set(key, path3.join("."));
2497
+ seenEndpointKeys.set(key, path4.join("."));
2498
2498
  }
2499
2499
  };
2500
2500
  for (let i = 0; i < manifest.endpoints.length; i += 1) {
@@ -2595,8 +2595,8 @@ var moduleManifestSchema = z.object({
2595
2595
  }).transform(({ version: _legacyVersion, ...manifest }) => manifest);
2596
2596
  function formatZodErrors(error) {
2597
2597
  return error.issues.map((issue) => {
2598
- const path3 = issue.path.length > 0 ? issue.path.join(".") : "root";
2599
- return `${path3}: ${issue.message}`;
2598
+ const path4 = issue.path.length > 0 ? issue.path.join(".") : "root";
2599
+ return `${path4}: ${issue.message}`;
2600
2600
  });
2601
2601
  }
2602
2602
  function parseModuleManifest(raw) {
@@ -2996,16 +2996,16 @@ var collectedE2eFactsSchema = z3.object({
2996
2996
  function validateRouteExportEvidenceCongruence(input) {
2997
2997
  const entrySet = new Set(input.routeEntries);
2998
2998
  const exportPaths = input.routePageExports.map((e) => e.nextRelativePath);
2999
- for (const path3 of exportPaths) {
3000
- if (!entrySet.has(path3)) {
3001
- input.onIssue(`routePageExports path "${path3}" is not listed in routeEntries`);
2999
+ for (const path4 of exportPaths) {
3000
+ if (!entrySet.has(path4)) {
3001
+ input.onIssue(`routePageExports path "${path4}" is not listed in routeEntries`);
3002
3002
  }
3003
3003
  }
3004
3004
  if (input.routeCollectionErrors.length === 0) {
3005
3005
  const exportSet = new Set(exportPaths);
3006
- for (const path3 of input.routeEntries) {
3007
- if (!exportSet.has(path3)) {
3008
- input.onIssue(`routeEntries path "${path3}" is missing from routePageExports`);
3006
+ for (const path4 of input.routeEntries) {
3007
+ if (!exportSet.has(path4)) {
3008
+ input.onIssue(`routeEntries path "${path4}" is missing from routePageExports`);
3009
3009
  }
3010
3010
  }
3011
3011
  }
@@ -4119,6 +4119,7 @@ var MODULE_CONTRIBUTIONS_GEN_PATH = `${APP_WEB_PREFIX}src/module-contributions.g
4119
4119
  var MODULE_DATASTORES_GEN_PATH = `${APP_WEB_PREFIX}src/module-datastores.gen.ts`;
4120
4120
  var MODULE_I18N_GEN_PATH = `${APP_WEB_PREFIX}src/module-i18n.gen.ts`;
4121
4121
  var MODULE_INIT_SERVER_GEN_PATH = `${APP_WEB_PREFIX}src/module-init.server.gen.ts`;
4122
+ var MODULE_SERVER_GEN_PATH = `${APP_WEB_PREFIX}src/module-server.gen.ts`;
4122
4123
  var MODULES_GEN_PATH = `${APP_WEB_PREFIX}src/modules.gen.ts`;
4123
4124
  var APP_EXTENSIONS_PATH = `${APP_WEB_PREFIX}src/lib/modules/app-extensions.ts`;
4124
4125
 
@@ -4135,7 +4136,8 @@ var COMPOSITION_RUNTIME_GEN_PATHS = [
4135
4136
  MODULE_CONTRIBUTIONS_GEN_PATH,
4136
4137
  MODULE_I18N_GEN_PATH,
4137
4138
  MODULE_INIT_SERVER_GEN_PATH,
4138
- MODULE_DATASTORES_GEN_PATH
4139
+ MODULE_DATASTORES_GEN_PATH,
4140
+ MODULE_SERVER_GEN_PATH
4139
4141
  ];
4140
4142
  function compareNames(a, b) {
4141
4143
  return a < b ? -1 : a > b ? 1 : 0;
@@ -4156,6 +4158,9 @@ function formatI18nMarker() {
4156
4158
  function formatInitMarker() {
4157
4159
  return `// ${MODULE_STUB_MARKER} scope=init`;
4158
4160
  }
4161
+ function formatServerMarker() {
4162
+ return `// ${MODULE_STUB_MARKER} scope=server`;
4163
+ }
4159
4164
  function formatDatastoresMarker() {
4160
4165
  return `// ${MODULE_STUB_MARKER} scope=datastores`;
4161
4166
  }
@@ -4251,13 +4256,13 @@ function assertClientSafeConventionalEntryImports(source, pathLabel, kind) {
4251
4256
  function createMessageTree() {
4252
4257
  return /* @__PURE__ */ Object.create(null);
4253
4258
  }
4254
- function assertMessageTree(value, path3) {
4259
+ function assertMessageTree(value, path4) {
4255
4260
  if (typeof value === "string") return;
4256
4261
  if (value === null || Array.isArray(value) || typeof value !== "object") {
4257
- throw new Error(`Invalid message at ${path3}: expected string or nested object`);
4262
+ throw new Error(`Invalid message at ${path4}: expected string or nested object`);
4258
4263
  }
4259
4264
  for (const [key, child] of Object.entries(value)) {
4260
- assertMessageTree(child, `${path3}.${key}`);
4265
+ assertMessageTree(child, `${path4}.${key}`);
4261
4266
  }
4262
4267
  }
4263
4268
  function cloneTree(value) {
@@ -4267,17 +4272,17 @@ function cloneTree(value) {
4267
4272
  }
4268
4273
  return clone;
4269
4274
  }
4270
- function collectLeafPaths(value, path3, out) {
4275
+ function collectLeafPaths(value, path4, out) {
4271
4276
  if (typeof value === "string") {
4272
- out.push(path3);
4277
+ out.push(path4);
4273
4278
  return;
4274
4279
  }
4275
- assertMessageTree(value, path3);
4276
- for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path3}.${key}`, out);
4280
+ assertMessageTree(value, path4);
4281
+ for (const [key, child] of Object.entries(value)) collectLeafPaths(child, `${path4}.${key}`, out);
4277
4282
  }
4278
- function overrideInto(target, source, owner, path3, missing) {
4283
+ function overrideInto(target, source, owner, path4, missing) {
4279
4284
  for (const [key, value] of Object.entries(source)) {
4280
- const nextPath = path3 ? `${path3}.${key}` : key;
4285
+ const nextPath = path4 ? `${path4}.${key}` : key;
4281
4286
  if (!Object.prototype.hasOwnProperty.call(target, key)) {
4282
4287
  collectLeafPaths(value, nextPath, missing);
4283
4288
  continue;
@@ -4304,9 +4309,9 @@ function overrideInto(target, source, owner, path3, missing) {
4304
4309
  function composeLocaleMessagesForPlan(layers) {
4305
4310
  const result = createMessageTree();
4306
4311
  const leafOwners = /* @__PURE__ */ new Map();
4307
- function mergeInto(target, source, owner, path3) {
4312
+ function mergeInto(target, source, owner, path4) {
4308
4313
  for (const [key, value] of Object.entries(source)) {
4309
- const nextPath = path3 ? `${path3}.${key}` : key;
4314
+ const nextPath = path4 ? `${path4}.${key}` : key;
4310
4315
  const hasExisting = Object.prototype.hasOwnProperty.call(target, key);
4311
4316
  const existing = hasExisting ? target[key] : void 0;
4312
4317
  if (typeof value === "string") {
@@ -4799,6 +4804,42 @@ function emitModuleInitServerGenTs(input) {
4799
4804
  ""
4800
4805
  ].join("\n");
4801
4806
  }
4807
+ function emitModuleServerGenTs(input) {
4808
+ const installed = new Set(input.registry.modules.map((m) => m.name));
4809
+ const moduleNames = input.facts.filter((f) => f.hasServerRegistrations && installed.has(f.moduleName)).map((f) => f.moduleName).sort(compareNames);
4810
+ for (const name of moduleNames) {
4811
+ if (!isValidModuleName(name)) {
4812
+ throw new Error(`[ModuleRail] composition artifacts: invalid module name "${name}"`);
4813
+ }
4814
+ }
4815
+ const importLines = moduleNames.map(
4816
+ (name, index) => [
4817
+ `@/modules/${name}/server/registrations`,
4818
+ `import * as serverRegistrations${index} from ${JSON.stringify(`@/modules/${name}/server/registrations`)};`
4819
+ ]
4820
+ ).sort((a, b) => compareImportSpecifiers(a[0], b[0])).map(([, line]) => line);
4821
+ const entryLines = moduleNames.map(
4822
+ (name, index) => ` { module: ${JSON.stringify(name)}, registrations: serverRegistrations${index} },`
4823
+ );
4824
+ return [
4825
+ formatServerMarker(),
4826
+ "/** DO NOT EDIT \u2014 owned by the module install rail. */",
4827
+ "",
4828
+ ...importLines,
4829
+ ...importLines.length > 0 ? [""] : [],
4830
+ "export type ModuleServerRegistration = {",
4831
+ " module: string;",
4832
+ " registrations: Readonly<Record<string, unknown>>;",
4833
+ "};",
4834
+ "",
4835
+ ...entryLines.length === 0 ? ["export const MODULE_SERVER_REGISTRATIONS: readonly ModuleServerRegistration[] = [];"] : [
4836
+ "export const MODULE_SERVER_REGISTRATIONS: readonly ModuleServerRegistration[] = [",
4837
+ ...entryLines,
4838
+ "];"
4839
+ ],
4840
+ ""
4841
+ ].join("\n");
4842
+ }
4802
4843
 
4803
4844
  // ../../packages/lib/src/server/module-rail/composition-artifacts.ts
4804
4845
  var COMPOSITION_ARTIFACTS_GENERATED_BY = "module-rail";
@@ -4814,8 +4855,8 @@ var METHOD_ORDER = {
4814
4855
  function compareNames2(a, b) {
4815
4856
  return a < b ? -1 : a > b ? 1 : 0;
4816
4857
  }
4817
- function endpointPairKey(method, path3) {
4818
- return `${method} ${path3}`;
4858
+ function endpointPairKey(method, path4) {
4859
+ return `${method} ${path4}`;
4819
4860
  }
4820
4861
  function apiPathToRouteFile(apiPath) {
4821
4862
  if (!apiPath.startsWith("/api/")) {
@@ -4829,11 +4870,11 @@ function routeEntryToAppPageFile(nextRelativePath) {
4829
4870
  }
4830
4871
  var APP_ROUTE_FILE_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
4831
4872
  var API_ROUTE_FILE_SUFFIXES = ["/route.ts", "/route.tsx", "/route.jsx", "/route.js"];
4832
- function appRouteFileToNextRelativePath(path3) {
4833
- const pagePath = appPageFileToNextRelativePath(path3, APP_PACKAGE_DIR);
4873
+ function appRouteFileToNextRelativePath(path4) {
4874
+ const pagePath = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
4834
4875
  if (pagePath != null) return pagePath;
4835
- if (!path3.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
4836
- const relative = path3.slice(APP_ROUTE_FILE_PREFIX.length);
4876
+ if (!path4.startsWith(APP_ROUTE_FILE_PREFIX)) return null;
4877
+ const relative = path4.slice(APP_ROUTE_FILE_PREFIX.length);
4837
4878
  if (!relative.startsWith("api/")) return null;
4838
4879
  const suffix = API_ROUTE_FILE_SUFFIXES.find((candidate) => relative.endsWith(candidate));
4839
4880
  if (!suffix) return null;
@@ -4861,6 +4902,14 @@ function formatRouteMarker(owner, nextRelativePath) {
4861
4902
  function formatRegistryMarker() {
4862
4903
  return `// ${MODULE_STUB_MARKER} scope=registry`;
4863
4904
  }
4905
+ var OWNERLESS_MARKER_KINDS = [
4906
+ "registry",
4907
+ "contributions",
4908
+ "datastores",
4909
+ "i18n",
4910
+ "init",
4911
+ "server"
4912
+ ];
4864
4913
  function parseCompositionArtifactMarker(source) {
4865
4914
  const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
4866
4915
  const match = firstLine.match(
@@ -4871,26 +4920,8 @@ function parseCompositionArtifactMarker(source) {
4871
4920
  if (!match) return null;
4872
4921
  const owner = match[1];
4873
4922
  const scope = match[2];
4874
- if (scope === "registry") {
4875
- if (owner) return null;
4876
- return { kind: "registry" };
4877
- }
4878
- if (scope === "contributions") {
4879
- if (owner) return null;
4880
- return { kind: "contributions" };
4881
- }
4882
- if (scope === "datastores") {
4883
- if (owner) return null;
4884
- return { kind: "datastores" };
4885
- }
4886
- if (scope === "i18n") {
4887
- if (owner) return null;
4888
- return { kind: "i18n" };
4889
- }
4890
- if (scope === "init") {
4891
- if (owner) return null;
4892
- return { kind: "init" };
4893
- }
4923
+ const ownerless = OWNERLESS_MARKER_KINDS.find((kind) => kind === scope);
4924
+ if (ownerless) return owner ? null : { kind: ownerless };
4894
4925
  if (!owner || !isValidModuleName(owner)) return null;
4895
4926
  if (scope === "route") {
4896
4927
  const nextRelativePath = match[3]?.trim();
@@ -4935,9 +4966,9 @@ function legacyHandlerPropertyMatchesPath(property, method, physicalPath, bindin
4935
4966
  const expectedTokens = knownAliases[`${binding} ${method} ${physicalPath}`] ?? pathTokens;
4936
4967
  return handlerTokens.length === expectedTokens.length && handlerTokens.every((token, index) => token === expectedTokens[index]);
4937
4968
  }
4938
- function parseLegacyApiAdapter(path3, source) {
4939
- const physicalPath = appRouteFileToNextRelativePath(path3);
4940
- if (expectedMarkerKindForPath(path3) !== "endpoint" || physicalPath == null) return null;
4969
+ function parseLegacyApiAdapter(path4, source) {
4970
+ const physicalPath = appRouteFileToNextRelativePath(path4);
4971
+ if (expectedMarkerKindForPath(path4) !== "endpoint" || physicalPath == null) return null;
4941
4972
  const firstLine = source.split(/\r?\n/, 1)[0]?.trim() ?? "";
4942
4973
  const match = firstLine.match(
4943
4974
  new RegExp(
@@ -4947,7 +4978,7 @@ function parseLegacyApiAdapter(path3, source) {
4947
4978
  if (!match || match[2] !== physicalPath) return null;
4948
4979
  const owner = match[1];
4949
4980
  if (!isValidModuleName(owner)) return null;
4950
- const file = ts4.createSourceFile(path3, source, ts4.ScriptTarget.Latest, false, ts4.ScriptKind.TS);
4981
+ const file = ts4.createSourceFile(path4, source, ts4.ScriptTarget.Latest, false, ts4.ScriptKind.TS);
4951
4982
  if (file.parseDiagnostics.length > 0) {
4952
4983
  return null;
4953
4984
  }
@@ -4989,9 +5020,9 @@ function parseLegacyApiAdapter(path3, source) {
4989
5020
  }
4990
5021
  return handlerBinding != null && methods.size > 0 ? { owner, methods: [...methods] } : null;
4991
5022
  }
4992
- function parseLegacyRouteAdapter(path3, source) {
4993
- const physicalPath = appRouteFileToNextRelativePath(path3);
4994
- if (expectedMarkerKindForPath(path3) !== "route" || physicalPath == null) return null;
5023
+ function parseLegacyRouteAdapter(path4, source) {
5024
+ const physicalPath = appRouteFileToNextRelativePath(path4);
5025
+ if (expectedMarkerKindForPath(path4) !== "route" || physicalPath == null) return null;
4995
5026
  const normalized = source.replace(/\r\n/g, "\n");
4996
5027
  const match = normalized.match(
4997
5028
  new RegExp(
@@ -5003,29 +5034,30 @@ function parseLegacyRouteAdapter(path3, source) {
5003
5034
  const nextRelativePath = match[2];
5004
5035
  return isValidModuleName(owner) && nextRelativePath === physicalPath ? { owner, nextRelativePath } : null;
5005
5036
  }
5006
- function legacyRouteAdapterMatchesStub(path3, source, stub) {
5007
- const legacy = parseLegacyRouteAdapter(path3, source);
5037
+ function legacyRouteAdapterMatchesStub(path4, source, stub) {
5038
+ const legacy = parseLegacyRouteAdapter(path4, source);
5008
5039
  return legacy != null && legacy.owner === stub.owner;
5009
5040
  }
5010
5041
  var API_ROUTE_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/api/`;
5011
5042
  var APP_DIR_PREFIX = `${APP_PACKAGE_DIR}/src/app/`;
5012
5043
  var PAGE_FILE_SUFFIXES = ["/page.tsx", "/page.ts", "/page.jsx", "/page.js"];
5013
- function expectedMarkerKindForPath(path3) {
5014
- if (path3 === MODULES_GEN_PATH) return "registry";
5015
- if (path3 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
5016
- if (path3 === MODULE_DATASTORES_GEN_PATH) return "datastores";
5017
- if (path3 === MODULE_I18N_GEN_PATH) return "i18n";
5018
- if (path3 === MODULE_INIT_SERVER_GEN_PATH) return "init";
5019
- if (path3.startsWith(API_ROUTE_DIR_PREFIX) && path3.endsWith("/route.ts")) return "endpoint";
5020
- if (path3.startsWith(APP_DIR_PREFIX) && !path3.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path3.endsWith(suffix))) {
5044
+ function expectedMarkerKindForPath(path4) {
5045
+ if (path4 === MODULES_GEN_PATH) return "registry";
5046
+ if (path4 === MODULE_CONTRIBUTIONS_GEN_PATH) return "contributions";
5047
+ if (path4 === MODULE_DATASTORES_GEN_PATH) return "datastores";
5048
+ if (path4 === MODULE_I18N_GEN_PATH) return "i18n";
5049
+ if (path4 === MODULE_INIT_SERVER_GEN_PATH) return "init";
5050
+ if (path4 === MODULE_SERVER_GEN_PATH) return "server";
5051
+ if (path4.startsWith(API_ROUTE_DIR_PREFIX) && path4.endsWith("/route.ts")) return "endpoint";
5052
+ if (path4.startsWith(APP_DIR_PREFIX) && !path4.startsWith(API_ROUTE_DIR_PREFIX) && PAGE_FILE_SUFFIXES.some((suffix) => path4.endsWith(suffix))) {
5021
5053
  return "route";
5022
5054
  }
5023
5055
  return null;
5024
5056
  }
5025
- function markerCongruentWithPath(path3, marker) {
5026
- if (expectedMarkerKindForPath(path3) !== marker.kind) return false;
5057
+ function markerCongruentWithPath(path4, marker) {
5058
+ if (expectedMarkerKindForPath(path4) !== marker.kind) return false;
5027
5059
  if (marker.kind === "route") {
5028
- const derived = appPageFileToNextRelativePath(path3, APP_PACKAGE_DIR);
5060
+ const derived = appPageFileToNextRelativePath(path4, APP_PACKAGE_DIR);
5029
5061
  return derived != null && derived === marker.nextRelativePath;
5030
5062
  }
5031
5063
  return true;
@@ -5296,8 +5328,8 @@ function buildGeneratedModuleRegistry(input) {
5296
5328
  function activeEnhancementPeers(registryModule) {
5297
5329
  return new Set(registryModule.enhancements.filter((e) => e.active).map((e) => e.peer));
5298
5330
  }
5299
- function endpointBucketKey(path3, owner, scope) {
5300
- return `${path3}\0${owner}\0${scopeLabel(scope)}`;
5331
+ function endpointBucketKey(path4, owner, scope) {
5332
+ return `${path4}\0${owner}\0${scopeLabel(scope)}`;
5301
5333
  }
5302
5334
  function planRouteStubs(input) {
5303
5335
  const endpointBuckets = /* @__PURE__ */ new Map();
@@ -5421,7 +5453,7 @@ function planRouteStubs(input) {
5421
5453
  for (const entry of entries) {
5422
5454
  const nextRelativePath = entry.nextRelativePath;
5423
5455
  const destination = normalizeAppRouterDestinationPattern(nextRelativePath);
5424
- const path3 = routeEntryToAppPageFile(nextRelativePath);
5456
+ const path4 = routeEntryToAppPageFile(nextRelativePath);
5425
5457
  const existing = routeDestinationOwners.get(destination);
5426
5458
  if (existing) {
5427
5459
  throw new Error(
@@ -5431,13 +5463,13 @@ function planRouteStubs(input) {
5431
5463
  routeDestinationOwners.set(destination, {
5432
5464
  owner: regMod.name,
5433
5465
  nextRelativePath,
5434
- path: path3
5466
+ path: path4
5435
5467
  });
5436
5468
  const importModule = routeEntryImport(regMod.name, nextRelativePath);
5437
5469
  const analyzed = analyzeRouteEntryPath(nextRelativePath);
5438
5470
  const mount = mounts.find((claim) => routeEntryCoveredByMountClaim(analyzed, claim));
5439
5471
  routeStubs.push({
5440
- path: path3,
5472
+ path: path4,
5441
5473
  owner: regMod.name,
5442
5474
  nextRelativePath,
5443
5475
  importModule,
@@ -5498,16 +5530,16 @@ function planCompositionArtifacts(input) {
5498
5530
  ...stubs.routeStubs.map((stub) => stub.path)
5499
5531
  ]);
5500
5532
  const existingGeneratedPathSet = new Set(input.existingGeneratedPaths);
5501
- const isProvenStaleDeletionCandidate = (path3, content) => {
5502
- if (path3 === MODULES_GEN_PATH) return false;
5503
- if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path3)) return false;
5504
- if (desiredPaths.has(path3)) return false;
5505
- if (!existingGeneratedPathSet.has(path3)) return false;
5506
- const legacy = parseLegacyApiAdapter(path3, content);
5533
+ const isProvenStaleDeletionCandidate = (path4, content) => {
5534
+ if (path4 === MODULES_GEN_PATH) return false;
5535
+ if (COMPOSITION_RUNTIME_GEN_PATHS.includes(path4)) return false;
5536
+ if (desiredPaths.has(path4)) return false;
5537
+ if (!existingGeneratedPathSet.has(path4)) return false;
5538
+ const legacy = parseLegacyApiAdapter(path4, content);
5507
5539
  if (legacy) return true;
5508
- if (parseLegacyRouteAdapter(path3, content)) return true;
5540
+ if (parseLegacyRouteAdapter(path4, content)) return true;
5509
5541
  const marker = parseCompositionArtifactMarker(content);
5510
- return marker != null && markerCongruentWithPath(path3, marker);
5542
+ return marker != null && markerCongruentWithPath(path4, marker);
5511
5543
  };
5512
5544
  const plannedByDestination = /* @__PURE__ */ new Map();
5513
5545
  for (const stub of stubs.routeStubs) {
@@ -5615,6 +5647,7 @@ function planCompositionArtifacts(input) {
5615
5647
  let datastoresContent;
5616
5648
  let i18nContent;
5617
5649
  let initContent;
5650
+ let serverContent;
5618
5651
  try {
5619
5652
  contributionsContent = emitModuleContributionsGenTs({
5620
5653
  registry: input.registry,
@@ -5639,6 +5672,10 @@ function planCompositionArtifacts(input) {
5639
5672
  registry: input.registry,
5640
5673
  facts: compositionEntryFacts
5641
5674
  });
5675
+ serverContent = emitModuleServerGenTs({
5676
+ registry: input.registry,
5677
+ facts: compositionEntryFacts
5678
+ });
5642
5679
  } catch (error) {
5643
5680
  return {
5644
5681
  ok: false,
@@ -5651,17 +5688,18 @@ function planCompositionArtifacts(input) {
5651
5688
  desired.set(MODULE_DATASTORES_GEN_PATH, datastoresContent);
5652
5689
  desired.set(MODULE_I18N_GEN_PATH, i18nContent);
5653
5690
  desired.set(MODULE_INIT_SERVER_GEN_PATH, initContent);
5691
+ desired.set(MODULE_SERVER_GEN_PATH, serverContent);
5654
5692
  for (const stub of stubs.endpointStubs) desired.set(stub.path, stub.content);
5655
5693
  for (const stub of stubs.routeStubs) desired.set(stub.path, stub.content);
5656
5694
  const endpointStubByPath = new Map(stubs.endpointStubs.map((stub) => [stub.path, stub]));
5657
5695
  const routeStubByPath = new Map(stubs.routeStubs.map((stub) => [stub.path, stub]));
5658
- for (const [path3] of desired) {
5659
- const existing = input.existingFiles[path3];
5696
+ for (const [path4] of desired) {
5697
+ const existing = input.existingFiles[path4];
5660
5698
  if (existing == null) continue;
5661
5699
  let marker = parseCompositionArtifactMarker(existing);
5662
- const plannedEndpoint = endpointStubByPath.get(path3);
5663
- const plannedRoute = routeStubByPath.get(path3);
5664
- const legacy = parseLegacyApiAdapter(path3, existing);
5700
+ const plannedEndpoint = endpointStubByPath.get(path4);
5701
+ const plannedRoute = routeStubByPath.get(path4);
5702
+ const legacy = parseLegacyApiAdapter(path4, existing);
5665
5703
  if (legacy && plannedEndpoint) {
5666
5704
  marker = {
5667
5705
  kind: "endpoint",
@@ -5669,66 +5707,66 @@ function planCompositionArtifacts(input) {
5669
5707
  scope: plannedEndpoint.scope
5670
5708
  };
5671
5709
  }
5672
- if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path3, existing, plannedRoute)) {
5710
+ if (!marker && plannedRoute && legacyRouteAdapterMatchesStub(path4, existing, plannedRoute)) {
5673
5711
  marker = {
5674
5712
  kind: "route",
5675
5713
  owner: plannedRoute.owner,
5676
5714
  nextRelativePath: plannedRoute.nextRelativePath
5677
5715
  };
5678
5716
  }
5679
- const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path3);
5717
+ const isGenRuntimePath = COMPOSITION_RUNTIME_GEN_PATHS.includes(path4);
5680
5718
  if (!marker) {
5681
5719
  return {
5682
5720
  ok: false,
5683
- 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}`,
5684
- path: path3
5721
+ 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}`,
5722
+ path: path4
5685
5723
  };
5686
5724
  }
5687
- const expectedKind = expectedMarkerKindForPath(path3);
5725
+ const expectedKind = expectedMarkerKindForPath(path4);
5688
5726
  if (marker.kind !== expectedKind) {
5689
5727
  return {
5690
5728
  ok: false,
5691
- 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)`,
5692
- path: path3
5729
+ 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)`,
5730
+ path: path4
5693
5731
  };
5694
5732
  }
5695
5733
  if (marker.kind === "endpoint") {
5696
- const stub = endpointStubByPath.get(path3);
5734
+ const stub = endpointStubByPath.get(path4);
5697
5735
  const ownerMatches = stub != null && marker.owner === stub.owner;
5698
5736
  const scopeMatches = stub != null && scopeLabel(marker.scope) === scopeLabel(stub.scope);
5699
5737
  if (!ownerMatches || !scopeMatches) {
5700
5738
  return {
5701
5739
  ok: false,
5702
- 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) : "?"})`,
5703
- path: path3,
5740
+ 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) : "?"})`,
5741
+ path: path4,
5704
5742
  owner: stub?.owner
5705
5743
  };
5706
5744
  }
5707
5745
  } else if (marker.kind === "route") {
5708
- const stub = routeStubByPath.get(path3);
5746
+ const stub = routeStubByPath.get(path4);
5709
5747
  const ownerMatches = stub != null && marker.owner === stub.owner;
5710
5748
  const pathMatches = stub != null && marker.nextRelativePath === stub.nextRelativePath;
5711
5749
  if (!ownerMatches || !pathMatches) {
5712
5750
  return {
5713
5751
  ok: false,
5714
- 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 ?? "?"})`,
5715
- path: path3,
5752
+ 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 ?? "?"})`,
5753
+ path: path4,
5716
5754
  owner: stub?.owner
5717
5755
  };
5718
5756
  }
5719
5757
  }
5720
5758
  }
5721
5759
  const deletes = [];
5722
- for (const path3 of input.existingGeneratedPaths) {
5723
- const existing = input.existingFiles[path3];
5760
+ for (const path4 of input.existingGeneratedPaths) {
5761
+ const existing = input.existingFiles[path4];
5724
5762
  if (existing == null) continue;
5725
- if (!isProvenStaleDeletionCandidate(path3, existing)) {
5763
+ if (!isProvenStaleDeletionCandidate(path4, existing)) {
5726
5764
  continue;
5727
5765
  }
5728
- deletes.push(path3);
5766
+ deletes.push(path4);
5729
5767
  }
5730
5768
  deletes.sort(compareNames2);
5731
- 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));
5769
+ 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));
5732
5770
  return {
5733
5771
  ok: true,
5734
5772
  plan: {
@@ -5757,8 +5795,16 @@ async function checkoutParsesTypedStores(sandbox) {
5757
5795
  try {
5758
5796
  if (!await sandbox.fileExists(APP_PACKAGE_JSON_PATH)) return false;
5759
5797
  const pkg = JSON.parse(await sandbox.readFile(APP_PACKAGE_JSON_PATH));
5760
- const range = pkg.devDependencies?.[COMPOSE_PACKAGE] ?? pkg.dependencies?.[COMPOSE_PACKAGE];
5761
- if (!range) return false;
5798
+ return composeRangeParsesTypedStores(
5799
+ pkg.devDependencies?.[COMPOSE_PACKAGE] ?? pkg.dependencies?.[COMPOSE_PACKAGE]
5800
+ );
5801
+ } catch {
5802
+ return false;
5803
+ }
5804
+ }
5805
+ function composeRangeParsesTypedStores(range) {
5806
+ if (!range) return false;
5807
+ try {
5762
5808
  const lowest = import_semver.default.minVersion(range);
5763
5809
  return lowest != null && import_semver.default.gte(lowest, TYPED_STORES_MIN_COMPOSE_VERSION);
5764
5810
  } catch {
@@ -6203,11 +6249,11 @@ function countedCompositionContext(context) {
6203
6249
  counts.sandboxExecs += 1;
6204
6250
  return context.sandbox.exec(...args);
6205
6251
  },
6206
- readFile: (path3) => {
6252
+ readFile: (path4) => {
6207
6253
  counts.sandboxReads += 1;
6208
- return context.sandbox.readFile(path3);
6254
+ return context.sandbox.readFile(path4);
6209
6255
  },
6210
- fileExists: (path3) => context.sandbox.fileExists(path3),
6256
+ fileExists: (path4) => context.sandbox.fileExists(path4),
6211
6257
  fetchRemoteRef: (...args) => context.sandbox.fetchRemoteRef(...args),
6212
6258
  addDependencies: (specs) => context.sandbox.addDependencies(specs)
6213
6259
  }
@@ -6419,14 +6465,14 @@ async function discoverModuleRouteEntries(context, moduleName) {
6419
6465
  }
6420
6466
  return pages.sort((a, b) => a.nextRelativePath.localeCompare(b.nextRelativePath));
6421
6467
  }
6422
- async function writeSandboxFileAtomic(target, path3, content) {
6423
- const dir = dirnamePosix(path3);
6424
- const tmp = `${path3}.tmp.${target.runId}`;
6468
+ async function writeSandboxFileAtomic(target, path4, content) {
6469
+ const dir = dirnamePosix(path4);
6470
+ const tmp = `${path4}.tmp.${target.runId}`;
6425
6471
  const b64 = Buffer.from(content, "utf8").toString("base64");
6426
6472
  const cmd = [
6427
6473
  `mkdir -p ${shellQuote(dir)}`,
6428
6474
  `printf '%s' ${shellQuote(b64)} | base64 -d > ${shellQuote(tmp)}`,
6429
- `mv -f ${shellQuote(tmp)} ${shellQuote(path3)}`
6475
+ `mv -f ${shellQuote(tmp)} ${shellQuote(path4)}`
6430
6476
  ].join(" && ");
6431
6477
  const result = await target.sandbox.exec(cmd, { raiseOnError: false });
6432
6478
  if (result.exitCode !== 0) {
@@ -6441,14 +6487,14 @@ async function writeSandboxFileAtomic(target, path3, content) {
6441
6487
  }
6442
6488
  const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
6443
6489
  throw new Error(
6444
- `[ModuleRail] composition artifacts: failed to write ${redactSecrets(path3)}: ${redactSecrets(detail)}`
6490
+ `[ModuleRail] composition artifacts: failed to write ${redactSecrets(path4)}: ${redactSecrets(detail)}`
6445
6491
  );
6446
6492
  }
6447
6493
  }
6448
6494
  async function collectAbsentWriteParentDirs(context, writePaths) {
6449
6495
  const absent = /* @__PURE__ */ new Set();
6450
- for (const path3 of writePaths) {
6451
- let dir = dirnamePosix(path3);
6496
+ for (const path4 of writePaths) {
6497
+ let dir = dirnamePosix(path4);
6452
6498
  while (dir !== "." && dir !== "") {
6453
6499
  if (absent.has(dir)) {
6454
6500
  dir = dirnamePosix(dir);
@@ -6485,12 +6531,12 @@ async function removeAbsentWriteParentDirsOnRollback(context, dirsDeepestFirst)
6485
6531
  );
6486
6532
  }
6487
6533
  }
6488
- async function deleteSandboxFile(context, path3) {
6489
- const result = await context.sandbox.exec(`rm -f ${shellQuote(path3)}`, { raiseOnError: false });
6534
+ async function deleteSandboxFile(context, path4) {
6535
+ const result = await context.sandbox.exec(`rm -f ${shellQuote(path4)}`, { raiseOnError: false });
6490
6536
  if (result.exitCode !== 0) {
6491
6537
  const detail = typeof result.error === "string" && result.error.trim() || result.output.trim() || `exit ${result.exitCode}`;
6492
6538
  throw new Error(
6493
- `[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path3)}: ${redactSecrets(detail)}`
6539
+ `[ModuleRail] composition artifacts: failed to delete ${redactSecrets(path4)}: ${redactSecrets(detail)}`
6494
6540
  );
6495
6541
  }
6496
6542
  }
@@ -6557,37 +6603,37 @@ async function applyCompositionArtifactMutations(context, plan, existingFiles) {
6557
6603
  await writeSandboxFileAtomic(context, write.path, write.content);
6558
6604
  appliedWrites.push({ path: write.path, previous });
6559
6605
  }
6560
- for (const path3 of plan.deletes) {
6561
- const previous = existingFiles[path3];
6606
+ for (const path4 of plan.deletes) {
6607
+ const previous = existingFiles[path4];
6562
6608
  if (previous == null) continue;
6563
- await deleteSandboxFile(context, path3);
6564
- appliedDeletes.push({ path: path3, previous });
6609
+ await deleteSandboxFile(context, path4);
6610
+ appliedDeletes.push({ path: path4, previous });
6565
6611
  }
6566
6612
  } catch (error) {
6567
6613
  console.log(
6568
6614
  `[ModuleRail] runId=${context.runId} composition artifacts mid-apply failure; rolling back writes=${appliedWrites.length} deletes=${appliedDeletes.length} newDirs=${newlyCreatedParentDirs.length}`
6569
6615
  );
6570
- for (const { path: path3, previous } of [...appliedDeletes].reverse()) {
6616
+ for (const { path: path4, previous } of [...appliedDeletes].reverse()) {
6571
6617
  try {
6572
- await writeSandboxFileAtomic(context, path3, previous);
6618
+ await writeSandboxFileAtomic(context, path4, previous);
6573
6619
  } catch (rollbackError) {
6574
6620
  console.log(
6575
- `[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path3)}: ${redactSecrets(
6621
+ `[ModuleRail] runId=${context.runId} composition artifacts rollback restore failed for ${redactSecrets(path4)}: ${redactSecrets(
6576
6622
  rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
6577
6623
  )}`
6578
6624
  );
6579
6625
  }
6580
6626
  }
6581
- for (const { path: path3, previous } of [...appliedWrites].reverse()) {
6627
+ for (const { path: path4, previous } of [...appliedWrites].reverse()) {
6582
6628
  try {
6583
6629
  if (previous == null) {
6584
- await deleteSandboxFile(context, path3);
6630
+ await deleteSandboxFile(context, path4);
6585
6631
  } else {
6586
- await writeSandboxFileAtomic(context, path3, previous);
6632
+ await writeSandboxFileAtomic(context, path4, previous);
6587
6633
  }
6588
6634
  } catch (rollbackError) {
6589
6635
  console.log(
6590
- `[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path3)}: ${redactSecrets(
6636
+ `[ModuleRail] runId=${context.runId} composition artifacts rollback write failed for ${redactSecrets(path4)}: ${redactSecrets(
6591
6637
  rollbackError instanceof Error ? rollbackError.message : String(rollbackError)
6592
6638
  )}`
6593
6639
  );
@@ -6621,39 +6667,39 @@ async function listExistingGeneratedStubPaths(context) {
6621
6667
  COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
6622
6668
  );
6623
6669
  const presentPaths = [];
6624
- for (const path3 of candidates) {
6670
+ for (const path4 of candidates) {
6625
6671
  const presence = assertRealCompositionArtifactFilePresence(
6626
- path3,
6627
- kinds.get(path3) ?? "unreadable"
6672
+ path4,
6673
+ kinds.get(path4) ?? "unreadable"
6628
6674
  );
6629
- if (presence === "file") presentPaths.push(path3);
6675
+ if (presence === "file") presentPaths.push(path4);
6630
6676
  }
6631
6677
  let contents;
6632
6678
  try {
6633
6679
  contents = await readFilesMany(context, presentPaths);
6634
6680
  } catch (error) {
6635
- const path3 = readFailurePath(error);
6681
+ const path4 = readFailurePath(error);
6636
6682
  throw new Error(
6637
- `[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path3)}: ${redactSecrets(
6683
+ `[ModuleRail] composition artifacts: cannot read candidate stub ${redactSecrets(path4)}: ${redactSecrets(
6638
6684
  error instanceof Error ? error.message : String(error)
6639
6685
  )}`
6640
6686
  );
6641
6687
  }
6642
6688
  const owned = [];
6643
- for (const path3 of presentPaths) {
6644
- const content = contents.get(path3);
6689
+ for (const path4 of presentPaths) {
6690
+ const content = contents.get(path4);
6645
6691
  const marker = parseCompositionArtifactMarker(content);
6646
- if (parseLegacyApiAdapter(path3, content) || parseLegacyRouteAdapter(path3, content)) {
6647
- owned.push(path3);
6692
+ if (parseLegacyApiAdapter(path4, content) || parseLegacyRouteAdapter(path4, content)) {
6693
+ owned.push(path4);
6648
6694
  continue;
6649
6695
  }
6650
6696
  if (marker == null) continue;
6651
- if (!markerCongruentWithPath(path3, marker)) {
6697
+ if (!markerCongruentWithPath(path4, marker)) {
6652
6698
  throw new Error(
6653
- `[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)`
6699
+ `[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)`
6654
6700
  );
6655
6701
  }
6656
- owned.push(path3);
6702
+ owned.push(path4);
6657
6703
  }
6658
6704
  return owned;
6659
6705
  }
@@ -6786,7 +6832,7 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
6786
6832
  { raiseOnError: false }
6787
6833
  );
6788
6834
  if (result.exitCode !== 0) {
6789
- for (const path3 of chunk) kinds.set(path3, "unreadable");
6835
+ for (const path4 of chunk) kinds.set(path4, "unreadable");
6790
6836
  continue;
6791
6837
  }
6792
6838
  const lines = result.output.split("\n").filter(Boolean);
@@ -6797,31 +6843,31 @@ async function classifySandboxPathPresenceKindMany(context, paths, probeToken) {
6797
6843
  }
6798
6844
  } else {
6799
6845
  for (const line of lines) {
6800
- const [rawKind, path3] = line.split(" ", 2);
6846
+ const [rawKind, path4] = line.split(" ", 2);
6801
6847
  const kind = rawKind?.replace(/^entry-kind:/, "");
6802
- if (path3 && chunk.includes(path3) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
6803
- kinds.set(path3, kind);
6848
+ if (path4 && chunk.includes(path4) && CONVENTIONAL_ENTRY_PRESENCE_KINDS.includes(kind ?? "")) {
6849
+ kinds.set(path4, kind);
6804
6850
  }
6805
6851
  }
6806
6852
  }
6807
- for (const path3 of chunk) {
6808
- if (!kinds.has(path3)) kinds.set(path3, "unreadable");
6853
+ for (const path4 of chunk) {
6854
+ if (!kinds.has(path4)) kinds.set(path4, "unreadable");
6809
6855
  }
6810
6856
  }
6811
6857
  return kinds;
6812
6858
  }
6813
- async function classifySandboxPathPresenceKind(context, path3, probeToken) {
6814
- return (await classifySandboxPathPresenceKindMany(context, [path3], probeToken)).get(path3);
6859
+ async function classifySandboxPathPresenceKind(context, path4, probeToken) {
6860
+ return (await classifySandboxPathPresenceKindMany(context, [path4], probeToken)).get(path4);
6815
6861
  }
6816
6862
  var LOCALE_JSON_KIND_PROBE_TOKEN = "locale-json-kind";
6817
- function assertRealLocaleJsonFilePresence(path3, presence) {
6863
+ function assertRealLocaleJsonFilePresence(path4, presence) {
6818
6864
  if (presence === "file") return;
6819
6865
  if (presence === "absent") {
6820
- throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path3}`);
6866
+ throw new Error(`[ModuleRail] composition artifacts: locale file missing at ${path4}`);
6821
6867
  }
6822
- const presenceError = formatConventionalEntryPresenceError(path3, presence);
6868
+ const presenceError = formatConventionalEntryPresenceError(path4, presence);
6823
6869
  throw new Error(
6824
- `[ModuleRail] composition artifacts: ${presenceError ?? `${path3} is not a real locale JSON file`}`
6870
+ `[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real locale JSON file`}`
6825
6871
  );
6826
6872
  }
6827
6873
  function splitTaggedLine(line) {
@@ -6896,11 +6942,11 @@ async function listLocaleBasenamesIfDirMany(context, dirs) {
6896
6942
  }
6897
6943
  async function readFilesMany(context, paths) {
6898
6944
  const sortedPaths = [...paths].sort((a, b) => a.localeCompare(b));
6899
- const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path3) => {
6945
+ const settled = await mapConcurrent(sortedPaths, SANDBOX_READ_CONCURRENCY, async (path4) => {
6900
6946
  try {
6901
- return { path: path3, value: await context.sandbox.readFile(path3) };
6947
+ return { path: path4, value: await context.sandbox.readFile(path4) };
6902
6948
  } catch (error) {
6903
- return { path: path3, error };
6949
+ return { path: path4, error };
6904
6950
  }
6905
6951
  });
6906
6952
  const files = /* @__PURE__ */ new Map();
@@ -6921,55 +6967,60 @@ function readFailurePath(error) {
6921
6967
  return typeof error === "object" && error !== null && "path" in error ? String(error.path) : "unknown";
6922
6968
  }
6923
6969
  var CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN = "conventional-entry-kind";
6970
+ var SERVER_REGISTRATIONS_FILE = "server/registrations.ts";
6924
6971
  var COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN = "composition-artifact-kind";
6925
- async function classifyCompositionArtifactPathKind(context, path3) {
6926
- return classifySandboxPathPresenceKind(context, path3, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
6972
+ async function classifyCompositionArtifactPathKind(context, path4) {
6973
+ return classifySandboxPathPresenceKind(context, path4, COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN);
6927
6974
  }
6928
- function assertRealCompositionArtifactFilePresence(path3, presence) {
6975
+ function assertRealCompositionArtifactFilePresence(path4, presence) {
6929
6976
  if (presence === "file") return "file";
6930
6977
  if (presence === "absent") return "absent";
6931
- const presenceError = formatConventionalEntryPresenceError(path3, presence);
6978
+ const presenceError = formatConventionalEntryPresenceError(path4, presence);
6932
6979
  throw new Error(
6933
- `[ModuleRail] composition artifacts: ${presenceError ?? `${path3} is not a real composition artifact file`}`
6980
+ `[ModuleRail] composition artifacts: ${presenceError ?? `${path4} is not a real composition artifact file`}`
6934
6981
  );
6935
6982
  }
6936
- async function readConventionalEntryIfRealFile(path3, kind, presences, files) {
6937
- const presence = presences.get(path3) ?? "unreadable";
6938
- const presenceError = formatConventionalEntryPresenceError(path3, presence);
6983
+ async function readConventionalEntryIfRealFile(path4, kind, presences, files) {
6984
+ const presence = presences.get(path4) ?? "unreadable";
6985
+ const presenceError = formatConventionalEntryPresenceError(path4, presence);
6939
6986
  if (presenceError) {
6940
6987
  throw new Error(`[ModuleRail] composition artifacts: ${presenceError}`);
6941
6988
  }
6942
6989
  if (presence === "absent") {
6943
6990
  return null;
6944
6991
  }
6945
- const source = files.get(path3);
6992
+ const source = files.get(path4);
6946
6993
  switch (kind) {
6947
6994
  case "contributions":
6948
- assertContributionsExport(source, path3);
6949
- assertClientSafeConventionalEntryImports(source, path3, "contributions");
6995
+ assertContributionsExport(source, path4);
6996
+ assertClientSafeConventionalEntryImports(source, path4, "contributions");
6950
6997
  break;
6951
6998
  case "slotCatalogs":
6952
- assertSlotCatalogsExport(source, path3);
6953
- assertClientSafeConventionalEntryImports(source, path3, "slotCatalogs");
6999
+ assertSlotCatalogsExport(source, path4);
7000
+ assertClientSafeConventionalEntryImports(source, path4, "slotCatalogs");
6954
7001
  break;
6955
7002
  case "initializeEnhancement":
6956
- assertInitializeEnhancementExport(source, path3);
7003
+ assertInitializeEnhancementExport(source, path4);
6957
7004
  break;
6958
7005
  }
6959
7006
  return source;
6960
7007
  }
6961
- async function readJsonObjectFile(path3, files) {
6962
- const raw = files.get(path3);
7008
+ function hasRealServerRegistrations(modulePath, presences) {
7009
+ const path4 = `${modulePath}/${SERVER_REGISTRATIONS_FILE}`;
7010
+ return assertRealCompositionArtifactFilePresence(path4, presences.get(path4) ?? "unreadable") === "file";
7011
+ }
7012
+ async function readJsonObjectFile(path4, files) {
7013
+ const raw = files.get(path4);
6963
7014
  let parsed;
6964
7015
  try {
6965
7016
  parsed = JSON.parse(raw);
6966
7017
  } catch (error) {
6967
7018
  throw new Error(
6968
- `[ModuleRail] composition artifacts: invalid JSON at ${path3}: ${error instanceof Error ? error.message : String(error)}`
7019
+ `[ModuleRail] composition artifacts: invalid JSON at ${path4}: ${error instanceof Error ? error.message : String(error)}`
6969
7020
  );
6970
7021
  }
6971
7022
  if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
6972
- throw new Error(`[ModuleRail] composition artifacts: ${path3} root must be an object`);
7023
+ throw new Error(`[ModuleRail] composition artifacts: ${path4} root must be an object`);
6973
7024
  }
6974
7025
  return parsed;
6975
7026
  }
@@ -6981,6 +7032,9 @@ async function collectCompositionEntryFacts(context, registry) {
6981
7032
  const overridesDir = `${APP_PACKAGE_DIR}/src/lib/i18n/overrides`;
6982
7033
  const localeDirs = [substrateDir, overridesDir];
6983
7034
  const conventionalPaths = [];
7035
+ const serverRegistrationPaths = registry.modules.map(
7036
+ (mod) => `${MODULES_SANDBOX_DIR}/${mod.name}/${SERVER_REGISTRATIONS_FILE}`
7037
+ );
6984
7038
  for (const mod of registry.modules) {
6985
7039
  const modulePath = `${MODULES_SANDBOX_DIR}/${mod.name}`;
6986
7040
  localeDirs.push(`${modulePath}/i18n`);
@@ -7009,17 +7063,17 @@ async function collectCompositionEntryFacts(context, registry) {
7009
7063
  const [conventionalPresences, localePresences] = await Promise.all([
7010
7064
  classifySandboxPathPresenceKindMany(
7011
7065
  context,
7012
- conventionalPaths,
7066
+ [...conventionalPaths, ...serverRegistrationPaths],
7013
7067
  CONVENTIONAL_ENTRY_KIND_PROBE_TOKEN
7014
7068
  ),
7015
7069
  classifySandboxPathPresenceKindMany(context, localePaths, LOCALE_JSON_KIND_PROBE_TOKEN)
7016
7070
  ]);
7017
7071
  const presentConventionalPaths = [];
7018
- for (const path3 of conventionalPaths) {
7019
- const presence = conventionalPresences.get(path3) ?? "unreadable";
7020
- if (presence === "file") presentConventionalPaths.push(path3);
7072
+ for (const path4 of conventionalPaths) {
7073
+ const presence = conventionalPresences.get(path4) ?? "unreadable";
7074
+ if (presence === "file") presentConventionalPaths.push(path4);
7021
7075
  }
7022
- const presentLocalePaths = localePaths.filter((path3) => localePresences.get(path3) === "file");
7076
+ const presentLocalePaths = localePaths.filter((path4) => localePresences.get(path4) === "file");
7023
7077
  const files = await readFilesMany(context, [...presentConventionalPaths, ...presentLocalePaths]);
7024
7078
  const substrateLocales = [];
7025
7079
  for (const locale of substrateNames) {
@@ -7071,6 +7125,7 @@ async function collectCompositionEntryFacts(context, registry) {
7071
7125
  );
7072
7126
  }
7073
7127
  }
7128
+ const hasServerRegistrations = hasRealServerRegistrations(modulePath, conventionalPresences);
7074
7129
  const rootLocales = localesByDir.get(`${modulePath}/i18n`);
7075
7130
  if (rootLocales.length > 0 && !rootLocales.includes("en")) {
7076
7131
  throw new Error(
@@ -7110,13 +7165,14 @@ async function collectCompositionEntryFacts(context, registry) {
7110
7165
  moduleName: mod.name,
7111
7166
  hasRootContributions,
7112
7167
  hasRootSlots,
7168
+ hasServerRegistrations,
7113
7169
  rootLocales,
7114
7170
  enhancements
7115
7171
  });
7116
7172
  if (rootLocales.length > 0) {
7117
7173
  for (const locale of rootLocales) {
7118
- const path3 = `${modulePath}/i18n/${locale}.json`;
7119
- assertRealLocaleJsonFilePresence(path3, localePresences.get(path3) ?? "unreadable");
7174
+ const path4 = `${modulePath}/i18n/${locale}.json`;
7175
+ assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
7120
7176
  }
7121
7177
  const enMessages = await readJsonObjectFile(`${modulePath}/i18n/en.json`, files);
7122
7178
  const localeMessages = { en: enMessages };
@@ -7140,8 +7196,8 @@ async function collectCompositionEntryFacts(context, registry) {
7140
7196
  if (!enh.active || enh.locales.length === 0) continue;
7141
7197
  const peerDir = `${modulePath}/enhancements/${enh.peer}`;
7142
7198
  for (const locale of enh.locales) {
7143
- const path3 = `${peerDir}/i18n/${locale}.json`;
7144
- assertRealLocaleJsonFilePresence(path3, localePresences.get(path3) ?? "unreadable");
7199
+ const path4 = `${peerDir}/i18n/${locale}.json`;
7200
+ assertRealLocaleJsonFilePresence(path4, localePresences.get(path4) ?? "unreadable");
7145
7201
  }
7146
7202
  const enMessages = await readJsonObjectFile(`${peerDir}/i18n/en.json`, files);
7147
7203
  const localeMessages = { en: enMessages };
@@ -7313,6 +7369,7 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7313
7369
  MODULE_DATASTORES_GEN_PATH,
7314
7370
  MODULE_I18N_GEN_PATH,
7315
7371
  MODULE_INIT_SERVER_GEN_PATH,
7372
+ MODULE_SERVER_GEN_PATH,
7316
7373
  ...existingGeneratedPaths,
7317
7374
  ...Object.keys(existingAppPages),
7318
7375
  ...Object.keys(existingAppRoutes)
@@ -7341,16 +7398,16 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7341
7398
  for (const stub of probePlan.plan.routeStubs) candidatePaths.add(stub.path);
7342
7399
  const existingFiles = {};
7343
7400
  const remainingCandidatePaths = [];
7344
- for (const path3 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
7345
- if (Object.prototype.hasOwnProperty.call(existingAppPages, path3)) {
7346
- existingFiles[path3] = existingAppPages[path3];
7401
+ for (const path4 of [...candidatePaths].sort((a, b) => a.localeCompare(b))) {
7402
+ if (Object.prototype.hasOwnProperty.call(existingAppPages, path4)) {
7403
+ existingFiles[path4] = existingAppPages[path4];
7347
7404
  continue;
7348
7405
  }
7349
- if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path3)) {
7350
- existingFiles[path3] = existingAppRoutes[path3];
7406
+ if (Object.prototype.hasOwnProperty.call(existingAppRoutes, path4)) {
7407
+ existingFiles[path4] = existingAppRoutes[path4];
7351
7408
  continue;
7352
7409
  }
7353
- remainingCandidatePaths.push(path3);
7410
+ remainingCandidatePaths.push(path4);
7354
7411
  }
7355
7412
  const candidateKinds = await classifySandboxPathPresenceKindMany(
7356
7413
  context,
@@ -7358,12 +7415,12 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7358
7415
  COMPOSITION_ARTIFACT_KIND_PROBE_TOKEN
7359
7416
  );
7360
7417
  const presentCandidatePaths = [];
7361
- for (const path3 of remainingCandidatePaths) {
7418
+ for (const path4 of remainingCandidatePaths) {
7362
7419
  const presence = assertRealCompositionArtifactFilePresence(
7363
- path3,
7364
- candidateKinds.get(path3) ?? "unreadable"
7420
+ path4,
7421
+ candidateKinds.get(path4) ?? "unreadable"
7365
7422
  );
7366
- if (presence === "file") presentCandidatePaths.push(path3);
7423
+ if (presence === "file") presentCandidatePaths.push(path4);
7367
7424
  }
7368
7425
  try {
7369
7426
  Object.assign(
@@ -7371,9 +7428,9 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7371
7428
  Object.fromEntries(await readFilesMany(context, presentCandidatePaths))
7372
7429
  );
7373
7430
  } catch (error) {
7374
- const path3 = readFailurePath(error);
7431
+ const path4 = readFailurePath(error);
7375
7432
  throw new Error(
7376
- `[ModuleRail] composition artifacts: cannot read ${redactSecrets(path3)} during preflight: ${redactSecrets(
7433
+ `[ModuleRail] composition artifacts: cannot read ${redactSecrets(path4)} during preflight: ${redactSecrets(
7377
7434
  error instanceof Error ? error.message : String(error)
7378
7435
  )}`
7379
7436
  );
@@ -7442,14 +7499,139 @@ async function reconcileCompositionArtifacts(context, options = {}) {
7442
7499
  var HOME_I18N_SUBSTRATE_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/substrate`;
7443
7500
  var HOME_I18N_MESSAGES_DIR = `${APP_PACKAGE_DIR}/src/lib/i18n/messages`;
7444
7501
 
7502
+ // src/module-boundaries.ts
7503
+ import fs2 from "fs";
7504
+ import path2 from "path";
7505
+ import ts5 from "typescript";
7506
+ var SOURCE_FILE = /\.[cm]?[jt]sx?$/;
7507
+ var RESOLVE_SUFFIXES = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
7508
+ var DATA_FILE = /\.(?:sql|json)$/;
7509
+ function dataPathsIn(fileName, source) {
7510
+ if (!/\.(?:sql|json)["'`]/.test(source)) return [];
7511
+ const paths = [];
7512
+ const visit = (node) => {
7513
+ if ((ts5.isStringLiteralLike(node) || ts5.isTemplateTail(node)) && DATA_FILE.test(node.text)) {
7514
+ paths.push(node.text);
7515
+ }
7516
+ ts5.forEachChild(node, visit);
7517
+ };
7518
+ visit(ts5.createSourceFile(fileName, source, ts5.ScriptTarget.Latest, false));
7519
+ return paths;
7520
+ }
7521
+ function scanModule(modulesDir, module) {
7522
+ const scanned = /* @__PURE__ */ new Map();
7523
+ const moduleDir = path2.join(modulesDir, module);
7524
+ if (!fs2.existsSync(moduleDir)) return scanned;
7525
+ const files = fs2.readdirSync(moduleDir, { recursive: true, encoding: "utf8" }).filter((relative) => SOURCE_FILE.test(relative) && !relative.includes("node_modules"));
7526
+ for (const relative of files) {
7527
+ const absolute = path2.join(moduleDir, relative);
7528
+ if (!fs2.statSync(absolute).isFile()) continue;
7529
+ const source = fs2.readFileSync(absolute, "utf8");
7530
+ const specifiers = ts5.preProcessFile(source, true, true).importedFiles.map((f) => f.fileName);
7531
+ const dataPaths = dataPathsIn(absolute, source);
7532
+ const targets = [...specifiers, ...dataPaths].flatMap((spec) => {
7533
+ if (spec.startsWith("@/modules/"))
7534
+ return [path2.join(modulesDir, spec.slice("@/modules/".length))];
7535
+ if (spec.startsWith("./") || spec.startsWith("../")) {
7536
+ return [path2.resolve(path2.dirname(absolute), spec)];
7537
+ }
7538
+ return [];
7539
+ });
7540
+ scanned.set(absolute, {
7541
+ module,
7542
+ relative: relative.split(path2.sep).join("/"),
7543
+ targets,
7544
+ dataPaths
7545
+ });
7546
+ }
7547
+ return scanned;
7548
+ }
7549
+ function findModuleBoundaryViolations(modulesDir, peersByModule, { pruning = false } = {}) {
7550
+ const files = /* @__PURE__ */ new Map();
7551
+ for (const [module, peers] of Object.entries(peersByModule)) {
7552
+ if (peers.length === 0) continue;
7553
+ for (const [absolute, file] of scanModule(modulesDir, module)) files.set(absolute, file);
7554
+ }
7555
+ const exempt = (file, peer) => (pruning ? peersByModule[file.module] ?? [] : [peer]).some(
7556
+ (q) => file.relative.startsWith(`enhancements/${q}/`)
7557
+ );
7558
+ const goneWith = (target, peer) => {
7559
+ const relative = path2.relative(modulesDir, target).split(path2.sep).join("/");
7560
+ return relative === peer || relative.startsWith(`${peer}/`) || new RegExp(`^[^/]+/enhancements/${peer}(/|$)`).test(relative);
7561
+ };
7562
+ const tainted = /* @__PURE__ */ new Map();
7563
+ const queue = [];
7564
+ const taint = (absolute, peer) => {
7565
+ const peers = tainted.get(absolute) ?? /* @__PURE__ */ new Set();
7566
+ if (peers.has(peer)) return;
7567
+ peers.add(peer);
7568
+ tainted.set(absolute, peers);
7569
+ queue.push([absolute, peer]);
7570
+ };
7571
+ const importers = /* @__PURE__ */ new Map();
7572
+ for (const [absolute, file] of files) {
7573
+ for (const target of file.targets) {
7574
+ const hit = RESOLVE_SUFFIXES.map((suffix) => target + suffix).find((c) => files.has(c));
7575
+ if (hit) importers.set(hit, [...importers.get(hit) ?? [], absolute]);
7576
+ }
7577
+ for (const peer of peersByModule[file.module] ?? []) {
7578
+ if (exempt(file, peer)) continue;
7579
+ if (file.targets.some((target) => goneWith(target, peer)) || file.dataPaths.some(
7580
+ (p) => p.startsWith(`modules/${peer}/`) || p.includes(`/modules/${peer}/`)
7581
+ )) {
7582
+ taint(absolute, peer);
7583
+ }
7584
+ }
7585
+ }
7586
+ for (let next = queue.shift(); next; next = queue.shift()) {
7587
+ const [target, peer] = next;
7588
+ for (const importer of importers.get(target) ?? []) {
7589
+ const file = files.get(importer);
7590
+ if ((peersByModule[file.module] ?? []).includes(peer) && !exempt(file, peer)) {
7591
+ taint(importer, peer);
7592
+ }
7593
+ }
7594
+ }
7595
+ const violations = [];
7596
+ for (const [absolute, peers] of tainted) {
7597
+ const file = files.get(absolute);
7598
+ for (const peer of peers) {
7599
+ violations.push({ file: `${file.module}/${file.relative}`, module: file.module, peer });
7600
+ }
7601
+ }
7602
+ return violations.sort((a, b) => a.file.localeCompare(b.file) || a.peer.localeCompare(b.peer));
7603
+ }
7604
+ function findOptionalPeerViolations(modulesDir) {
7605
+ if (!fs2.existsSync(modulesDir)) return [];
7606
+ const manifests = {};
7607
+ for (const name of fs2.readdirSync(modulesDir)) {
7608
+ const manifestPath = path2.join(modulesDir, name, "module.json");
7609
+ if (!fs2.existsSync(manifestPath)) continue;
7610
+ manifests[name] = JSON.parse(fs2.readFileSync(manifestPath, "utf8"));
7611
+ }
7612
+ const features = Object.keys(manifests).filter((name) => manifests[name].kind === "feature");
7613
+ const peersByModule = {};
7614
+ for (const [name, manifest] of Object.entries(manifests)) {
7615
+ const uses = new Set(manifest.uses ?? []);
7616
+ peersByModule[name] = features.filter((peer) => peer !== name && !uses.has(peer));
7617
+ }
7618
+ return findModuleBoundaryViolations(modulesDir, peersByModule);
7619
+ }
7620
+ function formatBoundaryViolations(modulesDirLabel, violations) {
7621
+ const files = new Set(violations.map((v) => v.file)).size;
7622
+ return `${files} Module file(s) import a peer Module that may be absent (not in the Module's \`uses\`, or excluded by --enabled).
7623
+ \`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).
7624
+ ` + violations.map((v) => `${modulesDirLabel}/${v.file}: ${v.peer}`).join("\n");
7625
+ }
7626
+
7445
7627
  // src/index.ts
7446
7628
  var MODULES_DIR2 = MODULES_SANDBOX_DIR;
7447
7629
  function readComposeInputs(cwd) {
7448
- const file = path2.join(cwd, COMPOSE_INPUTS_PATH);
7449
- if (!fs2.existsSync(file)) return { connectedStores: [], installs: {} };
7630
+ const file = path3.join(cwd, COMPOSE_INPUTS_PATH);
7631
+ if (!fs3.existsSync(file)) return { connectedStores: [], installs: {} };
7450
7632
  let raw;
7451
7633
  try {
7452
- raw = JSON.parse(fs2.readFileSync(file, "utf8"));
7634
+ raw = JSON.parse(fs3.readFileSync(file, "utf8"));
7453
7635
  } catch (error) {
7454
7636
  throw new Error(
7455
7637
  `[compose] ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`
@@ -7491,18 +7673,18 @@ function stubGitignoreEntries(artifactPaths) {
7491
7673
  function writeStubGitignore(cwd, artifactPaths) {
7492
7674
  const entries = stubGitignoreEntries(artifactPaths);
7493
7675
  const content = [STUB_GITIGNORE_HEADER, ...entries].join("\n") + "\n";
7494
- const file = path2.join(cwd, STUB_GITIGNORE_PATH);
7495
- const existing = fs2.existsSync(file) ? fs2.readFileSync(file, "utf8") : null;
7676
+ const file = path3.join(cwd, STUB_GITIGNORE_PATH);
7677
+ const existing = fs3.existsSync(file) ? fs3.readFileSync(file, "utf8") : null;
7496
7678
  if (existing !== content) {
7497
- fs2.mkdirSync(path2.dirname(file), { recursive: true });
7498
- fs2.writeFileSync(file, content);
7679
+ fs3.mkdirSync(path3.dirname(file), { recursive: true });
7680
+ fs3.writeFileSync(file, content);
7499
7681
  }
7500
7682
  return entries.length;
7501
7683
  }
7502
7684
  function assertStubGitignoreOwned(cwd) {
7503
- const file = path2.join(cwd, STUB_GITIGNORE_PATH);
7504
- if (!fs2.existsSync(file)) return;
7505
- const firstLine = fs2.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
7685
+ const file = path3.join(cwd, STUB_GITIGNORE_PATH);
7686
+ if (!fs3.existsSync(file)) return;
7687
+ const firstLine = fs3.readFileSync(file, "utf8").split("\n", 1)[0] ?? "";
7506
7688
  if (firstLine !== STUB_GITIGNORE_HEADER) {
7507
7689
  throw new Error(
7508
7690
  `[compose] refusing to overwrite ${STUB_GITIGNORE_PATH}: it is not compose-generated (line 1 is not the generated header)`
@@ -7510,8 +7692,8 @@ function assertStubGitignoreOwned(cwd) {
7510
7692
  }
7511
7693
  }
7512
7694
  function assertAppRoot(cwd) {
7513
- const appPath = path2.join(cwd, APP_PACKAGE_JSON);
7514
- if (!fs2.existsSync(appPath)) {
7695
+ const appPath = path3.join(cwd, APP_PACKAGE_JSON);
7696
+ if (!fs3.existsSync(appPath)) {
7515
7697
  throw new Error(
7516
7698
  `[compose] ${appPath} not found \u2014 run this from the repo root of a Stardeck app`
7517
7699
  );
@@ -7557,9 +7739,9 @@ ${porcelain.trim()}`
7557
7739
  }
7558
7740
  var APP_SRC_DIR = "apps/web/src";
7559
7741
  function walkSources(dir, skip, out) {
7560
- if (!fs2.existsSync(dir)) return;
7561
- for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
7562
- const full = path2.join(dir, entry.name);
7742
+ if (!fs3.existsSync(dir)) return;
7743
+ for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
7744
+ const full = path3.join(dir, entry.name);
7563
7745
  if (full === skip) continue;
7564
7746
  if (entry.isDirectory()) walkSources(full, skip, out);
7565
7747
  else if (/\.[cm]?[jt]sx?$/.test(entry.name)) out.push(full);
@@ -7568,13 +7750,13 @@ function walkSources(dir, skip, out) {
7568
7750
  function assertNoAppLayerImporters(cwd, excluded) {
7569
7751
  if (excluded.length === 0) return;
7570
7752
  const files = [];
7571
- walkSources(path2.join(cwd, APP_SRC_DIR), path2.join(cwd, MODULES_DIR2), files);
7753
+ walkSources(path3.join(cwd, APP_SRC_DIR), path3.join(cwd, MODULES_DIR2), files);
7572
7754
  const offenders = [];
7573
7755
  for (const file of files) {
7574
- const source = fs2.readFileSync(file, "utf8");
7756
+ const source = fs3.readFileSync(file, "utf8");
7575
7757
  for (const name of excluded) {
7576
7758
  if (new RegExp(`@/modules/${name}(?=["'\`/])`).test(source)) {
7577
- offenders.push(`${path2.relative(cwd, file)}: ${name}`);
7759
+ offenders.push(`${path3.relative(cwd, file)}: ${name}`);
7578
7760
  }
7579
7761
  }
7580
7762
  }
@@ -7587,8 +7769,8 @@ function assertNoAppLayerImporters(cwd, excluded) {
7587
7769
  }
7588
7770
  }
7589
7771
  function removeDir(absolute) {
7590
- if (!fs2.existsSync(absolute)) return false;
7591
- fs2.rmSync(absolute, { recursive: true, force: true });
7772
+ if (!fs3.existsSync(absolute)) return false;
7773
+ fs3.rmSync(absolute, { recursive: true, force: true });
7592
7774
  return true;
7593
7775
  }
7594
7776
  async function compose(options) {
@@ -7615,6 +7797,16 @@ async function compose(options) {
7615
7797
  dryRun: true
7616
7798
  });
7617
7799
  assertNoAppLayerImporters(options.cwd, preview.excludedModules);
7800
+ const violations = findModuleBoundaryViolations(
7801
+ path3.join(options.cwd, MODULES_DIR2),
7802
+ Object.fromEntries(preview.composedModules.map((name) => [name, preview.excludedModules])),
7803
+ { pruning: true }
7804
+ );
7805
+ if (violations.length > 0) {
7806
+ throw new Error(
7807
+ `[compose] refusing --prune: ${formatBoundaryViolations(MODULES_DIR2, violations)}`
7808
+ );
7809
+ }
7618
7810
  }
7619
7811
  const result = await reconcileCompositionArtifacts(context, {
7620
7812
  enabledModulesRaw: options.enabled,
@@ -7626,11 +7818,11 @@ async function compose(options) {
7626
7818
  let pruned = 0;
7627
7819
  if (options.prune) {
7628
7820
  for (const name of result.excludedModules) {
7629
- if (removeDir(path2.join(options.cwd, MODULES_DIR2, name))) pruned++;
7821
+ if (removeDir(path3.join(options.cwd, MODULES_DIR2, name))) pruned++;
7630
7822
  }
7631
7823
  for (const owner of result.composedModules) {
7632
7824
  for (const peer of result.excludedModules) {
7633
- if (removeDir(path2.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
7825
+ if (removeDir(path3.join(options.cwd, MODULES_DIR2, owner, "enhancements", peer))) pruned++;
7634
7826
  }
7635
7827
  }
7636
7828
  }
@@ -7648,5 +7840,8 @@ export {
7648
7840
  assertSupportedTypescript,
7649
7841
  compose,
7650
7842
  escapeGitignore,
7843
+ findModuleBoundaryViolations,
7844
+ findOptionalPeerViolations,
7845
+ formatBoundaryViolations,
7651
7846
  readComposeInputs
7652
7847
  };