@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
@@ -0,0 +1,117 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { WORKER_BUNDLE_DIRECTORY } from './constants';
4
+ import type { WorkerRendererBuild } from './renderer-identity';
5
+ import { isRecord } from './utils';
6
+
7
+ /**
8
+ * Build-validated native document inputs for each worker route. The Node host
9
+ * reads these from `dist`; a module worker has no filesystem, so the native
10
+ * build writes them beside its worker bundles and deploy inlines them.
11
+ */
12
+ export const NATIVE_RENDERER_WORKER_RESOURCES_FILE = `${WORKER_BUNDLE_DIRECTORY}/native-renderer.json`;
13
+
14
+ export interface NativeRendererWorkerEntry {
15
+ readonly assets: readonly unknown[];
16
+ readonly nativeManifest?: unknown;
17
+ readonly hydrationBuildId?: string;
18
+ readonly serverConfig: {
19
+ readonly ssr: boolean | 'stream';
20
+ readonly ssrByRouteIds?: readonly string[];
21
+ };
22
+ readonly csrFallbackHeader?: string;
23
+ readonly nonce?: string;
24
+ }
25
+
26
+ export interface NativeRendererWorkerResources {
27
+ readonly schema: 'ultramodern-native-worker-resources';
28
+ readonly version: 1;
29
+ /** The built renderer, whose adapter serves native documents. */
30
+ readonly renderer: string;
31
+ readonly entries: Readonly<Record<string, NativeRendererWorkerEntry>>;
32
+ }
33
+
34
+ const invalid = (detail: string) =>
35
+ new Error(
36
+ `Invalid native renderer worker resources (${NATIVE_RENDERER_WORKER_RESOURCES_FILE}): ${detail}. Rebuild the application with deploy.worker.ssr before deploying.`,
37
+ );
38
+
39
+ function validateEntry(entryName: string, value: unknown) {
40
+ if (!isRecord(value)) throw invalid(`entry ${entryName} is not an object`);
41
+ const { assets, hydrationBuildId, serverConfig, csrFallbackHeader, nonce } =
42
+ value;
43
+ if (!Array.isArray(assets) || !assets.length)
44
+ throw invalid(`entry ${entryName} has no document assets`);
45
+ if (
46
+ hydrationBuildId !== undefined &&
47
+ (typeof hydrationBuildId !== 'string' || !hydrationBuildId.trim())
48
+ )
49
+ throw invalid(`entry ${entryName} has an empty hydration build ID`);
50
+ if (
51
+ !isRecord(serverConfig) ||
52
+ ![true, false, 'stream'].includes(serverConfig.ssr as never) ||
53
+ (serverConfig.ssrByRouteIds !== undefined &&
54
+ (!Array.isArray(serverConfig.ssrByRouteIds) ||
55
+ serverConfig.ssrByRouteIds.some(id => typeof id !== 'string')))
56
+ )
57
+ throw invalid(`entry ${entryName} has an invalid server config`);
58
+ for (const [field, text] of [
59
+ ['csrFallbackHeader', csrFallbackHeader],
60
+ ['nonce', nonce],
61
+ ] as const)
62
+ if (text !== undefined && (typeof text !== 'string' || !text))
63
+ throw invalid(`entry ${entryName} has an invalid ${field}`);
64
+ }
65
+
66
+ /**
67
+ * Read the native worker resources for a built application. Every built entry
68
+ * of the resources' renderer must have validated document inputs.
69
+ */
70
+ export async function readNativeRendererWorkerResources(
71
+ distDirectory: string,
72
+ build: WorkerRendererBuild | undefined,
73
+ ): Promise<NativeRendererWorkerResources | undefined> {
74
+ let bytes: string;
75
+ try {
76
+ bytes = await fs.readFile(
77
+ path.join(distDirectory, NATIVE_RENDERER_WORKER_RESOURCES_FILE),
78
+ 'utf8',
79
+ );
80
+ } catch (error) {
81
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
82
+ return undefined;
83
+ throw error;
84
+ }
85
+ const value: unknown = JSON.parse(bytes);
86
+ if (
87
+ !isRecord(value) ||
88
+ value.schema !== 'ultramodern-native-worker-resources' ||
89
+ value.version !== 1 ||
90
+ typeof value.renderer !== 'string' ||
91
+ !value.renderer ||
92
+ !isRecord(value.entries)
93
+ )
94
+ throw invalid('unknown schema');
95
+ if (
96
+ value.renderer !== build?.renderer.name ||
97
+ !build.renderer.nativeDocuments
98
+ )
99
+ throw invalid(
100
+ `renderer ${value.renderer} is not the built native-document renderer`,
101
+ );
102
+ const entries = value.entries;
103
+ const nativeEntries = Object.entries(build.identities).filter(
104
+ ([, identity]) => identity.renderer === value.renderer,
105
+ );
106
+ for (const [entryName] of nativeEntries)
107
+ if (!Object.hasOwn(entries, entryName))
108
+ throw invalid(`entry ${entryName} is missing`);
109
+ for (const [entryName, entry] of Object.entries(entries)) {
110
+ if (!nativeEntries.some(([name]) => name === entryName))
111
+ throw invalid(
112
+ `entry ${entryName} has no built ${value.renderer} renderer identity`,
113
+ );
114
+ validateEntry(entryName, entry);
115
+ }
116
+ return value as unknown as NativeRendererWorkerResources;
117
+ }
@@ -0,0 +1,171 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { isDeepStrictEqual } from 'node:util';
4
+ import {
5
+ formatBackendFederationValidationErrors,
6
+ type RendererIdentity,
7
+ validateRendererIdentity,
8
+ validateRendererProfile,
9
+ } from '@modern-js/backend-federation-contracts';
10
+ import type { DeliveryUnitStamp } from './delivery-unit';
11
+ import { isRecord } from './utils';
12
+
13
+ export type WorkerRendererIdentities = Readonly<
14
+ Record<string, RendererIdentity>
15
+ >;
16
+
17
+ /** The built renderer and how its adapter serves worker documents. */
18
+ export interface WorkerRenderer {
19
+ readonly name: string;
20
+ /** Documents come only from the renderer's native server handler. */
21
+ readonly nativeDocuments: boolean;
22
+ readonly rsc: boolean;
23
+ }
24
+
25
+ export interface WorkerRendererBuild {
26
+ readonly renderer: WorkerRenderer;
27
+ readonly identities: WorkerRendererIdentities;
28
+ }
29
+
30
+ /** Bind response metadata to the finalized build and its generated entries. */
31
+ export async function readWorkerRendererBuild(
32
+ distDirectory: string,
33
+ routes: readonly {
34
+ entryName?: unknown;
35
+ entryPath?: unknown;
36
+ isApi?: unknown;
37
+ isSSR?: unknown;
38
+ worker?: unknown;
39
+ bundle?: unknown;
40
+ isRSC?: unknown;
41
+ isStream?: unknown;
42
+ }[],
43
+ deliveryUnit?: DeliveryUnitStamp,
44
+ ): Promise<WorkerRendererBuild | undefined> {
45
+ let bytes: string;
46
+ try {
47
+ bytes = await fs.readFile(
48
+ path.join(distDirectory, 'renderer-build.json'),
49
+ 'utf8',
50
+ );
51
+ } catch (error) {
52
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
53
+ return undefined;
54
+ throw error;
55
+ }
56
+ const build: unknown = JSON.parse(bytes);
57
+ if (
58
+ !isRecord(build) ||
59
+ build.schema !== 'ultramodern-renderer-build' ||
60
+ build.version !== 2 ||
61
+ typeof build.buildId !== 'string' ||
62
+ !/^[a-f0-9]{64}$/u.test(build.buildId) ||
63
+ !isRecord(build.profile) ||
64
+ build.renderer !== build.profile.renderer ||
65
+ !isRecord(build.worker) ||
66
+ typeof build.worker.nativeDocuments !== 'boolean' ||
67
+ typeof build.worker.rsc !== 'boolean' ||
68
+ !isRecord(build.entries) ||
69
+ !Object.keys(build.entries).length
70
+ )
71
+ throw new Error(
72
+ 'Invalid or outdated renderer-build.json for the Cloudflare worker; rebuild the application.',
73
+ );
74
+
75
+ // The SDK profile also carries compiler configuration. Only the neutral
76
+ // renderer protocol fields belong to the worker response binding.
77
+ const { renderer, protocolVersion, compiler, hydration, router } =
78
+ build.profile;
79
+ const validation = validateRendererProfile({
80
+ renderer,
81
+ protocolVersion,
82
+ compiler,
83
+ hydration,
84
+ router,
85
+ });
86
+ if (!validation.ok)
87
+ throw new Error(formatBackendFederationValidationErrors(validation.errors));
88
+
89
+ const identities: Record<string, RendererIdentity> = {};
90
+ let appId: string | undefined;
91
+ for (const [entryName, value] of Object.entries(build.entries)) {
92
+ const validation = validateRendererIdentity(value);
93
+ if (!validation.ok)
94
+ throw new Error(
95
+ formatBackendFederationValidationErrors(validation.errors),
96
+ );
97
+ const identity = value as RendererIdentity;
98
+ if (
99
+ entryName === '__proto__' ||
100
+ identity.entryName !== entryName ||
101
+ identity.renderer !== renderer ||
102
+ identity.protocolVersion !== protocolVersion ||
103
+ identity.buildId !== build.buildId ||
104
+ (appId !== undefined && identity.appId !== appId)
105
+ )
106
+ throw new Error(
107
+ 'Cloudflare renderer entry identity conflicts with its built manifest',
108
+ );
109
+ appId = identity.appId;
110
+ identities[entryName] = Object.freeze({ ...identity });
111
+ }
112
+ for (const route of routes) {
113
+ // Native public routes name a copied file rather than a renderer entry.
114
+ // Classify the original route shape so application dispatch markers cannot
115
+ // be erased by the worker manifest's normalization.
116
+ if (
117
+ route.isSSR === false &&
118
+ route.entryName === undefined &&
119
+ route.worker === undefined &&
120
+ route.bundle === undefined &&
121
+ (route.isRSC === undefined || route.isRSC === false) &&
122
+ (route.isStream === undefined || route.isStream === false) &&
123
+ typeof route.entryPath === 'string' &&
124
+ route.entryPath.startsWith('public/') &&
125
+ !route.entryPath.includes('\\') &&
126
+ path.posix.normalize(route.entryPath) === route.entryPath
127
+ ) {
128
+ const asset = await fs.stat(path.join(distDirectory, route.entryPath));
129
+ if (asset.isFile()) continue;
130
+ }
131
+ // The BFF prefix route is served by the API worker, never a renderer
132
+ // entry; it carries no document, bundle or dispatch marker.
133
+ if (
134
+ route.isApi === true &&
135
+ route.isSSR === false &&
136
+ route.entryName === undefined &&
137
+ route.entryPath === '' &&
138
+ route.worker === undefined &&
139
+ route.bundle === undefined &&
140
+ (route.isRSC === undefined || route.isRSC === false) &&
141
+ (route.isStream === undefined || route.isStream === false)
142
+ )
143
+ continue;
144
+ if (
145
+ typeof route.entryName !== 'string' ||
146
+ !Object.hasOwn(identities, route.entryName)
147
+ )
148
+ throw new Error(
149
+ `Cloudflare generated route has no built renderer identity for ${String(route.entryName)}`,
150
+ );
151
+ }
152
+ const ui = deliveryUnit?.surfaces.ui;
153
+ if (
154
+ ui &&
155
+ !isDeepStrictEqual(
156
+ identities[ui.rendererIdentity.entryName],
157
+ ui.rendererIdentity,
158
+ )
159
+ )
160
+ throw new Error(
161
+ 'Cloudflare built renderer identity conflicts with its finalized delivery unit',
162
+ );
163
+ return Object.freeze({
164
+ renderer: Object.freeze({
165
+ name: renderer as string,
166
+ nativeDocuments: build.worker.nativeDocuments,
167
+ rsc: build.worker.rsc,
168
+ }),
169
+ identities: Object.freeze(identities),
170
+ });
171
+ }
@@ -1,6 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import type {} from '@modern-js/server-runtime-extensions/server-config';
3
3
  import { fs as fse } from '@modern-js/utils';
