@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
@@ -1,8 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import promises from "node:fs/promises";
3
3
  import node_path from "node:path";
4
+ import { formatBackendFederationValidationErrors, immutableRendererRouterBindings, validateRendererIdentity, validateRendererProfile, validateRendererProfileCompatibility, validateRendererRouterBindings } from "@modern-js/backend-federation-contracts";
4
5
  import { canonicalSerializeMicroVerticalReleaseEnvelope, digestMicroVerticalReleaseEnvelopePayload, releaseEnvelopePayload } from "./canonical.mjs";
5
- import { MICROVERTICAL_RELEASE_ENVELOPE_KIND, MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION, MICROVERTICAL_RELEASE_TARGETS } from "./types.mjs";
6
+ import { MICROVERTICAL_RELEASE_ENVELOPE_KIND, MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION, MICROVERTICAL_RELEASE_TARGETS, SHELL_RELEASE_ENVELOPE_KIND } from "./types.mjs";
6
7
  const SHA256_PATTERN = /^[a-f0-9]{64}$/u;
7
8
  const SOURCE_REVISION_PATTERN = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u;
8
9
  const TARGETS = new Set(MICROVERTICAL_RELEASE_TARGETS);
@@ -26,6 +27,10 @@ const assertTarget = (value, location = 'target')=>{
26
27
  if (!TARGETS.has(target)) throw new Error(`${location} must be one of: ${MICROVERTICAL_RELEASE_TARGETS.join(', ')}.`);
27
28
  return target;
28
29
  };
