@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
@@ -1,14 +1,34 @@
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 { BACKEND_FEDERATION_MANIFEST_FILE, BACKEND_FEDERATION_REMOTE_ENTRY_FILE, ULTRAMODERN_BUILD_ARTIFACT_FILE, isUltramodernBuildArtifact } from "@modern-js/backend-federation-contracts";
4
+ import { isDeepStrictEqual } from "node:util";
5
+ import { BACKEND_FEDERATION_MANIFEST_FILE, BACKEND_FEDERATION_REMOTE_ENTRY_FILE, ULTRAMODERN_BUILD_ARTIFACT_FILE, ULTRAMODERN_BUILD_ARTIFACT_PATH, assertRendererProfileCompatibility, isUltramodernBuildArtifact } from "@modern-js/backend-federation-contracts";
6
+ import { readBuildIdentity } from "../backend-federation-build/config.mjs";
7
+ import { resolveTopologyDeliveryUnit, resolveTopologyReleaseRole, resolveWorkerDeliveryUnitStamp } from "../cloudflare/delivery-unit.mjs";
5
8
  import { canonicalSerializeMicroVerticalReleaseEnvelope, createMicroVerticalReleaseEnvelope, verifyMicroVerticalReleaseEnvelope } from "./index.mjs";
9
+ import { SHELL_RELEASE_ENVELOPE_KIND } from "./types.mjs";
6
10
  const MICROVERTICAL_RELEASE_ENVELOPE_PATH = 'release/microvertical-release-envelope.json';
7
11
  const MICROVERTICAL_RELEASE_IDENTITY_CARRIERS_PATH = 'release/microvertical-release-identity-carriers.json';
8
12
  const MICROVERTICAL_RELEASE_IDENTITY_CARRIERS_KIND = 'ultramodern-release-identity-carriers';
9
13
  const MICROVERTICAL_RELEASE_IDENTITY_CARRIERS_SCHEMA_VERSION = 1;
10
14
  const COMPILED_MODULE_PATTERN = /\.(?:c|m)?js$/u;
11
15
  const EFFECT_BFF_WORKER_PATTERN = /^worker\/__modern_bff_effect\.(?:c|m)?js$/u;
16
+ const EFFECT_BFF_WORKER_SUPPORT_PATTERN = /^worker\/__modern_worker_(?:runtime|shared)_*\.(?:c|m)?js$/u;
17
+ const isEffectBffWorkerArtifact = (logicalPath)=>EFFECT_BFF_WORKER_PATTERN.test(logicalPath) || EFFECT_BFF_WORKER_SUPPORT_PATTERN.test(logicalPath);
18
+ const effectBffWorkerArtifacts = (files)=>files.some((logicalPath)=>EFFECT_BFF_WORKER_PATTERN.test(logicalPath)) ? files.filter(isEffectBffWorkerArtifact) : [];
19
+ const CRAWLER_POLICY_PATH = 'public/robots.txt';
20
+ const isPublicMetadataPath = (logicalPath)=>'public/_headers' === logicalPath || /^public\/\.well-known\/[^/]+\.json$/u.test(logicalPath);
21
+ const PUBLIC_BUILD_ARTIFACT_PATH = `public/${ULTRAMODERN_BUILD_ARTIFACT_FILE}`;
22
+ const DECLARED_PUBLIC_ASSET_RUNTIME = 'public-asset';
23
+ const resolveDeclaredPublicAssets = (files, declaredPublicAssets, target)=>{
24
+ const staged = new Set(files);
25
+ const declared = new Set();
26
+ for (const logicalPath of declaredPublicAssets){
27
+ if (!logicalPath.startsWith('public/') || !staged.has(logicalPath)) throw new Error(`[ultramodern-release-envelope] final ${target} staging has no declared public asset file ${JSON.stringify(logicalPath)}.`);
28
+ declared.add(logicalPath);
29
+ }
30
+ return declared;
31
+ };
12
32
  const isRecord = (value)=>'object' == typeof value && null !== value && !Array.isArray(value);
13
33
  const flattenStrings = (value)=>'string' == typeof value ? [
14
34
  value
@@ -22,7 +42,12 @@ const collectNestedStrings = (value, key)=>{
22
42
  ]);
23
43
  };
