@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.9 → 3.9.3-ultramodern.2

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 (275) 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 +130 -13
  37. package/dist/cjs/policy-defaults.js +38 -29
  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/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  48. package/dist/cjs/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  49. package/dist/cjs/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  50. package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  51. package/dist/cjs/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  52. package/dist/cjs/templates/cloudflare-worker-route-data.mjs +1069 -0
  53. package/dist/cjs/workspace-topology.js +84 -0
  54. package/dist/esm/backend-federation-build/codegen.mjs +29 -18
  55. package/dist/esm/backend-federation-build/config.mjs +74 -38
  56. package/dist/esm/backend-federation-build/emit.mjs +64 -23
  57. package/dist/esm/build-config/build-environment.mjs +26 -160
  58. package/dist/esm/build-config/internal-effect-discovery.mjs +102 -0
  59. package/dist/esm/build-config/public.mjs +1 -1
  60. package/dist/esm/build-config/remote-address.mjs +20 -0
  61. package/dist/esm/cloudflare/artifacts.mjs +8 -19
  62. package/dist/esm/cloudflare/delivery-unit.mjs +124 -45
  63. package/dist/esm/cloudflare/index.mjs +15 -5
  64. package/dist/esm/cloudflare/native-renderer.mjs +48 -0
  65. package/dist/esm/cloudflare/renderer-identity.mjs +57 -0
  66. package/dist/esm/cloudflare/worker-manifest.mjs +55 -14
  67. package/dist/esm/cloudflare/worker-options.mjs +10 -0
  68. package/dist/esm/cloudflare/wrangler-config.mjs +203 -12
  69. package/dist/esm/cloudflare-builder.mjs +301 -89
  70. package/dist/esm/cloudflare-output-contract.mjs +29 -3
  71. package/dist/esm/cloudflare-output-verifier/identity.mjs +113 -24
  72. package/dist/esm/cloudflare-output-verifier/index.mjs +10 -0
  73. package/dist/esm/cloudflare-output-verifier/manifest.mjs +46 -0
  74. package/dist/esm/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  75. package/dist/esm/cloudflare-output-verifier/worker-bundles.mjs +11 -6
  76. package/dist/esm/config.mjs +3 -1
  77. package/dist/esm/deploy-output/npmAliases.mjs +92 -6
  78. package/dist/esm/deploy-output/plugin.mjs +24 -6
  79. package/dist/esm/deploy-output/public-assets.mjs +82 -0
  80. package/dist/esm/deploy-output/target.mjs +50 -3
  81. package/dist/esm/deploy-output/trace-files.mjs +47 -0
  82. package/dist/esm/module-federation-config/exposes.mjs +18 -0
  83. package/dist/esm/module-federation-config/index.mjs +4 -0
  84. package/dist/esm/module-federation-config/inspect.mjs +65 -0
  85. package/dist/esm/module-federation-config/object-literal.mjs +43 -0
  86. package/dist/esm/module-federation-config/syntax.mjs +101 -0
  87. package/dist/esm/module-federation-config/types.mjs +0 -0
  88. package/dist/esm/native-type-checker.mjs +124 -13
  89. package/dist/esm/policy-defaults.mjs +36 -30
  90. package/dist/esm/release-envelope/canonical.mjs +2 -9
  91. package/dist/esm/release-envelope/contract.mjs +21 -0
  92. package/dist/esm/release-envelope/framework-output.mjs +297 -106
  93. package/dist/esm/release-envelope/index.mjs +149 -38
  94. package/dist/esm/release-envelope/plugin.mjs +24 -15
  95. package/dist/esm/release-envelope/renderer-output-stamp.mjs +112 -0
  96. package/dist/esm/release-envelope/types.mjs +2 -3
  97. package/dist/esm/release-identity.mjs +8 -5
  98. package/dist/esm/renderer-build-identity.mjs +82 -0
  99. package/dist/esm/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  100. package/dist/esm/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  101. package/dist/esm/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  102. package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  103. package/dist/esm/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  104. package/dist/esm/templates/cloudflare-worker-route-data.mjs +1069 -0
  105. package/dist/esm/workspace-topology.mjs +21 -0
  106. package/dist/esm-node/backend-federation-build/codegen.mjs +29 -18
  107. package/dist/esm-node/backend-federation-build/config.mjs +74 -38
  108. package/dist/esm-node/backend-federation-build/emit.mjs +64 -23
  109. package/dist/esm-node/build-config/build-environment.mjs +26 -160
  110. package/dist/esm-node/build-config/internal-effect-discovery.mjs +103 -0
  111. package/dist/esm-node/build-config/public.mjs +1 -1
  112. package/dist/esm-node/build-config/remote-address.mjs +21 -0
  113. package/dist/esm-node/cloudflare/artifacts.mjs +8 -19
  114. package/dist/esm-node/cloudflare/delivery-unit.mjs +124 -45
  115. package/dist/esm-node/cloudflare/index.mjs +15 -5
  116. package/dist/esm-node/cloudflare/native-renderer.mjs +49 -0
  117. package/dist/esm-node/cloudflare/renderer-identity.mjs +58 -0
  118. package/dist/esm-node/cloudflare/worker-manifest.mjs +55 -14
  119. package/dist/esm-node/cloudflare/worker-options.mjs +11 -0
  120. package/dist/esm-node/cloudflare/wrangler-config.mjs +203 -12
  121. package/dist/esm-node/cloudflare-builder.mjs +301 -89
  122. package/dist/esm-node/cloudflare-output-contract.mjs +29 -3
  123. package/dist/esm-node/cloudflare-output-verifier/identity.mjs +113 -24
  124. package/dist/esm-node/cloudflare-output-verifier/index.mjs +10 -0
  125. package/dist/esm-node/cloudflare-output-verifier/manifest.mjs +46 -0
  126. package/dist/esm-node/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  127. package/dist/esm-node/cloudflare-output-verifier/worker-bundles.mjs +11 -6
  128. package/dist/esm-node/config.mjs +3 -1
  129. package/dist/esm-node/deploy-output/npmAliases.mjs +92 -6
  130. package/dist/esm-node/deploy-output/plugin.mjs +24 -6
  131. package/dist/esm-node/deploy-output/public-assets.mjs +83 -0
  132. package/dist/esm-node/deploy-output/target.mjs +50 -3
  133. package/dist/esm-node/deploy-output/trace-files.mjs +48 -0
  134. package/dist/esm-node/module-federation-config/exposes.mjs +19 -0
  135. package/dist/esm-node/module-federation-config/index.mjs +5 -0
  136. package/dist/esm-node/module-federation-config/inspect.mjs +66 -0
  137. package/dist/esm-node/module-federation-config/object-literal.mjs +44 -0
  138. package/dist/esm-node/module-federation-config/syntax.mjs +102 -0
  139. package/dist/esm-node/module-federation-config/types.mjs +1 -0
  140. package/dist/esm-node/native-type-checker.mjs +124 -13
  141. package/dist/esm-node/policy-defaults.mjs +36 -30
  142. package/dist/esm-node/release-envelope/canonical.mjs +2 -9
  143. package/dist/esm-node/release-envelope/contract.mjs +22 -0
  144. package/dist/esm-node/release-envelope/framework-output.mjs +297 -106
  145. package/dist/esm-node/release-envelope/index.mjs +149 -38
  146. package/dist/esm-node/release-envelope/plugin.mjs +24 -15
  147. package/dist/esm-node/release-envelope/renderer-output-stamp.mjs +113 -0
  148. package/dist/esm-node/release-envelope/types.mjs +2 -3
  149. package/dist/esm-node/release-identity.mjs +8 -5
  150. package/dist/esm-node/renderer-build-identity.mjs +83 -0
  151. package/dist/esm-node/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  152. package/dist/esm-node/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  153. package/dist/esm-node/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  154. package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  155. package/dist/esm-node/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  156. package/dist/esm-node/templates/cloudflare-worker-route-data.mjs +1069 -0
  157. package/dist/esm-node/workspace-topology.mjs +22 -0
  158. package/dist/types/backend-federation-build/codegen.d.ts +30 -0
  159. package/dist/types/backend-federation-build/config.d.ts +10 -57
  160. package/dist/types/backend-federation-build/emit.d.ts +12 -2
  161. package/dist/types/backend-federation-build/index.d.ts +1 -1
  162. package/dist/types/build-config/build-environment.d.ts +1 -9
  163. package/dist/types/build-config/internal-effect-discovery.d.ts +4 -0
  164. package/dist/types/build-config/public.d.ts +1 -1
  165. package/dist/types/build-config/remote-address.d.ts +3 -0
  166. package/dist/types/cloudflare/artifacts.d.ts +7 -6
  167. package/dist/types/cloudflare/constants.d.ts +1 -1
  168. package/dist/types/cloudflare/delivery-unit.d.ts +38 -7
  169. package/dist/types/cloudflare/native-renderer.d.ts +30 -0
  170. package/dist/types/cloudflare/renderer-identity.d.ts +25 -0
  171. package/dist/types/cloudflare/worker-manifest.d.ts +17 -2
  172. package/dist/types/cloudflare/worker-options.d.ts +20 -0
  173. package/dist/types/cloudflare/wrangler-config.d.ts +55 -2
  174. package/dist/types/cloudflare-builder.d.ts +51 -8
  175. package/dist/types/cloudflare-output-contract.d.ts +5 -3
  176. package/dist/types/cloudflare-output-verifier/identity.d.ts +7 -3
  177. package/dist/types/cloudflare-output-verifier/worker-bundles.d.ts +2 -2
  178. package/dist/types/config.d.ts +98 -13
  179. package/dist/types/deploy-output/plugin.d.ts +29 -9
  180. package/dist/types/deploy-output/public-assets.d.ts +51 -0
  181. package/dist/types/deploy-output/target.d.ts +28 -5
  182. package/dist/types/deploy-output/trace-files.d.ts +17 -0
  183. package/dist/types/module-federation-config/exposes.d.ts +11 -0
  184. package/dist/types/module-federation-config/index.d.ts +5 -0
  185. package/dist/types/module-federation-config/inspect.d.ts +2 -0
  186. package/dist/types/module-federation-config/object-literal.d.ts +4 -0
  187. package/dist/types/module-federation-config/syntax.d.ts +7 -0
  188. package/dist/types/module-federation-config/types.d.ts +20 -0
  189. package/dist/types/native-type-checker.d.ts +26 -3
  190. package/dist/types/policy-defaults.d.ts +12 -2
  191. package/dist/types/release-envelope/canonical.d.ts +5 -5
  192. package/dist/types/release-envelope/contract.d.mts +8 -0
  193. package/dist/types/release-envelope/framework-output.d.ts +23 -13
  194. package/dist/types/release-envelope/index.d.ts +17 -5
  195. package/dist/types/release-envelope/plugin.d.ts +10 -6
  196. package/dist/types/release-envelope/renderer-output-stamp.d.ts +34 -0
  197. package/dist/types/release-envelope/types.d.ts +42 -4
  198. package/dist/types/release-identity.d.ts +3 -2
  199. package/dist/types/renderer-build-identity.d.ts +30 -0
  200. package/dist/types/templates/cloudflare-worker-route-data.d.mts +14 -0
  201. package/dist/types/workspace-topology.d.ts +91 -0
  202. package/package.json +87 -27
  203. package/src/backend-federation-build/codegen.ts +28 -19
  204. package/src/backend-federation-build/config.ts +189 -123
  205. package/src/backend-federation-build/emit.ts +119 -38
  206. package/src/backend-federation-build/index.ts +4 -1
  207. package/src/build-config/build-environment.ts +59 -285
  208. package/src/build-config/internal-effect-discovery.ts +239 -0
  209. package/src/build-config/public.ts +0 -1
  210. package/src/build-config/remote-address.ts +33 -0
  211. package/src/cloudflare/artifacts.ts +22 -43
  212. package/src/cloudflare/delivery-unit.ts +283 -60
  213. package/src/cloudflare/index.ts +35 -19
  214. package/src/cloudflare/native-renderer.ts +117 -0
  215. package/src/cloudflare/renderer-identity.ts +171 -0
  216. package/src/cloudflare/worker-manifest.ts +108 -10
  217. package/src/cloudflare/worker-options.ts +60 -0
  218. package/src/cloudflare/wrangler-config.ts +390 -14
  219. package/src/cloudflare-builder.ts +543 -170
  220. package/src/cloudflare-output-contract.ts +45 -2
  221. package/src/cloudflare-output-verifier/identity.ts +138 -7
  222. package/src/cloudflare-output-verifier/index.ts +20 -0
  223. package/src/cloudflare-output-verifier/manifest.ts +77 -0
  224. package/src/cloudflare-output-verifier/mutation-policy.ts +11 -1
  225. package/src/cloudflare-output-verifier/worker-bundles.ts +27 -11
  226. package/src/config.ts +101 -19
  227. package/src/deploy-output/npmAliases.ts +141 -4
  228. package/src/deploy-output/plugin.ts +55 -18
  229. package/src/deploy-output/public-assets.ts +224 -0
  230. package/src/deploy-output/target.ts +97 -14
  231. package/src/deploy-output/trace-files.ts +80 -0
  232. package/src/module-federation-config/exposes.ts +46 -0
  233. package/src/module-federation-config/index.ts +22 -0
  234. package/src/module-federation-config/inspect.ts +157 -0
  235. package/src/module-federation-config/object-literal.ts +76 -0
  236. package/src/module-federation-config/syntax.ts +212 -0
  237. package/src/module-federation-config/types.ts +22 -0
  238. package/src/native-type-checker.ts +233 -20
  239. package/src/policy-defaults.ts +47 -17
  240. package/src/release-envelope/canonical.ts +6 -18
  241. package/src/release-envelope/contract.mjs +28 -0
  242. package/src/release-envelope/framework-output.ts +681 -156
  243. package/src/release-envelope/index.ts +315 -60
  244. package/src/release-envelope/plugin.ts +54 -38
  245. package/src/release-envelope/renderer-output-stamp.ts +210 -0
  246. package/src/release-envelope/types.ts +75 -6
  247. package/src/release-identity.ts +19 -2
  248. package/src/renderer-build-identity.ts +168 -0
  249. package/src/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  250. package/src/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  251. package/src/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  252. package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  253. package/src/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  254. package/src/templates/cloudflare-worker-route-data.mjs +1069 -0
  255. package/src/workspace-topology.ts +125 -0
  256. package/dist/cjs/templates/cloudflare-worker-fs-promises.mjs +0 -11
  257. package/dist/cjs/templates/cloudflare-worker-loadable-server.mjs +0 -281
  258. package/dist/cjs/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  259. package/dist/cjs/templates/cloudflare-worker-path.mjs +0 -98
  260. package/dist/esm/templates/cloudflare-worker-fs-promises.mjs +0 -11
  261. package/dist/esm/templates/cloudflare-worker-loadable-server.mjs +0 -281
  262. package/dist/esm/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  263. package/dist/esm/templates/cloudflare-worker-path.mjs +0 -98
  264. package/dist/esm-node/templates/cloudflare-worker-fs-promises.mjs +0 -11
  265. package/dist/esm-node/templates/cloudflare-worker-loadable-server.mjs +0 -281
  266. package/dist/esm-node/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  267. package/dist/esm-node/templates/cloudflare-worker-path.mjs +0 -98
  268. package/dist/types/templates/cloudflare-worker-fs-promises.d.mts +0 -5
  269. package/dist/types/templates/cloudflare-worker-loadable-server.d.mts +0 -48
  270. package/dist/types/templates/cloudflare-worker-mf-ssr-runtime-plugin.d.mts +0 -13
  271. package/dist/types/templates/cloudflare-worker-path.d.mts +0 -21
  272. package/src/templates/cloudflare-worker-fs-promises.mjs +0 -11
  273. package/src/templates/cloudflare-worker-loadable-server.mjs +0 -281
  274. package/src/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  275. package/src/templates/cloudflare-worker-path.mjs +0 -98
