@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.9 → 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 (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,9 +1,11 @@
1
1
  import path from 'node:path';
2
2
  import { fs as fse } from '@modern-js/utils';
3
- import type {
4
- CloudflareWorkerArtifactConfig,
5
- CloudflareWorkerPublicAssetConfig,
6
- } from '../config';
3
+ import type { CloudflareWorkerArtifactConfig } from '../config';
4
+ import {
5
+ CLOUDFLARE_PUBLIC_ASSET_SCOPE,
6
+ normalizeDeclaredPublicAssets,
7
+ stageDeclaredPublicAssets,
8
+ } from '../deploy-output/public-assets';
7
9
  import {
8
10
  RESERVED_ARTIFACT_DESTINATION_DIRECTORIES,
9
11
  RESERVED_ARTIFACT_DESTINATION_FILES,
@@ -54,34 +56,12 @@ export const getCloudflareArtifacts = (modernConfig: CloudflareModernConfig) =>
54
56
  normalizeCloudflareArtifact,
55
57
  );
56
58
 
57
- const normalizeCloudflarePublicAsset = (
58
- asset: CloudflareWorkerPublicAssetConfig,
59
- index: number,
60
- ) => {
61
- const from = normalizeRelativePath(
62
- asset.from,
63
- `deploy.worker.publicAssets[${index}].from`,
64
- 'app root',
65
- );
66
- const to = normalizeRelativePath(
67
- asset.to,
68
- `deploy.worker.publicAssets[${index}].to`,
69
- 'Cloudflare public output',
70
- { allowRoot: true },
71
- );
72
-
73
- return {
74
- from,
75
- to,
76
- index,
77
- };
78
- };
79
-
80
59
  export const getCloudflarePublicAssets = (
81
60
  modernConfig: CloudflareModernConfig,
82
61
  ) =>
83
- (modernConfig.deploy?.worker?.publicAssets ?? []).map(
84
- normalizeCloudflarePublicAsset,
62
+ normalizeDeclaredPublicAssets(
63
+ modernConfig.deploy?.worker?.publicAssets,
64
+ CLOUDFLARE_PUBLIC_ASSET_SCOPE,
85
65
  );
86
66
 
87
67
  export const copyCloudflareArtifacts = async (
@@ -102,23 +82,22 @@ export const copyCloudflareArtifacts = async (
102
82
  }
103
83
  };
104
84
 
85
+ /**
86
+ * Stage `deploy.worker.publicAssets` into Worker Static Assets.
87
+ *
88
+ * @returns the staged files relative to the Cloudflare output root.
89
+ */
105
90
  export const copyCloudflarePublicAssets = async (
106
91
  appDirectory: string,
107
- publicDirectory: string,
92
+ outputDirectory: string,
108
93
  publicAssets: ReturnType<typeof getCloudflarePublicAssets>,
109
- ) => {
110
- for (const asset of publicAssets) {
111
- const sourcePath = path.join(appDirectory, asset.from);
112
-
113
- if (!(await fse.pathExists(sourcePath))) {
114
- throw new Error(
115
- `deploy.worker.publicAssets[${asset.index}].from does not exist: ${asset.from}`,
116
- );
117
- }
118
-
119
- await fse.copy(sourcePath, path.join(publicDirectory, asset.to));
120
- }
121
- };
94
+ ) =>
95
+ stageDeclaredPublicAssets({
96
+ appDirectory,
97
+ outputDirectory,
98
+ assets: publicAssets,
99
+ scope: CLOUDFLARE_PUBLIC_ASSET_SCOPE,
100
+ });
122
101
 
123
102
  export const copyCloudflareD1Migrations = async (
124
103
  appDirectory: string,
@@ -1,26 +1,79 @@
1
1
  import path from 'node:path';
2
+ import { isDeepStrictEqual } from 'node:util';
2
3
  import {
4
+ assertRendererProfileCompatibility,
5
+ assertUltramodernBuildArtifact,
3
6
  type DeliveryUnitIdentity,
4
- isUltramodernBuildArtifact,
7
+ formatBackendFederationValidationErrors,
8
+ immutableRendererRouterBindings,
5
9
  nonEmptyString,
10
+ type RendererIdentity,
11
+ type RendererProfile,
12
+ type RendererRouterBindings,
6
13
  toDeliveryUnitIdentity,
14
+ ULTRAMODERN_BUILD_ARTIFACT_FILE,
7
15
  ULTRAMODERN_BUILD_ARTIFACT_PATH,
16
+ validateRendererIdentity,
17
+ validateRendererProfile,
18
+ validateRendererRouterBindings,
8
19
  } from '@modern-js/backend-federation-contracts';
9
20
  import { fs as fse } from '@modern-js/utils';
10
21
  import { resolveUltramodernReleaseIdentity } from '../release-identity';
11
22
  import { isRecord } from './utils';
12
23
 
13
- const COMPACT_CONFIG_PATH = '.modernjs/ultramodern.json';
24
+ const TOPOLOGY_PATH = 'topology/reference-topology.json';
14
25
 
15
26
  export type DeliveryUnitStamp = DeliveryUnitIdentity & {
16
27
  surfaces: {
17
- ui?: DeliveryUnitIdentity & { surface: 'ui' };
28
+ ui?: DeliveryUnitIdentity & {
29
+ surface: 'ui';
30
+ rendererIdentity: RendererIdentity;
31
+ rendererProfile: RendererProfile;
32
+ routerBindings: RendererRouterBindings;
33
+ };
18
34
  api?: DeliveryUnitIdentity & { surface: 'api' };
19
35
  };
20
36
  };
21
37
 
22
- type CompactAppResolution = {
38
+ /**
39
+ * The topology's declaration of one delivery unit. A UI surface may omit the
40
+ * renderer projection entirely (a React workspace authored before renderer
41
+ * selection); the build then resolves its renderer identity from the app
42
+ * config alone, and there is no declared projection to verify it against.
43
+ */
44
+ export type TopologyUiSurface = DeliveryUnitIdentity & { surface: 'ui' } & (
45
+ | {
46
+ rendererIdentity: RendererIdentity;
47
+ rendererProfile: RendererProfile;
48
+ routerBindings: RendererRouterBindings;
49
+ }
50
+ | {
51
+ rendererIdentity?: undefined;
52
+ rendererProfile?: undefined;
53
+ routerBindings?: undefined;
54
+ }
55
+ );
56
+
57
+ export type TopologyDeliveryUnit = DeliveryUnitIdentity & {
58
+ /** The declaring topology app. */
59
+ appId: string;
60
+ surfaces: {
61
+ ui?: TopologyUiSurface;
62
+ api?: DeliveryUnitIdentity & { surface: 'api' };
63
+ };
64
+ };
65
+
66
+ const RENDERER_PROJECTION_FIELDS = [
67
+ 'renderer',
68
+ 'rendererIdentity',
69
+ 'rendererIdentities',
70
+ 'rendererProfile',
71
+ 'routerBindings',
72
+ ] as const;
73
+
74
+ type TopologyAppResolution = {
23
75
  app?: Record<string, unknown>;
76
+ role: 'microvertical' | 'shell';
24
77
  workspaceRoot: string;
25
78
  };
26
79
 
@@ -30,7 +83,7 @@ const findWorkspaceRoot = async (
30
83
  let current = path.resolve(appDirectory);
31
84
 
32
85
  for (;;) {
33
- if (await fse.pathExists(path.join(current, COMPACT_CONFIG_PATH))) {
86
+ if (await fse.pathExists(path.join(current, TOPOLOGY_PATH))) {
34
87
  return current;
35
88
  }
36
89
 
@@ -60,34 +113,32 @@ const stampReleaseIdentity = (
60
113
  };
61
114
  };
62
115
 
63
- const resolveCompactApp = async (
116
+ const resolveTopologyApp = async (
64
117
  appDirectory: string,
65
- ): Promise<CompactAppResolution | undefined> => {
118
+ ): Promise<TopologyAppResolution | undefined> => {
66
119
  const workspaceRoot = await findWorkspaceRoot(appDirectory);
67
120
  if (!workspaceRoot) {
68
121
  return undefined;
69
122
  }
70
123
 
71
- let compactConfig: unknown;
72
- try {
73
- compactConfig = await fse.readJSON(
74
- path.join(workspaceRoot, COMPACT_CONFIG_PATH),
124
+ const topology: unknown = await fse.readJSON(
125
+ path.join(workspaceRoot, TOPOLOGY_PATH),
126
+ );
127
+ if (
128
+ !isRecord(topology) ||
129
+ !isRecord(topology.shell) ||
130
+ !Array.isArray(topology.verticals) ||
131
+ (topology.shells !== undefined && !Array.isArray(topology.shells))
132
+ ) {
133
+ throw new Error(
134
+ '[cloudflare-delivery-unit] Invalid declared reference topology.',
75
135
  );
76
- } catch {
77
- return { workspaceRoot };
78
- }
79
-
80
- if (!isRecord(compactConfig)) {
81
- return { workspaceRoot };
82
- }
83
-
84
- const topology = isRecord(compactConfig.topology)
85
- ? compactConfig.topology
86
- : undefined;
87
- const apps = Array.isArray(topology?.apps) ? topology.apps : undefined;
88
- if (!apps) {
89
- return { app: compactConfig, workspaceRoot };
90
136
  }
137
+ const apps = [
138
+ topology.shell,
139
+ ...topology.verticals,
140
+ ...(Array.isArray(topology.shells) ? topology.shells : []),
141
+ ];
91
142
 
92
143
  const resolvedAppDirectory = path.resolve(appDirectory);
93
144
  const app = apps.find(candidate => {
@@ -101,24 +152,122 @@ const resolveCompactApp = async (
101
152
  resolvedAppDirectory
102
153
  );
103
154
  });
155
+ if (!isRecord(app)) {
156
+ throw new Error(
157
+ `[cloudflare-delivery-unit] ${appDirectory} is missing from the declared reference topology.`,
158
+ );
159
+ }
104
160
  return {
105
- ...(isRecord(app) ? { app } : {}),
161
+ app,
162
+ role:
163
+ app === topology.shell ||
164
+ (Array.isArray(topology.shells) && topology.shells.includes(app))
165
+ ? 'shell'
166
+ : 'microvertical',
106
167
  workspaceRoot,
107
168
  };
108
169
  };
109
170
 
110
- const createDeliveryUnitStamp = (
171
+ /** Resolve the producer role from this app's authoritative topology membership. */
172
+ export const resolveTopologyReleaseRole = async (
173
+ appDirectory: string,
174
+ ): Promise<'microvertical' | 'shell' | undefined> => {
175
+ const resolved = await resolveTopologyApp(appDirectory);
176
+ if (!resolved?.app) return undefined;
177
+ const kind = resolved.role === 'shell' ? 'shell' : 'vertical';
178
+ if (resolved.app.kind !== kind) {
179
+ throw new Error(
180
+ '[cloudflare-delivery-unit] Declared app kind disagrees with its topology membership.',
181
+ );
182
+ }
183
+ return resolved.role;
184
+ };
185
+
186
+ const createTopologyDeliveryUnitStamp = (
111
187
  identity: DeliveryUnitIdentity,
112
- app?: Record<string, unknown>,
113
- ): DeliveryUnitStamp => {
114
- const surfaceProfile = nonEmptyString(app?.surfaceProfile);
188
+ app: Record<string, unknown>,
189
+ ): TopologyDeliveryUnit => {
190
+ const surfaceProfile = nonEmptyString(app.surfaceProfile);
191
+ if (
192
+ surfaceProfile !== undefined &&
193
+ !['api-only', 'ui-only', 'full-stack'].includes(surfaceProfile)
194
+ ) {
195
+ throw new Error(
196
+ '[cloudflare-delivery-unit] Declared app has an invalid surface profile.',
197
+ );
198
+ }
115
199
  const emitsUi = surfaceProfile !== 'api-only';
116
200
  const emitsApi = surfaceProfile !== 'ui-only';
201
+ const appId = nonEmptyString(app.id);
202
+ if (!appId)
203
+ throw new Error('[cloudflare-delivery-unit] Declared app requires an id.');
204
+ let ui: TopologyUiSurface | undefined;
205
+ if (emitsUi) {
206
+ if (RENDERER_PROJECTION_FIELDS.every(field => !Object.hasOwn(app, field))) {
207
+ ui = { ...identity, surface: 'ui' };
208
+ } else {
209
+ const routerBindings = isRecord(app.routerBindings)
210
+ ? app.routerBindings
211
+ : {};
212
+ const errors = [
213
+ ...validateRendererIdentity(app.rendererIdentity).errors,
214
+ ...validateRendererProfile(app.rendererProfile).errors,
215
+ ...validateRendererRouterBindings(
216
+ app.routerBindings,
217
+ Object.keys(routerBindings),
218
+ 'routerBindings',
219
+ ).errors,
220
+ ];
221
+ if (errors.length) {
222
+ throw new Error(
223
+ `[cloudflare-delivery-unit] ${formatBackendFederationValidationErrors(errors)}`,
224
+ );
225
+ }
226
+ const rendererIdentity = app.rendererIdentity as RendererIdentity;
227
+ const rendererProfile = app.rendererProfile as RendererProfile;
228
+ if (!Object.hasOwn(routerBindings, rendererIdentity.entryName))
229
+ throw new Error(
230
+ '[cloudflare-delivery-unit] Topology routerBindings must include the primary renderer identity entry.',
231
+ );
232
+ const declaredIdentity = toDeliveryUnitIdentity(app.deliveryUnit);
233
+ if (
234
+ rendererIdentity.renderer !== app.renderer ||
235
+ rendererIdentity.renderer !== rendererProfile.renderer ||
236
+ rendererIdentity.appId !== app.id ||
237
+ rendererIdentity.buildId !== declaredIdentity?.buildMarker
238
+ ) {
239
+ throw new Error(
240
+ '[cloudflare-delivery-unit] Topology UI renderer metadata must match its app and delivery-unit identity.',
241
+ );
242
+ }
243
+ ui = {
244
+ ...identity,
245
+ surface: 'ui',
246
+ rendererIdentity: {
247
+ ...rendererIdentity,
248
+ buildId: identity.buildMarker,
249
+ },
250
+ rendererProfile,
251
+ routerBindings: immutableRendererRouterBindings(
252
+ routerBindings as RendererRouterBindings,
253
+ ),
254
+ };
255
+ }
256
+ } else if (
257
+ app.rendererIdentity !== undefined ||
258
+ app.rendererProfile !== undefined ||
259
+ app.routerBindings !== undefined
260
+ ) {
261
+ throw new Error(
262
+ '[cloudflare-delivery-unit] API-only topology must not declare a UI renderer identity or profile.',
263
+ );
264
+ }
117
265
 
118
266
  return {
119
267
  ...identity,
268
+ appId,
120
269
  surfaces: {
121
- ...(emitsUi ? { ui: { ...identity, surface: 'ui' as const } } : {}),
270
+ ...(ui ? { ui } : {}),
122
271
  ...(emitsApi ? { api: { ...identity, surface: 'api' as const } } : {}),
123
272
  },
124
273
  };
@@ -126,53 +275,127 @@ const createDeliveryUnitStamp = (
126
275
 
127
276
  /**
128
277
  * Resolve the delivery-unit record declared for this app by the workspace
129
- * compact config (`.modernjs/ultramodern.json`). This is the topology source
278
+ * reference topology. This is the topology source
130
279
  * of truth the Cloudflare worker snapshot is verified against.
131
280
  */
132
281
  export const resolveTopologyDeliveryUnit = async (
133
282
  appDirectory: string,
134
- ): Promise<DeliveryUnitStamp | undefined> => {
135
- const resolved = await resolveCompactApp(appDirectory);
283
+ ): Promise<TopologyDeliveryUnit | undefined> => {
284
+ const resolved = await resolveTopologyApp(appDirectory);
136
285
  if (!resolved?.app) {
137
286
  return undefined;
138
287
  }
139
288
  const identity = toDeliveryUnitIdentity(resolved.app.deliveryUnit);
140
- return identity
141
- ? createDeliveryUnitStamp(
142
- stampReleaseIdentity(identity, resolved.workspaceRoot),
143
- resolved.app,
144
- )
145
- : undefined;
289
+ if (!identity) {
290
+ throw new Error(
291
+ '[cloudflare-delivery-unit] Declared app is missing a valid delivery-unit identity.',
292
+ );
293
+ }
294
+ return createTopologyDeliveryUnitStamp(
295
+ stampReleaseIdentity(identity, resolved.workspaceRoot),
296
+ resolved.app,
297
+ );
146
298
  };
147
299
 
148
300
  /**
149
- * Resolve the delivery-unit identity actually bundled into the worker by
150
- * reading the generated `shared/ultramodern-build.json` artifact. This is the
151
- * worker snapshot / declared surface source that gets stamped into the manifest.
301
+ * Preserve the finalized build artifact already stamped from renderer-build.json.
302
+ * The generation artifact cannot attest the compiled application.
152
303
  */
153
304
  export const resolveWorkerDeliveryUnitStamp = async (
154
305
  appDirectory: string,
306
+ distDirectory: string,
155
307
  ): Promise<DeliveryUnitStamp | undefined> => {
156
308
  const buildArtifactPath = path.join(
157
- appDirectory,
158
- ULTRAMODERN_BUILD_ARTIFACT_PATH,
309
+ distDirectory,
310
+ ULTRAMODERN_BUILD_ARTIFACT_FILE,
159
311
  );
160
- let identity: DeliveryUnitIdentity | undefined;
161
-
162
- if (await fse.pathExists(buildArtifactPath)) {
163
- const artifact = await fse.readJSON(buildArtifactPath);
164
- if (!isUltramodernBuildArtifact(artifact)) {
165
- return undefined;
166
- }
167
- identity = toDeliveryUnitIdentity(artifact.deliveryUnit);
168
- }
169
-
170
- if (!identity) {
312
+ if (!(await fse.pathExists(buildArtifactPath))) {
313
+ if (
314
+ await fse.pathExists(
315
+ path.join(appDirectory, ULTRAMODERN_BUILD_ARTIFACT_PATH),
316
+ )
317
+ )
318
+ throw new Error(
319
+ '[cloudflare-delivery-unit] Finalized build artifact is required before worker output stamping.',
320
+ );
171
321
  return undefined;
172
322
  }
173
- const resolved = await resolveCompactApp(appDirectory);
174
- if (resolved) {
175
- identity = stampReleaseIdentity(identity, resolved.workspaceRoot);
323
+ const artifact: unknown = await fse.readJSON(buildArtifactPath);
324
+ assertUltramodernBuildArtifact(artifact);
325
+ const identity: DeliveryUnitIdentity = {
326
+ unitId: artifact.deliveryUnit.unitId,
327
+ buildMarker: artifact.deliveryUnit.buildMarker,
328
+ sourceRevision: artifact.deliveryUnit.sourceRevision,
329
+ };
330
+ const resolved = await resolveTopologyApp(appDirectory);
331
+ const stampedArtifact = artifact;
332
+ let emitsApi = true;
333
+ if (resolved?.app) {
334
+ if (stampedArtifact.deliveryUnit.appId !== resolved.app.id) {
335
+ throw new Error(
336
+ '[cloudflare-delivery-unit] Build artifact appId must match the declared topology app.',
337
+ );
338
+ }
339
+ const declaredIdentity = toDeliveryUnitIdentity(resolved.app.deliveryUnit);
340
+ if (!declaredIdentity) {
341
+ throw new Error(
342
+ '[cloudflare-delivery-unit] Declared app is missing a valid delivery-unit identity.',
343
+ );
344
+ }
345
+ const expected = createTopologyDeliveryUnitStamp(identity, resolved.app);
346
+ for (const field of ['unitId'] as const) {
347
+ if (declaredIdentity[field] !== identity[field]) {
348
+ throw new Error(
349
+ `[cloudflare-delivery-unit] Build artifact ${field} must match the declared topology delivery-unit identity.`,
350
+ );
351
+ }
352
+ }
353
+ const ui = stampedArtifact.surfaces.ui;
354
+ if (Boolean(expected.surfaces.ui) !== Boolean(ui)) {
355
+ throw new Error(
356
+ '[cloudflare-delivery-unit] Build artifact UI surface must match the declared topology surface profile.',
357
+ );
358
+ }
359
+ if (expected.surfaces.ui?.rendererIdentity && ui) {
360
+ if (
361
+ !isDeepStrictEqual(
362
+ expected.surfaces.ui.routerBindings,
363
+ ui.routerBindings,
364
+ )
365
+ )
366
+ throw new Error(
367
+ '[cloudflare-delivery-unit] Finalized build routerBindings must match the declared topology router bindings.',
368
+ );
369
+ for (const field of [
370
+ 'renderer',
371
+ 'appId',
372
+ 'entryName',
373
+ 'protocolVersion',
374
+ 'buildId',
375
+ ] as const) {
376
+ if (
377
+ expected.surfaces.ui.rendererIdentity[field] !==
378
+ ui.rendererIdentity[field]
379
+ ) {
380
+ throw new Error(
381
+ `[cloudflare-delivery-unit] Build artifact UI rendererIdentity.${field} must match the declared topology renderer identity.`,
382
+ );
383
+ }
384
+ }
385
+ assertRendererProfileCompatibility(
386
+ expected.surfaces.ui.rendererProfile,
387
+ ui.rendererProfile,
388
+ );
389
+ }
390
+ emitsApi = expected.surfaces.api !== undefined;
176
391
  }
177
- return createDeliveryUnitStamp(identity, resolved?.app);
392
+ return {
393
+ ...stampedArtifact.deliveryUnit,
394
+ surfaces: {
395
+ ...(emitsApi ? { api: stampedArtifact.surfaces.api } : {}),
396
+ ...(stampedArtifact.surfaces.ui
397
+ ? { ui: stampedArtifact.surfaces.ui }
398
+ : {}),
399
+ },
400
+ };
178
401
  };
@@ -1,4 +1,9 @@
1
1
  import path from 'node:path';
2
+ import {
3
+ BACKEND_FEDERATION_MANIFEST_FILE,
4
+ BACKEND_FEDERATION_REMOTE_ENTRY_FILE,
5
+ ULTRAMODERN_BUILD_ARTIFACT_FILE,
6
+ } from '@modern-js/backend-federation-contracts';
2
7
  import { fs as fse } from '@modern-js/utils';
3
8
  import { createCloudflareOutputPlan } from '../cloudflare-output-plan';
4
9
  import { assertCloudflareOutput } from '../cloudflare-output-verifier/index';
@@ -22,14 +27,8 @@ import {
22
27
  ROUTE_SPEC_FILE,
23
28
  ROUTE_SPEC_OUTPUT,
24
29
  WORKER_BUNDLE_DIRECTORY,
25
- WORKER_ENTRY,
26
- WORKER_MANIFEST,
27
- WRANGLER_CONFIG_FILE,
28
30
  } from './constants';
29
- import {
30
- resolveTopologyDeliveryUnit,
31
- resolveWorkerDeliveryUnitStamp,
32
- } from './delivery-unit';
31
+ import { resolveWorkerDeliveryUnitStamp } from './delivery-unit';
33
32
  import type { CreateCloudflarePreset } from './types';
34
33
  import {
35
34
  createWorkerManifest,
@@ -87,21 +86,19 @@ export const createCloudflarePreset: CreateCloudflarePreset = ({
87
86
  '@modern-js/ultramodern-release-envelope',
88
87
  );
89
88
  let hasReleaseEnvelope = false;
89
+ let declaredPublicAssets: string[] = [];
90
90
 
91
91
  return {
92
92
  async prepare() {
93
93
  await fse.remove(outputDirectory);
94
94
  },
95
95
  async writeOutput() {
96
- if (
97
- releaseEnvelopeEnabled &&
98
- (await fse.pathExists(
99
- path.join(distDirectory, MICROVERTICAL_RELEASE_ENVELOPE_PATH),
100
- ))
101
- ) {
96
+ if (releaseEnvelopeEnabled) {
102
97
  await emitFrameworkMicroVerticalReleaseEnvelope({
103
98
  apiOnly,
99
+ appDirectory,
104
100
  distDirectory,
101
+ role: modernConfig.deploy?.releaseEnvelopeRole,
105
102
  target: 'cloudflare',
106
103
  });
107
104
  }
@@ -113,6 +110,20 @@ export const createCloudflarePreset: CreateCloudflarePreset = ({
113
110
  const relativePath = path
114
111
  .relative(distDirectory, src)
115
112
  .replace(/\\/gu, '/');
113
+ // API-only builds emit server modules at arbitrary paths under dist,
114
+ // including transitive imports outside api/. Only the framework's
115
+ // published backend contract belongs beside the explicit public tree.
116
+ if (
117
+ apiOnly &&
118
+ relativePath !== '' &&
119
+ ![
120
+ BACKEND_FEDERATION_MANIFEST_FILE,
121
+ BACKEND_FEDERATION_REMOTE_ENTRY_FILE,
122
+ ULTRAMODERN_BUILD_ARTIFACT_FILE,
123
+ ].includes(relativePath)
124
+ ) {
125
+ return false;
126
+ }
116
127
  return (
117
128
  relativePath !== 'release' &&
118
129
  !relativePath.startsWith('release/') &&
@@ -173,9 +184,9 @@ export const createCloudflarePreset: CreateCloudflarePreset = ({
173
184
  outputDirectory,
174
185
  cloudflareArtifacts,
175
186
  );
176
- await copyCloudflarePublicAssets(
187
+ declaredPublicAssets = await copyCloudflarePublicAssets(
177
188
  appDirectory,
178
- publicDirectory,
189
+ outputDirectory,
179
190
  getCloudflarePublicAssets(modernConfig),
180
191
  );
181
192
  await copyCloudflareD1Migrations(
@@ -192,8 +203,10 @@ export const createCloudflarePreset: CreateCloudflarePreset = ({
192
203
  },
193
204
  );
194
205
 
195
- const deliveryUnitStamp =
196
- await resolveWorkerDeliveryUnitStamp(appDirectory);
206
+ const deliveryUnitStamp = await resolveWorkerDeliveryUnitStamp(
207
+ appDirectory,
208
+ distDirectory,
209
+ );
197
210
  await fse.writeJSON(
198
211
  workerManifestPath,
199
212
  await createWorkerManifest(
@@ -232,8 +245,10 @@ export const createCloudflarePreset: CreateCloudflarePreset = ({
232
245
  createWorkerModuleLoaders(manifest),
233
246
  ),
234
247
  );
235
- const topologyDeliveryUnit =
236
- await resolveTopologyDeliveryUnit(appDirectory);
248
+ const topologyDeliveryUnit = await resolveWorkerDeliveryUnitStamp(
249
+ appDirectory,
250
+ distDirectory,
251
+ );
237
252
  await assertCloudflareOutput({
238
253
  outputDirectory,
239
254
  importWorker: false,
@@ -241,6 +256,7 @@ export const createCloudflarePreset: CreateCloudflarePreset = ({
241
256
  });
242
257
  if (hasReleaseEnvelope) {
243
258
  await emitCloudflareStagedReleaseEnvelope({
259
+ declaredPublicAssets,
244
260
  distDirectory,
245
261
  outputDirectory,
246
262
  });