4
+ import { getCloudflareWorkerRouteDataEntryName } from '../cloudflare-output-contract';
4
5
  import { readRouteSpec } from './artifacts';
5
6
  import {
6
7
  ASSETS_BINDING,
@@ -21,12 +22,16 @@ import {
21
22
  } from './constants';
22
23
  import type { DeliveryUnitStamp } from './delivery-unit';
23
24
  import { createI18nWorkerManifest } from './i18n-worker';
25
+ import { readNativeRendererWorkerResources } from './native-renderer';
26
+ import { readWorkerRendererBuild } from './renderer-identity';
24
27
  import { createCloudflareWorkerSecurityPolicy } from './security-policies';
25
28
  import type { CloudflareAppContext, CloudflareModernConfig } from './types';
26
29
  import { isRecord, normalizeRelativePath } from './utils';
27
30
  import {
28
31
  createWorkerManifestServiceBindings,
29
32
  createWorkerServiceBindings,
33
+ createWorkerVpcServiceBindings,
34
+ getWorkerEffectBffPrefix,
30
35
  } from './wrangler-config';
31
36
 
32
37
  const createMissingEffectBffWorkerError = (
@@ -118,7 +123,38 @@ const createModuleFederationWorkerManifest = async (
118
123
  ];
119
124
  });