@@ -0,0 +1,210 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { isDeepStrictEqual } from 'node:util';
5
+ import {
6
+ assertUltramodernBuildArtifact,
7
+ formatBackendFederationValidationErrors,
8
+ stampUltramodernBuildArtifactIdentity,
9
+ ULTRAMODERN_BUILD_ARTIFACT_FILE,
10
+ ULTRAMODERN_BUILD_ARTIFACT_PATH,
11
+ type UltramodernBuildArtifact,
12
+ type UltramodernBuildUiSurface,
13
+ validateRendererIdentity,
14
+ validateRendererProfile,
15
+ validateRendererRouterBindings,
16
+ } from '@modern-js/backend-federation-contracts';
17
+
18
+ export type RendererBuildOutputContext = {
19
+ appDirectory: string;
20
+ distDirectory: string;
21
+ entrypoints: readonly { entryName: string; isMainEntry?: boolean }[];
22
+ };
23
+
24
+ export type FinalizedRendererBuildOutput = {
25
+ buildMarker: string;
26
+ sourceRevision: string;
27
+ ui: Pick<
28
+ UltramodernBuildUiSurface,
29
+ 'rendererIdentity' | 'rendererProfile' | 'routerBindings'
30
+ >;
31
+ };
32
+
33
+ export type RendererBuildOutputOptions = {
34
+ resolveRendererBuild: (
35
+ context: RendererBuildOutputContext,
36
+ ) => Promise<FinalizedRendererBuildOutput>;
37
+ /** Exact metadata producer selected by the owning renderer composition. */
38
+ rendererBuildPlugin: string;
39
+ };
40
+
41
+ export const validateFinalizedRendererBuild = (
42
+ output: FinalizedRendererBuildOutput,
43
+ context: RendererBuildOutputContext,
44
+ appId: string,
45
+ ) => {
46
+ const entryNames = context.entrypoints.map(entry => entry.entryName);
47
+ const primary =
48
+ context.entrypoints.find(entry => entry.isMainEntry) ??
49
+ context.entrypoints[0];
50
+ const errors = [
51
+ ...validateRendererIdentity(output.ui.rendererIdentity).errors,
52
+ ...validateRendererProfile(output.ui.rendererProfile).errors,
53
+ ...validateRendererRouterBindings(
54
+ output.ui.routerBindings,
55
+ entryNames,
56
+ 'routerBindings',
57
+ ).errors,
58
+ ];
59
+ if (errors.length)
60
+ throw new Error(formatBackendFederationValidationErrors(errors));
61
+ if (
62
+ !primary ||
63
+ new Set(entryNames).size !== entryNames.length ||
64
+ context.entrypoints.filter(entry => entry.isMainEntry).length > 1 ||
65
+ output.ui.rendererIdentity.entryName !== primary.entryName ||
66
+ output.ui.rendererIdentity.appId !== appId ||
67
+ output.ui.rendererIdentity.buildId !== output.buildMarker ||
68
+ output.ui.rendererIdentity.renderer !== output.ui.rendererProfile.renderer
69
+ )
70
+ throw new Error(
71
+ 'Finalized renderer output conflicts with the actual application entries and release identity.',
72
+ );
73
+ };
74
+
75
+ export const stampFinalizedRendererBuildArtifact = (
76
+ artifact: UltramodernBuildArtifact,
77
+ output: FinalizedRendererBuildOutput,
78
+ context: RendererBuildOutputContext,
79
+ ) => {
80
+ assertUltramodernBuildArtifact(artifact);
81
+ const ui = artifact.surfaces.ui;
82
+ if (!ui)
83
+ throw new Error(
84
+ 'Finalized renderer output requires a declared UI artifact',
85
+ );
86
+ validateFinalizedRendererBuild(output, context, artifact.deliveryUnit.appId);
87
+ const drifted = [
88
+ !isDeepStrictEqual(
89
+ { ...ui.rendererIdentity, buildId: output.ui.rendererIdentity.buildId },
90
+ output.ui.rendererIdentity,
91
+ ) &&
92
+ `rendererIdentity (captured entry ${ui.rendererIdentity.entryName}, built entry ${output.ui.rendererIdentity.entryName})`,
93
+ !isDeepStrictEqual(ui.rendererProfile, output.ui.rendererProfile) &&
94
+ 'rendererProfile',
95
+ !isDeepStrictEqual(ui.routerBindings, output.ui.routerBindings) &&
96
+ `routerBindings (captured entries ${Object.keys(ui.routerBindings).join(', ')}, built entries ${Object.keys(output.ui.routerBindings).join(', ')})`,
97
+ ].filter(Boolean);
98
+ if (drifted.length)
99
+ throw new Error(
100
+ `Finalized renderer output conflicts with the captured application profile or router bindings: ${drifted.join('; ')} differ from ${ULTRAMODERN_BUILD_ARTIFACT_PATH}. After changing modern.config entries, renderer or router, recapture it with \`ultramodern-create ultramodern sync-delivery-unit\`.`,
101
+ );
102
+ const identity = {
103
+ buildMarker: output.buildMarker,
104
+ sourceRevision: output.sourceRevision,
105
+ };
106
+ const stamped = stampUltramodernBuildArtifactIdentity(artifact, identity);
107
+ return stampUltramodernBuildArtifactIdentity(
108
+ {
109
+ ...stamped,
110
+ surfaces: {
111
+ ...stamped.surfaces,
112
+ ui: { ...stamped.surfaces.ui!, ...output.ui },
113
+ },
114
+ },
115
+ identity,
116
+ );
117
+ };
118
+
119
+ async function writeOwnedArtifact(file: string, bytes: string) {
120
+ await fs.mkdir(path.dirname(file), { recursive: true });
121
+ const temporary = `${file}.${randomUUID()}.tmp`;
122
+ let created = false;
123
+ try {
124
+ const handle = await fs.open(temporary, 'wx');
125
+ created = true;
126
+ try {
127
+ await handle.writeFile(bytes);
128
+ } finally {
129
+ await handle.close();
130
+ }
131
+ await fs.rename(temporary, file);
132
+ } finally {
133
+ if (created) await fs.rm(temporary, { force: true });
134
+ }
135
+ }
136
+
137
+ export async function emitRendererBuildArtifact(
138
+ context: RendererBuildOutputContext,
139
+ options: RendererBuildOutputOptions,
140
+ ) {
141
+ const source = path.join(
142
+ context.appDirectory,
143
+ ULTRAMODERN_BUILD_ARTIFACT_PATH,
144
+ );
145
+ const bytes = await fs.readFile(source).catch(error => {
146
+ if (error.code === 'ENOENT') return undefined;
147
+ throw error;
148
+ });
149
+ if (!bytes) {
150
+ const declaration = await fs
151
+ .stat(source.replace(/\.json$/u, '.ts'))
152
+ .catch(error => {
153
+ if (error.code === 'ENOENT') return undefined;
154
+ throw error;
155
+ });
156
+ if (declaration)
157
+ throw new Error(
158
+ 'Generated UI artifact declaration is missing its build carrier',
159
+ );
160
+ return undefined;
161
+ }
162
+ const artifact: unknown = JSON.parse(bytes.toString('utf8'));
163
+ assertUltramodernBuildArtifact(artifact);
164
+ const finalized = await options.resolveRendererBuild(context);
165
+ const stamped = stampFinalizedRendererBuildArtifact(
166
+ artifact,
167
+ finalized,
168
+ context,
169
+ );
170
+ const serialized = `${JSON.stringify(stamped, null, 2)}\n`;
171
+ for (const directory of [
172
+ context.distDirectory,
173
+ path.join(context.distDirectory, 'public'),
174
+ ])
175
+ await writeOwnedArtifact(
176
+ path.join(directory, ULTRAMODERN_BUILD_ARTIFACT_FILE),
177
+ serialized,
178
+ );
179
+ return stamped;
180
+ }
181
+
182
+ /** Stamp generated UI carriers after their actual compiler manifest is committed. */
183
+ export function rendererBuildArtifactStampPlugin(
184
+ options: RendererBuildOutputOptions,
185
+ ) {
186
+ if (
187
+ typeof options.rendererBuildPlugin !== 'string' ||
188
+ !options.rendererBuildPlugin ||
189
+ options.rendererBuildPlugin.trim() !== options.rendererBuildPlugin ||
190
+ typeof options.resolveRendererBuild !== 'function'
191
+ )
192
+ throw new Error(
193
+ 'Renderer artifact stamping requires its owning metadata producer',
194
+ );
195
+ return {
196
+ name: '@modern-js/renderer-build-artifact-stamp',
197
+ pre: [options.rendererBuildPlugin],
198
+ required: [options.rendererBuildPlugin],
199
+ setup(api: {
200
+ getAppContext(): RendererBuildOutputContext & { apiOnly?: boolean };
201
+ onAfterBuild(handler: () => Promise<void>): void;
202
+ }) {
203
+ api.onAfterBuild(async () => {
204
+ const context = api.getAppContext();
205
+ if (context.apiOnly) return;
206
+ await emitRendererBuildArtifact(context, options);
207
+ });
208
+ },
209
+ };
210
+ }
@@ -1,7 +1,24 @@
1
- export const MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION = 3 as const;
1
+ import type {
2
+ RendererIdentity,
3
+ RendererProfile,
4
+ RendererRouterBindings,
5
+ } from '@modern-js/backend-federation-contracts';
2
6
 
