@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.8 → 3.9.3-ultramodern.1

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.
Files changed (280) hide show
  1. package/README.md +23 -0
  2. package/dist/cjs/backend-federation-build/codegen.js +29 -18
  3. package/dist/cjs/backend-federation-build/config.js +84 -45
  4. package/dist/cjs/backend-federation-build/emit.js +63 -22
  5. package/dist/cjs/build-config/build-environment.js +25 -162
  6. package/dist/cjs/build-config/internal-effect-discovery.js +143 -0
  7. package/dist/cjs/build-config/public.js +2 -5
  8. package/dist/cjs/build-config/remote-address.js +58 -0
  9. package/dist/cjs/cloudflare/artifacts.js +8 -19
  10. package/dist/cjs/cloudflare/delivery-unit.js +125 -43
  11. package/dist/cjs/cloudflare/index.js +14 -4
  12. package/dist/cjs/cloudflare/native-renderer.js +100 -0
  13. package/dist/cjs/cloudflare/renderer-identity.js +106 -0
  14. package/dist/cjs/cloudflare/worker-manifest.js +54 -13
  15. package/dist/cjs/cloudflare/worker-options.js +48 -0
  16. package/dist/cjs/cloudflare/wrangler-config.js +210 -13
  17. package/dist/cjs/cloudflare-builder.js +307 -89
  18. package/dist/cjs/cloudflare-output-contract.js +36 -4
  19. package/dist/cjs/cloudflare-output-verifier/identity.js +112 -23
  20. package/dist/cjs/cloudflare-output-verifier/index.js +10 -0
  21. package/dist/cjs/cloudflare-output-verifier/manifest.js +46 -0
  22. package/dist/cjs/cloudflare-output-verifier/mutation-policy.js +7 -1
  23. package/dist/cjs/cloudflare-output-verifier/worker-bundles.js +10 -5
  24. package/dist/cjs/config.js +10 -5
  25. package/dist/cjs/deploy-output/npmAliases.js +92 -6
  26. package/dist/cjs/deploy-output/plugin.js +27 -6
  27. package/dist/cjs/deploy-output/public-assets.js +146 -0
  28. package/dist/cjs/deploy-output/target.js +57 -1
  29. package/dist/cjs/deploy-output/trace-files.js +99 -0
  30. package/dist/cjs/module-federation-config/exposes.js +70 -0
  31. package/dist/cjs/module-federation-config/index.js +73 -0
  32. package/dist/cjs/module-federation-config/inspect.js +103 -0
  33. package/dist/cjs/module-federation-config/object-literal.js +87 -0
  34. package/dist/cjs/module-federation-config/syntax.js +151 -0
  35. package/dist/cjs/module-federation-config/types.js +18 -0
  36. package/dist/cjs/native-type-checker.js +164 -18
  37. package/dist/cjs/policy-defaults.js +110 -0
  38. package/dist/cjs/release-envelope/canonical.js +2 -9
  39. package/dist/cjs/release-envelope/contract.js +68 -0
  40. package/dist/cjs/release-envelope/framework-output.js +296 -105
  41. package/dist/cjs/release-envelope/index.js +150 -36
  42. package/dist/cjs/release-envelope/plugin.js +24 -15
  43. package/dist/cjs/release-envelope/renderer-output-stamp.js +170 -0
  44. package/dist/cjs/release-envelope/types.js +10 -8
  45. package/dist/cjs/release-identity.js +8 -5
  46. package/dist/cjs/renderer-build-identity.js +134 -0
  47. package/dist/cjs/runtime-package-resolution.js +118 -0
  48. package/dist/cjs/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  49. package/dist/cjs/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  50. package/dist/cjs/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  51. package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  52. package/dist/cjs/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  53. package/dist/cjs/templates/cloudflare-worker-route-data.mjs +1069 -0
  54. package/dist/cjs/workspace-topology.js +84 -0
  55. package/dist/esm/backend-federation-build/codegen.mjs +29 -18
  56. package/dist/esm/backend-federation-build/config.mjs +74 -38
  57. package/dist/esm/backend-federation-build/emit.mjs +64 -23
  58. package/dist/esm/build-config/build-environment.mjs +26 -160
  59. package/dist/esm/build-config/internal-effect-discovery.mjs +102 -0
  60. package/dist/esm/build-config/public.mjs +1 -1
  61. package/dist/esm/build-config/remote-address.mjs +20 -0
  62. package/dist/esm/cloudflare/artifacts.mjs +8 -19
  63. package/dist/esm/cloudflare/delivery-unit.mjs +124 -45
  64. package/dist/esm/cloudflare/index.mjs +15 -5
  65. package/dist/esm/cloudflare/native-renderer.mjs +48 -0
  66. package/dist/esm/cloudflare/renderer-identity.mjs +57 -0
  67. package/dist/esm/cloudflare/worker-manifest.mjs +55 -14
  68. package/dist/esm/cloudflare/worker-options.mjs +10 -0
  69. package/dist/esm/cloudflare/wrangler-config.mjs +203 -12
  70. package/dist/esm/cloudflare-builder.mjs +301 -89
  71. package/dist/esm/cloudflare-output-contract.mjs +29 -3
  72. package/dist/esm/cloudflare-output-verifier/identity.mjs +113 -24
  73. package/dist/esm/cloudflare-output-verifier/index.mjs +10 -0
  74. package/dist/esm/cloudflare-output-verifier/manifest.mjs +46 -0
  75. package/dist/esm/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  76. package/dist/esm/cloudflare-output-verifier/worker-bundles.mjs +11 -6
  77. package/dist/esm/config.mjs +3 -1
  78. package/dist/esm/deploy-output/npmAliases.mjs +92 -6
  79. package/dist/esm/deploy-output/plugin.mjs +24 -6
  80. package/dist/esm/deploy-output/public-assets.mjs +82 -0
  81. package/dist/esm/deploy-output/target.mjs +50 -3
  82. package/dist/esm/deploy-output/trace-files.mjs +47 -0
  83. package/dist/esm/module-federation-config/exposes.mjs +18 -0
  84. package/dist/esm/module-federation-config/index.mjs +4 -0
  85. package/dist/esm/module-federation-config/inspect.mjs +65 -0
  86. package/dist/esm/module-federation-config/object-literal.mjs +43 -0
  87. package/dist/esm/module-federation-config/syntax.mjs +101 -0
  88. package/dist/esm/module-federation-config/types.mjs +0 -0
  89. package/dist/esm/native-type-checker.mjs +158 -18
  90. package/dist/esm/policy-defaults.mjs +51 -0
  91. package/dist/esm/release-envelope/canonical.mjs +2 -9
  92. package/dist/esm/release-envelope/contract.mjs +21 -0
  93. package/dist/esm/release-envelope/framework-output.mjs +297 -106
  94. package/dist/esm/release-envelope/index.mjs +149 -38
  95. package/dist/esm/release-envelope/plugin.mjs +24 -15
  96. package/dist/esm/release-envelope/renderer-output-stamp.mjs +112 -0
  97. package/dist/esm/release-envelope/types.mjs +2 -3
  98. package/dist/esm/release-identity.mjs +8 -5
  99. package/dist/esm/renderer-build-identity.mjs +82 -0
  100. package/dist/esm/runtime-package-resolution.mjs +60 -0
  101. package/dist/esm/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  102. package/dist/esm/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  103. package/dist/esm/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  104. package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  105. package/dist/esm/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  106. package/dist/esm/templates/cloudflare-worker-route-data.mjs +1069 -0
  107. package/dist/esm/workspace-topology.mjs +21 -0
  108. package/dist/esm-node/backend-federation-build/codegen.mjs +29 -18
  109. package/dist/esm-node/backend-federation-build/config.mjs +74 -38
  110. package/dist/esm-node/backend-federation-build/emit.mjs +64 -23
  111. package/dist/esm-node/build-config/build-environment.mjs +26 -160
  112. package/dist/esm-node/build-config/internal-effect-discovery.mjs +103 -0
  113. package/dist/esm-node/build-config/public.mjs +1 -1
  114. package/dist/esm-node/build-config/remote-address.mjs +21 -0
  115. package/dist/esm-node/cloudflare/artifacts.mjs +8 -19
  116. package/dist/esm-node/cloudflare/delivery-unit.mjs +124 -45
  117. package/dist/esm-node/cloudflare/index.mjs +15 -5
  118. package/dist/esm-node/cloudflare/native-renderer.mjs +49 -0
  119. package/dist/esm-node/cloudflare/renderer-identity.mjs +58 -0
  120. package/dist/esm-node/cloudflare/worker-manifest.mjs +55 -14
  121. package/dist/esm-node/cloudflare/worker-options.mjs +11 -0
  122. package/dist/esm-node/cloudflare/wrangler-config.mjs +203 -12
  123. package/dist/esm-node/cloudflare-builder.mjs +301 -89
  124. package/dist/esm-node/cloudflare-output-contract.mjs +29 -3
  125. package/dist/esm-node/cloudflare-output-verifier/identity.mjs +113 -24
  126. package/dist/esm-node/cloudflare-output-verifier/index.mjs +10 -0
  127. package/dist/esm-node/cloudflare-output-verifier/manifest.mjs +46 -0
  128. package/dist/esm-node/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  129. package/dist/esm-node/cloudflare-output-verifier/worker-bundles.mjs +11 -6
  130. package/dist/esm-node/config.mjs +3 -1
  131. package/dist/esm-node/deploy-output/npmAliases.mjs +92 -6
  132. package/dist/esm-node/deploy-output/plugin.mjs +24 -6
  133. package/dist/esm-node/deploy-output/public-assets.mjs +83 -0
  134. package/dist/esm-node/deploy-output/target.mjs +50 -3
  135. package/dist/esm-node/deploy-output/trace-files.mjs +48 -0
  136. package/dist/esm-node/module-federation-config/exposes.mjs +19 -0
  137. package/dist/esm-node/module-federation-config/index.mjs +5 -0
  138. package/dist/esm-node/module-federation-config/inspect.mjs +66 -0
  139. package/dist/esm-node/module-federation-config/object-literal.mjs +44 -0
  140. package/dist/esm-node/module-federation-config/syntax.mjs +102 -0
  141. package/dist/esm-node/module-federation-config/types.mjs +1 -0
  142. package/dist/esm-node/native-type-checker.mjs +158 -18
  143. package/dist/esm-node/policy-defaults.mjs +52 -0
  144. package/dist/esm-node/release-envelope/canonical.mjs +2 -9
  145. package/dist/esm-node/release-envelope/contract.mjs +22 -0
  146. package/dist/esm-node/release-envelope/framework-output.mjs +297 -106
  147. package/dist/esm-node/release-envelope/index.mjs +149 -38
  148. package/dist/esm-node/release-envelope/plugin.mjs +24 -15
  149. package/dist/esm-node/release-envelope/renderer-output-stamp.mjs +113 -0
  150. package/dist/esm-node/release-envelope/types.mjs +2 -3
  151. package/dist/esm-node/release-identity.mjs +8 -5
  152. package/dist/esm-node/renderer-build-identity.mjs +83 -0
  153. package/dist/esm-node/runtime-package-resolution.mjs +61 -0
  154. package/dist/esm-node/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  155. package/dist/esm-node/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  156. package/dist/esm-node/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  157. package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  158. package/dist/esm-node/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  159. package/dist/esm-node/templates/cloudflare-worker-route-data.mjs +1069 -0
  160. package/dist/esm-node/workspace-topology.mjs +22 -0
  161. package/dist/types/backend-federation-build/codegen.d.ts +30 -0
  162. package/dist/types/backend-federation-build/config.d.ts +10 -57
  163. package/dist/types/backend-federation-build/emit.d.ts +12 -2
  164. package/dist/types/backend-federation-build/index.d.ts +1 -1
  165. package/dist/types/build-config/build-environment.d.ts +1 -9
  166. package/dist/types/build-config/internal-effect-discovery.d.ts +4 -0
  167. package/dist/types/build-config/public.d.ts +1 -1
  168. package/dist/types/build-config/remote-address.d.ts +3 -0
  169. package/dist/types/cloudflare/artifacts.d.ts +7 -6
  170. package/dist/types/cloudflare/constants.d.ts +1 -1
  171. package/dist/types/cloudflare/delivery-unit.d.ts +38 -7
  172. package/dist/types/cloudflare/native-renderer.d.ts +30 -0
  173. package/dist/types/cloudflare/renderer-identity.d.ts +25 -0
  174. package/dist/types/cloudflare/worker-manifest.d.ts +17 -2
  175. package/dist/types/cloudflare/worker-options.d.ts +20 -0
  176. package/dist/types/cloudflare/wrangler-config.d.ts +55 -2
  177. package/dist/types/cloudflare-builder.d.ts +51 -8
  178. package/dist/types/cloudflare-output-contract.d.ts +5 -3
  179. package/dist/types/cloudflare-output-verifier/identity.d.ts +7 -3
  180. package/dist/types/cloudflare-output-verifier/worker-bundles.d.ts +2 -2
  181. package/dist/types/config.d.ts +98 -13
  182. package/dist/types/deploy-output/plugin.d.ts +29 -9
  183. package/dist/types/deploy-output/public-assets.d.ts +51 -0
  184. package/dist/types/deploy-output/target.d.ts +28 -5
  185. package/dist/types/deploy-output/trace-files.d.ts +17 -0
  186. package/dist/types/module-federation-config/exposes.d.ts +11 -0
  187. package/dist/types/module-federation-config/index.d.ts +5 -0
  188. package/dist/types/module-federation-config/inspect.d.ts +2 -0
  189. package/dist/types/module-federation-config/object-literal.d.ts +4 -0
  190. package/dist/types/module-federation-config/syntax.d.ts +7 -0
  191. package/dist/types/module-federation-config/types.d.ts +20 -0
  192. package/dist/types/native-type-checker.d.ts +26 -3
  193. package/dist/types/policy-defaults.d.ts +56 -0
  194. package/dist/types/release-envelope/canonical.d.ts +5 -5
  195. package/dist/types/release-envelope/contract.d.mts +8 -0
  196. package/dist/types/release-envelope/framework-output.d.ts +23 -13
  197. package/dist/types/release-envelope/index.d.ts +17 -5
  198. package/dist/types/release-envelope/plugin.d.ts +10 -6
  199. package/dist/types/release-envelope/renderer-output-stamp.d.ts +34 -0
  200. package/dist/types/release-envelope/types.d.ts +42 -4
  201. package/dist/types/release-identity.d.ts +3 -2
  202. package/dist/types/renderer-build-identity.d.ts +30 -0
  203. package/dist/types/runtime-package-resolution.d.ts +28 -0
  204. package/dist/types/templates/cloudflare-worker-route-data.d.mts +14 -0
  205. package/dist/types/workspace-topology.d.ts +91 -0
  206. package/package.json +97 -26
  207. package/src/backend-federation-build/codegen.ts +28 -19
  208. package/src/backend-federation-build/config.ts +189 -123
  209. package/src/backend-federation-build/emit.ts +119 -38
  210. package/src/backend-federation-build/index.ts +4 -1
  211. package/src/build-config/build-environment.ts +59 -285
  212. package/src/build-config/internal-effect-discovery.ts +239 -0
  213. package/src/build-config/public.ts +0 -1
  214. package/src/build-config/remote-address.ts +33 -0
  215. package/src/cloudflare/artifacts.ts +22 -43
  216. package/src/cloudflare/delivery-unit.ts +283 -60
  217. package/src/cloudflare/index.ts +35 -19
  218. package/src/cloudflare/native-renderer.ts +117 -0
  219. package/src/cloudflare/renderer-identity.ts +171 -0
  220. package/src/cloudflare/worker-manifest.ts +108 -10
  221. package/src/cloudflare/worker-options.ts +60 -0
  222. package/src/cloudflare/wrangler-config.ts +390 -14
  223. package/src/cloudflare-builder.ts +543 -170
  224. package/src/cloudflare-output-contract.ts +45 -2
  225. package/src/cloudflare-output-verifier/identity.ts +138 -7
  226. package/src/cloudflare-output-verifier/index.ts +20 -0
  227. package/src/cloudflare-output-verifier/manifest.ts +77 -0
  228. package/src/cloudflare-output-verifier/mutation-policy.ts +11 -1
  229. package/src/cloudflare-output-verifier/worker-bundles.ts +27 -11
  230. package/src/config.ts +101 -19
  231. package/src/deploy-output/npmAliases.ts +141 -4
  232. package/src/deploy-output/plugin.ts +55 -18
  233. package/src/deploy-output/public-assets.ts +224 -0
  234. package/src/deploy-output/target.ts +97 -14
  235. package/src/deploy-output/trace-files.ts +80 -0
  236. package/src/module-federation-config/exposes.ts +46 -0
  237. package/src/module-federation-config/index.ts +22 -0
  238. package/src/module-federation-config/inspect.ts +157 -0
  239. package/src/module-federation-config/object-literal.ts +76 -0
  240. package/src/module-federation-config/syntax.ts +212 -0
  241. package/src/module-federation-config/types.ts +22 -0
  242. package/src/native-type-checker.ts +281 -26
  243. package/src/policy-defaults.ts +157 -0
  244. package/src/release-envelope/canonical.ts +6 -18
  245. package/src/release-envelope/contract.mjs +28 -0
  246. package/src/release-envelope/framework-output.ts +681 -156
  247. package/src/release-envelope/index.ts +315 -60
  248. package/src/release-envelope/plugin.ts +54 -38
  249. package/src/release-envelope/renderer-output-stamp.ts +210 -0
  250. package/src/release-envelope/types.ts +75 -6
  251. package/src/release-identity.ts +19 -2
  252. package/src/renderer-build-identity.ts +168 -0
  253. package/src/runtime-package-resolution.ts +115 -0
  254. package/src/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  255. package/src/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  256. package/src/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  257. package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  258. package/src/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  259. package/src/templates/cloudflare-worker-route-data.mjs +1069 -0
  260. package/src/workspace-topology.ts +125 -0
  261. package/dist/cjs/templates/cloudflare-worker-fs-promises.mjs +0 -11
  262. package/dist/cjs/templates/cloudflare-worker-loadable-server.mjs +0 -281
  263. package/dist/cjs/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  264. package/dist/cjs/templates/cloudflare-worker-path.mjs +0 -98
  265. package/dist/esm/templates/cloudflare-worker-fs-promises.mjs +0 -11
  266. package/dist/esm/templates/cloudflare-worker-loadable-server.mjs +0 -281
  267. package/dist/esm/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  268. package/dist/esm/templates/cloudflare-worker-path.mjs +0 -98
  269. package/dist/esm-node/templates/cloudflare-worker-fs-promises.mjs +0 -11
  270. package/dist/esm-node/templates/cloudflare-worker-loadable-server.mjs +0 -281
  271. package/dist/esm-node/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  272. package/dist/esm-node/templates/cloudflare-worker-path.mjs +0 -98
  273. package/dist/types/templates/cloudflare-worker-fs-promises.d.mts +0 -5
  274. package/dist/types/templates/cloudflare-worker-loadable-server.d.mts +0 -48
  275. package/dist/types/templates/cloudflare-worker-mf-ssr-runtime-plugin.d.mts +0 -13
  276. package/dist/types/templates/cloudflare-worker-path.d.mts +0 -21
  277. package/src/templates/cloudflare-worker-fs-promises.mjs +0 -11
  278. package/src/templates/cloudflare-worker-loadable-server.mjs +0 -281
  279. package/src/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  280. package/src/templates/cloudflare-worker-path.mjs +0 -98