120
125
 
121
- return name && exposes.length > 0 ? { name, exposes } : undefined;
126
+ if (!name || exposes.length === 0) return undefined;
127
+
128
+ // A fragment is rendered by this app's own route, so it depends on the
129
+ // route stylesheets too (for example a global Tailwind sheet no exposed
130
+ // component imports). Bundle them so the worker reports them to the
131
+ // composing shell without reading assets at request time.
132
+ const routeManifestPath = path.join(
133
+ outputDirectory,
134
+ PUBLIC_ASSETS_DIRECTORY,
135
+ ROUTE_MANIFEST_FILE,
136
+ );
137
+ const routeManifest = (await fse.pathExists(routeManifestPath))
138
+ ? ((await fse.readJson(routeManifestPath)) as unknown)
139
+ : undefined;
140
+ const routeAssets =
141
+ isRecord(routeManifest) && isRecord(routeManifest.routeAssets)
142
+ ? Object.values(routeManifest.routeAssets).filter(isRecord)
143
+ : [];
144
+ const routeCss = [
145
+ ...new Set(
146
+ routeAssets.flatMap(routeAsset =>
147
+ [routeAsset.referenceCssAssets, routeAsset.assets]
148
+ .flatMap(value => (Array.isArray(value) ? value : []))
149
+ .filter(
150
+ (asset): asset is string =>
151
+ typeof asset === 'string' && asset.endsWith('.css'),
152
+ ),
153
+ ),
154
+ ),
155
+ ];
156
+
157
+ return { name, exposes, routeCss };
122
158
  };