3
- export const MICROVERTICAL_RELEASE_ENVELOPE_KIND =
4
- 'ultramodern-target-microvertical-release-envelope' as const;
7
+ import {
8
+ MICROVERTICAL_RELEASE_ENVELOPE_KIND,
9
+ MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION,
10
+ SHELL_RELEASE_ENVELOPE_KIND,
11
+ } from './contract.mjs';
12
+
13
+ export {
14
+ MICROVERTICAL_RELEASE_ENVELOPE_KIND,
15
+ MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION,
16
+ SHELL_RELEASE_ENVELOPE_KIND,
17
+ };
18
+
19
+ export type ReleaseEnvelopeKind =
20
+ | typeof MICROVERTICAL_RELEASE_ENVELOPE_KIND
21
+ | typeof SHELL_RELEASE_ENVELOPE_KIND;
5
22
 
6
23
  export const MICROVERTICAL_RELEASE_TARGETS = ['node', 'cloudflare'] as const;
7
24
 
@@ -48,17 +65,35 @@ export type MicroVerticalReleaseSurfaces = {
48
65
  uiClient: string[];
49
66
  ssr: string[];
50
67
  apiBackend: string[];
51
- backendFederation: {
68
+ backendFederation?: {
52
69
  manifest: string;
53
70
  container: string;
54
71
  };
55
72
  };
56
73
 
74
+ export type MicroVerticalReleaseUi = {
75
+ rendererIdentity: RendererIdentity;
76
+ rendererProfile: RendererProfile;
77
+ routerBindings: RendererRouterBindings;
78
+ };
79
+
80
+ /** A Shell consumes remotes; it never publishes a backend federation pair. */
81
+ export type ShellReleaseSurfaces = {
82
+ uiClient: string[];
83
+ ssr: string[];
84
+ apiBackend: string[];
85
+ };
86
+
87
+ export type ReleaseSurfaces =
88
+ | MicroVerticalReleaseSurfaces
89
+ | ShellReleaseSurfaces;
90
+
57
91
  export type MicroVerticalReleaseEnvelopePayload = {
58
92
  schemaVersion: typeof MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION;
59
93
  kind: typeof MICROVERTICAL_RELEASE_ENVELOPE_KIND;
60
94
  target: MicroVerticalReleaseTarget;
61
95
  identity: MicroVerticalReleaseIdentity;
96
+ ui?: MicroVerticalReleaseUi;
62
97
  artifacts: MicroVerticalReleaseArtifact[];
63
98
  surfaces: MicroVerticalReleaseSurfaces;
64
99
  };
@@ -68,16 +103,50 @@ export type MicroVerticalReleaseEnvelope =
68
103
  envelopeDigest: string;
69
104
  };
