wrangler 0.0.0-e178d6fb → 0.0.0-e1c2f5b9

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 (239) hide show
  1. package/README.md +16 -2
  2. package/bin/wrangler.js +31 -11
  3. package/miniflare-dist/index.mjs +1340 -330
  4. package/package.json +40 -13
  5. package/src/__tests__/access.test.ts +25 -0
  6. package/src/__tests__/api-dev.test.ts +238 -0
  7. package/src/__tests__/api-devregistry.test.js +64 -0
  8. package/src/__tests__/api.test.ts +80 -0
  9. package/src/__tests__/configuration.test.ts +683 -470
  10. package/src/__tests__/d1.test.ts +123 -0
  11. package/src/__tests__/delete.test.ts +263 -0
  12. package/src/__tests__/deployments.test.ts +105 -0
  13. package/src/__tests__/dev.test.tsx +453 -70
  14. package/src/__tests__/dialogs.test.tsx +40 -0
  15. package/src/__tests__/generate.test.ts +235 -0
  16. package/src/__tests__/get-host-from-url.test.ts +16 -0
  17. package/src/__tests__/helpers/mock-auth-domain.ts +20 -0
  18. package/src/__tests__/helpers/mock-cfetch.ts +92 -26
  19. package/src/__tests__/helpers/mock-console.ts +11 -2
  20. package/src/__tests__/helpers/mock-dialogs.ts +1 -1
  21. package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -0
  22. package/src/__tests__/helpers/mock-known-routes.ts +7 -0
  23. package/src/__tests__/helpers/mock-kv.ts +1 -1
  24. package/src/__tests__/helpers/mock-oauth-flow.ts +74 -53
  25. package/src/__tests__/helpers/mock-process.ts +8 -13
  26. package/src/__tests__/helpers/msw/handlers/access.ts +13 -0
  27. package/src/__tests__/helpers/msw/handlers/deployments.ts +59 -0
  28. package/src/__tests__/helpers/msw/handlers/namespaces.ts +104 -0
  29. package/src/__tests__/helpers/msw/handlers/oauth.ts +31 -0
  30. package/src/__tests__/helpers/msw/handlers/r2.ts +80 -0
  31. package/src/__tests__/helpers/msw/handlers/script.ts +75 -0
  32. package/src/__tests__/helpers/msw/handlers/user.ts +63 -0
  33. package/src/__tests__/helpers/msw/handlers/zones.ts +22 -0
  34. package/src/__tests__/helpers/msw/index.ts +21 -0
  35. package/src/__tests__/helpers/worker-scripts/child-wrangler.toml +1 -0
  36. package/src/__tests__/helpers/worker-scripts/hello-world-worker.js +5 -0
  37. package/src/__tests__/helpers/worker-scripts/hello-world-wrangler.toml +1 -0
  38. package/src/__tests__/helpers/worker-scripts/parent-worker.js +8 -0
  39. package/src/__tests__/helpers/worker-scripts/parent-wrangler.toml +5 -0
  40. package/src/__tests__/index.test.ts +103 -97
  41. package/src/__tests__/init.test.ts +1484 -382
  42. package/src/__tests__/jest.setup.ts +88 -5
  43. package/src/__tests__/kv.test.ts +655 -540
  44. package/src/__tests__/logout.test.ts +47 -0
  45. package/src/__tests__/metrics.test.ts +95 -45
  46. package/src/__tests__/middleware.scheduled.test.ts +145 -0
  47. package/src/__tests__/middleware.test.ts +750 -0
  48. package/src/__tests__/pages-deployment-tail.test.ts +884 -0
  49. package/src/__tests__/pages.test.ts +1301 -119
  50. package/src/__tests__/paths.test.ts +39 -0
  51. package/src/__tests__/publish.test.ts +2266 -1180
  52. package/src/__tests__/pubsub.test.ts +211 -88
  53. package/src/__tests__/queues.test.ts +522 -0
  54. package/src/__tests__/r2.test.ts +363 -203
  55. package/src/__tests__/secret.test.ts +261 -59
  56. package/src/__tests__/tail.test.ts +352 -151
  57. package/src/__tests__/test-old-node-version.js +3 -3
  58. package/src/__tests__/tsconfig-sanity.ts +12 -0
  59. package/src/__tests__/tsconfig.json +8 -0
  60. package/src/__tests__/tsconfig.tsbuildinfo +1 -0
  61. package/src/__tests__/type-generation.test.ts +233 -0
  62. package/src/__tests__/user.test.ts +59 -73
  63. package/src/__tests__/utils-collectKeyValues.test.ts +47 -0
  64. package/src/__tests__/validate-dev-props.test.ts +56 -0
  65. package/src/__tests__/whoami.test.tsx +114 -66
  66. package/src/__tests__/worker-namespace.test.ts +154 -155
  67. package/src/api/dev.ts +170 -38
  68. package/src/api/index.ts +1 -0
  69. package/src/bundle-reporter.tsx +41 -2
  70. package/src/bundle.ts +668 -81
  71. package/src/cfetch/index.ts +40 -3
  72. package/src/cfetch/internal.ts +144 -32
  73. package/src/cli.ts +4 -3
  74. package/src/config/config.ts +27 -3
  75. package/src/config/environment.ts +131 -7
  76. package/src/config/index.ts +128 -8
  77. package/src/config/validation-helpers.ts +19 -6
  78. package/src/config/validation.ts +411 -14
  79. package/src/config-cache.ts +3 -2
  80. package/src/create-worker-preview.ts +31 -6
  81. package/src/create-worker-upload-form.ts +84 -22
  82. package/src/d1/backups.tsx +223 -0
  83. package/src/d1/constants.ts +2 -0
  84. package/src/d1/create.tsx +63 -0
  85. package/src/d1/delete.tsx +56 -0
  86. package/src/d1/execute.tsx +352 -0
  87. package/src/d1/formatTimeAgo.ts +14 -0
  88. package/src/d1/index.ts +101 -0
  89. package/src/d1/list.tsx +48 -0
  90. package/src/d1/migrations/apply.tsx +196 -0
  91. package/src/d1/migrations/create.tsx +77 -0
  92. package/src/d1/migrations/helpers.ts +147 -0
  93. package/src/d1/migrations/index.tsx +3 -0
  94. package/src/d1/migrations/list.tsx +79 -0
  95. package/src/d1/migrations/options.ts +18 -0
  96. package/src/d1/options.ts +22 -0
  97. package/src/d1/types.tsx +24 -0
  98. package/src/d1/utils.ts +48 -0
  99. package/src/delete.ts +108 -0
  100. package/src/deployments.ts +83 -0
  101. package/src/deprecated/index.ts +145 -0
  102. package/src/dev/dev-vars.ts +11 -8
  103. package/src/dev/dev.tsx +220 -58
  104. package/src/dev/get-local-persistence-path.tsx +31 -0
  105. package/src/dev/local.tsx +740 -181
  106. package/src/dev/remote.tsx +340 -123
  107. package/src/dev/start-server.ts +543 -0
  108. package/src/dev/use-esbuild.ts +61 -7
  109. package/src/dev/validate-dev-props.ts +31 -0
  110. package/src/dev-registry.tsx +201 -0
  111. package/src/dev.tsx +614 -258
  112. package/src/dialogs.tsx +41 -8
  113. package/src/{worker-namespace.ts → dispatch-namespace.ts} +23 -22
  114. package/src/docs/index.ts +97 -0
  115. package/src/entry.ts +3 -2
  116. package/src/environment-variables/factory.ts +88 -0
  117. package/src/environment-variables/misc-variables.ts +30 -0
  118. package/src/generate/index.ts +300 -0
  119. package/src/git-client.ts +100 -7
  120. package/src/index.tsx +249 -1560
  121. package/src/init.ts +626 -150
  122. package/src/inspect.ts +137 -32
  123. package/src/{metrics/is-ci.ts → is-ci.ts} +0 -0
  124. package/src/is-interactive.ts +4 -0
  125. package/src/jest.d.ts +4 -0
  126. package/src/{kv.ts → kv/helpers.ts} +3 -3
  127. package/src/kv/index.ts +596 -0
  128. package/src/logger.ts +27 -8
  129. package/src/metrics/index.ts +1 -0
  130. package/src/metrics/metrics-config.ts +4 -4
  131. package/src/metrics/metrics-dispatcher.ts +1 -0
  132. package/src/metrics/metrics-usage-headers.ts +24 -0
  133. package/src/metrics/send-event.ts +13 -8
  134. package/src/miniflare-cli/assets.ts +172 -465
  135. package/src/miniflare-cli/index.ts +149 -24
  136. package/src/miniflare-cli/tsconfig.json +9 -0
  137. package/src/miniflare-cli/tsconfig.tsbuildinfo +1 -0
  138. package/src/miniflare-cli/types.ts +11 -0
  139. package/src/module-collection.ts +12 -3
  140. package/src/pages/build.tsx +108 -42
  141. package/src/pages/constants.ts +11 -0
  142. package/src/pages/deployment-tails.tsx +284 -0
  143. package/src/pages/deployments.tsx +9 -31
  144. package/src/pages/dev.tsx +417 -108
  145. package/src/pages/errors.ts +66 -0
  146. package/src/pages/functions/buildPlugin.ts +86 -84
  147. package/src/pages/functions/buildWorker.ts +121 -106
  148. package/src/pages/functions/routes-consolidation.test.ts +250 -0
  149. package/src/pages/functions/routes-consolidation.ts +73 -0
  150. package/src/pages/functions/routes-transformation.test.ts +282 -0
  151. package/src/pages/functions/routes-transformation.ts +115 -0
  152. package/src/pages/functions/routes-validation.test.ts +403 -0
  153. package/src/pages/functions/routes-validation.ts +202 -0
  154. package/src/pages/functions/tsconfig.json +8 -0
  155. package/src/pages/functions/tsconfig.tsbuildinfo +1 -0
  156. package/src/pages/functions.tsx +82 -0
  157. package/src/pages/hash.tsx +13 -0
  158. package/src/pages/index.tsx +73 -55
  159. package/src/pages/projects.tsx +9 -3
  160. package/src/pages/prompt-select-project.tsx +31 -0
  161. package/src/pages/publish.tsx +183 -60
  162. package/src/pages/types.ts +1 -0
  163. package/src/pages/upload.tsx +107 -56
  164. package/src/pages/utils.ts +13 -2
  165. package/src/paths.ts +49 -4
  166. package/src/proxy.ts +375 -157
  167. package/src/publish/index.ts +270 -0
  168. package/src/{publish.ts → publish/publish.ts} +431 -188
  169. package/src/pubsub/pubsub-commands.tsx +4 -3
  170. package/src/queues/cli/commands/consumer/add.ts +71 -0
  171. package/src/queues/cli/commands/consumer/index.ts +22 -0
  172. package/src/queues/cli/commands/consumer/remove.ts +38 -0
  173. package/src/queues/cli/commands/create.ts +25 -0
  174. package/src/queues/cli/commands/delete.ts +26 -0
  175. package/src/queues/cli/commands/index.ts +36 -0
  176. package/src/queues/cli/commands/list.ts +25 -0
  177. package/src/queues/client.ts +135 -0
  178. package/src/queues/utils.ts +18 -0
  179. package/src/r2/constants.ts +4 -0
  180. package/src/r2/helpers.ts +131 -0
  181. package/src/r2/index.ts +289 -0
  182. package/src/routes.ts +140 -0
  183. package/src/secret/index.ts +379 -0
  184. package/src/sites.tsx +3 -3
  185. package/src/tail/createTail.ts +379 -0
  186. package/src/tail/filters.ts +4 -6
  187. package/src/tail/index.ts +199 -284
  188. package/src/tail/printing.ts +43 -3
  189. package/src/tsconfig-sanity.ts +16 -0
  190. package/src/type-generation.ts +181 -0
  191. package/src/user/access.ts +68 -0
  192. package/src/user/auth-variables.ts +113 -0
  193. package/src/user/choose-account.tsx +1 -1
  194. package/src/user/index.ts +0 -1
  195. package/src/user/user.tsx +116 -49
  196. package/src/utils/collectKeyValues.ts +14 -0
  197. package/src/whoami.tsx +80 -2
  198. package/src/worker.ts +69 -9
  199. package/src/yargs-types.ts +24 -0
  200. package/src/zones.ts +124 -9
  201. package/templates/__tests__/pages-dev-util.test.ts +128 -0
  202. package/templates/__tests__/tsconfig-sanity.ts +12 -0
  203. package/templates/__tests__/tsconfig.json +8 -0
  204. package/templates/__tests__/tsconfig.tsbuildinfo +1 -0
  205. package/templates/d1-beta-facade.js +205 -0
  206. package/templates/facade.d.ts +14 -0
  207. package/templates/first-party-worker-module-facade.ts +19 -0
  208. package/templates/format-dev-errors.ts +33 -0
  209. package/templates/init-tests/test-jest-new-worker.js +23 -0
  210. package/templates/init-tests/test-vitest-new-worker.js +24 -0
  211. package/templates/init-tests/test-vitest-new-worker.ts +25 -0
  212. package/templates/middleware/common.ts +62 -0
  213. package/templates/middleware/loader-modules.ts +82 -0
  214. package/templates/middleware/loader-sw.ts +233 -0
  215. package/templates/middleware/middleware-miniflare3-json-error.ts +20 -0
  216. package/templates/middleware/middleware-pretty-error.ts +40 -0
  217. package/templates/middleware/middleware-scheduled.ts +15 -0
  218. package/templates/new-worker.js +1 -1
  219. package/templates/pages-dev-pipeline.ts +31 -0
  220. package/templates/pages-dev-util.ts +52 -0
  221. package/templates/pages-shim.ts +4 -1
  222. package/templates/pages-template-plugin.ts +21 -5
  223. package/templates/pages-template-worker.ts +37 -9
  224. package/templates/{static-asset-facade.js → serve-static-assets.ts} +23 -7
  225. package/templates/service-bindings-module-facade.js +54 -0
  226. package/templates/service-bindings-sw-facade.js +42 -0
  227. package/templates/tsconfig-sanity.ts +11 -0
  228. package/templates/tsconfig.init.json +106 -0
  229. package/templates/tsconfig.json +5 -102
  230. package/templates/tsconfig.tsbuildinfo +1 -0
  231. package/wrangler-dist/cli.d.ts +621 -42
  232. package/wrangler-dist/cli.js +144441 -102606
  233. package/src/environment-variables.ts +0 -35
  234. package/src/faye-websocket.d.ts +0 -6
  235. package/src/generate.ts +0 -33
  236. package/src/miniflare-cli/enum-keys.ts +0 -17
  237. package/src/preview.ts +0 -31
  238. package/src/r2.ts +0 -50
  239. package/src/user/env-vars.ts +0 -46