@@ -0,0 +1,21 @@
1
+ import { existsSync } from "node:fs";
2
+ import node_path from "node:path";
3
+ const REFERENCE_TOPOLOGY_PATH = 'topology/reference-topology.json';
4
+ const DEVELOPMENT_OVERLAY_PATH = 'topology/local-overlays/development.json';
5
+ const normalizeRelativePath = (value)=>value.replace(/\\/gu, '/').replace(/^\.\/+/u, '');
6
+ const distributedSsrFragmentSlug = (expose)=>{
7
+ const slug = expose.replace(/^\.\//u, '').trim().replace(/([a-z0-9])([A-Z])/gu, '$1-$2').replace(/[^a-zA-Z0-9._-]+/gu, '-').replace(/[._]+/gu, '-').toLowerCase().replace(/-+/gu, '-').replace(/^-+|-+$/gu, '');
8
+ if (!slug) throw new Error(`Invalid distributed SSR expose ${expose}.`);
9
+ return slug;
10
+ };
11
+ const distributedSsrFragmentRoute = (expose)=>`/{locale}/_mf/fragment/${distributedSsrFragmentSlug(expose)}`;
12
+ const findWorkspaceRoot = (appDirectory)=>{
13
+ let current = appDirectory;
14
+ while(true){
15
+ if (existsSync(node_path.join(current, REFERENCE_TOPOLOGY_PATH))) return current;
16
+ const parent = node_path.dirname(current);
17
+ if (parent === current) return;
18
+ current = parent;
19
+ }
20
+ };
21
+ export { DEVELOPMENT_OVERLAY_PATH, REFERENCE_TOPOLOGY_PATH, distributedSsrFragmentRoute, distributedSsrFragmentSlug, findWorkspaceRoot, normalizeRelativePath };
@@ -46,28 +46,34 @@ const createBackendRemoteEntrySource = async (workspaceRoot, app, effectApiPath,
46
46
  } : {}
47
47
  };