70
105
 
106
+ export type ShellReleaseEnvelopePayload = {
107
+ schemaVersion: typeof MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION;
108
+ kind: typeof SHELL_RELEASE_ENVELOPE_KIND;
109
+ target: MicroVerticalReleaseTarget;
110
+ identity: MicroVerticalReleaseIdentity;
111
+ ui?: MicroVerticalReleaseUi;
112
+ artifacts: MicroVerticalReleaseArtifact[];
113
+ surfaces: ShellReleaseSurfaces;
114
+ };
115
+
116
+ export type ShellReleaseEnvelope = ShellReleaseEnvelopePayload & {
117
+ envelopeDigest: string;
118
+ };
119
+
120
+ export type ReleaseEnvelopePayload =
121
+ | MicroVerticalReleaseEnvelopePayload
122
+ | ShellReleaseEnvelopePayload;
123
+
124
+ export type ReleaseEnvelope =
125
+ | MicroVerticalReleaseEnvelope
126
+ | ShellReleaseEnvelope;
127
+
71
128
  export type CreateMicroVerticalReleaseEnvelopeInput = {
72
129
  artifactRoot: string;
73
130
  target: MicroVerticalReleaseTarget;
74
131
  identity: MicroVerticalReleaseIdentity;
132
+ ui?: MicroVerticalReleaseUi;
75
133
  artifacts: MicroVerticalReleaseArtifactInput[];
76
- surfaces: MicroVerticalReleaseSurfaces;
77
- };
134
+ } & (
135
+ | {
136
+ kind?: typeof MICROVERTICAL_RELEASE_ENVELOPE_KIND;
137
+ surfaces: MicroVerticalReleaseSurfaces;
138
+ }
139
+ | {
140
+ kind: typeof SHELL_RELEASE_ENVELOPE_KIND;
141
+ surfaces: ShellReleaseSurfaces;
142
+ }
143
+ );
78
144
 