package/src/dev/local.tsx CHANGED
@@ -1,51 +1,102 @@
1
+ import assert from "node:assert";
1
2
  import { fork } from "node:child_process";
2
3
  import { realpathSync } from "node:fs";
3
- import { writeFile } from "node:fs/promises";
4
+ import { readFile, writeFile } from "node:fs/promises";
4
5
  import path from "node:path";
6
+ import chalk from "chalk";
7
+ import getPort from "get-port";
8
+ import { npxImport } from "npx-import";
5
9
  import { useState, useEffect, useRef } from "react";
6
10
  import onExit from "signal-exit";
11
+ import { fetch } from "undici";
12
+ import { performApiFetch } from "../cfetch/internal";
13
+ import { registerWorker } from "../dev-registry";
7
14
  import useInspector from "../inspect";
8
15
  import { logger } from "../logger";
9
- import { DEFAULT_MODULE_RULES } from "../module-collection";
16
+ import {
17
+ DEFAULT_MODULE_RULES,
18
+ ModuleTypeToRuleType,
19
+ } from "../module-collection";
20
+ import { getBasePath } from "../paths";
10
21
  import { waitForPortToBeAvailable } from "../proxy";
22
+ import { requireAuth } from "../user";
11
23
  import type { Config } from "../config";
12
- import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli";
24
+ import type { WorkerRegistry } from "../dev-registry";
25
+ import type { LoggerLevel } from "../logger";
26
+ import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli/types";
13
27
  import type { AssetPaths } from "../sites";