30
+ const assertEnvelopeKind = (value)=>{
31
+ if (value !== MICROVERTICAL_RELEASE_ENVELOPE_KIND && value !== SHELL_RELEASE_ENVELOPE_KIND) throw new Error(`envelope.kind must be "${MICROVERTICAL_RELEASE_ENVELOPE_KIND}" or "${SHELL_RELEASE_ENVELOPE_KIND}".`);
32
+ return value;
33
+ };
29
34
  const assertReleaseIdentity = (value, location = 'identity')=>{
30
35
  const identity = assertRecord(value, location);
31
36
  assertExactKeys(identity, [
@@ -184,7 +189,7 @@ const assertUniqueSortedArtifacts = (artifacts, location)=>{
184
189
  if (paths.some((logicalPath, index)=>logicalPath !== sorted[index])) throw new Error(`${location} must be sorted by logicalPath.`);
185
190
  };
186
191
  const assertSurfacePaths = (value, location)=>{
187
- if (!Array.isArray(value) || 0 === value.length) throw new Error(`${location} must contain at least one artifact path.`);
192
+ if (!Array.isArray(value)) throw new Error(`${location} must be an array of artifact paths.`);
188
193
  const paths = value.map((item, index)=>assertNormalizedLogicalPath(item, `${location}[${index}]`));
189
194
  const sorted = [
190
195
  ...paths
@@ -193,26 +198,51 @@ const assertSurfacePaths = (value, location)=>{
193
198
  if (new Set(paths).size !== paths.length) throw new Error(`${location} must not contain duplicate artifact paths.`);
194
199
  return paths;
195
200
  };
196
- const assertSurfaces = (value)=>{
201
+ const assertSurfaces = (value, kind)=>{
197
202
  const surfaces = assertRecord(value, 'surfaces');
198
203
  assertExactKeys(surfaces, [
199
204
  'uiClient',
200
205
  'ssr',
201
206
  'apiBackend',
202
- 'backendFederation'
207
+ ...kind !== SHELL_RELEASE_ENVELOPE_KIND && Object.hasOwn(surfaces, 'backendFederation') ? [
208
+ 'backendFederation'
209
+ ] : []
203
210
  ], 'surfaces');
204
- const backendFederation = assertRecord(surfaces.backendFederation, 'surfaces.backendFederation');
205
- assertExactKeys(backendFederation, [
211
+ const backendFederation = Object.hasOwn(surfaces, 'backendFederation') ? assertRecord(surfaces.backendFederation, 'surfaces.backendFederation') : void 0;
212
+ if (backendFederation) assertExactKeys(backendFederation, [
206
213
  'manifest',
207
214
  'container'
208
215
  ], 'surfaces.backendFederation');
216
+ const uiClient = assertSurfacePaths(surfaces.uiClient, 'surfaces.uiClient');
217
+ const ssr = assertSurfacePaths(surfaces.ssr, 'surfaces.ssr');
218
+ const apiBackend = assertSurfacePaths(surfaces.apiBackend, 'surfaces.apiBackend');
219
+ if (uiClient.length > 0 && 0 === ssr.length) throw new Error('surfaces.ssr must contain at least one artifact path when UI/client is declared.');
220
+ if (ssr.length > 0 && 0 === uiClient.length) throw new Error('surfaces.uiClient must contain at least one artifact path when SSR is declared.');
221
+ if (0 === apiBackend.length && (backendFederation || 0 === uiClient.length)) throw new Error('surfaces.apiBackend must contain at least one artifact path.');
222
+ if (kind === SHELL_RELEASE_ENVELOPE_KIND) {
223
+ if (0 === uiClient.length || 0 === ssr.length) throw new Error('Shell surfaces.uiClient and surfaces.ssr must each contain at least one artifact path.');
224
+ return {
225
+ kind,
226
+ surfaces: {
227
+ uiClient,
228
+ ssr,
229
+ apiBackend
230
+ }
231
+ };
232
+ }
233
+ if (apiBackend.length > 0 && !backendFederation) throw new Error('surfaces.backendFederation is required when API/backend is declared.');
209
234
  return {
210
- uiClient: assertSurfacePaths(surfaces.uiClient, 'surfaces.uiClient'),
211
- ssr: assertSurfacePaths(surfaces.ssr, 'surfaces.ssr'),
212
- apiBackend: assertSurfacePaths(surfaces.apiBackend, 'surfaces.apiBackend'),
213
- backendFederation: {
214
- manifest: assertNormalizedLogicalPath(backendFederation.manifest, 'surfaces.backendFederation.manifest'),
215
- container: assertNormalizedLogicalPath(backendFederation.container, 'surfaces.backendFederation.container')
235
+ kind,
236
+ surfaces: {
237
+ uiClient,
238
+ ssr,
239
+ apiBackend,
240
+ ...backendFederation ? {
241
+ backendFederation: {
242
+ manifest: assertNormalizedLogicalPath(backendFederation.manifest, 'surfaces.backendFederation.manifest'),
243
+ container: assertNormalizedLogicalPath(backendFederation.container, 'surfaces.backendFederation.container')
244
+ }
245
+ } : {}
216
246
  }
217
247
  };
218
248
  };
@@ -222,12 +252,14 @@ const assertSurfaceReferences = (artifacts, surfaces)=>{
222
252
  uiClient: surfaces.uiClient,
223
253
  ssr: surfaces.ssr,
224
254
  apiBackend: surfaces.apiBackend,
225
- 'backendFederation.manifest': [
226
- surfaces.backendFederation.manifest
227
- ],
228
- 'backendFederation.container': [
229
- surfaces.backendFederation.container
230
- ]
255
+ ...'backendFederation' in surfaces && surfaces.backendFederation ? {
256
+ 'backendFederation.manifest': [
257
+ surfaces.backendFederation.manifest
258
+ ],
259
+ 'backendFederation.container': [
260
+ surfaces.backendFederation.container
261
+ ]
262
+ } : {}
231
263
  }))for (const logicalPath of paths)if (!artifactPaths.has(logicalPath)) throw new Error(`surfaces.${surface} references unbound artifact "${logicalPath}".`);
232
264
  };
233
265
  const assertTargetSurfaceContract = (target, artifacts, surfaces)=>{
@@ -245,12 +277,14 @@ const assertTargetSurfaceContract = (target, artifacts, surfaces)=>{
245
277
  assertRuntime(surfaces.uiClient, 'browser', 'UI/client');
246
278
  assertRuntime(surfaces.ssr, 'node' === target ? 'nodejs' : 'workerd', 'SSR');
247
279
  assertRuntime(surfaces.apiBackend, 'node' === target ? 'nodejs' : 'workerd-effect', 'API/backend');
248
- assertRuntime([
249
- surfaces.backendFederation.manifest
250
- ], 'module-federation-manifest', 'backend federation manifest');
251
- assertRuntime([
252
- surfaces.backendFederation.container
253
- ], 'node' === target ? 'nodejs' : 'commonjs-module', 'backend federation container');
280
+ if ('backendFederation' in surfaces && surfaces.backendFederation) {
281
+ assertRuntime([
282
+ surfaces.backendFederation.manifest
283
+ ], 'module-federation-manifest', 'backend federation manifest');
284
+ assertRuntime([
285
+ surfaces.backendFederation.container
286
+ ], 'node' === target ? 'nodejs' : 'commonjs-module', 'backend federation container');
287
+ }
254
288
  };
255
289
  const deepFreeze = (value)=>{
256
290
  if (null !== value && 'object' == typeof value && !Object.isFrozen(value)) {
@@ -259,6 +293,51 @@ const deepFreeze = (value)=>{
259
293
  }
260
294
  return value;
261
295
  };
296
+ const assertReleaseUi = (value, identity, surfaces)=>{
297
+ if (0 === surfaces.uiClient.length) {
298
+ if (void 0 !== value) throw new Error('envelope.ui is forbidden for an API-only release.');
299
+ return;
300
+ }
301
+ const ui = assertRecord(value, 'envelope.ui');
302
+ assertExactKeys(ui, [
303
+ 'rendererIdentity',
304
+ 'rendererProfile',
305
+ 'routerBindings'
306
+ ], 'envelope.ui');
307
+ const routerBindings = assertRecord(ui.routerBindings, 'envelope.ui.routerBindings');
308
+ const errors = [
309
+ ...validateRendererIdentity(ui.rendererIdentity, 'envelope.ui.rendererIdentity').errors,
310
+ ...validateRendererProfile(ui.rendererProfile, 'envelope.ui.rendererProfile').errors,
311
+ ...validateRendererRouterBindings(routerBindings, Object.keys(routerBindings), 'envelope.ui.routerBindings').errors
312
+ ];
313
+ if (errors.length > 0) throw new Error(formatBackendFederationValidationErrors(errors));
314
+ const rendererIdentity = ui.rendererIdentity;
315
+ const rendererProfile = ui.rendererProfile;
316
+ if (!Object.hasOwn(routerBindings, rendererIdentity.entryName)) throw new Error('envelope.ui.routerBindings must include the primary rendererIdentity.entryName.');
317
+ if (rendererIdentity.buildId !== identity.buildMarker) throw new Error('envelope.ui.rendererIdentity.buildId must match identity.buildMarker.');
318
+ for (const field of [
319
+ 'renderer',
320
+ 'protocolVersion'
321
+ ])if (rendererIdentity[field] !== rendererProfile[field]) throw new Error(`envelope.ui.rendererProfile.${field} must match rendererIdentity.${field}.`);
322
+ return {
323
+ rendererIdentity: {
324
+ ...rendererIdentity
325
+ },
326
+ rendererProfile: {
327
+ ...rendererProfile,
328
+ compiler: {
329
+ ...rendererProfile.compiler
330
+ },
331
+ hydration: {
332
+ ...rendererProfile.hydration
333
+ },
334
+ router: {
335
+ ...rendererProfile.router
336
+ }
337
+ },
338
+ routerBindings: immutableRendererRouterBindings(routerBindings)
339
+ };
340
+ };
262
341
  const assertEnvelope = (value)=>{
263
342
  const envelope = assertRecord(value, 'envelope');
264
343
  assertExactKeys(envelope, [
@@ -266,26 +345,35 @@ const assertEnvelope = (value)=>{
266
345
  'kind',
267
346
  'target',
268
347
  'identity',
348
+ ...Object.hasOwn(envelope, 'ui') ? [
349
+ 'ui'
350
+ ] : [],
269
351
  'artifacts',
270
352
  'surfaces',
271
353
  'envelopeDigest'
272
354
  ], 'envelope');
273
355
  if (envelope.schemaVersion !== MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION) throw new Error(`envelope.schemaVersion must be ${MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION}.`);
274
- if (envelope.kind !== MICROVERTICAL_RELEASE_ENVELOPE_KIND) throw new Error(`envelope.kind must be "${MICROVERTICAL_RELEASE_ENVELOPE_KIND}".`);
356
+ const kind = assertEnvelopeKind(envelope.kind);
275
357
  if (!Array.isArray(envelope.artifacts) || 0 === envelope.artifacts.length) throw new Error('envelope.artifacts must contain at least one artifact.');
276
358
  const artifacts = envelope.artifacts.map((artifact, index)=>assertReleaseArtifact(artifact, `envelope.artifacts[${index}]`));
277
359
  assertUniqueSortedArtifacts(artifacts, 'envelope.artifacts');
278
360
  const target = assertTarget(envelope.target, 'envelope.target');
279
- const surfaces = assertSurfaces(envelope.surfaces);
361
+ const releaseSurfaces = assertSurfaces(envelope.surfaces, kind);
362
+ const { surfaces } = releaseSurfaces;
363
+ if (0 === surfaces.uiClient.length && Object.hasOwn(envelope, 'ui')) throw new Error('envelope.ui is forbidden for an API-only release.');
280
364
  assertSurfaceReferences(artifacts, surfaces);
281
365
  assertTargetSurfaceContract(target, artifacts, surfaces);
366
+ const identity = assertReleaseIdentity(envelope.identity, 'envelope.identity');
367
+ const ui = assertReleaseUi(envelope.ui, identity, surfaces);
282
368
  const parsed = {
369
+ ...releaseSurfaces,
283
370
  schemaVersion: MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION,
284
- kind: MICROVERTICAL_RELEASE_ENVELOPE_KIND,
285
371
  target,
286
- identity: assertReleaseIdentity(envelope.identity, 'envelope.identity'),
372
+ identity,
373
+ ...ui ? {
374
+ ui
375
+ } : {},
287
376
  artifacts,
288
- surfaces,
289
377
  envelopeDigest: assertNonEmptyString(envelope.envelopeDigest, 'envelope.envelopeDigest')
290
378
  };
291
379
  if (!SHA256_PATTERN.test(parsed.envelopeDigest)) throw new Error('envelope.envelopeDigest must be a lowercase SHA-256 digest.');
@@ -293,36 +381,59 @@ const assertEnvelope = (value)=>{
293
381
  if (parsed.envelopeDigest !== expectedDigest) throw new Error('envelope.envelopeDigest does not match canonical payload.');
294
382
  return parsed;
295
383
  };
296
- const createMicroVerticalReleaseEnvelope = async (input)=>{
384
+ async function createMicroVerticalReleaseEnvelope(input) {
297
385
  const artifactRoot = await promises.realpath(node_path.resolve(input.artifactRoot));
298
386
  const target = assertTarget(input.target);
299
387
  const identity = assertReleaseIdentity(input.identity);
388
+ const kind = assertEnvelopeKind(void 0 === input.kind ? MICROVERTICAL_RELEASE_ENVELOPE_KIND : input.kind);
300
389
  if (!Array.isArray(input.artifacts) || 0 === input.artifacts.length) throw new Error('artifacts must contain at least one artifact.');
301
390
  const inputs = input.artifacts.map((artifact, index)=>assertArtifactInput(artifact, `artifacts[${index}]`));
302
391
  const sortedInputs = [
303
392
  ...inputs
304
393
  ].sort((left, right)=>left.logicalPath.localeCompare(right.logicalPath));
305
394
  assertUniqueSortedArtifacts(sortedInputs, 'artifacts');
306
- const surfaces = assertSurfaces(input.surfaces);
395
+ const releaseSurfaces = assertSurfaces(input.surfaces, kind);
396
+ const { surfaces } = releaseSurfaces;
397
+ if (0 === surfaces.uiClient.length && Object.hasOwn(input, 'ui')) throw new Error('envelope.ui is forbidden for an API-only release.');
307
398
  assertSurfaceReferences(sortedInputs, surfaces);
308
399
  assertTargetSurfaceContract(target, sortedInputs, surfaces);
400
+ const ui = assertReleaseUi(input.ui, identity, surfaces);
309
401
  const artifacts = await Promise.all(sortedInputs.map((artifact)=>readFinalArtifact(artifactRoot, artifact)));
310
402
  assertTargetSurfaceContract(target, artifacts, surfaces);
311
403
  const payload = {
404
+ ...releaseSurfaces,
312
405
  schemaVersion: MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION,
313
- kind: MICROVERTICAL_RELEASE_ENVELOPE_KIND,
314
406
  target,
315
407
  identity,
316
- artifacts,
317
- surfaces
408
+ ...ui ? {
409
+ ui
410
+ } : {},
411
+ artifacts
318
412
  };
319
413
  return deepFreeze({
320
414
  ...payload,
321
415
  envelopeDigest: digestMicroVerticalReleaseEnvelopePayload(payload)
322
416
  });
323
- };
324
- const verifyMicroVerticalReleaseEnvelope = async (value, options)=>{
417
+ }
418
+ async function verifyMicroVerticalReleaseEnvelope(value, options) {
325
419
  const envelope = assertEnvelope(value);
420
+ if (void 0 !== options.expectedRendererProfile) {
421
+ const result = validateRendererProfileCompatibility(options.expectedRendererProfile, envelope.ui?.rendererProfile, 'envelope.ui.rendererProfile');
422
+ if (!result.ok) throw new Error(formatBackendFederationValidationErrors(result.errors));
423
+ }
424
+ if (void 0 !== options.expectedRendererIdentity) {
425
+ const expected = options.expectedRendererIdentity;
426
+ const result = validateRendererIdentity(expected, 'expectedRendererIdentity');
427
+ if (!result.ok) throw new Error(formatBackendFederationValidationErrors(result.errors));
428
+ for (const field of [
429
+ 'renderer',
430
+ 'protocolVersion',
431
+ 'appId',
432
+ 'entryName',
433
+ 'buildId'
434
+ ])if (envelope.ui?.rendererIdentity[field] !== expected[field]) throw new Error(`envelope.ui.rendererIdentity.${field} must match the consuming renderer identity.`);
435
+ }
436
+ if (void 0 !== options.expectedKind && envelope.kind !== options.expectedKind) throw new Error(`envelope.kind must be "${options.expectedKind}" for this release owner; received "${envelope.kind}".`);
326
437
  if (void 0 !== options.expectedTarget && envelope.target !== options.expectedTarget) throw new Error(`envelope.target must be "${options.expectedTarget}" for this staging target; received "${envelope.target}".`);
327
438
  const artifactRoot = await promises.realpath(node_path.resolve(options.artifactRoot));
328
439
  for (const artifact of envelope.artifacts){
@@ -331,5 +442,5 @@ const verifyMicroVerticalReleaseEnvelope = async (value, options)=>{
331
442
  if (!matches) throw new Error('file' === artifact.kind && 'file' === finalArtifact.kind ? `Artifact "${artifact.logicalPath}" digest does not match final artifact bytes.` : `Artifact "${artifact.logicalPath}" does not match its final filesystem binding.`);
332
443
  }
333
444
  return deepFreeze(envelope);
334
- };
335
- export { MICROVERTICAL_RELEASE_ENVELOPE_KIND, MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION, MICROVERTICAL_RELEASE_TARGETS, canonicalSerializeMicroVerticalReleaseEnvelope, createMicroVerticalReleaseEnvelope, verifyMicroVerticalReleaseEnvelope };
445
+ }
446
+ export { MICROVERTICAL_RELEASE_ENVELOPE_KIND, MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION, MICROVERTICAL_RELEASE_TARGETS, SHELL_RELEASE_ENVELOPE_KIND, canonicalSerializeMicroVerticalReleaseEnvelope, createMicroVerticalReleaseEnvelope, verifyMicroVerticalReleaseEnvelope };
@@ -1,48 +1,57 @@
1
1
  import node_path from "node:path";
2
+ import { NODE_PUBLIC_ASSET_SCOPE, normalizeDeclaredPublicAssets, resolveAddedDeclaredPublicAssetPaths } from "../deploy-output/public-assets.mjs";
3
+ import { getDeployingTarget } from "../deploy-output/target.mjs";
2
4
  import { emitFrameworkMicroVerticalReleaseEnvelope, emitNodeStagedReleaseEnvelope, verifyBuildOutputReleaseEnvelope, verifyNodeReleaseEnvelopeStaging } from "./framework-output.mjs";
3
- const resolveActiveDeployTarget = (api, resolveDeployTarget)=>{
4
- const { metaName } = api.getAppContext();
5
- const config = api.getNormalizedConfig();
6
- if ('modern-js' !== metaName && !config.deploy?.target && !process.env.MODERNJS_DEPLOY) return;
7
- return resolveDeployTarget(config);
8
- };
9
- const createUltramodernReleaseEnvelopePlugin = ({ resolveDeployTarget })=>({
5
+ const createUltramodernReleaseEnvelopePlugin = ({ resolveRendererProfile } = {})=>({
10
6
  name: '@modern-js/ultramodern-release-envelope',
11
7
  pre: [
12
8
  '@modern-js/backend-federation-build',
13
9
  '@modern-js/plugin-bff',
14
- '@modern-js/deploy-output-aliases'
10
+ '@modern-js/deploy-output-aliases',
11
+ '@modern-js/deploy-output-public-assets'
15
12
  ],
16
13
  post: [
17
14
  '@modern-js/plugin-deploy'
18
15
  ],
19
16
  setup (api) {
20
- const emitBuildEnvelope = async (target)=>{
21
- const { apiOnly, distDirectory } = api.getAppContext();
17
+ const emitBuildEnvelope = async (target, requirePromotable = true)=>{
18
+ const { apiOnly, appDirectory, distDirectory } = api.getAppContext();
22
19
  await emitFrameworkMicroVerticalReleaseEnvelope({
23
20
  apiOnly,
21
+ appDirectory,
24
22
  distDirectory,
25
- target
23
+ role: api.getNormalizedConfig().deploy?.releaseEnvelopeRole,
24
+ requirePromotable,
25
+ target,
26
+ ...!apiOnly && resolveRendererProfile ? {
27
+ expectedRendererProfile: resolveRendererProfile(api.getNormalizedConfig())
28
+ } : {}
26
29
  });
27
30
  };
28
31
  api.onAfterBuild(async ()=>{
29
- const configuredTarget = resolveDeployTarget(api.getNormalizedConfig());
32
+ const configuredTarget = api.getAppContext().deployTarget?.target;
30
33
  if ('node' !== configuredTarget && 'cloudflare' !== configuredTarget) return;
31
- await emitBuildEnvelope(configuredTarget);
34
+ await emitBuildEnvelope(configuredTarget, false);
32
35
  });
33
36
  api.onBeforeDeploy(async ()=>{
34
- const configuredTarget = resolveActiveDeployTarget(api, resolveDeployTarget);
37
+ const configuredTarget = getDeployingTarget(api.getAppContext());
35
38
  if ('node' !== configuredTarget) return;
36
39
  await emitBuildEnvelope(configuredTarget);
37
40
  const { distDirectory } = api.getAppContext();
38
41
  await verifyBuildOutputReleaseEnvelope(distDirectory, configuredTarget);
39
42
  });
40
43
  api.onAfterDeploy(async ()=>{
41
- const configuredTarget = resolveActiveDeployTarget(api, resolveDeployTarget);
44
+ const configuredTarget = getDeployingTarget(api.getAppContext());
42
45
  if ('node' !== configuredTarget) return;
43
46
  const { appDirectory, distDirectory } = api.getAppContext();
44
47
  const outputDirectory = node_path.join(appDirectory, '.output');
45
48
  const releaseEnvelope = await emitNodeStagedReleaseEnvelope({
49
+ declaredPublicAssets: await resolveAddedDeclaredPublicAssetPaths({
50
+ appDirectory,
51
+ assets: normalizeDeclaredPublicAssets(api.getNormalizedConfig().deploy?.node?.publicAssets, NODE_PUBLIC_ASSET_SCOPE),
52
+ generatedRoot: distDirectory,
53
+ scope: NODE_PUBLIC_ASSET_SCOPE
54
+ }),
46
55
  distDirectory,
47
56
  outputDirectory
48
57
  });
@@ -0,0 +1,112 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import promises from "node:fs/promises";
3
+ import node_path from "node:path";
4
+ import { isDeepStrictEqual } from "node:util";
5
+ import { ULTRAMODERN_BUILD_ARTIFACT_FILE, ULTRAMODERN_BUILD_ARTIFACT_PATH, assertUltramodernBuildArtifact, formatBackendFederationValidationErrors, stampUltramodernBuildArtifactIdentity, validateRendererIdentity, validateRendererProfile, validateRendererRouterBindings } from "@modern-js/backend-federation-contracts";
6
+ const validateFinalizedRendererBuild = (output, context, appId)=>{
7
+ const entryNames = context.entrypoints.map((entry)=>entry.entryName);
8
+ const primary = context.entrypoints.find((entry)=>entry.isMainEntry) ?? context.entrypoints[0];
9
+ const errors = [
10
+ ...validateRendererIdentity(output.ui.rendererIdentity).errors,
11
+ ...validateRendererProfile(output.ui.rendererProfile).errors,
12
+ ...validateRendererRouterBindings(output.ui.routerBindings, entryNames, 'routerBindings').errors
13
+ ];
14
+ if (errors.length) throw new Error(formatBackendFederationValidationErrors(errors));
15
+ if (!primary || new Set(entryNames).size !== entryNames.length || context.entrypoints.filter((entry)=>entry.isMainEntry).length > 1 || output.ui.rendererIdentity.entryName !== primary.entryName || output.ui.rendererIdentity.appId !== appId || output.ui.rendererIdentity.buildId !== output.buildMarker || output.ui.rendererIdentity.renderer !== output.ui.rendererProfile.renderer) throw new Error('Finalized renderer output conflicts with the actual application entries and release identity.');
16
+ };
17
+ const stampFinalizedRendererBuildArtifact = (artifact, output, context)=>{
18
+ assertUltramodernBuildArtifact(artifact);
19
+ const ui = artifact.surfaces.ui;
20
+ if (!ui) throw new Error('Finalized renderer output requires a declared UI artifact');
21
+ validateFinalizedRendererBuild(output, context, artifact.deliveryUnit.appId);
22
+ const drifted = [
23
+ !isDeepStrictEqual({
24
+ ...ui.rendererIdentity,
25
+ buildId: output.ui.rendererIdentity.buildId
26
+ }, output.ui.rendererIdentity) && `rendererIdentity (captured entry ${ui.rendererIdentity.entryName}, built entry ${output.ui.rendererIdentity.entryName})`,
27
+ !isDeepStrictEqual(ui.rendererProfile, output.ui.rendererProfile) && 'rendererProfile',
28
+ !isDeepStrictEqual(ui.routerBindings, output.ui.routerBindings) && `routerBindings (captured entries ${Object.keys(ui.routerBindings).join(', ')}, built entries ${Object.keys(output.ui.routerBindings).join(', ')})`
29
+ ].filter(Boolean);
30
+ if (drifted.length) throw new Error(`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\`.`);
31
+ const identity = {
32
+ buildMarker: output.buildMarker,
33
+ sourceRevision: output.sourceRevision
34
+ };
35
+ const stamped = stampUltramodernBuildArtifactIdentity(artifact, identity);
36
+ return stampUltramodernBuildArtifactIdentity({
37
+ ...stamped,
38
+ surfaces: {
39
+ ...stamped.surfaces,
40
+ ui: {
41
+ ...stamped.surfaces.ui,
42
+ ...output.ui
43
+ }
44
+ }
45
+ }, identity);
46
+ };
47
+ async function writeOwnedArtifact(file, bytes) {
48
+ await promises.mkdir(node_path.dirname(file), {
49
+ recursive: true
50
+ });
51
+ const temporary = `${file}.${randomUUID()}.tmp`;
52
+ let created = false;
53
+ try {
54
+ const handle = await promises.open(temporary, 'wx');
55
+ created = true;
56
+ try {
57
+ await handle.writeFile(bytes);
58
+ } finally{
59
+ await handle.close();
60
+ }
61
+ await promises.rename(temporary, file);
62
+ } finally{
63
+ if (created) await promises.rm(temporary, {
64
+ force: true
65
+ });
66
+ }
67
+ }
68
+ async function emitRendererBuildArtifact(context, options) {
69
+ const source = node_path.join(context.appDirectory, ULTRAMODERN_BUILD_ARTIFACT_PATH);
70
+ const bytes = await promises.readFile(source).catch((error)=>{
71
+ if ('ENOENT' === error.code) return;
72
+ throw error;
73
+ });
74
+ if (!bytes) {
75
+ const declaration = await promises.stat(source.replace(/\.json$/u, '.ts')).catch((error)=>{
76
+ if ('ENOENT' === error.code) return;
77
+ throw error;
78
+ });
79
+ if (declaration) throw new Error('Generated UI artifact declaration is missing its build carrier');
80
+ return;
81
+ }
82
+ const artifact = JSON.parse(bytes.toString('utf8'));
83
+ assertUltramodernBuildArtifact(artifact);
84
+ const finalized = await options.resolveRendererBuild(context);
85
+ const stamped = stampFinalizedRendererBuildArtifact(artifact, finalized, context);
86
+ const serialized = `${JSON.stringify(stamped, null, 2)}\n`;
87
+ for (const directory of [
88
+ context.distDirectory,
89
+ node_path.join(context.distDirectory, 'public')
90
+ ])await writeOwnedArtifact(node_path.join(directory, ULTRAMODERN_BUILD_ARTIFACT_FILE), serialized);
91
+ return stamped;
92
+ }
93
+ function rendererBuildArtifactStampPlugin(options) {
94
+ if ('string' != typeof options.rendererBuildPlugin || !options.rendererBuildPlugin || options.rendererBuildPlugin.trim() !== options.rendererBuildPlugin || 'function' != typeof options.resolveRendererBuild) throw new Error('Renderer artifact stamping requires its owning metadata producer');
95
+ return {
96
+ name: '@modern-js/renderer-build-artifact-stamp',
97
+ pre: [
98
+ options.rendererBuildPlugin
99
+ ],
100
+ required: [
101
+ options.rendererBuildPlugin
102
+ ],
103
+ setup (api) {
104
+ api.onAfterBuild(async ()=>{
105
+ const context = api.getAppContext();
106
+ if (context.apiOnly) return;
107
+ await emitRendererBuildArtifact(context, options);
108
+ });
109
+ }
110
+ };
111
+ }
112
+ export { emitRendererBuildArtifact, rendererBuildArtifactStampPlugin, stampFinalizedRendererBuildArtifact, validateFinalizedRendererBuild };
@@ -1,7 +1,6 @@
1
- const MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION = 3;
2
- const MICROVERTICAL_RELEASE_ENVELOPE_KIND = 'ultramodern-target-microvertical-release-envelope';
3
1
  const MICROVERTICAL_RELEASE_TARGETS = [
4
2
  'node',
5
3
  'cloudflare'
6
4
  ];
7
- export { MICROVERTICAL_RELEASE_ENVELOPE_KIND, MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION, MICROVERTICAL_RELEASE_TARGETS };
5
+ export { MICROVERTICAL_RELEASE_ENVELOPE_KIND, MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION, SHELL_RELEASE_ENVELOPE_KIND } from "./contract.mjs";
6
+ export { MICROVERTICAL_RELEASE_TARGETS };
@@ -1,12 +1,15 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { createHash } from "node:crypto";
3
+ const invokeGit = execFileSync;
3
4
  const BUILD_MARKER_NAMESPACE = 'ultramodern-delivery-unit-release-build-marker:v1';
4
5
  const PROMOTABLE_SOURCE_REVISION_PATTERN = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u;
6
+ const workspaceGitEnv = ()=>Object.fromEntries(Object.entries(process.env).filter(([name])=>!name.toUpperCase().startsWith('GIT_')));
5
7
  const gitOutput = (workspaceRoot, args)=>{
6
8
  try {
7
- return execFileSync('git', args, {
9
+ return invokeGit('git', args, {
8
10
  cwd: workspaceRoot,
9
11
  encoding: 'utf8',
12
+ env: workspaceGitEnv(),
10
13
  stdio: [
11
14
  'ignore',
12
15
  'pipe',
@@ -18,9 +21,9 @@ const gitOutput = (workspaceRoot, args)=>{
18
21
  }
19
22
  };
20
23
  const createUltramodernReleaseBuildMarker = ({ generationBuildMarker, sourceRevision, unitId })=>createHash('sha256').update(`${BUILD_MARKER_NAMESPACE}:${unitId}:${generationBuildMarker}:${sourceRevision}`).digest('hex').slice(0, 16);
21
- const resolveUltramodernSourceRevision = (workspaceRoot = process.cwd(), configuredSourceRevision)=>{
24
+ const resolveUltramodernSourceRevision = (workspaceRoot = process.cwd(), configuredSourceRevision, environment = process.env)=>{
22
25
  const configuredArgumentValue = configuredSourceRevision?.trim();
23
- const configuredEnvironmentValue = process.env.ULTRAMODERN_SOURCE_REVISION?.trim();
26
+ const configuredEnvironmentValue = environment.ULTRAMODERN_SOURCE_REVISION?.trim();
24
27
  const configuredArgument = configuredArgumentValue && 'workspace' !== configuredArgumentValue ? configuredArgumentValue : void 0;
25
28
  const configuredEnvironment = configuredEnvironmentValue && 'workspace' !== configuredEnvironmentValue ? configuredEnvironmentValue : void 0;
26
29
  if (configuredArgument && configuredEnvironment && configuredArgument !== configuredEnvironment) throw new Error(`Configured source revision ${configuredArgument} does not match environment source revision ${configuredEnvironment}.`);
@@ -48,9 +51,9 @@ const resolveUltramodernSourceRevision = (workspaceRoot = process.cwd(), configu
48
51
  if (configured && configured !== revision) throw new Error(`Configured source revision ${configured} does not match clean Git HEAD ${revision}.`);
49
52
  return revision;
50
53
  };
51
- const resolveUltramodernReleaseIdentity = ({ generationBuildMarker, sourceRevision: configuredSourceRevision, unitId, workspaceRoot })=>{
54
+ const resolveUltramodernReleaseIdentity = ({ generationBuildMarker, sourceRevision: configuredSourceRevision, unitId, workspaceRoot, environment })=>{
52
55
  const explicitSourceRevision = configuredSourceRevision?.trim();
53
- const sourceRevision = resolveUltramodernSourceRevision(workspaceRoot, explicitSourceRevision);
56
+ const sourceRevision = resolveUltramodernSourceRevision(workspaceRoot, explicitSourceRevision, environment);
54
57
  return {
55
58
  buildMarker: 'workspace' === sourceRevision ? generationBuildMarker : createUltramodernReleaseBuildMarker({
56
59
  generationBuildMarker,
@@ -0,0 +1,82 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import node_fs from "node:fs";
3
+ import node_path from "node:path";
4
+ import { immutableRendererRouterBindings, validateRendererProfile, validateRendererRouterBindings } from "@modern-js/backend-federation-contracts";
5
+ import { resolveUltramodernSourceRevision } from "./release-identity.mjs";
6
+ const LOCKFILES = [
7
+ 'pnpm-lock.yaml',
8
+ 'package-lock.json',
9
+ 'yarn.lock',
10
+ 'bun.lock',
11
+ 'bun.lockb'
12
+ ];
13
+ const processNonce = randomUUID();
14
+ const sha256 = (value)=>createHash('sha256').update(value).digest('hex');
15
+ function rendererProfileKey(profile) {
16
+ return sha256(JSON.stringify([
17
+ profile.renderer,
18
+ profile.protocolVersion,
19
+ profile.compiler,
20
+ profile.hydration,
21
+ profile.router,
22
+ Object.entries(profile.dependencies ?? {}).sort(([left], [right])=>left < right ? -1 : left > right ? 1 : 0)
23
+ ]));
24
+ }
25
+ function lockfileHash(projectRoot) {
26
+ let directory = node_path.resolve(projectRoot);
27
+ for(;;){
28
+ for (const name of LOCKFILES)try {
29
+ return sha256(node_fs.readFileSync(node_path.join(directory, name)));
30
+ } catch (error) {
31
+ if ('ENOENT' !== error.code) throw error;
32
+ }
33
+ const parent = node_path.dirname(directory);
34
+ if (parent === directory) return '';
35
+ directory = parent;
36
+ }
37
+ }
38
+ function resolveRendererBuildIdentities(options) {
39
+ const profile = {
40
+ renderer: options.profile.renderer,
41
+ protocolVersion: options.profile.protocolVersion,
42
+ compiler: options.profile.compiler,
43
+ hydration: options.profile.hydration,
44
+ router: options.profile.router
45
+ };
46
+ const validation = validateRendererProfile(profile);
47
+ if (!validation.ok || options.renderer !== profile.renderer) throw new Error(`Renderer/profile identity mismatch: ${JSON.stringify(validation.errors)}.`);
48
+ const entries = [
49
+ ...options.entryNames
50
+ ].sort();
51
+ if (0 === entries.length || new Set(entries).size !== entries.length || entries.some((value)=>!value || '__proto__' === value)) throw new Error('Renderer build identity requires unique nonempty entry names.');
52
+ const routerValidation = validateRendererRouterBindings(options.routerBindings, entries, 'routerBindings', options.routerFrameworks);
53
+ if (!routerValidation.ok) throw new Error(`Invalid renderer router bindings: ${JSON.stringify(routerValidation.errors)}.`);
54
+ const projectRoot = node_path.resolve(options.projectRoot);
55
+ const appId = options.appId ?? JSON.parse(node_fs.readFileSync(node_path.join(projectRoot, 'package.json'), 'utf8')).name;
56
+ if ('string' != typeof appId || !appId || appId.trim() !== appId) throw new Error('Renderer application identity requires a delivery artifact appId or a package name.');
57
+ const profileKey = rendererProfileKey(options.profile);
58
+ const sourceRevision = resolveUltramodernSourceRevision(projectRoot, options.sourceRevision);
59
+ const buildId = sha256(JSON.stringify([
60
+ profileKey,
61
+ lockfileHash(projectRoot),
62
+ 'development' === options.mode || 'workspace' === sourceRevision ? processNonce : sourceRevision
63
+ ]));
64
+ const identities = Object.fromEntries(entries.map((entryName)=>[
65
+ entryName,
66
+ Object.freeze({
67
+ renderer: options.renderer,
68
+ appId,
69
+ entryName,
70
+ protocolVersion: 1,
71
+ buildId
72
+ })
73
+ ]));
74
+ return Object.freeze({
75
+ identities: Object.freeze(identities),
76
+ buildId,
77
+ profileKey,
78
+ sourceRevision,
79
+ routerBindings: immutableRendererRouterBindings(options.routerBindings)
80
+ });
81
+ }
82
+ export { rendererProfileKey, resolveRendererBuildIdentities };