48
48
  const source = `// Generated by Modern.js backend federation build.
49
- import * as exposedNamespace from ${JSON.stringify(effectApiSpecifier)};
49
+ import { adoptEffectBffShareScope } from '@modern-js/bff-effect/effect-edge';
50
50
 
51
- const exposedModule = {
52
- ...exposedNamespace,
53
- backendFederationContract: {
54
- ...(exposedNamespace.backendFederationContract ?? {}),
55
- compatibility: {
56
- ...(exposedNamespace.backendFederationContract?.compatibility ?? {}),
57
- ...${JSON.stringify(compatibility)},
51
+ // The expose evaluates lazily so defineEffectBff registers its handler
52
+ // factories with the registry the host shares through init().
53
+ let exposedModule;
54
+ const loadExposedModule = () =>
55
+ (exposedModule ??= import(${JSON.stringify(effectApiSpecifier)}).then(exposedNamespace => ({
56
+ ...exposedNamespace,
57
+ backendFederationContract: {
58
+ ...(exposedNamespace.backendFederationContract ?? {}),
59
+ compatibility: {
60
+ ...(exposedNamespace.backendFederationContract?.compatibility ?? {}),
61
+ ...${JSON.stringify(compatibility)},
62
+ },
63
+ name: ${JSON.stringify(app.backendName)},
64
+ role: 'microvertical-server',
65
+ runtimeFramework: 'effect',
66
+ strictEffectApproach: true,
58
67
  },
59
- name: ${JSON.stringify(app.backendName)},
60
- role: 'microvertical-server',
61
- runtimeFramework: 'effect',
62
- strictEffectApproach: true,
63
- },
64
- };
68
+ })));
65
69
  const exposedModules = {
66
- ${JSON.stringify(BACKEND_FEDERATION_EFFECT_EXPOSE)}: () => Promise.resolve(exposedModule),
70
+ ${JSON.stringify(BACKEND_FEDERATION_EFFECT_EXPOSE)}: loadExposedModule,
67
71
  };
68
72
  const __modernjsBackendFederation = ${JSON.stringify(containerMetadata)};
69
73
 
70
- function init() {}
74
+ function init(shareScope) {
75
+ return adoptEffectBffShareScope(shareScope);
76
+ }
71
77
 
72
78
  function get(id) {
73
79
  const load = exposedModules[id];
@@ -171,8 +177,13 @@ const createBackendManifest = (workspaceRoot, distDirectory, app, entryIntegrity
171
177
  source: {
172
178
  module: sourceModule
173
179
  },
174
- readinessPath: `${app.apiPrefix}/${app.apiStem}/readiness`,
175
- openapiPath: `${app.apiPrefix}/openapi.json`,
180
+ ...'rpc' === app.apiProtocol ? {
181
+ rpcPath: app.rpcPath,
182
+ rpcSerialization: 'json'
183
+ } : {
184
+ readinessPath: `${app.apiPrefix}/${app.apiStem}/readiness`,
185
+ openapiPath: `${app.apiPrefix}/openapi.json`
186
+ },
176
187
  ...app.deliveryUnit ? {
177
188
  deliveryUnit: app.deliveryUnit
178
189
  } : {},
@@ -4,8 +4,7 @@ import promises from "node:fs/promises";
4
4
  import node_path from "node:path";
5
5
  import { resolveUltramodernSourceRevision } from "@modern-js/app-tools-extensions/release-identity";
6
6
  import { BACKEND_FEDERATION_MANIFEST_FILE, BACKEND_FEDERATION_REMOTE_ENTRY_FILE, DELIVERY_UNIT_DEPLOY_PROFILE, DELIVERY_UNIT_KIND, DELIVERY_UNIT_SCHEMA_VERSION, ULTRAMODERN_BUILD_ARTIFACT_PATH, deliveryUnitContractBlock, isUltramodernBuildArtifact, validateDeliveryUnitRecord, validateUltramodernBuildArtifact } from "@modern-js/backend-federation-contracts";
7
- const COMPACT_CONFIG_PATH = '.modernjs/ultramodern.json';
8
- const normalizeRelativePath = (value)=>value.replace(/\\/gu, '/').replace(/^\.\/+/u, '');
7
+ import { DEVELOPMENT_OVERLAY_PATH, REFERENCE_TOPOLOGY_PATH, findWorkspaceRoot, normalizeRelativePath } from "../workspace-topology.mjs";
9
8
  const isRecord = (value)=>'object' == typeof value && null !== value;
10
9
  const readJsonFile = async (filePath)=>JSON.parse(await promises.readFile(filePath, 'utf8'));
11
10
  const buildArtifactPathFor = (appDirectory)=>node_path.join(appDirectory, ULTRAMODERN_BUILD_ARTIFACT_PATH);
@@ -32,19 +31,20 @@ const readBuildIdentity = async (appDirectory)=>{
32
31
  const toPascalCase = (value)=>value.split(/[^a-zA-Z0-9]+/u).filter(Boolean).map((part)=>part.charAt(0).toUpperCase() + part.slice(1)).join('');
33
32
  const stringValue = (value)=>'string' == typeof value && value.length > 0 ? value : void 0;
34
33
  const createBackendName = (app, id)=>{
35
- const configuredName = stringValue(app.backendFederation?.name) ?? stringValue(app.backendFederation?.executionSurfaces?.node?.remoteName) ?? stringValue(app.serverExecution?.node?.remoteName);
34
+ const configuredName = stringValue(app.backendFederation?.name) ?? stringValue(app.backendFederation?.executionSurfaces?.node?.remoteName);
36
35
  if (configuredName) return configuredName;
37
36
  const mfName = stringValue(app.moduleFederation?.name);
38
37
  return mfName ? `${mfName}Backend` : `vertical${toPascalCase(id)}Backend`;
39
38
  };
40
- const findWorkspaceRoot = (appDirectory)=>{
41
- let current = appDirectory;
42
- while(true){
43
- if (existsSync(node_path.join(current, COMPACT_CONFIG_PATH))) return current;
44
- const parent = node_path.dirname(current);
45
- if (parent === current) return;
46
- current = parent;
39
+ const rebaseDefaultLocalUrl = (value, defaultPort, publicOrigin)=>{
40
+ let url;
41
+ try {
42
+ url = new URL(value);
43
+ } catch {
44
+ return value;
47
45
  }
46
+ if ('http:' !== url.protocol || 'localhost' !== url.hostname || Number(url.port) !== defaultPort || url.username || url.password) return value;
47
+ return `${publicOrigin}${url.pathname}${url.search}${url.hash}`;
48
48
  };
49
49
  const resolveWorkspaceSourceRevision = async (workspaceRoot)=>resolveUltramodernSourceRevision(workspaceRoot);
50
50
  const createStampedDeliveryUnit = (input)=>{
@@ -64,54 +64,90 @@ const createStampedDeliveryUnit = (input)=>{
64
64
  if (!validation.ok) return;
65
65
  return deliveryUnitContractBlock(record);
66
66
  };
67
- const createAppFromCompactMetadata = (workspaceRoot, appDirectory, compactApp)=>{
68
- const id = stringValue(compactApp.id);
69
- const appPath = stringValue(compactApp.path);
70
- if (!id || 'vertical' !== compactApp.kind || !isRecord(compactApp.api)) return;
71
- const directory = appPath ? normalizeRelativePath(appPath) : `verticals/${id}`;
67
+ const createAppFromTopology = (workspaceRoot, appDirectory, topologyApp, overlay, packageManifest)=>{
68
+ const id = stringValue(topologyApp.id);
69
+ const appPath = stringValue(topologyApp.path);
70
+ if (!id || !appPath || 'vertical' !== topologyApp.kind || !isRecord(topologyApp.api) || !isRecord(topologyApp.backendFederation)) return;
71
+ const directory = normalizeRelativePath(appPath);
72
72
  if (node_path.resolve(workspaceRoot, directory) !== node_path.resolve(appDirectory)) return;
73
- const port = 'number' == typeof compactApp.port ? compactApp.port : void 0;
74
- if (void 0 === port) return;
75
- const apiPrefix = stringValue(compactApp.api.prefix) ?? `/${id}-api`;
76
- const apiStem = stringValue(compactApp.api.stem) ?? id;
77
- const backendName = createBackendName(compactApp, id);
78
- const manifestUrl = stringValue(compactApp.backendFederation?.executionSurfaces?.node?.manifestUrl) ?? stringValue(compactApp.serverExecution?.node?.manifestUrl) ?? `http://localhost:${port}/${BACKEND_FEDERATION_MANIFEST_FILE}`;
79
- const containerEntry = stringValue(compactApp.backendFederation?.executionSurfaces?.node?.containerEntry) ?? stringValue(compactApp.serverExecution?.node?.containerEntry) ?? `http://localhost:${port}/${BACKEND_FEDERATION_REMOTE_ENTRY_FILE}`;
80
- const configuredRemoteType = stringValue(compactApp.backendFederation?.executionSurfaces?.node?.remoteType) ?? stringValue(compactApp.serverExecution?.node?.remoteType);
73
+ const defaultPort = overlay.ports?.[id];
74
+ if ('number' != typeof defaultPort || !Number.isInteger(defaultPort) || defaultPort < 1 || defaultPort > 65535) throw new Error(`[backend-federation-build] Invalid development port for ${id}.`);
75
+ const portEnv = stringValue(topologyApp.portEnv) ?? `VERTICAL_${(stringValue(topologyApp.domain) ?? id).replace(/[^a-zA-Z0-9]/gu, '_').toUpperCase()}_PORT`;
76
+ const port = Number(process.env[portEnv] ?? defaultPort);
77
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`[backend-federation-build] Invalid ${portEnv} development port for ${id}.`);
78
+ const publicUrlEnv = stringValue(topologyApp.cloudflare?.publicUrlEnv);
79
+ const configuredPublicUrl = publicUrlEnv ? process.env[publicUrlEnv]?.trim() : void 0;
80
+ let publicOrigin = `http://localhost:${port}`;
81
+ if (configuredPublicUrl) {
82
+ let url;
83
+ try {
84
+ url = new URL(configuredPublicUrl);
85
+ } catch {
86
+ throw new Error(`[backend-federation-build] Invalid ${publicUrlEnv} public URL for ${id}.`);
87
+ }
88
+ if (![
89
+ 'http:',
90
+ 'https:'
91
+ ].includes(url.protocol) || url.username || url.password) throw new Error(`[backend-federation-build] Invalid ${publicUrlEnv} public URL for ${id}.`);
92
+ publicOrigin = url.origin;
93
+ }
94
+ const apiPrefix = stringValue(topologyApp.api.bff?.prefix) ?? `/${id}-api`;
95
+ const apiProtocol = topologyApp.api.protocol ?? 'rest';
96
+ if ('rest' !== apiProtocol && 'rpc' !== apiProtocol) throw new Error(`[backend-federation-build] Unsupported API protocol for ${id}.`);
97
+ const rpcPath = 'rpc' === apiProtocol ? stringValue(topologyApp.api.rpcPath) : void 0;
98
+ if ('rpc' === apiProtocol && (!rpcPath || 'json' !== topologyApp.api.rpcSerialization)) throw new Error(`[backend-federation-build] Missing JSON RPC route metadata for ${id}.`);
99
+ const apiStem = stringValue(topologyApp.api.stem) ?? id;
100
+ const backendName = createBackendName(topologyApp, id);
101
+ const nodeOverlay = overlay.serverExecution?.[id]?.node;
102
+ if (!isRecord(nodeOverlay)) throw new Error(`[backend-federation-build] Missing declared Node server execution for ${id}.`);
103
+ const manifestUrl = rebaseDefaultLocalUrl(stringValue(nodeOverlay?.manifestUrl) ?? `http://localhost:${defaultPort}/${BACKEND_FEDERATION_MANIFEST_FILE}`, defaultPort, publicOrigin);
104
+ const containerEntry = rebaseDefaultLocalUrl(stringValue(nodeOverlay?.containerEntry) ?? `http://localhost:${defaultPort}/${BACKEND_FEDERATION_REMOTE_ENTRY_FILE}`, defaultPort, publicOrigin);
105
+ const configuredRemoteType = stringValue(nodeOverlay?.remoteType) ?? stringValue(topologyApp.backendFederation?.executionSurfaces?.node?.remoteType);
81
106
  if (void 0 !== configuredRemoteType && 'commonjs-module' !== configuredRemoteType) throw new Error(`[backend-federation-build] Node backend federation remoteType must be "commonjs-module"; received "${configuredRemoteType}" for ${id}.`);