14
- import type { CfWorkerInit, CfScriptFormat } from "../worker";
28
+ import type {
29
+ CfWorkerInit,
30
+ CfScriptFormat,
31
+ CfWasmModuleBindings,
32
+ CfTextBlobBindings,
33
+ CfDataBlobBindings,
34
+ CfDurableObject,
35
+ CfKvNamespace,
36
+ CfR2Bucket,
37
+ CfVars,
38
+ CfQueue,
39
+ CfD1Database,
40
+ } from "../worker";
15
41
  import type { EsbuildBundle } from "./use-esbuild";
42
+ import type {
43
+ Miniflare as Miniflare3Type,
44
+ MiniflareOptions as Miniflare3Options,
45
+ Log as Miniflare3LogType,
46
+ CloudflareFetch,
47
+ } from "@miniflare/tre";
16
48
  import type { MiniflareOptions } from "miniflare";
17
49
  import type { ChildProcess } from "node:child_process";
18
- interface LocalProps {
50
+ import type { RequestInit } from "undici";
51
+
52
+ export interface LocalProps {
19
53
  name: string | undefined;
20
54
  bundle: EsbuildBundle | undefined;
21
55
  format: CfScriptFormat | undefined;
22
56
  compatibilityDate: string;
23
57
  compatibilityFlags: string[] | undefined;
58
+ usageModel: "bundled" | "unbound" | undefined;
24
59
  bindings: CfWorkerInit["bindings"];
60
+ workerDefinitions: WorkerRegistry | undefined;
25
61
  assetPaths: AssetPaths | undefined;
26
- isWorkersSite: boolean;
27
- port: number;
28
- ip: string;
62
+ initialPort: number;
63
+ initialIp: string;
29
64
  rules: Config["rules"];
30
65
  inspectorPort: number;
31
- enableLocalPersistence: boolean;
66
+ localPersistencePath: string | null;
32
67
  liveReload: boolean;
33
68
  crons: Config["triggers"]["crons"];
69
+ queueConsumers: Config["queues"]["consumers"];
34
70
  localProtocol: "http" | "https";
35
71
  localUpstream: string | undefined;
36
72
  inspect: boolean;
37
- onReady: (() => void) | undefined;
38
- logLevel: "none" | "error" | "log" | "warn" | "debug" | undefined;
73
+ onReady: ((ip: string, port: number) => void) | undefined;
39
74
  logPrefix?: string;
40
75
  enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
76
+ testScheduled?: boolean;
77
+ experimentalLocal: boolean | undefined;
78
+ accountId: string | undefined; // Account ID? In local mode??? :exploding_head:
79
+ experimentalLocalRemoteKv: boolean | undefined;
41
80
  }
42
81
 
82
+ type InspectorJSON = {
83
+ id: string;
84
+ title: string;
85
+ type: "node";
86
+ description: string;
87
+ webSocketDebuggerUrl: string;
88
+ devtoolsFrontendUrl: string;
89
+ devtoolsFrontendUrlCompat: string;
90
+ faviconUrl: string;
91
+ url: string;
92
+ }[];
93
+
43
94
  export function Local(props: LocalProps) {
44
95
  const { inspectorUrl } = useLocalWorker(props);
45
96
  useInspector({
46
97
  inspectorUrl,
47
98
  port: props.inspectorPort,
48
- logToTerminal: false,
99
+ logToTerminal: props.experimentalLocal ?? false,
49
100
  });
50
101
  return null;
51
102
  }
@@ -56,55 +107,64 @@ function useLocalWorker({
56
107
  format,
57
108
  compatibilityDate,
58
109
  compatibilityFlags,
110
+ usageModel,
59
111
  bindings,
112
+ workerDefinitions,
60
113
  assetPaths,
61
- isWorkersSite,
62
- port,
114
+ initialPort,
63
115
  rules,
64
- enableLocalPersistence,
116
+ localPersistencePath,
65
117
  liveReload,
66
- ip,
118
+ initialIp,
67
119
  crons,
120
+ queueConsumers,
68
121
  localProtocol,
69
122
  localUpstream,
70
123
  inspect,
71
124
  onReady,
72
- logLevel,
73
125
  logPrefix,
74
126
  enablePagesAssetsServiceBinding,
127
+ experimentalLocal,
128
+ accountId,
129
+ experimentalLocalRemoteKv,
75
130
  }: LocalProps) {
76
131
  // TODO: pass vars via command line
77
132
  const local = useRef<ChildProcess>();
133
+ const experimentalLocalRef = useRef<Miniflare3Type>();
78
134
  const removeSignalExitListener = useRef<() => void>();
135
+ const removeExperimentalLocalSignalExitListener = useRef<() => void>();
79
136
  const [inspectorUrl, setInspectorUrl] = useState<string | undefined>();
80
- // if we're using local persistence for data, we should use the cwd
81
- // as an explicit path, or else it'll use the temp dir
82
- // which disappears when dev ends
83
- const localPersistencePath = enableLocalPersistence
84
- ? // Maybe we could make the path configurable as well?
85
- path.join(process.cwd(), "wrangler-local-state")
86
- : // We otherwise choose null, but choose true later
87
- // so that it's persisted in the temp dir across a dev session
88
- // even when we change source and reload
89
- null;
137
+
138
+ // Our inspector proxy server will be binding to `LocalProps`'s `inspectorPort`.
139
+ // If we attempted to bind Node.js/workerd to the same inspector port, we'd get a port already in use error.
140
+ // Therefore, generate a new random port for our runtime's to bind their inspector service to.
141
+ const runtimeInspectorPortRef = useRef<number>();
142
+
143
+ useEffect(() => {
144
+ if (bindings.services && bindings.services.length > 0) {
145
+ logger.warn(
146
+ "⎔ Support for service bindings in local mode is experimental and may change."
147
+ );
148
+ }
149
+ }, [bindings.services]);
150
+
151
+ useEffect(() => {
152
+ const externalDurableObjects = (
153
+ bindings.durable_objects?.bindings || []
154
+ ).filter((binding) => binding.script_name);
155
+
156
+ if (externalDurableObjects.length > 0) {
157
+ logger.warn(
158
+ "⎔ Support for external Durable Objects in local mode is experimental and may change."
159
+ );
160
+ }
161
+ }, [bindings.durable_objects?.bindings]);
162
+
90
163
  useEffect(() => {
91
164
  const abortController = new AbortController();
92
165
  async function startLocalWorker() {
93
166
  if (!bundle || !format) return;
94
167
 
95
- // port for the worker
96
- await waitForPortToBeAvailable(port, {
97
- retryPeriod: 200,
98
- timeout: 2000,
99
- abortSignal: abortController.signal,
100
- });
101
-
102
- if (bindings.services && bindings.services.length > 0) {
103
- throw new Error(
104
- "⎔ Service bindings are not yet supported in local mode."
105
- );
106
- }
107
-
108
168
  // In local mode, we want to copy all referenced modules into
109
169
  // the output bundle directory before starting up
110
170
  for (const module of bundle.modules) {
@@ -116,165 +176,178 @@ function useLocalWorker({
116
176
 
117
177
  const scriptPath = realpathSync(bundle.path);
118
178
 
119
- // the wasm_modules/text_blobs/data_blobs bindings are
120
- // relative to process.cwd(), but the actual worker bundle
121
- // is in the temp output directory; so we rewrite the paths to be absolute,
122
- // letting miniflare resolve them correctly
123
-
124
- // wasm
125
- const wasmBindings: Record<string, string> = {};
126
- for (const [name, filePath] of Object.entries(
127
- bindings.wasm_modules || {}
128
- )) {
129
- wasmBindings[name] = path.join(process.cwd(), filePath);
130
- }
131
-
132
- // text
133
- const textBlobBindings: Record<string, string> = {};
134
- for (const [name, filePath] of Object.entries(
135
- bindings.text_blobs || {}
136
- )) {
137
- textBlobBindings[name] = path.join(process.cwd(), filePath);
138
- }
139
-
140
- // data
141
- const dataBlobBindings: Record<string, string> = {};
142
- for (const [name, filePath] of Object.entries(
143
- bindings.data_blobs || {}
144
- )) {
145
- dataBlobBindings[name] = path.join(process.cwd(), filePath);
146
- }
147
-
148
- if (format === "service-worker") {
149
- for (const { type, name } of bundle.modules) {
150
- if (type === "compiled-wasm") {
151
- // In service-worker format, .wasm modules are referenced by global identifiers,
152
- // so we convert it here.
153
- // This identifier has to be a valid JS identifier, so we replace all non alphanumeric
154
- // characters with an underscore.
155
- const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
156
- wasmBindings[identifier] = name;
157
- } else if (type === "text") {
158
- // In service-worker format, text modules are referenced by global identifiers,
159
- // so we convert it here.
160
- // This identifier has to be a valid JS identifier, so we replace all non alphanumeric
161
- // characters with an underscore.
162
- const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
163
- textBlobBindings[identifier] = name;
164
- } else if (type === "buffer") {
165
- // In service-worker format, data blobs are referenced by global identifiers,
166
- // so we convert it here.
167
- // This identifier has to be a valid JS identifier, so we replace all non alphanumeric
168
- // characters with an underscore.
169
- const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
170
- dataBlobBindings[identifier] = name;
171
- }
172
- }
173
- }
174
-
175
179
  const upstream =
176
180
  typeof localUpstream === "string"
177
181
  ? `${localProtocol}://${localUpstream}`
178
182
  : undefined;
179
183
 
180
- const options: MiniflareOptions = {
181
- name: workerName,
182
- port,
184
+ const {
185
+ externalDurableObjects,
186
+ internalDurableObjects,
187
+ wasmBindings,
188
+ textBlobBindings,
189
+ dataBlobBindings,
190
+ } = setupBindings({
191
+ wasm_modules: bindings.wasm_modules,
192
+ text_blobs: bindings.text_blobs,
193
+ data_blobs: bindings.data_blobs,
194
+ durable_objects: bindings.durable_objects,
195
+ format,
196
+ bundle,
197
+ });
198
+
199
+ runtimeInspectorPortRef.current ??= await getPort();
200
+ const runtimeInspectorPort = runtimeInspectorPortRef.current;
201
+
202
+ const { forkOptions, miniflareCLIPath, options } = setupMiniflareOptions({
203
+ workerName,
204
+ port: initialPort,
183
205
  scriptPath,
184
- https: localProtocol === "https",
185
- host: ip,
186
- modules: format === "modules",
187
- modulesRules: (rules || [])
188
- .concat(DEFAULT_MODULE_RULES)
189
- .map(({ type, globs: include, fallthrough }) => ({
190
- type,
191
- include,
192
- fallthrough,
193
- })),
206
+ localProtocol,
207
+ ip: initialIp,
208
+ format,
209
+ rules,
194
210
  compatibilityDate,
195
211
  compatibilityFlags,
196
- kvNamespaces: bindings.kv_namespaces?.map((kv) => kv.binding),
197
- r2Buckets: bindings.r2_buckets?.map((r2) => r2.binding),
198
- durableObjects: Object.fromEntries(
199
- (bindings.durable_objects?.bindings ?? []).map<[string, string]>(
200
- (value) => [value.name, value.class_name]
201
- )
202
- ),
203
- ...(localPersistencePath
204
- ? {
205
- cachePersist: path.join(localPersistencePath, "cache"),
206
- durableObjectsPersist: path.join(localPersistencePath, "do"),
207
- kvPersist: path.join(localPersistencePath, "kv"),
208
- r2Persist: path.join(localPersistencePath, "r2"),
209
- }
210
- : {
211
- // We mark these as true, so that they'll
212
- // persist in the temp directory.
213
- // This means they'll persist across a dev session,
214
- // even if we change source and reload,
215
- // and be deleted when the dev session ends
216
- cachePersist: true,
217
- durableObjectsPersist: true,
218
- kvPersist: true,
219
- r2Persist: true,
220
- }),
221
-
212
+ usageModel,
213
+ kv_namespaces: bindings?.kv_namespaces,
214
+ r2_buckets: bindings?.r2_buckets,
215
+ queueBindings: bindings?.queues,
216
+ queueConsumers: queueConsumers,
217
+ d1_databases: bindings?.d1_databases,
218
+ internalDurableObjects,
219
+ externalDurableObjects,
220
+ localPersistencePath,
222
221
  liveReload,
223
- sitePath: assetPaths?.assetDirectory
224
- ? path.join(assetPaths.baseDirectory, assetPaths.assetDirectory)
225
- : undefined,
226
- siteInclude: assetPaths?.includePatterns.length
227
- ? assetPaths?.includePatterns
228
- : undefined,
229
- siteExclude: assetPaths?.excludePatterns.length
230
- ? assetPaths.excludePatterns
231
- : undefined,
232
- bindings: bindings.vars,
222
+ assetPaths,
223
+ vars: bindings?.vars,
233
224
  wasmBindings,
234
225
  textBlobBindings,
235
226
  dataBlobBindings,
236
- sourceMap: true,
237
- logUnhandledRejections: true,
238
227
  crons,
239
228
  upstream,
240
- disableLogs: logLevel === "none",
241
- logOptions: logPrefix ? { prefix: logPrefix } : undefined,
242
- };
229
+ logPrefix,
230
+ workerDefinitions,
231
+ enablePagesAssetsServiceBinding,
232
+ });
243
233
 
244
- // The path to the Miniflare CLI assumes that this file is being run from
245
- // `wrangler-dist` and that the CLI is found in `miniflare-dist`.
246
- // If either of those paths change this line needs updating.
247
- const miniflareCLIPath = path.resolve(
248
- __dirname,
249
- "../miniflare-dist/index.mjs"
250
- );
251
- const miniflareOptions = JSON.stringify(options, null);
234
+ if (experimentalLocal) {
235
+ const log = await buildMiniflare3Logger(logPrefix);
236
+ const mf3Options = await transformMf2OptionsToMf3Options({
237
+ miniflare2Options: options,
238
+ format,
239
+ bundle,
240
+ log,
241
+ enablePagesAssetsServiceBinding,
242
+ kvNamespaces: bindings?.kv_namespaces,
243
+ r2Buckets: bindings?.r2_buckets,
244
+ authenticatedAccountId: accountId,
245
+ kvRemote: experimentalLocalRemoteKv,
246
+ inspectorPort: runtimeInspectorPort,
247
+ });
252
248
 
253
- logger.log("⎔ Starting a local server...");
254
- const nodeOptions = [
255
- "--experimental-vm-modules", // ensures that Miniflare can run ESM Workers
256
- "--no-warnings", // hide annoying Node warnings
257
- // "--log=VERBOSE", // uncomment this to Miniflare to log "everything"!
258
- ];
259
- if (inspect) {
260
- nodeOptions.push("--inspect"); // start Miniflare listening for a debugger to attach
261
- }
249
+ const current = experimentalLocalRef.current;
262
250
 
263
- const forkOptions = [miniflareOptions];
251
+ if (current === undefined) {
252
+ // If we don't have an active Miniflare instance, create a new one
253
+ const { Miniflare } = await getMiniflare3();
254
+ if (abortController.signal.aborted) return;
255
+ const mf = new Miniflare(mf3Options);
256
+ experimentalLocalRef.current = mf;
257
+ removeExperimentalLocalSignalExitListener.current = onExit(() => {
258
+ logger.log("⎔ Shutting down experimental local server.");
259
+ void mf.dispose();
260
+ experimentalLocalRef.current = undefined;
261
+ });
262
+ await mf.ready;
263
+ } else {
264
+ // Otherwise, reuse the existing instance with its loopback server
265
+ // and just update the options
266
+ if (abortController.signal.aborted) return;
267
+ logger.log("⎔ Reloading experimental local server.");
268
+ await current.setOptions(mf3Options);
269
+ }
264
270
 
265
- if (enablePagesAssetsServiceBinding) {
266
- forkOptions.push(JSON.stringify(enablePagesAssetsServiceBinding));
271
+ try {
272
+ // fetch the inspector JSON response from the DevTools Inspector protocol
273
+ const inspectorJSONArr = (await (
274
+ await fetch(`http://127.0.0.1:${runtimeInspectorPort}/json`)
275
+ ).json()) as InspectorJSON;
276
+
277
+ const foundInspectorURL = inspectorJSONArr?.find((inspectorJSON) =>
278
+ inspectorJSON.id.startsWith("core:user")
279
+ )?.webSocketDebuggerUrl;
280
+ if (foundInspectorURL === undefined) {
281
+ setInspectorUrl(undefined);
282
+ } else {
283
+ const url = new URL(foundInspectorURL);
284
+ // Force inspector URL to be different on each reload so `useEffect`
285
+ // in `useInspector` is re-run to connect to newly restarted
286
+ // `workerd` server when updating options. Can't use a query param
287
+ // here as that seems to cause an infinite connection loop, can't
288
+ // use a hash as those are forbidden by `ws`, so username it is.
289
+ url.username = `${Date.now()}-${Math.floor(
290
+ Math.random() * Number.MAX_SAFE_INTEGER
291
+ )}`;
292
+ setInspectorUrl(url.toString());
293
+ }
294
+ } catch (error: unknown) {
295
+ logger.error("Error attempting to retrieve Debugger URL:", error);
296
+ }
297
+
298
+ return;
267
299
  }
268
300
 
301
+ // Wait for the Worker port to be available. We don't want to do this in experimental local
302
+ // mode, as we only `dispose()` the Miniflare 3 instance, and shutdown the server when
303
+ // unmounting the component, not when props change. If we did, we'd just timeout every time.
304
+ await waitForPortToBeAvailable(initialPort, {
305
+ retryPeriod: 200,
306
+ timeout: 2000,
307
+ abortSignal: abortController.signal,
308
+ });
309
+
310
+ const nodeOptions = setupNodeOptions({
311
+ inspect,
312
+ inspectorPort: runtimeInspectorPort,
313
+ });
314
+ logger.log("⎔ Starting a local server...");
315
+
316
+ const hasColourSupport =
317
+ chalk.supportsColor.hasBasic && process.env.FORCE_COLOR !== "0";
269
318
  const child = (local.current = fork(miniflareCLIPath, forkOptions, {
270
319
  cwd: path.dirname(scriptPath),
271
320
  execArgv: nodeOptions,
272
321
  stdio: "pipe",
322
+ env: {
323
+ ...process.env,
324
+ FORCE_COLOR: hasColourSupport ? "1" : undefined,
325
+ },
273
326
  }));
274
327
 
275
- child.on("message", (message) => {
276
- if (message === "ready") {
277
- onReady?.();
328
+ child.on("message", async (messageString) => {
329
+ const message = JSON.parse(messageString as string);
330
+ if (message.ready) {
331
+ // Let's register our presence in the dev registry
332
+ if (workerName) {
333
+ await registerWorker(workerName, {
334
+ protocol: localProtocol,
335
+ mode: "local",
336
+ port: message.port,
337
+ host: initialIp,
338
+ durableObjects: internalDurableObjects.map((binding) => ({
339
+ name: binding.name,
340
+ className: binding.class_name,
341
+ })),
342
+ ...(message.durableObjectsPort
343
+ ? {
344
+ durableObjectsHost: initialIp,
345
+ durableObjectsPort: message.durableObjectsPort,
346
+ }
347
+ : {}),
348
+ });
349
+ }
350
+ onReady?.(initialIp, message.port);
278
351
  }
279
352
  });
280
353
 
@@ -326,7 +399,13 @@ function useLocalWorker({
326
399
  }
327
400
 
328
401
  startLocalWorker().catch((err) => {
329
- logger.error("local worker:", err);
402
+ if (err.code === "ERR_RUNTIME_FAILURE") {
403
+ // Don't log a full verbose stack-trace when Miniflare 3's workerd instance fails to start.
404
+ // workerd will log its own errors, and our stack trace won't have any useful information.
405
+ logger.error(err.message);
406
+ } else {
407
+ logger.error("local worker:", err);
408
+ }
330
409
  });
331
410
 
332
411
  return () => {
@@ -335,27 +414,31 @@ function useLocalWorker({
335
414
  logger.log("⎔ Shutting down local server.");
336
415
  local.current?.kill();
337
416
  local.current = undefined;
338
- removeSignalExitListener.current && removeSignalExitListener.current();
339
- removeSignalExitListener.current = undefined;
340
417
  }
418
+ removeSignalExitListener.current?.();
419
+ removeSignalExitListener.current = undefined;
341
420
  };
342
421
  }, [
343
422
  bundle,
344
423
  workerName,
345
424
  format,
346
- port,
347
- ip,
348
- bindings.durable_objects?.bindings,
425
+ initialPort,
426
+ initialIp,
427
+ queueConsumers,
428
+ bindings.queues,
429
+ bindings.durable_objects,
349
430
  bindings.kv_namespaces,
350
431
  bindings.r2_buckets,
432
+ bindings.d1_databases,
351
433
  bindings.vars,
352
434
  bindings.services,
435
+ workerDefinitions,
353
436
  compatibilityDate,
354
437
  compatibilityFlags,
438
+ usageModel,
355
439
  localPersistencePath,
356
440
  liveReload,
357
441
  assetPaths,
358
- isWorkersSite,
359
442
  rules,
360
443
  bindings.wasm_modules,
361
444
  bindings.text_blobs,
@@ -364,10 +447,486 @@ function useLocalWorker({
364
447
  localProtocol,
365
448
  localUpstream,
366
449
  inspect,
367
- logLevel,
368
450
  logPrefix,
369
451
  onReady,
370
452
  enablePagesAssetsServiceBinding,
453
+ experimentalLocal,
454
+ accountId,
455
+ experimentalLocalRemoteKv,
371
456
  ]);
457
+
458
+ // Rather than disposing the Miniflare instance on every reload, only dispose
459
+ // it if local mode is disabled and the `Local` component is unmounted. This
460
+ // allows us to use the more efficient `Miniflare#setOptions` on reload which
461
+ // retains internal state (e.g. the Miniflare loopback server).
462
+ useEffect(
463
+ () => () => {
464
+ if (experimentalLocalRef.current) {
465
+ logger.log("⎔ Shutting down experimental local server.");
466
+ // Initialisation errors are also thrown asynchronously by dispose().
467
+ // The catch() above should've caught them though.
468
+ experimentalLocalRef.current?.dispose().catch(() => {});
469
+ experimentalLocalRef.current = undefined;
470
+ }
471
+ removeExperimentalLocalSignalExitListener.current?.();
472
+ removeExperimentalLocalSignalExitListener.current = undefined;
473
+ },
474
+ []
475
+ );
476
+
372
477
  return { inspectorUrl };
373
478
  }
479
+
480
+ interface SetupBindingsProps {
481
+ wasm_modules: CfWasmModuleBindings | undefined;
482
+ text_blobs: CfTextBlobBindings | undefined;
483
+ data_blobs: CfDataBlobBindings | undefined;
484
+ durable_objects: { bindings: CfDurableObject[] } | undefined;
485
+ bundle: EsbuildBundle;
486
+ format: CfScriptFormat;
487
+ }
488
+
489
+ export function setupBindings({
490
+ wasm_modules,
491
+ text_blobs,
492
+ data_blobs,
493
+ durable_objects,
494
+ format,
495
+ bundle,
496
+ }: SetupBindingsProps) {
497
+ // the wasm_modules/text_blobs/data_blobs bindings are
498
+ // relative to process.cwd(), but the actual worker bundle
499
+ // is in the temp output directory; so we rewrite the paths to be absolute,
500
+ // letting miniflare resolve them correctly
501
+
502
+ // wasm
503
+ const wasmBindings: Record<string, string> = {};
504
+ for (const [name, filePath] of Object.entries(wasm_modules || {})) {
505
+ wasmBindings[name] = path.join(process.cwd(), filePath);
506
+ }
507
+
508
+ // text
509
+ const textBlobBindings: Record<string, string> = {};
510
+ for (const [name, filePath] of Object.entries(text_blobs || {})) {
511
+ textBlobBindings[name] = path.join(process.cwd(), filePath);
512
+ }
513
+
514
+ // data
515
+ const dataBlobBindings: Record<string, string> = {};
516
+ for (const [name, filePath] of Object.entries(data_blobs || {})) {
517
+ dataBlobBindings[name] = path.join(process.cwd(), filePath);
518
+ }
519
+
520
+ if (format === "service-worker") {
521
+ for (const { type, name } of bundle.modules) {
522
+ if (type === "compiled-wasm") {
523
+ // In service-worker format, .wasm modules are referenced by global identifiers,
524
+ // so we convert it here.
525
+ // This identifier has to be a valid JS identifier, so we replace all non alphanumeric
526
+ // characters with an underscore.
527
+ const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
528
+ wasmBindings[identifier] = name;
529
+ } else if (type === "text") {
530
+ // In service-worker format, text modules are referenced by global identifiers,
531
+ // so we convert it here.
532
+ // This identifier has to be a valid JS identifier, so we replace all non alphanumeric
533
+ // characters with an underscore.
534
+ const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
535
+ textBlobBindings[identifier] = name;
536
+ } else if (type === "buffer") {
537
+ // In service-worker format, data blobs are referenced by global identifiers,
538
+ // so we convert it here.
539
+ // This identifier has to be a valid JS identifier, so we replace all non alphanumeric
540
+ // characters with an underscore.
541
+ const identifier = name.replace(/[^a-zA-Z0-9_$]/g, "_");
542
+ dataBlobBindings[identifier] = name;
543
+ }
544
+ }
545
+ }
546
+
547
+ const internalDurableObjects = (durable_objects?.bindings || []).filter(
548
+ (binding) => !binding.script_name
549
+ );
550
+ const externalDurableObjects = (durable_objects?.bindings || []).filter(
551
+ (binding) => binding.script_name
552
+ );
553
+ return {
554
+ internalDurableObjects,
555
+ externalDurableObjects,
556
+ wasmBindings,
557
+ textBlobBindings,
558
+ dataBlobBindings,
559
+ };
560
+ }
561
+
562
+ interface SetupMiniflareOptionsProps {
563
+ workerName: string | undefined;
564
+ port: number;
565
+ scriptPath: string;
566
+ localProtocol: "http" | "https";
567
+ ip: string;
568
+ format: CfScriptFormat;
569
+ rules: Config["rules"];
570
+ compatibilityDate: string;
571
+ compatibilityFlags: string[] | undefined;
572
+ usageModel: "bundled" | "unbound" | undefined;
573
+ kv_namespaces: CfKvNamespace[] | undefined;
574
+ queueBindings: CfQueue[] | undefined;
575
+ queueConsumers: Config["queues"]["consumers"];
576
+ r2_buckets: CfR2Bucket[] | undefined;
577
+ d1_databases: CfD1Database[] | undefined;
578
+ internalDurableObjects: CfDurableObject[];
579
+ externalDurableObjects: CfDurableObject[];
580
+ localPersistencePath: string | null;
581
+ liveReload: boolean;
582
+ assetPaths: AssetPaths | undefined;
583
+ vars: CfVars | undefined;
584
+ wasmBindings: Record<string, string>;
585
+ textBlobBindings: Record<string, string>;
586
+ dataBlobBindings: Record<string, string>;
587
+ crons: Config["triggers"]["crons"];
588
+ upstream: string | undefined;
589
+ logPrefix: string | undefined;
590
+ workerDefinitions: WorkerRegistry | undefined;
591
+ enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
592
+ }
593
+
594
+ export function setupMiniflareOptions({
595
+ workerName,
596
+ port,
597
+ scriptPath,
598
+ localProtocol,
599
+ ip,
600
+ format,
601
+ rules,
602
+ compatibilityDate,
603
+ compatibilityFlags,
604
+ usageModel,
605
+ kv_namespaces,
606
+ queueBindings,
607
+ queueConsumers,
608
+ r2_buckets,
609
+ d1_databases,
610
+ internalDurableObjects,
611
+ externalDurableObjects,
612
+ localPersistencePath,
613
+ liveReload,
614
+ assetPaths,
615
+ vars,
616
+ wasmBindings,
617
+ textBlobBindings,
618
+ dataBlobBindings,
619
+ crons,
620
+ upstream,
621
+ logPrefix,
622
+ workerDefinitions,
623
+ enablePagesAssetsServiceBinding,
624
+ }: SetupMiniflareOptionsProps): {
625
+ miniflareCLIPath: string;
626
+ forkOptions: string[];
627
+ options: MiniflareOptions;
628
+ } {
629
+ // It's now getting _really_ messy now with Pages ASSETS binding outside and the external Durable Objects inside.
630
+ const options: MiniflareOptions = {
631
+ name: workerName,
632
+ port,
633
+ scriptPath,
634
+ https: localProtocol === "https",
635
+ host: ip,
636
+ modules: format === "modules",
637
+ modulesRules: (rules || [])
638
+ .concat(DEFAULT_MODULE_RULES)
639
+ .map(({ type, globs: include, fallthrough }) => ({
640
+ type,
641
+ include,
642
+ fallthrough,
643
+ })),
644
+ compatibilityDate,
645
+ compatibilityFlags,
646
+ usageModel,
647
+ kvNamespaces: kv_namespaces?.map((kv) => kv.binding),
648
+ queueBindings: queueBindings?.map((queue) => {
649
+ return { name: queue.binding, queueName: queue.queue_name };
650
+ }),
651
+ queueConsumers: queueConsumers?.map((consumer) => {
652
+ const waitMs = consumer.max_batch_timeout
653
+ ? 1000 * consumer.max_batch_timeout
654
+ : undefined;
655
+ return {
656
+ queueName: consumer.queue,
657
+ maxBatchSize: consumer.max_batch_size,
658
+ maxWaitMs: waitMs,
659
+ maxRetries: consumer.max_retries,
660
+ deadLetterQueue: consumer.dead_letter_queue,
661
+ };
662
+ }),
663
+ r2Buckets: r2_buckets?.map((r2) => r2.binding),
664
+ durableObjects: Object.fromEntries(
665
+ internalDurableObjects.map((binding) => [
666
+ binding.name,
667
+ binding.class_name,
668
+ ])
669
+ ),
670
+ externalDurableObjects: Object.fromEntries(
671
+ externalDurableObjects
672
+ .map((binding) => {
673
+ const service =
674
+ workerDefinitions &&
675
+ workerDefinitions[binding.script_name as string];
676
+ if (!service) return [binding.name, undefined];
677
+
678
+ const name = service.durableObjects.find(
679
+ (durableObject) => durableObject.className === binding.class_name
680
+ )?.name;
681
+ if (!name) return [binding.name, undefined];
682
+
683
+ return [
684
+ binding.name,
685
+ {
686
+ name,
687
+ host: service.durableObjectsHost,
688
+ port: service.durableObjectsPort,
689
+ },
690
+ ];
691
+ })
692
+ .filter(([_, details]) => !!details)
693
+ ),
694
+ d1Databases: d1_databases?.map((db) => db.binding),
695
+ ...(localPersistencePath
696
+ ? {
697
+ cachePersist: path.join(localPersistencePath, "cache"),
698
+ durableObjectsPersist: path.join(localPersistencePath, "do"),
699
+ kvPersist: path.join(localPersistencePath, "kv"),
700
+ r2Persist: path.join(localPersistencePath, "r2"),
701
+ d1Persist: path.join(localPersistencePath, "d1"),
702
+ }
703
+ : {
704
+ // We mark these as true, so that they'll
705
+ // persist in the temp directory.
706
+ // This means they'll persist across a dev session,
707
+ // even if we change source and reload,
708
+ // and be deleted when the dev session ends
709
+ cachePersist: true,
710
+ durableObjectsPersist: true,
711
+ kvPersist: true,
712
+ r2Persist: true,
713
+ }),
714
+
715
+ liveReload,
716
+ sitePath: assetPaths?.assetDirectory
717
+ ? path.join(assetPaths.baseDirectory, assetPaths.assetDirectory)
718
+ : undefined,
719
+ siteInclude: assetPaths?.includePatterns.length
720
+ ? assetPaths?.includePatterns
721
+ : undefined,
722
+ siteExclude: assetPaths?.excludePatterns.length
723
+ ? assetPaths.excludePatterns
724
+ : undefined,
725
+ bindings: vars,
726
+ wasmBindings,
727
+ textBlobBindings,
728
+ dataBlobBindings,
729
+ sourceMap: true,
730
+ logUnhandledRejections: true,
731
+ crons,
732
+ upstream,
733
+ logLevel: logger.loggerLevel,
734
+ logOptions: logPrefix ? { prefix: logPrefix } : undefined,
735
+ enablePagesAssetsServiceBinding,
736
+ };
737
+ // The path to the Miniflare CLI assumes that this file is being run from
738
+ // `wrangler-dist` and that the CLI is found in `miniflare-dist`.
739
+ // If either of those paths change this line needs updating.
740
+ const miniflareCLIPath = path.resolve(
741
+ getBasePath(),
742
+ "miniflare-dist/index.mjs"
743
+ );
744
+ const miniflareOptions = JSON.stringify(options, null);
745
+ const forkOptions = [miniflareOptions];
746
+ if (enablePagesAssetsServiceBinding) {
747
+ forkOptions.push(JSON.stringify(enablePagesAssetsServiceBinding));
748
+ }
749
+ return { miniflareCLIPath, forkOptions, options };
750
+ }
751
+
752
+ export function setupNodeOptions({
753
+ inspect,
754
+ inspectorPort,
755
+ }: {
756
+ inspect: boolean;
757
+ inspectorPort: number;
758
+ }) {
759
+ const nodeOptions = [
760
+ "--experimental-vm-modules", // ensures that Miniflare can run ESM Workers
761
+ "--no-warnings", // hide annoying Node warnings
762
+ // "--log=VERBOSE", // uncomment this to Miniflare to log "everything"!
763
+ ];
764
+ if (inspect) {
765
+ nodeOptions.push("--inspect=" + `127.0.0.1:${inspectorPort}`); // start Miniflare listening for a debugger to attach
766
+ }
767
+ return nodeOptions;
768
+ }
769
+
770
+ export interface SetupMiniflare3Options {
771
+ // Regular Miniflare 2 options to transform
772
+ miniflare2Options: MiniflareOptions;
773
+ // Miniflare 3 requires all modules to be manually specified
774
+ format: CfScriptFormat;
775
+ bundle: EsbuildBundle;
776
+
777
+ // Miniflare's logger
778
+ log: Miniflare3LogType;
779
+
780
+ enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
781
+
782
+ // Miniflare 3 accepts namespace/bucket names in addition to binding names.
783
+ // This means multiple workers persisting to the same location can have
784
+ // different binding names for the same namespace/bucket. Therefore, we need
785
+ // the full KV/R2 arrays. This is also required for remote KV storage, as
786
+ // we need actual namespace IDs to connect to.
787
+ kvNamespaces: CfKvNamespace[] | undefined;
788
+ r2Buckets: CfR2Bucket[] | undefined;
789
+
790
+ // Account ID to use for authenticated Cloudflare fetch. If true, prompt
791
+ // user for ID if multiple available.
792
+ authenticatedAccountId: string | true | undefined;
793
+ // Whether to read/write from/to real KV namespaces
794
+ kvRemote: boolean | undefined;
795
+
796
+ // Port to start DevTools inspector server on
797
+ inspectorPort: number;
798
+ }
799
+
800
+ export async function buildMiniflare3Logger(
801
+ logPrefix?: string
802
+ ): Promise<Miniflare3LogType> {
803
+ const { Log, NoOpLog, LogLevel } = await getMiniflare3();
804
+
805
+ let level = logger.loggerLevel.toUpperCase() as Uppercase<LoggerLevel>;
806
+ if (level === "LOG") level = "INFO";
807
+ const logLevel = LogLevel[level];
808
+
809
+ return logLevel === LogLevel.NONE
810
+ ? new NoOpLog()
811
+ : new Log(logLevel, { prefix: logPrefix });
812
+ }
813
+
814
+ export async function transformMf2OptionsToMf3Options({
815
+ miniflare2Options,
816
+ format,
817
+ bundle,
818
+ log,
819
+ enablePagesAssetsServiceBinding,
820
+ kvNamespaces,
821
+ r2Buckets,
822
+ authenticatedAccountId,
823
+ kvRemote,
824
+ inspectorPort,
825
+ }: SetupMiniflare3Options): Promise<Miniflare3Options> {
826
+ // Build authenticated Cloudflare API fetch function if required
827
+ let cloudflareFetch: CloudflareFetch | undefined;
828
+ if (kvRemote && authenticatedAccountId !== undefined) {
829
+ const preferredAccountId =
830
+ authenticatedAccountId === true ? undefined : authenticatedAccountId;
831
+ const accountId = await requireAuth({ account_id: preferredAccountId });
832
+ cloudflareFetch = (resource, searchParams, init) => {
833
+ resource = `/accounts/${accountId}/${resource}`;
834
+ // Miniflare and Wrangler's `undici` versions may be slightly different,
835
+ // but their `RequestInit` types *should* be compatible
836
+ return performApiFetch(resource, init as RequestInit, searchParams);
837
+ };
838
+ }
839
+
840
+ const options: Miniflare3Options = {
841
+ ...miniflare2Options,
842
+ // Miniflare 3 distinguishes between binding name and namespace/bucket IDs.
843
+ kvNamespaces: Object.fromEntries(
844
+ kvNamespaces?.map(({ binding, id }) => [binding, id]) ?? []
845
+ ),
846
+ r2Buckets: Object.fromEntries(
847
+ r2Buckets?.map(({ binding, bucket_name }) => [binding, bucket_name]) ?? []
848
+ ),
849
+ inspectorPort,
850
+ verbose: true,
851
+ cloudflareFetch,
852
+ log,
853
+ };
854
+
855
+ if (enablePagesAssetsServiceBinding !== undefined) {
856
+ // `../miniflare-cli/assets` dynamically imports`@cloudflare/pages-shared/environment-polyfills`.
857
+ // `@cloudflare/pages-shared/environment-polyfills/types.ts` defines `global`
858
+ // augmentations that pollute the `import`-site's typing environment.
859
+ //
860
+ // We `require` instead of `import`ing here to avoid polluting the main
861
+ // `wrangler` TypeScript project with the `global` augmentations. This
862
+ // relies on the fact that `require` is untyped.
863
+ //
864
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
865
+ const generateASSETSBinding = require("../miniflare-cli/assets");
866
+ options.serviceBindings = {
867
+ ...options.serviceBindings,
868
+ ASSETS: (await generateASSETSBinding({
869
+ log,
870
+ ...enablePagesAssetsServiceBinding,
871
+ tre: true,
872
+ // We can get rid of this `any` easily once we do experimental-local/tre by default
873
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
874
+ })) as any,
875
+ };
876
+ }
877
+
878
+ if (format === "modules") {
879
+ // Manually specify all modules from the bundle. If we didn't do this,
880
+ // Miniflare 3 would try collect them automatically again itself.
881
+
882
+ // Resolve entrypoint relative to the temporary directory, ensuring
883
+ // path doesn't start with `..`, which causes issues in `workerd`.
884
+ // Also ensures other modules with relative names can be resolved.
885
+ const root = path.dirname(bundle.path);
886
+
887
+ assert.strictEqual(bundle.type, "esm");
888
+ // Required for source mapped paths to resolve correctly
889
+ options.modulesRoot = root;
890
+ options.modules = [
891
+ // Entrypoint
892
+ {
893
+ type: "ESModule",
894
+ path: bundle.path,
895
+ contents: await readFile(bundle.path, "utf-8"),
896
+ },
897
+ // Misc (WebAssembly, etc, ...)
898
+ ...bundle.modules.map((module) => ({
899
+ type: ModuleTypeToRuleType[module.type ?? "esm"],
900
+ path: path.resolve(root, module.name),
901
+ contents: module.content,
902
+ })),
903
+ ];
904
+ }
905
+
906
+ if (kvRemote) {
907
+ // `kvPersist` is always assigned a truthy value in `setupMiniflareOptions`
908
+ assert(options.kvPersist);
909
+ const kvRemoteCache =
910
+ options.kvPersist === true
911
+ ? // If storing in temporary directory, find this path from the bundle
912
+ // output path
913
+ path.join(path.dirname(bundle.path), ".mf", "kv-remote")
914
+ : // Otherwise, `kvPersist` looks like `.../kv`, so rewrite it to
915
+ // `kv-remote` since the expected metadata format for remote storage
916
+ // is different to local
917
+ path.join(path.dirname(options.kvPersist), "kv-remote");
918
+ options.kvPersist = `remote:?cache=${encodeURIComponent(kvRemoteCache)}`;
919
+ }
920
+
921
+ return options;
922
+ }
923
+
924
+ // Caching of the `npx-import`ed `@miniflare/tre` package
925
+ // eslint-disable-next-line @typescript-eslint/consistent-type-imports
926
+ let miniflare3Module: typeof import("@miniflare/tre");
927
+ export async function getMiniflare3(): Promise<
928
+ // eslint-disable-next-line @typescript-eslint/consistent-type-imports
929
+ typeof import("@miniflare/tre")
930
+ > {
931
+ return (miniflare3Module ??= await npxImport("@miniflare/tre@3.0.0-next.8"));
932
+ }