79
145
  export type VerifyMicroVerticalReleaseEnvelopeOptions = {
80
146
  artifactRoot: string;
81
147
  logicalPathForArtifact?: (artifact: MicroVerticalReleaseArtifact) => string;
82
148
  expectedTarget?: MicroVerticalReleaseTarget;
149
+ expectedKind?: ReleaseEnvelopeKind;
150
+ expectedRendererIdentity?: RendererIdentity;
151
+ expectedRendererProfile?: RendererProfile;
83
152
  };
@@ -1,6 +1,9 @@
1
1
  import { execFileSync } from 'node:child_process';
2
2
  import { createHash } from 'node:crypto';
3
3
 
4
+ // The framework initializes this fixed reader before authored config observation.
5
+ // Its commands remain private; authored subprocess APIs keep their own guards.
6
+ const invokeGit = execFileSync;
4
7
  const BUILD_MARKER_NAMESPACE =
5
8
  'ultramodern-delivery-unit-release-build-marker:v1';
6
9
  const PROMOTABLE_SOURCE_REVISION_PATTERN = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u;
@@ -10,14 +13,24 @@ export type UltramodernReleaseIdentity = {
10
13
  sourceRevision: string;
11
14
  };
12
15
 
16
+ // Inherited GIT_* variables (git hooks export GIT_DIR and GIT_INDEX_FILE)
17
+ // would make git describe some other repository than `workspaceRoot`.
18
+ const workspaceGitEnv = (): NodeJS.ProcessEnv =>
19
+ Object.fromEntries(
20
+ Object.entries(process.env).filter(
21
+ ([name]) => !name.toUpperCase().startsWith('GIT_'),
22
+ ),
23
+ );
24
+
13
25
  const gitOutput = (
14
26
  workspaceRoot: string,
15
27
  args: string[],
16
28
  ): string | undefined => {
17
29
  try {
18
- return execFileSync('git', args, {
30
+ return invokeGit('git', args, {
19
31
  cwd: workspaceRoot,
20
32
  encoding: 'utf8',
33
+ env: workspaceGitEnv(),
21
34
  stdio: ['ignore', 'pipe', 'ignore'],
22
35
  }).trim();
23
36
  } catch {
@@ -44,10 +57,11 @@ export const createUltramodernReleaseBuildMarker = ({
44
57
  export const resolveUltramodernSourceRevision = (
45
58
  workspaceRoot = process.cwd(),
46
59
  configuredSourceRevision?: string,
60
+ environment: Readonly<NodeJS.ProcessEnv> = process.env,
47
61
  ): string => {
48
62
  const configuredArgumentValue = configuredSourceRevision?.trim();
49
63
  const configuredEnvironmentValue =
50
- process.env.ULTRAMODERN_SOURCE_REVISION?.trim();
64
+ environment.ULTRAMODERN_SOURCE_REVISION?.trim();
51
65
  const configuredArgument =
52
66
  configuredArgumentValue && configuredArgumentValue !== 'workspace'
53
67
  ? configuredArgumentValue
@@ -110,16 +124,19 @@ export const resolveUltramodernReleaseIdentity = ({
110
124
  sourceRevision: configuredSourceRevision,
111
125
  unitId,
112
126
  workspaceRoot,
127
+ environment,
113
128
  }: {
114
129
  generationBuildMarker: string;
115
130
  sourceRevision?: string;
116
131
  unitId: string;
117
132
  workspaceRoot?: string;
133
+ environment?: Readonly<NodeJS.ProcessEnv>;
118
134
  }): UltramodernReleaseIdentity => {
119
135
  const explicitSourceRevision = configuredSourceRevision?.trim();
120
136
  const sourceRevision = resolveUltramodernSourceRevision(
121
137
  workspaceRoot,
122
138
  explicitSourceRevision,
139
+ environment,
123
140
  );
124
141
  return {
125
142
  buildMarker:
@@ -0,0 +1,168 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import {
5
+ immutableRendererRouterBindings,
6
+ type RendererIdentity,
7
+ type RendererName,
8
+ type RendererProfile,
9
+ type RendererRouterBindings,
10
+ type RouterFramework,
11
+ validateRendererProfile,
12
+ validateRendererRouterBindings,
13
+ } from '@modern-js/backend-federation-contracts';
14
+ import { resolveUltramodernSourceRevision } from './release-identity';
15
+
16
+ export type RendererProfileKeyInput = RendererProfile & {
17
+ readonly dependencies?: Readonly<Record<string, string>>;
18
+ };
19
+
20
+ export interface RendererBuildIdentityOptions {
21
+ projectRoot: string;
22
+ renderer: RendererName;
23
+ profile: RendererProfileKeyInput;
24
+ entryNames: readonly string[];
25
+ mode: 'development' | 'production';
26
+ /** Delivery-unit app id; defaults to the application package name. */
27
+ appId?: string;
28
+ /** Externally authenticated source revision (delivery unit or CI). */
29
+ sourceRevision?: string;
30
+ /** Actual final entry ownership, supplied by the selected renderer composition. */
31
+ routerBindings: RendererRouterBindings;
32
+ /** Admitted router frameworks, supplied by the selected renderer owner. */
33
+ routerFrameworks?: readonly RouterFramework[];
34
+ }
35
+
36
+ export interface RendererBuildIdentities {
37
+ readonly identities: Readonly<Record<string, RendererIdentity>>;
38
+ readonly buildId: string;
39
+ readonly profileKey: string;
40
+ readonly sourceRevision: string;
41
+ readonly routerBindings: RendererRouterBindings;
42
+ }
43
+
44
+ const LOCKFILES = [
45
+ 'pnpm-lock.yaml',
46
+ 'package-lock.json',
47
+ 'yarn.lock',
48
+ 'bun.lock',
49
+ 'bun.lockb',
50
+ ];
51
+
52
+ /** Stable per process: one dev session or dirty build keeps one buildId. */
53
+ const processNonce = randomUUID();
54
+
55
+ const sha256 = (value: string | Uint8Array) =>
56
+ createHash('sha256').update(value).digest('hex');
57
+
58
+ /** Renderer, protocol and installed renderer/framework/compiler versions. */
59
+ export function rendererProfileKey(profile: RendererProfileKeyInput): string {
60
+ return sha256(
61
+ JSON.stringify([
62
+ profile.renderer,
63
+ profile.protocolVersion,
64
+ profile.compiler,
65
+ profile.hydration,
66
+ profile.router,
67
+ Object.entries(profile.dependencies ?? {}).sort(([left], [right]) =>
68
+ left < right ? -1 : left > right ? 1 : 0,
69
+ ),
70
+ ]),
71
+ );
72
+ }
73
+
74
+ /** Hash of the nearest lockfile, or an empty marker when there is none. */
75
+ function lockfileHash(projectRoot: string): string {
76
+ let directory = path.resolve(projectRoot);
77
+ for (;;) {
78
+ for (const name of LOCKFILES) {
79
+ try {
80
+ return sha256(fs.readFileSync(path.join(directory, name)));
81
+ } catch (error) {
82
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
83
+ }
84
+ }
85
+ const parent = path.dirname(directory);
86
+ if (parent === directory) return '';
87
+ directory = parent;
88
+ }
89
+ }
90
+
91
+ /** Resolve once per build before emitting entries; never rehashes sources. */
92
+ export function resolveRendererBuildIdentities(
93
+ options: RendererBuildIdentityOptions,
94
+ ): RendererBuildIdentities {
95
+ const profile: RendererProfile = {
96
+ renderer: options.profile.renderer,
97
+ protocolVersion: options.profile.protocolVersion,
98
+ compiler: options.profile.compiler,
99
+ hydration: options.profile.hydration,
100
+ router: options.profile.router,
101
+ };
102
+ const validation = validateRendererProfile(profile);
103
+ if (!validation.ok || options.renderer !== profile.renderer)
104
+ throw new Error(
105
+ `Renderer/profile identity mismatch: ${JSON.stringify(validation.errors)}.`,
106
+ );
107
+ const entries = [...options.entryNames].sort();
108
+ if (
109
+ entries.length === 0 ||
110
+ new Set(entries).size !== entries.length ||
111
+ entries.some(value => !value || value === '__proto__')
112
+ )
113
+ throw new Error(
114
+ 'Renderer build identity requires unique nonempty entry names.',
115
+ );
116
+ const routerValidation = validateRendererRouterBindings(
117
+ options.routerBindings,
118
+ entries,
119
+ 'routerBindings',
120
+ options.routerFrameworks,
121
+ );
122
+ if (!routerValidation.ok)
123
+ throw new Error(
124
+ `Invalid renderer router bindings: ${JSON.stringify(routerValidation.errors)}.`,
125
+ );
126
+ const projectRoot = path.resolve(options.projectRoot);
127
+ const appId =
128
+ options.appId ??
129
+ JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'))
130
+ .name;
131
+ if (typeof appId !== 'string' || !appId || appId.trim() !== appId)
132
+ throw new Error(
133
+ 'Renderer application identity requires a delivery artifact appId or a package name.',
134
+ );
135
+ const profileKey = rendererProfileKey(options.profile);
136
+ const sourceRevision = resolveUltramodernSourceRevision(
137
+ projectRoot,
138
+ options.sourceRevision,
139
+ );
140
+ const buildId = sha256(
141
+ JSON.stringify([
142
+ profileKey,
143
+ lockfileHash(projectRoot),
144
+ options.mode === 'development' || sourceRevision === 'workspace'
145
+ ? processNonce
146
+ : sourceRevision,
147
+ ]),
148
+ );
149
+ const identities = Object.fromEntries(
150
+ entries.map(entryName => [
151
+ entryName,
152
+ Object.freeze({
153
+ renderer: options.renderer,
154
+ appId,
155
+ entryName,
156
+ protocolVersion: 1 as const,
157
+ buildId,
158
+ }),
159
+ ]),
160
+ );
161
+ return Object.freeze({
162
+ identities: Object.freeze(identities),
163
+ buildId,
164
+ profileKey,
165
+ sourceRevision,
166
+ routerBindings: immutableRendererRouterBindings(options.routerBindings),
167
+ });
168
+ }
@@ -3,8 +3,12 @@ const MODERN_WORKER_MANIFEST = p_workerManifest;
3
3
  export const modernWorkerManifest = MODERN_WORKER_MANIFEST;
4
4
  const WORKER_MODULE_LOADERS = p_workerModuleLoaders;
5
5
  const workerModulePromises = new Map();
6
- const effectBffDispatcherPromises = new Map();
7
- const remoteJsonPromises = new Map();
6
+ // workerd forbids awaiting I/O (a `fetch()` Promise) that was started by a
7
+ // different request's execution context. This worker isolate outlives any
8
+ // single request, so this map may only ever hold settled, plain JSON values
9
+ // — never a live fetch Promise shared across requests. In-flight dedupe is
10
+ // instead scoped per request; see `createRemoteJsonFetchScope`.
11
+ const remoteJsonCache = new Map();
8
12
  const CORS_POLICY = MODERN_WORKER_MANIFEST.security?.cors || {};
9
13
  const ASSET_CORS_ENABLED = CORS_POLICY.assets !== false;
10
14
  const APP_CORS_ALLOWED_ORIGINS = (CORS_POLICY.allowedOrigins || []).map(
@@ -24,9 +28,6 @@ const ASSET_CORS_HEADERS = {
24
28
  'access-control-allow-origin': '*',
25
29
  };
26
30
 
27
- globalThis.__dirname ??= '/';
28
- globalThis.__filename ??= '/index.js';
29
-
30
31
  function getAllowedAppCorsOrigin(request) {
31
32
  if (APP_CORS_ALLOWED_ORIGINS.length === 0) {
32
33
  return null;
@@ -226,7 +227,11 @@ function withCloudflareSecurityHeaders(response, request) {
226
227
  }
227
228
 
228
229
  function createRenderableRequest(request) {
229
- if (request.method !== 'HEAD') {
230
+ if (
231
+ request.method !== 'HEAD' ||
232
+ request.headers.get('x-rsc-action') ||
233
+ request.headers.get('x-rsc-tree')
234
+ ) {
230
235
  return request;
231
236
  }
232
237
 
@@ -242,6 +247,9 @@ function finalizeResponseForRequest(response, request) {
242
247
 
243
248
  const headers = new Headers(securedResponse.headers);
244
249
  headers.delete('content-length');
250
+ // A HEAD response sends no body, so release the producer's stream instead of abandoning it:
251
+ // a stream nobody reads never finishes, and whatever waits for its end never runs.
252
+ securedResponse.body?.cancel().catch(() => undefined);
245
253
 
246
254
  return new Response(null, {
247
255
  headers,
@@ -20,7 +20,7 @@ function withAssetHeaders(response, request) {
20
20
  });
21
21
  }
22
22
 
23
- async function createCorsPreflightResponse(request, env) {
23
+ async function createCorsPreflightResponse(request, _env) {
24
24
  if (request.method !== 'OPTIONS') {
25
25
  return null;
26
26
  }
@@ -71,7 +71,15 @@ async function fetchAssetByPath(pathname, request, env) {
71
71
  const url = new URL(request.url);
72
72
  url.pathname = `/${pathname.replace(/^\/+/u, '')}`;
73
73
 
74
- return fetchAsset(new Request(url, request), env);
74
+ return fetchAsset(
75
+ new Request(url, {
76
+ method: 'GET',
77
+ headers: request.headers,
78
+ redirect: request.redirect,
79
+ signal: request.signal,
80
+ }),
81
+ env,
82
+ );
75
83
  }
76
84
 
77
85
  async function fetchAssetByPathFollowingRedirects(