82
107
  if (!new URL(containerEntry).pathname.endsWith(`/${BACKEND_FEDERATION_REMOTE_ENTRY_FILE}`)) throw new Error(`[backend-federation-build] Node backend federation containerEntry must end with "/${BACKEND_FEDERATION_REMOTE_ENTRY_FILE}"; received "${containerEntry}" for ${id}.`);
83
108
  const remoteType = 'commonjs-module';
84
- const packageName = stringValue(compactApp.package);
85
- const uiManifestUrl = stringValue(compactApp.backendFederation?.versionBoundary?.ui?.manifestUrl) ?? stringValue(compactApp.moduleFederation?.manifestUrl);
86
- const compactDeliveryUnit = isRecord(compactApp.deliveryUnit) ? {
87
- unitId: stringValue(compactApp.deliveryUnit.unitId),
88
- buildMarker: stringValue(compactApp.deliveryUnit.buildMarker),
89
- sourceRevision: stringValue(compactApp.deliveryUnit.sourceRevision),
90
- packageName: stringValue(compactApp.deliveryUnit.packageName),
91
- version: stringValue(compactApp.deliveryUnit.version)
109
+ const packageName = stringValue(packageManifest.name);
110
+ const version = stringValue(packageManifest.version);
111
+ if (stringValue(topologyApp.package) !== packageName || !version) throw new Error(`[backend-federation-build] Topology package identity must match package.json for ${id}.`);
112
+ const declaredUiManifestUrl = stringValue(overlay.manifests?.[id]);
113
+ const uiManifestUrl = declaredUiManifestUrl ? rebaseDefaultLocalUrl(declaredUiManifestUrl, defaultPort, publicOrigin) : void 0;
114
+ const topologyDeliveryUnit = isRecord(topologyApp.deliveryUnit) ? {
115
+ unitId: stringValue(topologyApp.deliveryUnit.unitId),
116
+ buildMarker: stringValue(topologyApp.deliveryUnit.buildMarker),
117
+ sourceRevision: stringValue(topologyApp.deliveryUnit.sourceRevision),
118
+ packageName: stringValue(topologyApp.deliveryUnit.packageName),
119
+ version: stringValue(topologyApp.deliveryUnit.version)
92
120
  } : void 0;
93
121
  return {
94
122
  id,
95
123
  directory,
96
124
  packageName,
125
+ version,
97
126
  port,
98
127
  apiPrefix,
128
+ apiProtocol,
129
+ rpcPath,
99
130
  apiStem,
100
131
  backendName,
101
132
  manifestUrl,
102
133
  containerEntry,
103
134
  remoteType,
104
135
  uiManifestUrl,
105
- compactDeliveryUnit
136
+ topologyDeliveryUnit
106
137
  };
107
138
  };
