@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
@@ -2,6 +2,7 @@ import path from 'node:path';
2
2
  import type {
3
3
  CloudflareWorkerD1DatabaseConfig,
4
4
  CloudflareWorkerServiceBindingConfig,
5
+ CloudflareWorkerVpcServiceConfig,
5
6
  JsonValue,
6
7
  } from '../config';
7
8
  import {
@@ -15,21 +16,51 @@ import {
15
16
  import type { CloudflareModernConfig } from './types';
16
17
  import { isJsonRecord, normalizeRelativePath } from './utils';
17
18
 
18
- const getCompatibilityDate = (modernConfig: CloudflareModernConfig) => {
19
- const configuredDate = modernConfig.deploy?.worker?.compatibilityDate?.trim();
20
- const compatibilityDate = configuredDate || DEFAULT_COMPATIBILITY_DATE;
21
-
19
+ // Workers only add Node.js built-ins at later dates, and the bundler
20
+ // externalizes the set probed at DEFAULT_COMPATIBILITY_DATE; an earlier date
21
+ // could leave an externalized built-in unresolvable at deploy time.
22
+ const assertSupportedCompatibilityDate = (
23
+ compatibilityDate: JsonValue | undefined,
24
+ label: string,
25
+ ) => {
26
+ if (typeof compatibilityDate !== 'string') {
27
+ throw new Error(`${label} must be a YYYY-MM-DD string.`);
28
+ }
22
29
  if (!COMPATIBILITY_DATE_PATTERN.test(compatibilityDate)) {
23
30
  throw new Error(
24
- `deploy.worker.compatibilityDate must use YYYY-MM-DD, received ${JSON.stringify(
31
+ `${label} must use YYYY-MM-DD, received ${JSON.stringify(
32
+ compatibilityDate,
33
+ )}.`,
34
+ );
35
+ }
36
+ if (compatibilityDate < DEFAULT_COMPATIBILITY_DATE) {
37
+ throw new Error(
38
+ `${label} must be ${DEFAULT_COMPATIBILITY_DATE} or later, the date the Cloudflare worker Node.js built-in contract is verified against; received ${JSON.stringify(
25
39
  compatibilityDate,
26
40
  )}.`,
27
41
  );
28
42
  }
29
-
30
43
  return compatibilityDate;
31
44
  };
32
45
 
46
+ const getCompatibilityDate = (
47
+ modernConfig: CloudflareModernConfig,
48
+ wranglerCompatibilityDate: JsonValue | undefined,
49
+ ) => {
50
+ if (wranglerCompatibilityDate !== undefined) {
51
+ // A raw Wrangler override is the effective date, so it is validated too.
52
+ return assertSupportedCompatibilityDate(
53
+ wranglerCompatibilityDate,
54
+ 'deploy.worker.wrangler.compatibility_date',
55
+ );
56
+ }
57
+ return assertSupportedCompatibilityDate(
58
+ modernConfig.deploy?.worker?.compatibilityDate?.trim() ||
59
+ DEFAULT_COMPATIBILITY_DATE,
60
+ 'deploy.worker.compatibilityDate',
61
+ );
62
+ };
63
+
33
64
  const getWorkerName = (appDirectory: string) => {
34
65
  const basename = path.basename(appDirectory);
35
66
  return basename.replace(/[^a-zA-Z0-9-_]/g, '-') || 'modern-cloudflare-worker';
@@ -59,6 +90,7 @@ const getConfiguredWrangler = (modernConfig: CloudflareModernConfig) => {
59
90
 
60
91
  const createWranglerCompatibilityFlags = (
61
92
  configuredFlags: JsonValue | undefined,
93
+ label = 'deploy.worker.wrangler.compatibility_flags',
62
94
  ) => {
63
95
  if (configuredFlags === undefined) {
64
96
  return [...REQUIRED_COMPATIBILITY_FLAGS];
@@ -68,14 +100,56 @@ const createWranglerCompatibilityFlags = (
68
100
  !Array.isArray(configuredFlags) ||
69
101
  configuredFlags.some(flag => typeof flag !== 'string')
70
102
  ) {
71
- throw new Error(
72
- 'deploy.worker.wrangler.compatibility_flags must be an array of strings.',
73
- );
103
+ throw new Error(`${label} must be an array of strings.`);
74
104
  }
75
105
 
76
106
  return [...new Set([...configuredFlags, ...REQUIRED_COMPATIBILITY_FLAGS])];
77
107
  };
78
108
 
109
+ // `wrangler deploy --env <name>` uses an environment's own compatibility_date
110
+ // and compatibility_flags instead of the top-level values, so each override
111
+ // is held to the same verified date floor and required flags.
112
+ const createWranglerEnvironments = (
113
+ wranglerEnv: JsonValue | undefined,
114
+ ): Record<string, Record<string, JsonValue>> | undefined => {
115
+ if (wranglerEnv === undefined) {
116
+ return undefined;
117
+ }
118
+ if (!isJsonRecord(wranglerEnv)) {
119
+ throw new Error('deploy.worker.wrangler.env must be an object.');
120
+ }
121
+ return Object.fromEntries(
122
+ Object.entries(wranglerEnv).map(([name, environment]) => {
123
+ const label = `deploy.worker.wrangler.env.${name}`;
124
+ if (!isJsonRecord(environment)) {
125
+ throw new Error(`${label} must be an object.`);
126
+ }
127
+ return [
128
+ name,
129
+ {
130
+ ...environment,
131
+ ...(environment.compatibility_date === undefined
132
+ ? {}
133
+ : {
134
+ compatibility_date: assertSupportedCompatibilityDate(
135
+ environment.compatibility_date,
136
+ `${label}.compatibility_date`,
137
+ ),
138
+ }),
139
+ ...(environment.compatibility_flags === undefined
140
+ ? {}
141
+ : {
142
+ compatibility_flags: createWranglerCompatibilityFlags(
143
+ environment.compatibility_flags,
144
+ `${label}.compatibility_flags`,
145
+ ),
146
+ }),
147
+ },
148
+ ];
149
+ }),
150
+ );
151
+ };
152
+
79
153
  const createWranglerAssetsConfig = (
80
154
  configuredAssets: JsonValue | undefined,
81
155
  ) => {
@@ -242,6 +316,232 @@ export const createWorkerServiceBindings = (
242
316
  return services.map(normalizeServiceBinding);
243
317
  };
244
318
 
319
+ const normalizeVpcService = (
320
+ vpcService: CloudflareWorkerVpcServiceConfig,
321
+ index: number,
322
+ ) => {
323
+ const binding = assertNonEmptyString(
324
+ vpcService.binding,
325
+ `deploy.worker.vpcServices[${index}].binding`,
326
+ );
327
+ const serviceId = assertNonEmptyString(
328
+ vpcService.serviceId,
329
+ `deploy.worker.vpcServices[${index}].serviceId`,
330
+ );
331
+ const prefix =
332
+ vpcService.prefix === undefined
333
+ ? undefined
334
+ : assertNonEmptyString(
335
+ vpcService.prefix,
336
+ `deploy.worker.vpcServices[${index}].prefix`,
337
+ );
338
+
339
+ return { binding, serviceId, ...(prefix === undefined ? {} : { prefix }) };
340
+ };
341
+
342
+ // The worker dispatcher percent-decodes a request path (up to four rounds, as
343
+ // `resolvePrefixPathname` does), matches a prefix on path-segment boundaries
344
+ // and takes the first matching binding, so overlapping prefixes, including
345
+ // encoded aliases of one another, route ambiguously.
346
+ const decodeRoutePrefix = (prefix: string) => {
347
+ let decoded = prefix;
348
+ for (let round = 0; round < 4; round += 1) {
349
+ let next: string;
350
+ try {
351
+ next = decodeURIComponent(decoded);
352
+ } catch {
353
+ return decoded;
354
+ }
355
+ if (next === decoded) {
356
+ return decoded;
357
+ }
358
+ decoded = next;
359
+ }
360
+ return decoded;
361
+ };
362
+
363
+ const normalizeRoutePrefix = (prefix: string) => {
364
+ const decoded = decodeRoutePrefix(prefix);
365
+ return decoded === '/' ? decoded : decoded.replace(/\/+$/u, '');
366
+ };
367
+
368
+ // The dispatcher matches `new URL(request.url).pathname`, so a prefix is
369
+ // routable only if URL parsing keeps it unchanged: no query or fragment, no
370
+ // `.`/`..` segments, nothing URL parsing would re-encode, and no percent-encoding
371
+ // (the dispatcher decodes request paths before matching).
372
+ const isCanonicalRoutePrefix = (prefix: string) =>
373
+ prefix.startsWith('/') &&
374
+ !prefix.includes('\\') &&
375
+ decodeRoutePrefix(prefix) === prefix &&
376
+ new URL(prefix, 'https://route.invalid').pathname === prefix;
377
+
378
+ /**
379
+ * Names the generated Worker may already expose on `env`, collected
380
+ * conservatively so no Wrangler binding shape is missed: every `binding` field
381
+ * at any depth (assets, D1, KV, Hyperdrive, queue producers, …), the `name` of
382
+ * every array entry (Durable Objects, `send_email`, `ratelimits`, `unsafe` and
383
+ * `logfwdr` bindings, …), and `vars` keys. A non-binding array `name` can only
384
+ * make a VPC name collide, never let a real collision through. Service and VPC
385
+ * bindings are checked separately; per-environment overrides live under `env`.
386
+ */
387
+ const collectWorkerBindingNames = (config: Record<string, JsonValue>) => {
388
+ const names = new Set<string>();
389
+ const visit = (value: JsonValue, inArray: boolean) => {
390
+ if (Array.isArray(value)) {
391
+ for (const item of value) {
392
+ visit(item, true);
393
+ }
394
+ return;
395
+ }
396
+ if (!isJsonRecord(value)) {
397
+ return;
398
+ }
399
+ for (const field of inArray ? ['binding', 'name'] : ['binding']) {
400
+ const name = value[field];
401
+ if (typeof name === 'string') {
402
+ names.add(name.trim());
403
+ }
404
+ }
405
+ for (const child of Object.values(value)) {
406
+ visit(child, false);
407
+ }
408
+ };
409
+ for (const [key, value] of Object.entries(config)) {
410
+ if (key === 'env' || key === 'services' || key === 'vpc_services') {
411
+ continue;
412
+ }
413
+ if (key === 'vars' && isJsonRecord(value)) {
414
+ for (const name of Object.keys(value)) {
415
+ names.add(name);
416
+ }
417
+ continue;
418
+ }
419
+ visit(value, false);
420
+ }
421
+ return names;
422
+ };
423
+
424
+ const prefixesOverlap = (left: string, right: string) => {
425
+ const [first, second] = [
426
+ normalizeRoutePrefix(left),
427
+ normalizeRoutePrefix(right),
428
+ ];
429
+ return (
430
+ first === '/' ||
431
+ second === '/' ||
432
+ first === second ||
433
+ first.startsWith(`${second}/`) ||
434
+ second.startsWith(`${first}/`)
435
+ );
436
+ };
437
+
438
+ /**
439
+ * The prefix the worker's Effect BFF dispatcher owns. It runs before any
440
+ * service binding, so it takes part in route-overlap checks.
441
+ */
442
+ export const getWorkerEffectBffPrefix = (
443
+ modernConfig: CloudflareModernConfig,
444
+ ) => {
445
+ const bffPrefix = modernConfig.bff?.prefix;
446
+ const primaryBffPrefix = Array.isArray(bffPrefix) ? bffPrefix[0] : bffPrefix;
447
+ const isEffectApi =
448
+ Boolean(modernConfig.bff) && modernConfig.bff?.runtimeFramework !== 'hono';
449
+ return isEffectApi && primaryBffPrefix ? primaryBffPrefix : undefined;
450
+ };
451
+
452
+ const effectiveServiceBindings = (
453
+ serviceBindings: ReturnType<typeof createWorkerServiceBindings>,
454
+ ) => {
455
+ const bindings: unknown[] = Array.isArray(serviceBindings)
456
+ ? serviceBindings
457
+ : [];
458
+ return bindings.flatMap(service =>
459
+ isJsonRecord(service) && typeof service.binding === 'string'
460
+ ? [
461
+ {
462
+ binding: service.binding.trim(),
463
+ prefix:
464
+ typeof service.prefix === 'string' ? service.prefix : undefined,
465
+ },
466
+ ]
467
+ : [],
468
+ );
469
+ };
470
+
471
+ export const createWorkerVpcServiceBindings = (
472
+ modernConfig: CloudflareModernConfig,
473
+ configuredWranglerVpcServices: JsonValue | undefined,
474
+ serviceBindings: ReturnType<typeof createWorkerServiceBindings>,
475
+ workerBindingNames: ReadonlySet<string>,
476
+ ) => {
477
+ const vpcServices = modernConfig.deploy?.worker?.vpcServices;
478
+
479
+ if (vpcServices === undefined) {
480
+ return undefined;
481
+ }
482
+
483
+ if (configuredWranglerVpcServices !== undefined) {
484
+ throw new Error(
485
+ 'Use deploy.worker.vpcServices or deploy.worker.wrangler.vpc_services, not both.',
486
+ );
487
+ }
488
+
489
+ if (!Array.isArray(vpcServices)) {
490
+ throw new Error('deploy.worker.vpcServices must be an array.');
491
+ }
492
+
493
+ const services = effectiveServiceBindings(serviceBindings);
494
+ const serviceBindingNames = new Set(services.map(({ binding }) => binding));
495
+ const effectBffPrefix = getWorkerEffectBffPrefix(modernConfig);
496
+ const routes = [
497
+ ...(effectBffPrefix === undefined
498
+ ? []
499
+ : [{ owner: 'the Effect BFF', prefix: effectBffPrefix }]),
500
+ ...services.flatMap(({ binding, prefix }) =>
501
+ prefix === undefined ? [] : [{ owner: `binding "${binding}"`, prefix }],
502
+ ),
503
+ ];
504
+ const vpcBindingNames = new Set<string>();
505
+
506
+ return vpcServices.map((vpcService, index) => {
507
+ const normalized = normalizeVpcService(vpcService, index);
508
+ if (serviceBindingNames.has(normalized.binding)) {
509
+ throw new Error(
510
+ `deploy.worker.vpcServices[${index}].binding "${normalized.binding}" is already declared by deploy.worker.services.`,
511
+ );
512
+ }
513
+ if (workerBindingNames.has(normalized.binding)) {
514
+ throw new Error(
515
+ `deploy.worker.vpcServices[${index}].binding "${normalized.binding}" is already a binding of this Worker.`,
516
+ );
517
+ }
518
+ if (vpcBindingNames.has(normalized.binding)) {
519
+ throw new Error(
520
+ `deploy.worker.vpcServices[${index}].binding "${normalized.binding}" is declared more than once.`,
521
+ );
522
+ }
523
+ vpcBindingNames.add(normalized.binding);
524
+ if (normalized.prefix !== undefined) {
525
+ const { prefix } = normalized;
526
+ if (!isCanonicalRoutePrefix(prefix)) {
527
+ throw new Error(
528
+ `deploy.worker.vpcServices[${index}].prefix "${prefix}" must be a decoded URL path that starts with "/" (no query, fragment, "." or ".." segments).`,
529
+ );
530
+ }
531
+ const shadow = routes.find(route =>
532
+ prefixesOverlap(route.prefix, prefix),
533
+ );
534
+ if (shadow !== undefined) {
535
+ throw new Error(
536
+ `deploy.worker.vpcServices[${index}].prefix "${prefix}" overlaps the prefix "${shadow.prefix}" of ${shadow.owner}; the worker dispatcher would route one of them ambiguously.`,
537
+ );
538
+ }
539
+ routes.push({ owner: `binding "${normalized.binding}"`, prefix });
540
+ }
541
+ return normalized;
542
+ });
543
+ };
544
+
245
545
  const createWranglerServices = (
246
546
  serviceBindings: ReturnType<typeof createWorkerServiceBindings>,
247
547
  ) => {
@@ -267,9 +567,24 @@ const createWranglerServices = (
267
567
 
268
568
  export const createWorkerManifestServiceBindings = (
269
569
  serviceBindings: ReturnType<typeof createWorkerServiceBindings>,
570
+ vpcServiceBindings: ReturnType<typeof createWorkerVpcServiceBindings>,
270
571
  ) => {
572
+ const vpcManifestBindings = (vpcServiceBindings ?? []).flatMap(
573
+ ({ binding, prefix, serviceId }) =>
574
+ prefix === undefined
575
+ ? []
576
+ : [
577
+ {
578
+ binding,
579
+ interface: 'fetch',
580
+ prefix,
581
+ vpcServiceId: serviceId,
582
+ },
583
+ ],
584
+ );
585
+
271
586
  if (!Array.isArray(serviceBindings)) {
272
- return undefined;
587
+ return vpcManifestBindings.length > 0 ? vpcManifestBindings : undefined;
273
588
  }
274
589
 
275
590
  const bindings: unknown[] = serviceBindings;
@@ -304,7 +619,9 @@ export const createWorkerManifestServiceBindings = (
304
619
  : { fragments: service.fragments }),
305
620
  }));
306
621
 
307
- return manifestBindings.length > 0 ? manifestBindings : undefined;
622
+ const allBindings = [...manifestBindings, ...vpcManifestBindings];
623
+
624
+ return allBindings.length > 0 ? allBindings : undefined;
308
625
  };
309
626
 
310
627
  export const createWranglerConfig = (
@@ -312,6 +629,7 @@ export const createWranglerConfig = (
312
629
  modernConfig: CloudflareModernConfig,
313
630
  ) => {
314
631
  const wrangler = getConfiguredWrangler(modernConfig);
632
+ const environments = createWranglerEnvironments(wrangler.env);
315
633
  const d1Databases = createWranglerD1Databases(
316
634
  modernConfig,
317
635
  wrangler.d1_databases,
@@ -321,18 +639,76 @@ export const createWranglerConfig = (
321
639
  wrangler.services,
322
640
  );
323
641
  const wranglerServices = createWranglerServices(serviceBindings);
324
-
325
- return {
642
+ const workerConfig = {
326
643
  $schema: 'node_modules/wrangler/config-schema.json',
327
644
  name: getConfiguredWorkerName(appDirectory, modernConfig),
328
- compatibility_date: getCompatibilityDate(modernConfig),
329
645
  ...wrangler,
646
+ compatibility_date: getCompatibilityDate(
647
+ modernConfig,
648
+ wrangler.compatibility_date,
649
+ ),
330
650
  main: WORKER_ENTRY,
331
651
  compatibility_flags: createWranglerCompatibilityFlags(
332
652
  wrangler.compatibility_flags,
333
653
  ),
334
654
  assets: createWranglerAssetsConfig(wrangler.assets),
655
+ ...(environments === undefined ? {} : { env: environments }),
335
656
  ...(d1Databases === undefined ? {} : { d1_databases: d1Databases }),
336
657
  ...(wranglerServices === undefined ? {} : { services: wranglerServices }),
337
658
  };
659
+ const vpcServiceBindings = createWorkerVpcServiceBindings(
660
+ modernConfig,
661
+ wrangler.vpc_services,
662
+ serviceBindings,
663
+ collectWorkerBindingNames(workerConfig),
664
+ );
665
+
666
+ if (vpcServiceBindings === undefined) {
667
+ return workerConfig;
668
+ }
669
+ const vpcServicesConfig = vpcServiceBindings.map(
670
+ ({ binding, serviceId }) => ({
671
+ binding,
672
+ service_id: serviceId,
673
+ }),
674
+ );
675
+
676
+ return {
677
+ ...workerConfig,
678
+ // Wrangler bindings are not inherited by named environments, so every
679
+ // configured environment receives the typed VPC bindings as well.
680
+ ...(environments === undefined
681
+ ? {}
682
+ : {
683
+ env: Object.fromEntries(
684
+ Object.entries(environments).map(([name, environment]) => {
685
+ if (environment.vpc_services !== undefined) {
686
+ throw new Error(
687
+ `Use deploy.worker.vpcServices or deploy.worker.wrangler.env.${name}.vpc_services, not both.`,
688
+ );
689
+ }
690
+ // A named environment declares its own (non-inherited) bindings.
691
+ const environmentBindingNames = new Set([
692
+ ...collectWorkerBindingNames(environment),
693
+ ...effectiveServiceBindings(environment.services).map(
694
+ ({ binding }) => binding,
695
+ ),
696
+ ]);
697
+ const collision = vpcServiceBindings.find(({ binding }) =>
698
+ environmentBindingNames.has(binding),
699
+ );
700
+ if (collision !== undefined) {
701
+ throw new Error(
702
+ `deploy.worker.vpcServices binding "${collision.binding}" is already a binding of deploy.worker.wrangler.env.${name}.`,
703
+ );
704
+ }
705
+ return [
706
+ name,
707
+ { ...environment, vpc_services: vpcServicesConfig },
708
+ ];
709
+ }),
710
+ ),
711
+ }),
712
+ vpc_services: vpcServicesConfig,
713
+ };
338
714
  };