123
159
 
124
160
  export const createWorkerManifest = async (
@@ -132,6 +168,15 @@ export const createWorkerManifest = async (
132
168
  routeSpec.routes.map(async (route: Record<string, any>) => {
133
169
  const worker =
134
170
  typeof route.worker === 'string' ? route.worker : undefined;
171
+ const routeDataWorker =
172
+ route.isSSR && typeof route.entryName === 'string'
173
+ ? `${WORKER_BUNDLE_DIRECTORY}/${getCloudflareWorkerRouteDataEntryName(
174
+ route.entryName,
175
+ )}.js`
176
+ : undefined;
177
+ const hasRouteDataWorker = routeDataWorker
178
+ ? await fse.pathExists(path.join(outputDirectory, routeDataWorker))
179
+ : false;
135
180
 
136
181
  return {
137
182
  urlPath: route.urlPath,
@@ -142,24 +187,68 @@ export const createWorkerManifest = async (
142
187
  workerExists: worker
143
188
  ? await fse.pathExists(path.join(outputDirectory, worker))
144
189
  : false,
190
+ ...(hasRouteDataWorker ? { routeDataWorker } : {}),
191
+ // Native workers apply configured route headers as the Node host does.
192
+ ...(route.responseHeaders && typeof route.responseHeaders === 'object'
193
+ ? { responseHeaders: route.responseHeaders }
194
+ : {}),
145
195
  };
146
196
  }),
147
197
  );
198
+ const rendererBuild = await readWorkerRendererBuild(
199
+ appContext.distDirectory,
200
+ routeSpec.routes,
201
+ deliveryUnitStamp,
202
+ );
203
+ const rendererIdentities = rendererBuild?.identities;
204
+ const nativeRenderer = await readNativeRendererWorkerResources(
205
+ appContext.distDirectory,
206
+ rendererBuild,
207
+ );
208
+ // Native-document renderers serve pages only through their native server
209
+ // handler. Fail deploy before emitting an entry that could only reject.
210
+ for (const route of routes) {
211
+ const renderer =
212
+ typeof route.entryName === 'string' &&
213
+ rendererIdentities &&
214
+ Object.hasOwn(rendererIdentities, route.entryName)
215
+ ? rendererIdentities[route.entryName].renderer
216
+ : undefined;
217
+ if (
218
+ renderer !== undefined &&
219
+ rendererBuild?.renderer.nativeDocuments &&
220
+ (nativeRenderer?.renderer !== renderer || !route.workerExists)
221
+ )
222
+ throw new Error(
223
+ `Cloudflare worker deploy of the ${renderer} renderer requires its native worker build for entry ${route.entryName}. Set deploy.worker.ssr: true and rebuild with the Cloudflare deploy target.`,
224
+ );
225
+ }
148
226
 
149
- const bffPrefix = modernConfig.bff?.prefix;
150
- const primaryBffPrefix = Array.isArray(bffPrefix) ? bffPrefix[0] : bffPrefix;
151
227
  const isEffectApi =
152
228
  Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
229
+ const effectBffPrefix = getWorkerEffectBffPrefix(modernConfig);
153
230
  const effectApiWorkerExists = await fse.pathExists(
154
231
  path.join(outputDirectory, BFF_EFFECT_WORKER_ENTRY),
155
232
  );
233
+ const typedServiceBindings = createWorkerServiceBindings(
234
+ modernConfig,
235
+ undefined,
236
+ );
156
237
  const serviceBindings = createWorkerManifestServiceBindings(
157
- createWorkerServiceBindings(modernConfig, undefined),
238
+ typedServiceBindings,
239
+ // The wrangler config checks VPC names against every Worker binding; the
240
+ // manifest only needs the framework-owned assets binding reserved.
241
+ createWorkerVpcServiceBindings(
242
+ modernConfig,
243
+ undefined,
244
+ typedServiceBindings,
245
+ new Set([ASSETS_BINDING]),
246
+ ),
158
247
  );
159
248
  const moduleFederation =
160
249
  await createModuleFederationWorkerManifest(outputDirectory);
161
250
 
162
- if (isEffectApi && primaryBffPrefix && !effectApiWorkerExists) {
251
+ if (effectBffPrefix !== undefined && !effectApiWorkerExists) {
163
252
  throw createMissingEffectBffWorkerError(
164
253
  outputDirectory,
165
254
  BFF_EFFECT_WORKER_ENTRY,
@@ -199,13 +288,17 @@ export const createWorkerManifest = async (
199
288
  security: createCloudflareWorkerSecurityPolicy(modernConfig),
200
289
  ...(moduleFederation === undefined ? {} : { moduleFederation }),
201
290
  ...(deliveryUnitStamp ? { deliveryUnit: deliveryUnitStamp } : {}),
291
+ ...(rendererBuild
292
+ ? { renderer: rendererBuild.renderer, rendererIdentities }
293
+ : {}),
294
+ ...(nativeRenderer ? { nativeRenderer } : {}),
202
295
  i18n: createI18nWorkerManifest(routeSpec, appContext),
203
296
  bff:
204
- isEffectApi && primaryBffPrefix && effectApiWorkerExists
297
+ effectBffPrefix !== undefined && effectApiWorkerExists
205
298
  ? {
206
299
  dispatcherExport: BFF_EFFECT_WORKER_DISPATCHER_EXPORT,
207
300
  runtimeFramework: 'effect',
208
- prefix: primaryBffPrefix,
301
+ prefix: effectBffPrefix,
209
302
  worker: BFF_EFFECT_WORKER_ENTRY,
210
303
  effect: effectBffManifest,
211
304
  }
@@ -218,9 +311,14 @@ export const createWorkerModuleLoaders = (manifest: any) => {
218
311
  const imports = new Map<string, string>();
219
312
 
220
313
  for (const route of manifest.routeSpec.routes) {
221
- if (route.worker && route.workerExists) {
222
- const importPath = `../${String(route.worker).replace(/^\/+/u, '')}`;
223
- imports.set(route.worker, `() => import(${JSON.stringify(importPath)})`);
314
+ for (const worker of [
315
+ route.workerExists ? route.worker : undefined,
316
+ route.routeDataWorker,
317
+ ]) {
318
+ if (worker) {
319
+ const importPath = `../${String(worker).replace(/^\/+/u, '')}`;
320
+ imports.set(worker, `() => import(${JSON.stringify(importPath)})`);
321
+ }
224
322
  }
225
323
  }
226
324
 
@@ -0,0 +1,60 @@
1
+ import type { RequestHandlerOptions } from '@modern-js/server-core';
2
+
3
+ /** The native context supplied to a Cloudflare module worker's fetch method. */
4
+ export interface CloudflareExecutionContext {
5
+ waitUntil(promise: Promise<unknown>): void;
6
+ passThroughOnException(): void;
7
+ }
8
+
9
+ /** Worker request bindings follow the shared RequestPlatform kind/bindings shape. */
10
+ export interface CloudflareWorkerPlatform<Bindings extends object = object> {
11
+ readonly kind: 'worker';
12
+ /** The original env object. Direct worker invocations may omit it. */
13
+ readonly bindings: Bindings | undefined;
14
+ }
15
+
16
+ /** Request-owned options for manifest HTML and Flight worker handlers. */
17
+ export interface CloudflareWorkerRequestHandlerOptions<
18
+ Bindings extends object = object,
19
+ > extends RequestHandlerOptions {
20
+ readonly platform: CloudflareWorkerPlatform<Bindings>;
21
+ /** The original context, including its native method receivers. */
22
+ readonly executionContext: CloudflareExecutionContext | undefined;
23
+ }
24
+
25
+ /** Narrow native handler options without replacing their worker references. */
26
+ export function isCloudflareWorkerRequestHandlerOptions(
27
+ options: RequestHandlerOptions,
28
+ ): options is CloudflareWorkerRequestHandlerOptions {
29
+ if (!('platform' in options) || !('executionContext' in options))
30
+ return false;
31
+ const platform = options.platform;
32
+ if (
33
+ platform === null ||
34
+ typeof platform !== 'object' ||
35
+ !('kind' in platform) ||
36
+ platform.kind !== 'worker' ||
37
+ !('bindings' in platform)
38
+ ) {
39
+ return false;
40
+ }
41
+ const bindings = platform.bindings;
42
+ if (
43
+ bindings !== undefined &&
44
+ (bindings === null ||
45
+ typeof bindings !== 'object' ||
46
+ Array.isArray(bindings))
47
+ ) {
48
+ return false;
49
+ }
50
+ const context = options.executionContext;
51
+ return (
52
+ context === undefined ||
53
+ (context !== null &&
54
+ typeof context === 'object' &&
55
+ 'waitUntil' in context &&
56
+ typeof context.waitUntil === 'function' &&
57
+ 'passThroughOnException' in context &&
58
+ typeof context.passThroughOnException === 'function')
59
+ );
60
+ }