108
139
  const findBackendFederationApp = async (workspaceRoot, appDirectory)=>{
109
- const compactConfigPath = node_path.join(workspaceRoot, COMPACT_CONFIG_PATH);
110
- const compactConfig = await readJsonFile(compactConfigPath);
111
- const apps = Array.isArray(compactConfig.topology?.apps) ? compactConfig.topology.apps : [];
112
- for (const app of apps){
113
- const resolved = createAppFromCompactMetadata(workspaceRoot, appDirectory, app);
140
+ const topologyPath = node_path.join(workspaceRoot, REFERENCE_TOPOLOGY_PATH);
141
+ const topology = await readJsonFile(topologyPath);
142
+ if (!isRecord(topology) || !Array.isArray(topology.verticals)) throw new Error(`[backend-federation-build] Invalid declared topology at ${topologyPath}.`);
143
+ const overlayPath = node_path.join(workspaceRoot, DEVELOPMENT_OVERLAY_PATH);
144
+ const overlay = await readJsonFile(overlayPath);
145
+ if (!isRecord(overlay) || !isRecord(overlay.ports)) throw new Error(`[backend-federation-build] Invalid development overlay at ${overlayPath}.`);
146
+ const packageManifest = await readJsonFile(node_path.join(appDirectory, 'package.json'));
147
+ for (const app of topology.verticals){
148
+ const resolved = createAppFromTopology(workspaceRoot, appDirectory, app, overlay, packageManifest);
114
149
  if (resolved) return resolved;
115
150
  }
151
+ throw new Error(`[backend-federation-build] ${appDirectory} is missing from the declared reference topology.`);
116
152
  };
117
- export { COMPACT_CONFIG_PATH, buildArtifactPathFor, createAppFromCompactMetadata, createStampedDeliveryUnit, findBackendFederationApp, findWorkspaceRoot, normalizeRelativePath, readBuildIdentity, resolveWorkspaceSourceRevision };
153
+ export { DEVELOPMENT_OVERLAY_PATH, REFERENCE_TOPOLOGY_PATH, buildArtifactPathFor, createAppFromTopology, createStampedDeliveryUnit, findBackendFederationApp, findWorkspaceRoot, normalizeRelativePath, readBuildIdentity, resolveWorkspaceSourceRevision };
@@ -5,9 +5,10 @@ import node_path from "node:path";
5
5
  import { resolveUltramodernReleaseIdentity } from "@modern-js/app-tools-extensions/release-identity";
6
6
  import { BACKEND_FEDERATION_MANIFEST_FILE, BACKEND_FEDERATION_REMOTE_ENTRY_FILE, ULTRAMODERN_BUILD_ARTIFACT_FILE, stampUltramodernBuildArtifactIdentity } from "@modern-js/backend-federation-contracts";
7
7
  import { createBackendFederationEntryIntegrity } from "@modern-js/server-runtime-extensions/backend-federation-security/node";
8
+ import { emitRendererBuildArtifact, stampFinalizedRendererBuildArtifact, validateFinalizedRendererBuild } from "../release-envelope/renderer-output-stamp.mjs";
8
9
  import { createBackendManifest, createBackendRemoteEntrySource } from "./codegen.mjs";
9
- import { COMPACT_CONFIG_PATH, buildArtifactPathFor, createStampedDeliveryUnit, findBackendFederationApp, findWorkspaceRoot, readBuildIdentity, resolveWorkspaceSourceRevision } from "./config.mjs";
10
- const emitBackendFederationArtifacts = async (appDirectory, distDirectory)=>{
10
+ import { REFERENCE_TOPOLOGY_PATH, buildArtifactPathFor, createStampedDeliveryUnit, findBackendFederationApp, findWorkspaceRoot, readBuildIdentity, resolveWorkspaceSourceRevision } from "./config.mjs";
11
+ const emitBackendFederationArtifacts = async (appDirectory, distDirectory, options = {})=>{
11
12
  const workspaceRoot = findWorkspaceRoot(appDirectory);
12
13
  if (!workspaceRoot) return;
13
14
  const effectApiPath = node_path.join(appDirectory, 'api/effect-api.ts');
@@ -16,35 +17,50 @@ const emitBackendFederationArtifacts = async (appDirectory, distDirectory)=>{
16
17
  const app = await findBackendFederationApp(workspaceRoot, appDirectory);
17
18
  if (!app) return;
18
19
  const buildIdentity = await readBuildIdentity(appDirectory);
19
- const compactDeliveryUnit = app.compactDeliveryUnit;
20
- const hasCompactDeliveryUnit = void 0 !== compactDeliveryUnit && (void 0 !== compactDeliveryUnit.unitId || void 0 !== compactDeliveryUnit.buildMarker || void 0 !== compactDeliveryUnit.packageName || void 0 !== compactDeliveryUnit.version);
20
+ const topologyDeliveryUnit = app.topologyDeliveryUnit;
21
+ const hasTopologyDeliveryUnit = void 0 !== topologyDeliveryUnit && (void 0 !== topologyDeliveryUnit.unitId || void 0 !== topologyDeliveryUnit.buildMarker || void 0 !== topologyDeliveryUnit.packageName || void 0 !== topologyDeliveryUnit.version);
21
22
  const hasBuildIdentity = void 0 !== buildIdentity.unitId || void 0 !== buildIdentity.buildVersion || void 0 !== buildIdentity.packageName || void 0 !== buildIdentity.version;
22
23
  if (hasBuildIdentity) {
23
- const compactConfigPath = node_path.join(workspaceRoot, COMPACT_CONFIG_PATH);
24
+ const topologyPath = node_path.join(workspaceRoot, REFERENCE_TOPOLOGY_PATH);
24
25
  const buildIdentityPath = buildArtifactPathFor(appDirectory);
25
26
  const mismatches = [];
26
27
  const compare = (label, a, b, leftLabel = 'deliveryUnit')=>{
27
28
  if (void 0 !== a && void 0 !== b && a !== b) mismatches.push(`${label}: ${leftLabel}=${a} vs ultramodern-build=${b}`);
28
29
  };
29
30
  compare('appId', app.id, buildIdentity.appId, 'topology');
30
- if (hasCompactDeliveryUnit) {
31
- compare('unitId', compactDeliveryUnit?.unitId, buildIdentity.unitId);
32
- compare('buildMarker/build', compactDeliveryUnit?.buildMarker, buildIdentity.buildVersion);
33
- compare('packageName', compactDeliveryUnit?.packageName, buildIdentity.packageName);
34
- compare('version', compactDeliveryUnit?.version, buildIdentity.version);
31
+ if (hasTopologyDeliveryUnit) {
32
+ compare('unitId', topologyDeliveryUnit?.unitId, buildIdentity.unitId);
33
+ compare('buildMarker/build', topologyDeliveryUnit?.buildMarker, buildIdentity.buildVersion);
34
+ compare('packageName', topologyDeliveryUnit?.packageName, buildIdentity.packageName);
35
+ compare('version', topologyDeliveryUnit?.version, buildIdentity.version);
35
36
  }
36
- if (mismatches.length > 0) throw new Error(`[backend-federation-build] Delivery-unit identity drift between ${compactConfigPath} (topology) and ${buildIdentityPath}: ${mismatches.join('; ')}`);
37
+ compare('packageName', app.packageName, buildIdentity.packageName, 'package.json');
38
+ compare('packageName', topologyDeliveryUnit?.packageName, app.packageName, 'topology');
39
+ compare('version', app.version, buildIdentity.version, 'package.json');
40
+ compare('version', topologyDeliveryUnit?.version, app.version, 'topology');
41
+ if (mismatches.length > 0) throw new Error(`[backend-federation-build] Delivery-unit identity drift between ${topologyPath}, package.json and ${buildIdentityPath}: ${mismatches.join('; ')}`);
37
42
  }
38
- const unitId = compactDeliveryUnit?.unitId ?? buildIdentity.unitId;
39
- const generationBuildMarker = compactDeliveryUnit?.buildMarker ?? buildIdentity.buildVersion;
40
- const sourceRevision = await resolveWorkspaceSourceRevision(workspaceRoot);
41
- const buildVersion = generationBuildMarker && unitId ? resolveUltramodernReleaseIdentity({
43
+ const unitId = topologyDeliveryUnit?.unitId ?? buildIdentity.unitId;
44
+ const generationBuildMarker = topologyDeliveryUnit?.buildMarker ?? buildIdentity.buildVersion;
45
+ let finalizedRendererBuild;
46
+ if (options.resolveRendererBuild && buildIdentity.artifact?.surfaces.ui) {
47
+ if (!options.entrypoints?.length) throw new Error('[backend-federation-build] Renderer output stamping requires the actual finalized application entries.');
48
+ const context = {
49
+ appDirectory,
50
+ distDirectory,
51
+ entrypoints: options.entrypoints
52
+ };
53
+ finalizedRendererBuild = await options.resolveRendererBuild(context);
54
+ validateFinalizedRendererBuild(finalizedRendererBuild, context, app.id);
55
+ }
56
+ const sourceRevision = finalizedRendererBuild?.sourceRevision ?? await resolveWorkspaceSourceRevision(workspaceRoot);
57
+ const buildVersion = finalizedRendererBuild?.buildMarker ?? (generationBuildMarker && unitId ? resolveUltramodernReleaseIdentity({
42
58
  generationBuildMarker,
43
59
  unitId,
44
60
  workspaceRoot
45
- }).buildMarker : void 0;
46
- const packageName = compactDeliveryUnit?.packageName ?? buildIdentity.packageName ?? app.packageName;
47
- const version = compactDeliveryUnit?.version ?? buildIdentity.version;
61
+ }).buildMarker : void 0);
62
+ const packageName = app.packageName;
63
+ const version = app.version;
48
64
  const deliveryUnit = createStampedDeliveryUnit({
49
65
  appId: app.id,
50
66
  unitId,
@@ -53,10 +69,15 @@ const emitBackendFederationArtifacts = async (appDirectory, distDirectory)=>{
53
69
  version,
54
70
  sourceRevision
55
71
  });
56
- const stampedBuildArtifact = buildIdentity.artifact ? stampUltramodernBuildArtifactIdentity(buildIdentity.artifact, {
72
+ let stampedBuildArtifact = buildIdentity.artifact ? stampUltramodernBuildArtifactIdentity(buildIdentity.artifact, {
57
73
  buildMarker: buildVersion ?? buildIdentity.artifact.deliveryUnit.buildMarker,
58
74
  sourceRevision
59
75
  }) : void 0;
76
+ if (finalizedRendererBuild && stampedBuildArtifact?.surfaces.ui) stampedBuildArtifact = stampFinalizedRendererBuildArtifact(stampedBuildArtifact, finalizedRendererBuild, {
77
+ appDirectory,
78
+ distDirectory,
79
+ entrypoints: options.entrypoints
80
+ });
60
81
  const resolvedApp = {
61
82
  ...app,
62
83
  packageName,
@@ -87,14 +108,34 @@ const emitBackendFederationArtifacts = async (appDirectory, distDirectory)=>{
87
108
  remoteType: resolvedApp.remoteType
88
109
  };
89
110
  };
90
- const emit = ()=>({
111
+ const emit = (options = {})=>{
112
+ if (options.resolveRendererBuild && !options.rendererBuildPlugin) throw new Error('[backend-federation-build] A finalized renderer resolver requires its owning metadata producer plugin.');
113
+ return {
91
114
  name: '@modern-js/backend-federation-build',
115
+ pre: options.resolveRendererBuild ? [
116
+ options.rendererBuildPlugin
117
+ ] : [],
118
+ required: options.resolveRendererBuild ? [
119
+ options.rendererBuildPlugin
120
+ ] : [],
92
121
  setup (api) {
93
122
  api.onAfterBuild(async ()=>{
94
- const { appDirectory, distDirectory } = api.getAppContext();
95
- await emitBackendFederationArtifacts(appDirectory, distDirectory);
123
+ const { appDirectory, distDirectory, entrypoints, apiOnly } = api.getAppContext();
124
+ if (options.resolveRendererBuild && !apiOnly) await emitRendererBuildArtifact({
125
+ appDirectory,
126
+ distDirectory,
127
+ entrypoints: entrypoints ?? []
128
+ }, {
129
+ resolveRendererBuild: options.resolveRendererBuild,
130
+ rendererBuildPlugin: options.rendererBuildPlugin
131
+ });
132
+ await emitBackendFederationArtifacts(appDirectory, distDirectory, {
133
+ ...options,
134
+ entrypoints
135
+ });
96
136
  });
97
137
  }
98
- });
138
+ };
139
+ };
99
140
  export default emit;
100
141
  export { emitBackendFederationArtifacts };
@@ -1,148 +1,29 @@
1
1
  import "node:module";
2
- import { execFileSync } from "node:child_process";
3
2
  import { createHash } from "node:crypto";
4
3
  import { accessSync, chmodSync, constants, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
5
- import { createRequire } from "node:module";
4
+ import { createRequire, findPackageJSON } from "node:module";
6
5
  import { tmpdir } from "node:os";
7
- import { basename, dirname, join, resolve } from "node:path";
8
- const EFFECT_TSGO_PACKAGE = '@effect/tsgo';
9
- const EFFECT_TSGO_BIN = 'effect-tsgo';
6
+ import { basename, join } from "node:path";
7
+ import "node:url";
8
+ const owningModuleUrl = import.meta.url;
9
+ const owningManifest = findPackageJSON(owningModuleUrl, owningModuleUrl);
10
+ if (!owningManifest) throw new Error('Cannot find owning build-config package manifest');
11
+ const owningName = JSON.parse(readFileSync(owningManifest, 'utf8')).name;
12
+ if ('string' != typeof owningName || 0 === owningName.length) throw new Error(`Invalid owning build-config package name: ${owningManifest}`);
13
+ const { effectCompilerDiscoveryFailureStage: effectCompilerDiscoveryFailureStage, resolveInstalledEffectCompiler: resolveInstalledEffectCompiler } = createRequire(owningModuleUrl)(`${owningName}/internal-effect-discovery`);
10
14
  const EFFECT_TSGO_RESOLUTION_ERROR = 'Unable to resolve the Effect TS-Go compiler. Install "@effect/tsgo" and a native TypeScript backend for this build config, or set EFFECT_TSGO_BIN.';
11
15
  const executableEffectTsgoCompilers = new Map();
12
- const BUILD_CONFIG_ENVIRONMENT_PLUGIN = 'ModernJsBuildConfigEnvironmentLeasePlugin';
13
- const ENVIRONMENT_LEASE_REGISTRY_KEY = Symbol.for('@modern-js/app-tools/build-config-environment-lease-registry/v1');
14
- const ENVIRONMENT_LEASE_REGISTRY_BRAND = Symbol.for('@modern-js/app-tools/build-config-environment-lease-registry-brand/v1');
15
- const LIFECYCLE_HOOK_NAMES = [
16
- 'run',
17
- 'watchRun',
18
- 'afterDone',
19
- 'failed',
20
- 'shutdown',
21
- 'watchClose'
22
- ];
23
- function restoreEnvironment(name, value) {
24
- if (void 0 === value) delete process.env[name];
25
- else process.env[name] = value;
26
- }
27
- function createEnvironmentLeaseRegistry() {
28
- const environmentLeases = new Map();
29
- return Object.freeze({
30
- acquire (name, value) {
31
- let lease = environmentLeases.get(name);
32
- if (lease) {
33
- if (lease.value !== value) throw new Error(`Build config environment "${name}" already has an active lease for a different value.`);
34
- if (process.env[name] !== lease.value) throw new Error(`Build config environment lease for "${name}" lost ownership before another lease was acquired.`);
35
- } else {
36
- lease = {
37
- originalValue: process.env[name],
38
- owners: new Set(),
39
- value
40
- };
41
- process.env[name] = value;
42
- environmentLeases.set(name, lease);
43
- }
44
- const owner = Symbol(name);
45
- lease.owners.add(owner);
46
- let released = false;
47
- return ()=>{
48
- if (released) return;
49
- if (environmentLeases.get(name) !== lease || !lease.owners.has(owner)) throw new Error(`Build config environment lease for "${name}" cannot be restored because its ownership record was lost.`);
50
- released = true;
51
- const lostOwnership = process.env[name] !== lease.value;
52
- lease.owners.delete(owner);
53
- if (0 === lease.owners.size) {
54
- environmentLeases.delete(name);
55
- restoreEnvironment(name, lease.originalValue);
56
- } else if (lostOwnership) process.env[name] = lease.value;
57
- if (lostOwnership) throw new Error(`Build config environment lease for "${name}" lost ownership before restoration.`);
58
- };
59
- },
60
- brand: ENVIRONMENT_LEASE_REGISTRY_BRAND
61
- });
62
- }
63
- function getEnvironmentLeaseRegistry() {
64
- const descriptor = Object.getOwnPropertyDescriptor(process, ENVIRONMENT_LEASE_REGISTRY_KEY);
65
- if (descriptor) {
66
- const registry = descriptor.value;
67
- if (descriptor.configurable || descriptor.enumerable || descriptor.writable || registry?.brand !== ENVIRONMENT_LEASE_REGISTRY_BRAND || 'function' != typeof registry.acquire) throw new Error('The process-global build config environment lease registry is occupied by an incompatible value.');
68
- return registry;
69
- }
70
- const registry = createEnvironmentLeaseRegistry();
71
- Object.defineProperty(process, ENVIRONMENT_LEASE_REGISTRY_KEY, {
72
- configurable: false,
73
- enumerable: false,
74
- value: registry,
75
- writable: false
16
+ function effectTsgoResolutionError(cause, stage, from) {
17
+ const detail = cause instanceof Error ? `${cause.name}: ${cause.message}` : String(cause);
18
+ return new Error(`${EFFECT_TSGO_RESOLUTION_ERROR}\n${stage} failed for ${String(from)}: ${detail}`, {
19
+ cause
76
20
  });
77
- return registry;
78
- }
79
- function acquireEnvironmentLease(name, value) {
80
- return getEnvironmentLeaseRegistry().acquire(name, value);
81
- }
82
- function releaseAfterFailure(release, originalError) {
83
- try {
84
- release();
85
- } catch (restorationError) {
86
- throw new AggregateError([
87
- originalError,
88
- restorationError
89
- ], 'Build config environment setup and restoration both failed.');
90
- }
91
- throw originalError;
92
- }
93
- class BuildConfigEnvironmentLeasePlugin {
94
- apply(compiler) {
95
- const hooks = compiler.hooks;
96
- try {
97
- const lifecycleHooks = {};
98
- for (const hookName of LIFECYCLE_HOOK_NAMES){
99
- const hook = hooks[hookName];
100
- if (!hook || 'function' != typeof hook.tap) throw new Error(`Rspack does not expose the "${hookName}" lifecycle hook required to restore build config environment leases.`);
101
- lifecycleHooks[hookName] = hook;
102
- }
103
- let mode = 'pending';
104
- const modeTap = {
105
- name: BUILD_CONFIG_ENVIRONMENT_PLUGIN,
106
- stage: Number.MIN_SAFE_INTEGER
107
- };
108
- const releaseTap = {
109
- name: BUILD_CONFIG_ENVIRONMENT_PLUGIN,
110
- stage: Number.MAX_SAFE_INTEGER
111
- };
112
- const releaseAfterOneShot = ()=>{
113
- if ('watch' !== mode && true !== compiler.watchMode) this.release();
114
- };
115
- lifecycleHooks.run.tap(modeTap, ()=>{
116
- mode = 'run';
117
- });
118
- lifecycleHooks.watchRun.tap(modeTap, ()=>{
119
- mode = 'watch';
120
- });
121
- lifecycleHooks.afterDone.tap(releaseTap, releaseAfterOneShot);
122
- lifecycleHooks.failed.tap(releaseTap, releaseAfterOneShot);
123
- lifecycleHooks.shutdown.tap(releaseTap, this.release);
124
- lifecycleHooks.watchClose.tap(releaseTap, this.release);
125
- } catch (error) {
126
- releaseAfterFailure(this.release, error);
127
- }
128
- }
129
- constructor(release){
130
- this.release = release;
131
- }
132
- }
133
- function resolveEffectTsgoCli(from) {
134
- const projectRequire = createRequire(from);
135
- const packageJsonPath = projectRequire.resolve(`${EFFECT_TSGO_PACKAGE}/package.json`);
136
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
137
- const bin = 'string' == typeof packageJson.bin ? packageJson.bin : packageJson.bin?.[EFFECT_TSGO_BIN];
138
- if (!bin) throw new Error(EFFECT_TSGO_RESOLUTION_ERROR);
139
- return resolve(dirname(packageJsonPath), bin);
140
21
  }
141
22
  function resolveExecutableEffectTsgoCompiler(compilerPath) {
142
23
  if ('win32' === process.platform) return compilerPath;
143
24
  const sourcePath = realpathSync(compilerPath);
144
25
  const source = lstatSync(sourcePath);
145
- if (!source.isFile()) throw new Error(EFFECT_TSGO_RESOLUTION_ERROR);
26
+ if (!source.isFile()) throw new Error(`Effect TS-Go compiler path is not a regular file: ${sourcePath}`);
146
27
  try {
147
28
  accessSync(sourcePath, constants.X_OK);
148
29
  return compilerPath;
@@ -189,37 +70,22 @@ function resolveExecutableEffectTsgoCompiler(compilerPath) {
189
70
  function getBuildConfigEnvironment(name) {
190
71
  return process.env[name];
191
72
  }
192
- function withBuildConfigEnvironment(name, value, setup) {
193
- return async (config, ...args)=>{
194
- const release = acquireEnvironmentLease(name, value);
195
- try {
196
- const configured = await setup(config, ...args) ?? config;
197
- configured.plugins = [
198
- ...configured.plugins ?? [],
199
- new BuildConfigEnvironmentLeasePlugin(release)
200
- ];
201
- return configured;
202
- } catch (error) {
203
- releaseAfterFailure(release, error);
204
- }
205
- };
206
- }
207
73
  function resolveEffectTsgoCompiler(options) {
208
74
  const configuredCompiler = getBuildConfigEnvironment('EFFECT_TSGO_BIN')?.trim();
209
75
  if (configuredCompiler) try {
210
76
  return resolveExecutableEffectTsgoCompiler(configuredCompiler);
211
- } catch {
212
- throw new Error(EFFECT_TSGO_RESOLUTION_ERROR);
77
+ } catch (cause) {
78
+ throw effectTsgoResolutionError(cause, 'Configured compiler validation', options.from);
213
79
  }
80
+ let stage = 'Package CLI resolution';
214
81
  try {
215
- const compiler = execFileSync(process.execPath, [
216
- resolveEffectTsgoCli(options.from),
217
- 'get-exe-path'
218
- ], {
219
- encoding: 'utf-8'
220
- }).trim();
221
- if (compiler) return resolveExecutableEffectTsgoCompiler(compiler);
222
- } catch {}
223
- throw new Error(EFFECT_TSGO_RESOLUTION_ERROR);
82
+ const compiler = resolveInstalledEffectCompiler(options.from).trim();
83
+ stage = 'Compiler backend lookup';
84
+ if (!compiler) throw new Error('Effect TS-Go discovery returned an empty compiler path');
85
+ stage = 'Compiler executable validation';
86
+ return resolveExecutableEffectTsgoCompiler(compiler);
87
+ } catch (cause) {
88
+ throw effectTsgoResolutionError(cause, effectCompilerDiscoveryFailureStage(cause) ?? stage, options.from);
89
+ }
224
90
  }
225
- export { getBuildConfigEnvironment, resolveEffectTsgoCompiler, withBuildConfigEnvironment };
91
+ export { getBuildConfigEnvironment, resolveEffectTsgoCompiler };