24
44
  const emptyProducerRouteModules = async (distDirectory, files, manifest, manifestLogicalPath)=>{
25
- if (!isRecord(manifest) || !Array.isArray(manifest.exposes) || 0 !== manifest.exposes.length || !Array.isArray(manifest.remotes) || 0 !== manifest.remotes.length || !isRecord(manifest.metaData) || !isRecord(manifest.metaData.remoteEntry) || '' !== manifest.metaData.remoteEntry.name || '' !== manifest.metaData.remoteEntry.path || 'global' !== manifest.metaData.remoteEntry.type) return [];
45
+ if (!isRecord(manifest) || !Array.isArray(manifest.exposes) || 0 !== manifest.exposes.length || !Array.isArray(manifest.remotes) || manifest.remotes.some((remote)=>!isRecord(remote) || [
46
+ 'federationContainerName',
47
+ 'moduleName',
48
+ 'alias',
49
+ 'entry'
50
+ ].some((key)=>'string' != typeof remote[key] || 0 === remote[key].trim().length)) || !isRecord(manifest.metaData) || !isRecord(manifest.metaData.remoteEntry) || '' !== manifest.metaData.remoteEntry.name || '' !== manifest.metaData.remoteEntry.path || 'global' !== manifest.metaData.remoteEntry.type) return [];
26
51
  const publicPath = manifest.metaData.publicPath;
27
52
  const safeReference = (value)=>'string' == typeof value && value.length > 0 && !/[\\\\%?#\s]/u.test(value) && !value.split('/').some((segment)=>'.' === segment || '..' === segment);
28
53
  if (!safeReference(publicPath)) return [];
@@ -123,11 +148,14 @@ const writeReleaseEnvelope = async (artifactRoot, envelope)=>{
123
148
  if (!envelopePath) throw new Error('[ultramodern-release-envelope] could not resolve release envelope path.');
124
149
  await promises.writeFile(envelopePath, `${canonicalSerializeMicroVerticalReleaseEnvelope(envelope)}\n`);
125
150
  };
126
- const readBuildArtifact = async (distDirectory)=>{
151
+ const readBuildArtifact = async (distDirectory, requirePromotable = true)=>{
127
152
  const artifactPath = node_path.join(distDirectory, ULTRAMODERN_BUILD_ARTIFACT_FILE);
128
153
  const artifact = await readJson(artifactPath);
129
154
  if (!isUltramodernBuildArtifact(artifact)) throw new Error(`[ultramodern-release-envelope] ${ULTRAMODERN_BUILD_ARTIFACT_FILE} is missing or invalid.`);
130
- if ('workspace' === artifact.deliveryUnit.sourceRevision) throw new Error('[ultramodern-release-envelope] sourceRevision "workspace" cannot produce a promotable full-stack envelope.');
155
+ if ('workspace' === artifact.deliveryUnit.sourceRevision) {
156
+ if (!requirePromotable) return;
157
+ throw new Error('[ultramodern-release-envelope] sourceRevision "workspace" cannot produce a promotable envelope.');
158
+ }
131
159
  return artifact;
132
160
  };
133
161
  const assertIdentityBlock = (value, identity, location, includeReleaseVersion)=>{
@@ -196,7 +224,7 @@ const releaseArtifact = (logicalPath, runtime)=>({
196
224
  const writeReleaseIdentityCarrierMetadata = async (artifactRoot, identity, carrierPaths)=>{
197
225
  const surfacesByPath = new Map();
198
226
  for (const [surface, logicalPaths] of Object.entries(carrierPaths)){
199
- if (0 === logicalPaths.length) throw new Error(`[ultramodern-release-envelope] ${surface} has no declared release-identity carrier artifact.`);
227
+ if (0 === logicalPaths.length && 'ssr' !== surface && 'uiClient' !== surface) throw new Error(`[ultramodern-release-envelope] ${surface} has no declared release-identity carrier artifact.`);
200
228
  for (const logicalPath of logicalPaths){
201
229
  const surfaces = surfacesByPath.get(logicalPath) ?? new Set();
202
230
  surfaces.add(surface);
@@ -236,12 +264,14 @@ const writeReleaseIdentityCarrierMetadata = async (artifactRoot, identity, carri
236
264
  }, null, 2)}\n`);
237
265
  return releaseArtifact(MICROVERTICAL_RELEASE_IDENTITY_CARRIERS_PATH, 'release-identity-metadata');
238
266
  };
239
- const createReleaseArtifactInputs = async (distDirectory, identity, target)=>{
267
+ const createReleaseArtifactInputs = async (distDirectory, identity, target, apiOnly, uiOnly, shellConsumer)=>{
268
+ const backendProducer = !uiOnly && !shellConsumer;
240
269
  const files = await collectFiles(distDirectory);
241
- if ('node' === target && files.some((logicalPath)=>EFFECT_BFF_WORKER_PATTERN.test(logicalPath))) throw new Error('[ultramodern-release-envelope] Node target conflicts with a Cloudflare Effect API/BFF worker artifact; resolve the canonical deploy target instead of emitting a Node envelope for Cloudflare output.');
270
+ const effectWorkerPaths = effectBffWorkerArtifacts(files);
271
+ if ('node' === target && files.some(isEffectBffWorkerArtifact)) throw new Error('[ultramodern-release-envelope] Node target conflicts with a Cloudflare Effect API/BFF worker artifact; resolve the canonical deploy target instead of emitting a Node envelope for Cloudflare output.');
242
272
  const uiClientPaths = files.filter((logicalPath)=>{
243
273
  const topLevel = logicalPath.split('/')[0];
244
- return 'static' === topLevel || 'html' === topLevel || 'public' === topLevel || [
274
+ return 'static' === topLevel || 'html' === topLevel || 'public' === topLevel && logicalPath !== CRAWLER_POLICY_PATH && logicalPath !== PUBLIC_BUILD_ARTIFACT_PATH && !isPublicMetadataPath(logicalPath) || [
245
275
  'index.html',
246
276
  'mf-manifest.json',
247
277
  'mf-stats.json',
@@ -250,16 +280,19 @@ const createReleaseArtifactInputs = async (distDirectory, identity, target)=>{
250
280
  'loadable-stats.json'
251
281
  ].includes(logicalPath);
252
282
  });
253
- const ssrPaths = 'node' === target ? files.filter((logicalPath)=>logicalPath.startsWith('bundles/') && COMPILED_MODULE_PATTERN.test(logicalPath)) : files.filter((logicalPath)=>logicalPath.startsWith('worker/') && COMPILED_MODULE_PATTERN.test(logicalPath) && !EFFECT_BFF_WORKER_PATTERN.test(logicalPath));
283
+ const ssrPaths = 'node' === target ? files.filter((logicalPath)=>logicalPath.startsWith('bundles/') && COMPILED_MODULE_PATTERN.test(logicalPath)) : files.filter((logicalPath)=>logicalPath.startsWith('worker/') && COMPILED_MODULE_PATTERN.test(logicalPath) && !effectWorkerPaths.includes(logicalPath));
254
284
  const nodeApiEntryPaths = files.filter((logicalPath)=>logicalPath.startsWith('api/') && COMPILED_MODULE_PATTERN.test(logicalPath));
255
- const apiBackendPaths = 'node' === target ? files.filter((logicalPath)=>(logicalPath.startsWith('api/') || logicalPath.startsWith('shared/')) && COMPILED_MODULE_PATTERN.test(logicalPath)) : files.filter((logicalPath)=>EFFECT_BFF_WORKER_PATTERN.test(logicalPath));
256
- if (0 === uiClientPaths.length) throw new Error(`[ultramodern-release-envelope] ${target} full-stack MicroVertical has no UI/client artifacts.`);
257
- if (0 === ssrPaths.length) throw new Error(`[ultramodern-release-envelope] ${target} full-stack MicroVertical has no ${'node' === target ? 'Node' : 'workerd'} SSR artifacts.`);
258
- if ('node' === target && 0 === nodeApiEntryPaths.length || 0 === apiBackendPaths.length) throw new Error(`[ultramodern-release-envelope] ${target} full-stack MicroVertical has no actual ${'node' === target ? 'compiled Node Effect API artifact' : 'Effect API/BFF worker artifact'}.`);
259
- const clientExecutionPaths = await manifestReferencedClientModules(distDirectory, files);
260
- if (0 === clientExecutionPaths.length) throw new Error('[ultramodern-release-envelope] UI/client manifest references no compiled execution module.');
261
- const ssrExecutionPaths = await routeReferencedSsrModules(distDirectory, files, target);
262
- if (0 === ssrExecutionPaths.length) throw new Error(`[ultramodern-release-envelope] route manifest references no emitted ${'node' === target ? 'Node' : 'Cloudflare'} SSR execution module.`);
285
+ const apiBackendPaths = 'node' === target ? files.filter((logicalPath)=>(logicalPath.startsWith('api/') || logicalPath.startsWith('shared/')) && COMPILED_MODULE_PATTERN.test(logicalPath)) : effectWorkerPaths;
286
+ if (backendProducer && 'cloudflare' === target && 0 === effectWorkerPaths.length) throw new Error('[ultramodern-release-envelope] cloudflare MicroVertical has no actual Effect API/BFF worker artifact.');
287
+ if (apiOnly && (uiClientPaths.length > 0 || ssrPaths.length > 0)) throw new Error('[ultramodern-release-envelope] API-only MicroVertical emitted an undeclared UI/client or SSR surface.');
288
+ if (!apiOnly && 0 === uiClientPaths.length) throw new Error(`[ultramodern-release-envelope] ${target} full-stack MicroVertical has no UI/client artifacts.`);
289
+ if (!apiOnly && 0 === ssrPaths.length) throw new Error(`[ultramodern-release-envelope] ${target} full-stack MicroVertical has no ${'node' === target ? 'Node' : 'workerd'} SSR artifacts.`);
290
+ if (uiOnly && (apiBackendPaths.length > 0 || nodeApiEntryPaths.length > 0)) throw new Error('[ultramodern-release-envelope] UI-only application emitted an undeclared API/backend artifact.');
291
+ if (backendProducer && ('node' === target && 0 === nodeApiEntryPaths.length || 0 === apiBackendPaths.length)) throw new Error(`[ultramodern-release-envelope] ${target} MicroVertical has no actual ${'node' === target ? 'compiled Node Effect API artifact' : 'Effect API/BFF worker artifact'}.`);
292
+ const clientExecutionPaths = apiOnly ? [] : await manifestReferencedClientModules(distDirectory, files);
293
+ if (!apiOnly && 0 === clientExecutionPaths.length) throw new Error('[ultramodern-release-envelope] UI/client manifest references no compiled execution module.');
294
+ const ssrExecutionPaths = apiOnly ? [] : await routeReferencedSsrModules(distDirectory, files, target);
295
+ if (!apiOnly && 0 === ssrExecutionPaths.length) throw new Error(`[ultramodern-release-envelope] route manifest references no emitted ${'node' === target ? 'Node' : 'Cloudflare'} SSR execution module.`);
263
296
  const runtimeByPath = new Map();
264
297
  const add = (paths, runtime)=>{
265
298
  for (const logicalPath of paths){
@@ -269,14 +302,25 @@ const createReleaseArtifactInputs = async (distDirectory, identity, target)=>{
269
302
  }
270
303
  };
271
304
  add(uiClientPaths, 'browser');
305
+ add(files.filter((logicalPath)=>logicalPath === CRAWLER_POLICY_PATH), 'crawler-policy');
306
+ add(files.filter(isPublicMetadataPath), 'public-metadata');
272
307
  add(ssrPaths, 'node' === target ? 'nodejs' : 'workerd');
273
308
  add(apiBackendPaths, 'node' === target ? 'nodejs' : 'workerd-effect');
274
309
  add([
275
- BACKEND_FEDERATION_MANIFEST_FILE
276
- ], 'module-federation-manifest');
277
- add([
278
- BACKEND_FEDERATION_REMOTE_ENTRY_FILE
279
- ], 'node' === target ? 'nodejs' : 'commonjs-module');
310
+ ULTRAMODERN_BUILD_ARTIFACT_FILE
311
+ ], 'release-identity-metadata');
312
+ add(files.filter((logicalPath)=>logicalPath === PUBLIC_BUILD_ARTIFACT_PATH), 'release-identity-metadata');
313
+ if (backendProducer) {
314
+ add([
315
+ BACKEND_FEDERATION_MANIFEST_FILE
316
+ ], 'module-federation-manifest');
317
+ add([
318
+ BACKEND_FEDERATION_REMOTE_ENTRY_FILE
319
+ ], 'node' === target ? 'nodejs' : 'commonjs-module');
320
+ }
321
+ if (shellConsumer) {
322
+ for (const logicalPath of files)if (!runtimeByPath.has(logicalPath)) runtimeByPath.set(logicalPath, `${target}-deployment`);
323
+ }
280
324
  const surfaces = {
281
325
  uiClient: [
282
326
  ...uiClientPaths
@@ -287,20 +331,26 @@ const createReleaseArtifactInputs = async (distDirectory, identity, target)=>{
287
331
  apiBackend: [
288
332
  ...apiBackendPaths
289
333
  ].sort((left, right)=>left.localeCompare(right)),
290
- backendFederation: {
291
- manifest: BACKEND_FEDERATION_MANIFEST_FILE,
292
- container: BACKEND_FEDERATION_REMOTE_ENTRY_FILE
293
- }
334
+ ...backendProducer ? {
335
+ backendFederation: {
336
+ manifest: BACKEND_FEDERATION_MANIFEST_FILE,
337
+ container: BACKEND_FEDERATION_REMOTE_ENTRY_FILE
338
+ }
339
+ } : {}
294
340
  };
295
341
  const artifacts = [
296
342
  ...runtimeByPath
297
343
  ].map(([logicalPath, runtime])=>releaseArtifact(logicalPath, runtime)).sort((left, right)=>left.logicalPath.localeCompare(right.logicalPath));
298
344
  const identityCarrierMetadata = await writeReleaseIdentityCarrierMetadata(distDirectory, identity, {
299
- apiBackend: apiBackendPaths,
300
- backendFederation: [
301
- BACKEND_FEDERATION_MANIFEST_FILE,
302
- BACKEND_FEDERATION_REMOTE_ENTRY_FILE
303
- ],
345
+ ...apiBackendPaths.length > 0 ? {
346
+ apiBackend: apiBackendPaths
347
+ } : {},
348
+ ...backendProducer ? {
349
+ backendFederation: [
350
+ BACKEND_FEDERATION_MANIFEST_FILE,
351
+ BACKEND_FEDERATION_REMOTE_ENTRY_FILE
352
+ ]
353
+ } : {},
304
354
  ssr: [
305
355
  ...new Set([
306
356
  ...ssrExecutionPaths,
@@ -317,6 +367,25 @@ const createReleaseArtifactInputs = async (distDirectory, identity, target)=>{
317
367
  surfaces
318
368
  };
319
369
  };
370
+ const releaseEnvelopeSurfaces = (surfaces, shellConsumer)=>{
371
+ if (!shellConsumer) return {
372
+ surfaces
373
+ };
374
+ const { uiClient, ssr, apiBackend } = surfaces;
375
+ return {
376
+ kind: SHELL_RELEASE_ENVELOPE_KIND,
377
+ surfaces: {
378
+ uiClient,
379
+ ssr,
380
+ apiBackend
381
+ }
382
+ };
383
+ };
384
+ const assertShellBuildIdentity = async (appDirectory, distDirectory)=>{
385
+ const source = await readBuildIdentity(appDirectory);
386
+ const finalized = await pathExists(node_path.join(distDirectory, ULTRAMODERN_BUILD_ARTIFACT_FILE)) ? await resolveWorkerDeliveryUnitStamp(appDirectory, distDirectory) : void 0;
387
+ if (!source.artifact || !finalized || finalized.unitId !== source.unitId) throw new Error('[ultramodern-release-envelope] Shell is missing its native compiled delivery-unit identity.');
388
+ };
320
389
  const pathExists = async (filePath)=>{
321
390
  try {
322
391
  await promises.access(filePath);
@@ -325,36 +394,79 @@ const pathExists = async (filePath)=>{
325
394
  return false;
326
395
  }
327
396
  };
328
- const emitFrameworkMicroVerticalReleaseEnvelope = async ({ apiOnly, distDirectory, target })=>{
329
- if (apiOnly) return;
397
+ const emitFrameworkMicroVerticalReleaseEnvelope = async ({ apiOnly, appDirectory, distDirectory, requirePromotable = true, expectedRendererProfile, role, target })=>{
398
+ const declaredRole = appDirectory ? await resolveTopologyReleaseRole(appDirectory) : void 0;
399
+ if (void 0 !== role && void 0 !== declaredRole && role !== declaredRole) throw new Error('[ultramodern-release-envelope] Configured release role disagrees with the declared topology app.');
400
+ const shellConsumer = (declaredRole ?? role ?? 'microvertical') === 'shell';
330
401
  const backendManifestPath = node_path.join(distDirectory, BACKEND_FEDERATION_MANIFEST_FILE);
331
402
  const backendContainerPath = node_path.join(distDirectory, BACKEND_FEDERATION_REMOTE_ENTRY_FILE);
332
403
  const [hasBackendManifest, hasBackendContainer] = await Promise.all([
333
404
  pathExists(backendManifestPath),
334
405
  pathExists(backendContainerPath)
335
406
  ]);
336
- if (!hasBackendManifest && !hasBackendContainer) return;
337
- if (!hasBackendManifest || !hasBackendContainer) throw new Error('[ultramodern-release-envelope] full-stack MicroVertical backend federation manifest and container must be emitted together.');
338
- const buildArtifact = await readBuildArtifact(distDirectory);
407
+ if (shellConsumer) {
408
+ if (apiOnly || hasBackendManifest || hasBackendContainer || !appDirectory) throw new Error('[ultramodern-release-envelope] Shell requires a full-stack application source and cannot declare a backend federation producer.');
409
+ await assertShellBuildIdentity(appDirectory, distDirectory);
410
+ } else if (!apiOnly && !hasBackendManifest && !hasBackendContainer) {
411
+ const source = appDirectory ? node_path.join(appDirectory, ULTRAMODERN_BUILD_ARTIFACT_PATH) : void 0;
412
+ const participationPaths = [
413
+ node_path.join(distDirectory, ULTRAMODERN_BUILD_ARTIFACT_FILE),
414
+ backendManifestPath,
415
+ backendContainerPath,
416
+ ...source ? [
417
+ source,
418
+ source.replace(/\.json$/u, '.ts')
419
+ ] : []
420
+ ];
421
+ const hasParticipationEntry = (await Promise.all(participationPaths.map(async (filePath)=>{
422
+ try {
423
+ await promises.lstat(filePath);
424
+ return true;
425
+ } catch (error) {
426
+ if (isMissingPathError(error)) return false;
427
+ throw error;
428
+ }
429
+ }))).some(Boolean);
430
+ if (!hasParticipationEntry && (!appDirectory || !await resolveTopologyDeliveryUnit(appDirectory))) return;
431
+ }
432
+ const buildArtifact = await readBuildArtifact(distDirectory, requirePromotable);
433
+ const declared = appDirectory && buildArtifact ? await resolveWorkerDeliveryUnitStamp(appDirectory, distDirectory) : void 0;
434
+ const uiOnly = Boolean(declared?.surfaces.ui && !declared.surfaces.api);
435
+ if (uiOnly && (apiOnly || hasBackendManifest || hasBackendContainer)) throw new Error('[ultramodern-release-envelope] UI-only application cannot declare API-only configuration or backend federation output.');
436
+ if (!hasBackendManifest && !hasBackendContainer && !apiOnly && !buildArtifact) return;
437
+ if (!shellConsumer && !uiOnly && (!hasBackendManifest || !hasBackendContainer)) throw new Error('[ultramodern-release-envelope] MicroVertical backend federation manifest and container must be emitted together.');
438
+ if (!buildArtifact) return;
439
+ const builtUi = buildArtifact.surfaces.ui;
440
+ if (Boolean(builtUi) === apiOnly) throw new Error('[ultramodern-release-envelope] build artifact UI surface must match the configured application surface.');
441
+ if (void 0 !== expectedRendererProfile) assertRendererProfileCompatibility(expectedRendererProfile, builtUi?.rendererProfile);
442
+ const ui = builtUi ? {
443
+ rendererIdentity: builtUi.rendererIdentity,
444
+ rendererProfile: builtUi.rendererProfile,
445
+ routerBindings: builtUi.routerBindings
446
+ } : void 0;
339
447
  const identity = {
340
448
  unitId: buildArtifact.deliveryUnit.unitId,
341
449
  buildMarker: buildArtifact.deliveryUnit.buildMarker,
342
450
  sourceRevision: buildArtifact.deliveryUnit.sourceRevision,
343
451
  releaseVersion: buildArtifact.deliveryUnit.version
344
452
  };
345
- await readBackendManifest(distDirectory, identity);
346
- const { artifacts, surfaces } = await createReleaseArtifactInputs(distDirectory, identity, target);
453
+ if (!shellConsumer && !uiOnly) await readBackendManifest(distDirectory, identity);
454
+ const { artifacts, surfaces } = await createReleaseArtifactInputs(distDirectory, identity, target, apiOnly, uiOnly, shellConsumer);
347
455
  const envelope = await createMicroVerticalReleaseEnvelope({
348
456
  artifactRoot: distDirectory,
349
457
  target,
350
458
  identity,
459
+ ...ui ? {
460
+ ui
461
+ } : {},
351
462
  artifacts,
352
- surfaces
463
+ ...releaseEnvelopeSurfaces(surfaces, shellConsumer)
353
464
  });
465
+ await verifyBuildArtifactCarriers(distDirectory, envelope);
354
466
  await writeReleaseEnvelope(distDirectory, envelope);
355
467
  return envelope;
356
468
  };
357
- const readFrameworkMicroVerticalReleaseEnvelope = async ({ artifactRoot, expectedTarget, logicalPathForArtifact, required = false })=>{
469
+ const readFrameworkMicroVerticalReleaseEnvelope = async ({ artifactRoot, expectedTarget, logicalPathForArtifact, expectedRendererProfile, required = false })=>{
358
470
  const envelopePath = await resolveSafeReleaseEnvelopePath({
359
471
  artifactRoot,
360
472
  createDirectory: false
@@ -363,30 +475,55 @@ const readFrameworkMicroVerticalReleaseEnvelope = async ({ artifactRoot, expecte
363
475
  if (required) throw new Error(`[ultramodern-release-envelope] required envelope is missing at ${MICROVERTICAL_RELEASE_ENVELOPE_PATH}.`);
364
476
  return;
365
477
  }
366
- return verifyMicroVerticalReleaseEnvelope(await readJson(envelopePath), {
478
+ const envelope = await verifyMicroVerticalReleaseEnvelope(await readJson(envelopePath), {
367
479
  artifactRoot,
368
480
  ...expectedTarget ? {
369
481
  expectedTarget
370
482
  } : {},
371
483
  ...logicalPathForArtifact ? {
372
484
  logicalPathForArtifact
485
+ } : {},
486
+ ...expectedRendererProfile ? {
487
+ expectedRendererProfile
373
488
  } : {}
374
489
  });
490
+ await verifyBuildArtifactCarriers(artifactRoot, envelope, logicalPathForArtifact);
491
+ return envelope;
492
+ };
493
+ const verifyBuildArtifactCarriers = async (artifactRoot, envelope, logicalPathForArtifact)=>{
494
+ const carriers = envelope.artifacts.filter((artifact)=>artifact.logicalPath === ULTRAMODERN_BUILD_ARTIFACT_FILE || artifact.logicalPath === PUBLIC_BUILD_ARTIFACT_PATH);
495
+ if (0 === carriers.length) throw new Error('[ultramodern-release-envelope] immutable build artifact must be bound to the release envelope.');
496
+ for (const artifact of carriers){
497
+ if ('file' !== artifact.kind || 'release-identity-metadata' !== artifact.runtime) throw new Error('[ultramodern-release-envelope] immutable build artifact must be a real file with runtime "release-identity-metadata".');
498
+ const logicalPath = logicalPathForArtifact?.(artifact) ?? artifact.logicalPath;
499
+ const built = await readJson(node_path.join(artifactRoot, logicalPath));
500
+ if (!isUltramodernBuildArtifact(built)) throw new Error('[ultramodern-release-envelope] bound build artifact is invalid.');
501
+ assertIdentityBlock(built.deliveryUnit, envelope.identity, 'buildArtifact.deliveryUnit', true);
502
+ if (Boolean(built.surfaces.ui) !== Boolean(envelope.ui)) throw new Error('[ultramodern-release-envelope] bound build artifact UI surface differs from the release envelope.');
503
+ if (envelope.ui && built.surfaces.ui) {
504
+ if (!isDeepStrictEqual(envelope.ui.routerBindings, built.surfaces.ui.routerBindings)) throw new Error('[ultramodern-release-envelope] buildArtifact.ui.routerBindings differ from the release envelope.');
505
+ assertRendererProfileCompatibility(envelope.ui.rendererProfile, built.surfaces.ui.rendererProfile);
506
+ for (const field of [
507
+ 'renderer',
508
+ 'protocolVersion',
509
+ 'appId',
510
+ 'entryName',
511
+ 'buildId'
512
+ ])if (envelope.ui.rendererIdentity[field] !== built.surfaces.ui.rendererIdentity[field]) throw new Error(`[ultramodern-release-envelope] buildArtifact.ui.rendererIdentity.${field} differs from the release envelope.`);
513
+ }
514
+ }
375
515
  };
376
516
  const verifyBuildOutputReleaseEnvelope = async (distDirectory, expectedTarget)=>{
377
- const [hasEnvelope, hasBuildArtifact, hasBackendManifest, hasBackendEntry] = await Promise.all([
517
+ const [hasEnvelope, hasBuildArtifact] = await Promise.all([
378
518
  pathExists(node_path.join(distDirectory, MICROVERTICAL_RELEASE_ENVELOPE_PATH)),
379
- pathExists(node_path.join(distDirectory, ULTRAMODERN_BUILD_ARTIFACT_FILE)),
380
- pathExists(node_path.join(distDirectory, BACKEND_FEDERATION_MANIFEST_FILE)),
381
- pathExists(node_path.join(distDirectory, BACKEND_FEDERATION_REMOTE_ENTRY_FILE))
519
+ pathExists(node_path.join(distDirectory, ULTRAMODERN_BUILD_ARTIFACT_FILE))
382
520
  ]);
383
- const isFullStackMicroVertical = hasBuildArtifact && (hasBackendManifest || hasBackendEntry);
384
521
  return readFrameworkMicroVerticalReleaseEnvelope({
385
522
  artifactRoot: distDirectory,
386
523
  ...expectedTarget ? {
387
524
  expectedTarget
388
525
  } : {},
389
- required: hasEnvelope || isFullStackMicroVertical
526
+ required: hasEnvelope || hasBuildArtifact
390
527
  });
391
528
  };
392
529
  const verifyNodeReleaseEnvelopeStaging = async ({ outputDirectory })=>readFrameworkMicroVerticalReleaseEnvelope({
@@ -394,21 +531,26 @@ const verifyNodeReleaseEnvelopeStaging = async ({ outputDirectory })=>readFramew
394
531
  expectedTarget: 'node',
395
532
  required: true
396
533
  });
397
- const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity)=>{
534
+ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity, apiOnly, uiOnly, declaredPublicAssets, shellConsumer)=>{
535
+ const backendProducer = !uiOnly && !shellConsumer;
398
536
  const files = await collectFiles(outputDirectory);
537
+ const declaredPublicAssetPaths = resolveDeclaredPublicAssets(files, declaredPublicAssets, 'Node');
399
538
  for (const requiredPath of [
400
539
  'index.js',
401
540
  'package.json',
402
- BACKEND_FEDERATION_MANIFEST_FILE,
403
- BACKEND_FEDERATION_REMOTE_ENTRY_FILE
541
+ ...backendProducer ? [
542
+ BACKEND_FEDERATION_MANIFEST_FILE,
543
+ BACKEND_FEDERATION_REMOTE_ENTRY_FILE
544
+ ] : []
404
545
  ])if (!files.includes(requiredPath)) throw new Error(`[ultramodern-release-envelope] final Node staging is missing ${requiredPath}.`);
405
546
  const backendFederationPaths = new Set([
406
547
  BACKEND_FEDERATION_MANIFEST_FILE,
407
548
  BACKEND_FEDERATION_REMOTE_ENTRY_FILE
408
549
  ]);
550
+ if (shellConsumer && files.some((logicalPath)=>backendFederationPaths.has(logicalPath))) throw new Error('[ultramodern-release-envelope] final Shell Node staging cannot contain a backend federation producer artifact.');
409
551
  const uiClientPaths = files.filter((logicalPath)=>{
410
552
  const topLevel = logicalPath.split('/')[0];
411
- return 'static' === topLevel || 'html' === topLevel || 'public' === topLevel || [
553
+ return 'static' === topLevel || 'html' === topLevel || 'public' === topLevel && logicalPath !== CRAWLER_POLICY_PATH && logicalPath !== PUBLIC_BUILD_ARTIFACT_PATH && !isPublicMetadataPath(logicalPath) && !declaredPublicAssetPaths.has(logicalPath) || [
412
554
  'index.html',
413
555
  'mf-manifest.json',
414
556
  'mf-stats.json',
@@ -419,6 +561,8 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity)=
419
561
  });
420
562
  const ssrPaths = files.filter((logicalPath)=>'index.js' === logicalPath || logicalPath.startsWith('bundles/') && COMPILED_MODULE_PATTERN.test(logicalPath));
421
563
  const apiBackendPaths = files.filter((logicalPath)=>(logicalPath.startsWith('api/') || logicalPath.startsWith('shared/')) && COMPILED_MODULE_PATTERN.test(logicalPath));
564
+ if (uiOnly && (apiBackendPaths.length > 0 || files.some((logicalPath)=>backendFederationPaths.has(logicalPath)))) throw new Error('[ultramodern-release-envelope] final UI-only Node staging contains an undeclared API/backend or backend federation artifact.');
565
+ if (apiOnly && (uiClientPaths.length > 0 || ssrPaths.some((path)=>'index.js' !== path))) throw new Error('[ultramodern-release-envelope] final API-only Node staging contains an undeclared UI/client or SSR surface.');
422
566
  for (const [surface, paths] of [
423
567
  [
424
568
  'UI/client',
@@ -426,32 +570,45 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity)=
426
570
  ],
427
571
  [
428
572
  'Node SSR',
429
- ssrPaths
573
+ apiOnly ? [] : ssrPaths
430
574
  ],
431
575
  [
432
576
  'Node API/backend',
433
577
  apiBackendPaths
434
578
  ]
435
- ])if (0 === paths.length) throw new Error(`[ultramodern-release-envelope] final Node staging has no ${surface} artifacts.`);
436
- const clientExecutionPaths = await manifestReferencedClientModules(outputDirectory, files);
437
- const ssrExecutionPaths = await routeReferencedSsrModules(outputDirectory, files, 'node');
438
- if (0 === clientExecutionPaths.length || 0 === ssrExecutionPaths.length) throw new Error('[ultramodern-release-envelope] final Node manifests must reference emitted UI/client and SSR execution modules.');
579
+ ])if (0 === paths.length && ('Node API/backend' === surface ? backendProducer : !apiOnly)) throw new Error(`[ultramodern-release-envelope] final Node staging has no ${surface} artifacts.`);
580
+ const clientExecutionPaths = apiOnly ? [] : await manifestReferencedClientModules(outputDirectory, files);
581
+ const ssrExecutionPaths = apiOnly ? [] : await routeReferencedSsrModules(outputDirectory, files, 'node');
582
+ if (!apiOnly && (0 === clientExecutionPaths.length || 0 === ssrExecutionPaths.length)) throw new Error('[ultramodern-release-envelope] final Node manifests must reference emitted UI/client and SSR execution modules.');
439
583
  const runtimeByPath = new Map(files.filter((logicalPath)=>!backendFederationPaths.has(logicalPath)).map((logicalPath)=>[
440
584
  logicalPath,
441
585
  'nodejs-deployment'
442
586
  ]));
443
587
  for (const logicalPath of uiClientPaths)runtimeByPath.set(logicalPath, 'browser');
444
- for (const logicalPath of ssrPaths)runtimeByPath.set(logicalPath, 'nodejs');
588
+ for (const logicalPath of declaredPublicAssetPaths)runtimeByPath.set(logicalPath, DECLARED_PUBLIC_ASSET_RUNTIME);
589
+ if (files.includes(CRAWLER_POLICY_PATH)) runtimeByPath.set(CRAWLER_POLICY_PATH, 'crawler-policy');
590
+ for (const logicalPath of files.filter(isPublicMetadataPath))runtimeByPath.set(logicalPath, 'public-metadata');
591
+ for (const logicalPath of apiOnly ? [] : ssrPaths)runtimeByPath.set(logicalPath, 'nodejs');
445
592
  for (const logicalPath of apiBackendPaths)runtimeByPath.set(logicalPath, 'nodejs');
446
- runtimeByPath.set(BACKEND_FEDERATION_MANIFEST_FILE, 'module-federation-manifest');
447
- runtimeByPath.set(BACKEND_FEDERATION_REMOTE_ENTRY_FILE, 'nodejs');
593
+ if (backendProducer) {
594
+ runtimeByPath.set(BACKEND_FEDERATION_MANIFEST_FILE, 'module-federation-manifest');
595
+ runtimeByPath.set(BACKEND_FEDERATION_REMOTE_ENTRY_FILE, 'nodejs');
596
+ }
597
+ for (const logicalPath of [
598
+ ULTRAMODERN_BUILD_ARTIFACT_FILE,
599
+ PUBLIC_BUILD_ARTIFACT_PATH
600
+ ])if (files.includes(logicalPath)) runtimeByPath.set(logicalPath, 'release-identity-metadata');
448
601
  const identityCarrierMetadata = await writeReleaseIdentityCarrierMetadata(outputDirectory, identity, {
449
- apiBackend: apiBackendPaths,
450
- backendFederation: [
451
- BACKEND_FEDERATION_MANIFEST_FILE,
452
- BACKEND_FEDERATION_REMOTE_ENTRY_FILE
453
- ],
454
- ssr: [
602
+ ...apiBackendPaths.length > 0 ? {
603
+ apiBackend: apiBackendPaths
604
+ } : {},
605
+ ...backendProducer ? {
606
+ backendFederation: [
607
+ BACKEND_FEDERATION_MANIFEST_FILE,
608
+ BACKEND_FEDERATION_REMOTE_ENTRY_FILE
609
+ ]
610
+ } : {},
611
+ ssr: apiOnly ? [] : [
455
612
  ...new Set([
456
613
  ...ssrExecutionPaths,
457
614
  ...ssrPaths.filter((logicalPath)=>'index.js' !== logicalPath)
@@ -470,30 +627,37 @@ const createNodeStagedReleaseArtifactInputs = async (outputDirectory, identity)=
470
627
  uiClient: [
471
628
  ...uiClientPaths
472
629
  ].sort((left, right)=>left.localeCompare(right)),
473
- ssr: [
630
+ ssr: apiOnly ? [] : [
474
631
  ...ssrPaths
475
632
  ].sort((left, right)=>left.localeCompare(right)),
476
633
  apiBackend: [
477
634
  ...apiBackendPaths
478
635
  ].sort((left, right)=>left.localeCompare(right)),
479
- backendFederation: {
480
- manifest: BACKEND_FEDERATION_MANIFEST_FILE,
481
- container: BACKEND_FEDERATION_REMOTE_ENTRY_FILE
482
- }
636
+ ...backendProducer ? {
637
+ backendFederation: {
638
+ manifest: BACKEND_FEDERATION_MANIFEST_FILE,
639
+ container: BACKEND_FEDERATION_REMOTE_ENTRY_FILE
640
+ }
641
+ } : {}
483
642
  }
484
643
  };
485
644
  };
486
- const emitNodeStagedReleaseEnvelope = async ({ distDirectory, outputDirectory })=>{
645
+ const emitNodeStagedReleaseEnvelope = async ({ declaredPublicAssets = [], distDirectory, outputDirectory })=>{
487
646
  const source = await verifyBuildOutputReleaseEnvelope(distDirectory, 'node');
488
647
  if (!source) return;
489
- const { artifacts, surfaces } = await createNodeStagedReleaseArtifactInputs(outputDirectory, source.identity);
648
+ const shellConsumer = source.kind === SHELL_RELEASE_ENVELOPE_KIND;
649
+ const { artifacts, surfaces } = await createNodeStagedReleaseArtifactInputs(outputDirectory, source.identity, 0 === source.surfaces.uiClient.length, 0 === source.surfaces.apiBackend.length, declaredPublicAssets, shellConsumer);
490
650
  const staged = await createMicroVerticalReleaseEnvelope({
491
651
  artifactRoot: outputDirectory,
492
652
  target: 'node',
493
653
  identity: source.identity,
654
+ ...source.ui ? {
655
+ ui: source.ui
656
+ } : {},
494
657
  artifacts,
495
- surfaces
658
+ ...releaseEnvelopeSurfaces(surfaces, shellConsumer)
496
659
  });
660
+ await verifyBuildArtifactCarriers(outputDirectory, staged);
497
661
  await writeReleaseEnvelope(outputDirectory, staged);
498
662
  return staged;
499
663
  };
@@ -504,21 +668,29 @@ const stageCloudflareReleaseEnvelope = async ({ distDirectory, outputDirectory }
504
668
  if (await pathExists(stagedEnvelopePath)) throw new Error('[ultramodern-release-envelope] Cloudflare staging contains a stale envelope before final output generation.');
505
669
  return source;
506
670
  };
507
- const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, identity)=>{
671
+ const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, identity, apiOnly, uiOnly, declaredPublicAssets, shellConsumer)=>{
672
+ const backendProducer = !uiOnly && !shellConsumer;
508
673
  const files = await collectFiles(outputDirectory);
674
+ const declaredPublicAssetPaths = resolveDeclaredPublicAssets(files, declaredPublicAssets, 'Cloudflare');
509
675
  for (const requiredPath of [
510
676
  'server/index.mjs',
511
677
  'server/modern-worker-manifest.json',
512
678
  'wrangler.json',
513
679
  'package.json',
514
- 'worker/package.json'
680
+ ...apiOnly ? [] : [
681
+ 'worker/package.json'
682
+ ]
515
683
  ])if (!files.includes(requiredPath)) throw new Error(`[ultramodern-release-envelope] final Cloudflare staging is missing ${requiredPath}.`);
516
684
  if (files.some((logicalPath)=>'public/release' === logicalPath || logicalPath.startsWith('public/release/'))) throw new Error('[ultramodern-release-envelope] final Cloudflare envelope must remain private and cannot be staged under public/release.');
517
685
  const backendManifestPath = `public/${BACKEND_FEDERATION_MANIFEST_FILE}`;
518
686
  const backendContainerPath = `public/${BACKEND_FEDERATION_REMOTE_ENTRY_FILE}`;
519
- const apiBackendPaths = files.filter((logicalPath)=>EFFECT_BFF_WORKER_PATTERN.test(logicalPath));
520
- const ssrPaths = files.filter((logicalPath)=>(logicalPath.startsWith('server/') || logicalPath.startsWith('worker/')) && COMPILED_MODULE_PATTERN.test(logicalPath) && !EFFECT_BFF_WORKER_PATTERN.test(logicalPath));
521
- const uiClientPaths = files.filter((logicalPath)=>logicalPath.startsWith('public/') && logicalPath !== backendManifestPath && logicalPath !== backendContainerPath);
687
+ const apiBackendPaths = effectBffWorkerArtifacts(files);
688
+ if (backendProducer && !files.some((logicalPath)=>EFFECT_BFF_WORKER_PATTERN.test(logicalPath))) throw new Error('[ultramodern-release-envelope] final Cloudflare staging has no actual Effect API/BFF worker artifact.');
689
+ const ssrPaths = files.filter((logicalPath)=>(logicalPath.startsWith('server/') || logicalPath.startsWith('worker/')) && COMPILED_MODULE_PATTERN.test(logicalPath) && !apiBackendPaths.includes(logicalPath));
690
+ const uiClientPaths = files.filter((logicalPath)=>logicalPath.startsWith('public/') && logicalPath !== CRAWLER_POLICY_PATH && !isPublicMetadataPath(logicalPath) && !declaredPublicAssetPaths.has(logicalPath) && logicalPath !== PUBLIC_BUILD_ARTIFACT_PATH && logicalPath !== backendManifestPath && logicalPath !== backendContainerPath);
691
+ if (shellConsumer && files.some((logicalPath)=>logicalPath === backendManifestPath || logicalPath === backendContainerPath)) throw new Error('[ultramodern-release-envelope] final Shell Cloudflare staging cannot contain a backend federation producer artifact.');
692
+ if (uiOnly && (apiBackendPaths.length > 0 || files.some((logicalPath)=>logicalPath === backendManifestPath || logicalPath === backendContainerPath || logicalPath.startsWith('api/') && COMPILED_MODULE_PATTERN.test(logicalPath)))) throw new Error('[ultramodern-release-envelope] final UI-only Cloudflare staging contains an undeclared API/backend or backend federation artifact.');
693
+ if (apiOnly && (uiClientPaths.length > 0 || ssrPaths.some((path)=>'server/index.mjs' !== path))) throw new Error('[ultramodern-release-envelope] final API-only Cloudflare staging contains an undeclared UI/client or SSR surface.');
522
694
  for (const [surface, paths] of [
523
695
  [
524
696
  'UI/client',
@@ -526,41 +698,53 @@ const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, iden
526
698
  ],
527
699
  [
528
700
  'Cloudflare SSR',
529
- ssrPaths
701
+ apiOnly ? [] : ssrPaths
530
702
  ],
531
703
  [
532
704
  'Cloudflare API/backend',
533
705
  apiBackendPaths
534
706
  ]
535
- ])if (0 === paths.length) throw new Error(`[ultramodern-release-envelope] final Cloudflare staging has no ${surface} artifacts.`);
536
- for (const logicalPath of [
707
+ ])if (0 === paths.length && ('Cloudflare API/backend' === surface ? backendProducer : !apiOnly)) throw new Error(`[ultramodern-release-envelope] final Cloudflare staging has no ${surface} artifacts.`);
708
+ for (const logicalPath of backendProducer ? [
537
709
  backendManifestPath,
538
710
  backendContainerPath
539
- ])if (!files.includes(logicalPath)) throw new Error(`[ultramodern-release-envelope] final Cloudflare staging is missing ${logicalPath}.`);
540
- const clientExecutionPaths = await manifestReferencedClientModules(outputDirectory, files, 'public/mf-manifest.json');
541
- const ssrExecutionPaths = await routeReferencedSsrModules(outputDirectory, files, 'cloudflare', 'server/route.json');
542
- if (0 === clientExecutionPaths.length || 0 === ssrExecutionPaths.length) throw new Error('[ultramodern-release-envelope] final Cloudflare manifests must reference emitted UI/client and SSR execution modules.');
711
+ ] : [])if (!files.includes(logicalPath)) throw new Error(`[ultramodern-release-envelope] final Cloudflare staging is missing ${logicalPath}.`);
712
+ const clientExecutionPaths = apiOnly ? [] : await manifestReferencedClientModules(outputDirectory, files, 'public/mf-manifest.json');
713
+ const ssrExecutionPaths = apiOnly ? [] : await routeReferencedSsrModules(outputDirectory, files, 'cloudflare', 'server/route.json');
714
+ if (!apiOnly && (0 === clientExecutionPaths.length || 0 === ssrExecutionPaths.length)) throw new Error('[ultramodern-release-envelope] final Cloudflare manifests must reference emitted UI/client and SSR execution modules.');
543
715
  const runtimeByPath = new Map();
544
716
  for (const logicalPath of files)runtimeByPath.set(logicalPath, 'cloudflare-deployment');
545
717
  const add = (paths, runtime)=>{
546
718
  for (const logicalPath of paths)runtimeByPath.set(logicalPath, runtime);
547
719
  };
548
720
  add(uiClientPaths, 'browser');
549
- add(ssrPaths, 'workerd');
550
- add(apiBackendPaths, 'workerd-effect');
551
721
  add([
552
- backendManifestPath
553
- ], 'module-federation-manifest');
554
- add([
555
- backendContainerPath
556
- ], 'commonjs-module');
557
- const identityCarrierMetadata = await writeReleaseIdentityCarrierMetadata(outputDirectory, identity, {
558
- apiBackend: apiBackendPaths,
559
- backendFederation: [
560
- backendManifestPath,
722
+ ...declaredPublicAssetPaths
723
+ ], DECLARED_PUBLIC_ASSET_RUNTIME);
724
+ if (files.includes(CRAWLER_POLICY_PATH)) runtimeByPath.set(CRAWLER_POLICY_PATH, 'crawler-policy');
725
+ add(files.filter(isPublicMetadataPath), 'public-metadata');
726
+ add(apiOnly ? [] : ssrPaths, 'workerd');
727
+ add(apiBackendPaths, 'workerd-effect');
728
+ if (backendProducer) {
729
+ add([
730
+ backendManifestPath
731
+ ], 'module-federation-manifest');
732
+ add([
561
733
  backendContainerPath
562
- ],
563
- ssr: [
734
+ ], 'commonjs-module');
735
+ }
736
+ add(files.filter((logicalPath)=>logicalPath === ULTRAMODERN_BUILD_ARTIFACT_FILE || logicalPath === PUBLIC_BUILD_ARTIFACT_PATH), 'release-identity-metadata');
737
+ const identityCarrierMetadata = await writeReleaseIdentityCarrierMetadata(outputDirectory, identity, {
738
+ ...apiBackendPaths.length > 0 ? {
739
+ apiBackend: apiBackendPaths
740
+ } : {},
741
+ ...backendProducer ? {
742
+ backendFederation: [
743
+ backendManifestPath,
744
+ backendContainerPath
745
+ ]
746
+ } : {},
747
+ ssr: apiOnly ? [] : [
564
748
  ...new Set([
565
749
  ...ssrExecutionPaths,
566
750
  ...ssrPaths.filter((logicalPath)=>logicalPath.startsWith('worker/'))
@@ -579,30 +763,37 @@ const createCloudflareStagedReleaseArtifactInputs = async (outputDirectory, iden
579
763
  uiClient: [
580
764
  ...uiClientPaths
581
765
  ].sort((left, right)=>left.localeCompare(right)),
582
- ssr: [
766
+ ssr: apiOnly ? [] : [
583
767
  ...ssrPaths
584
768
  ].sort((left, right)=>left.localeCompare(right)),
585
769
  apiBackend: [
586
770
  ...apiBackendPaths
587
771
  ].sort((left, right)=>left.localeCompare(right)),
588
- backendFederation: {
589
- manifest: backendManifestPath,
590
- container: backendContainerPath
591
- }
772
+ ...backendProducer ? {
773
+ backendFederation: {
774
+ manifest: backendManifestPath,
775
+ container: backendContainerPath
776
+ }
777
+ } : {}
592
778
  }
593
779
  };
594
780
  };
595
- const emitCloudflareStagedReleaseEnvelope = async ({ distDirectory, outputDirectory })=>{
781
+ const emitCloudflareStagedReleaseEnvelope = async ({ declaredPublicAssets = [], distDirectory, outputDirectory })=>{
596
782
  const source = await verifyBuildOutputReleaseEnvelope(distDirectory, 'cloudflare');
597
783
  if (!source) return;
598
- const { artifacts, surfaces } = await createCloudflareStagedReleaseArtifactInputs(outputDirectory, source.identity);
784
+ const shellConsumer = source.kind === SHELL_RELEASE_ENVELOPE_KIND;
785
+ const { artifacts, surfaces } = await createCloudflareStagedReleaseArtifactInputs(outputDirectory, source.identity, 0 === source.surfaces.uiClient.length, 0 === source.surfaces.apiBackend.length, declaredPublicAssets, shellConsumer);
599
786
  const staged = await createMicroVerticalReleaseEnvelope({
600
787
  artifactRoot: outputDirectory,
601
788
  target: 'cloudflare',
602
789
  identity: source.identity,
790
+ ...source.ui ? {
791
+ ui: source.ui
792
+ } : {},
603
793
  artifacts,
604
- surfaces
794
+ ...releaseEnvelopeSurfaces(surfaces, shellConsumer)
605
795
  });
796
+ await verifyBuildArtifactCarriers(outputDirectory, staged);
606
797
  await writeReleaseEnvelope(outputDirectory, staged);
607
798
  return staged;
608
799
  };