wrangler 0.0.0-e7a0cd5b → 0.0.0-e7cf0caa

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 (237) hide show
  1. package/README.md +3 -3
  2. package/bin/wrangler.js +30 -10
  3. package/miniflare-dist/index.mjs +126 -74
  4. package/package.json +27 -19
  5. package/src/__tests__/api-dev.test.ts +65 -36
  6. package/src/__tests__/api-devregistry.test.ts +121 -0
  7. package/src/__tests__/api.test.ts +27 -5
  8. package/src/__tests__/configuration.test.ts +567 -86
  9. package/src/__tests__/d1/d1.test.ts +82 -0
  10. package/src/__tests__/d1/execute.test.ts +66 -0
  11. package/src/__tests__/d1/migrate.test.ts +157 -0
  12. package/src/__tests__/d1/splitter.test.ts +255 -0
  13. package/src/__tests__/delete.test.ts +91 -49
  14. package/src/__tests__/deployments.test.ts +330 -56
  15. package/src/__tests__/dev.test.tsx +198 -90
  16. package/src/__tests__/generate.test.ts +12 -10
  17. package/src/__tests__/guess-worker-format.test.ts +35 -0
  18. package/src/__tests__/helpers/end-event-loop.ts +6 -0
  19. package/src/__tests__/helpers/mock-auth-domain.ts +20 -0
  20. package/src/__tests__/helpers/mock-console.ts +6 -0
  21. package/src/__tests__/helpers/mock-dialogs.ts +71 -85
  22. package/src/__tests__/helpers/mock-get-pages-upload-token.ts +25 -0
  23. package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -5
  24. package/src/__tests__/helpers/mock-known-routes.ts +7 -2
  25. package/src/__tests__/helpers/mock-kv.ts +29 -16
  26. package/src/__tests__/helpers/mock-oauth-flow.ts +103 -92
  27. package/src/__tests__/helpers/mock-process.ts +8 -13
  28. package/src/__tests__/helpers/mock-set-timeout.ts +16 -0
  29. package/src/__tests__/helpers/msw/blob-worker.cjs +19 -0
  30. package/src/__tests__/helpers/msw/handlers/deployments.ts +128 -27
  31. package/src/__tests__/helpers/msw/handlers/namespaces.ts +18 -41
  32. package/src/__tests__/helpers/msw/handlers/oauth.ts +13 -18
  33. package/src/__tests__/helpers/msw/handlers/r2.ts +14 -34
  34. package/src/__tests__/helpers/msw/handlers/script.ts +9 -28
  35. package/src/__tests__/helpers/msw/handlers/user.ts +13 -24
  36. package/src/__tests__/helpers/msw/handlers/zones.ts +6 -8
  37. package/src/__tests__/helpers/msw/index.ts +32 -1
  38. package/src/__tests__/helpers/msw/read-file-sync.js +61 -0
  39. package/src/__tests__/helpers/string-dynamic-values-matcher.ts +28 -0
  40. package/src/__tests__/helpers/worker-scripts/parent-worker.js +4 -1
  41. package/src/__tests__/index.test.ts +90 -72
  42. package/src/__tests__/init.test.ts +927 -631
  43. package/src/__tests__/jest.setup.ts +19 -42
  44. package/src/__tests__/kv.test.ts +355 -231
  45. package/src/__tests__/logger.test.ts +68 -2
  46. package/src/__tests__/logout.test.ts +47 -0
  47. package/src/__tests__/metrics.test.ts +94 -51
  48. package/src/__tests__/middleware.scheduled.test.ts +42 -32
  49. package/src/__tests__/middleware.test.ts +212 -122
  50. package/src/__tests__/mtls-certificates.test.ts +588 -0
  51. package/src/__tests__/pages/deployment-list.test.ts +80 -0
  52. package/src/__tests__/pages/functions-build.test.ts +452 -0
  53. package/src/__tests__/pages/pages.test.ts +81 -0
  54. package/src/__tests__/pages/project-create.test.ts +63 -0
  55. package/src/__tests__/pages/project-list.test.ts +110 -0
  56. package/src/__tests__/pages/project-upload.test.ts +500 -0
  57. package/src/__tests__/pages/publish.test.ts +2864 -0
  58. package/src/__tests__/pages-deployment-tail.test.ts +955 -0
  59. package/src/__tests__/parse.test.ts +106 -0
  60. package/src/__tests__/publish.test.ts +1615 -852
  61. package/src/__tests__/pubsub.test.ts +226 -100
  62. package/src/__tests__/queues.test.ts +238 -77
  63. package/src/__tests__/r2.test.ts +55 -57
  64. package/src/__tests__/secret.test.ts +179 -130
  65. package/src/__tests__/tail.test.ts +267 -109
  66. package/src/__tests__/test-old-node-version.js +3 -3
  67. package/src/__tests__/traverse-module-graph.test.ts +220 -0
  68. package/src/__tests__/tsconfig-sanity.ts +12 -0
  69. package/src/__tests__/tsconfig.json +8 -0
  70. package/src/__tests__/tsconfig.tsbuildinfo +1 -0
  71. package/src/__tests__/type-generation.test.ts +43 -26
  72. package/src/__tests__/user.test.ts +35 -72
  73. package/src/__tests__/whoami.test.tsx +8 -114
  74. package/src/__tests__/worker-namespace.test.ts +83 -69
  75. package/src/api/dev.ts +168 -60
  76. package/src/api/index.ts +10 -1
  77. package/src/api/mtls-certificate.ts +148 -0
  78. package/src/api/pages/create-worker-bundle-contents.ts +76 -0
  79. package/src/api/pages/index.ts +5 -0
  80. package/src/api/pages/publish.tsx +355 -0
  81. package/src/bundle.ts +263 -140
  82. package/src/cfetch/index.ts +0 -2
  83. package/src/cfetch/internal.ts +16 -21
  84. package/src/cli.ts +4 -4
  85. package/src/config/config.ts +14 -7
  86. package/src/config/environment.ts +79 -24
  87. package/src/config/index.ts +99 -38
  88. package/src/config/validation-helpers.ts +1 -1
  89. package/src/config/validation.ts +248 -19
  90. package/src/create-worker-preview.ts +19 -8
  91. package/src/create-worker-upload-form.ts +60 -22
  92. package/src/d1/backups.tsx +43 -42
  93. package/src/d1/constants.ts +2 -0
  94. package/src/d1/create.tsx +44 -30
  95. package/src/d1/{delete.tsx → delete.ts} +11 -14
  96. package/src/d1/execute.tsx +209 -151
  97. package/src/d1/index.ts +31 -7
  98. package/src/d1/list.tsx +22 -10
  99. package/src/d1/migrations/apply.tsx +212 -0
  100. package/src/d1/migrations/create.tsx +79 -0
  101. package/src/d1/migrations/helpers.ts +172 -0
  102. package/src/d1/migrations/index.ts +3 -0
  103. package/src/d1/migrations/list.tsx +95 -0
  104. package/src/d1/migrations/options.ts +23 -0
  105. package/src/d1/options.ts +12 -2
  106. package/src/d1/splitter.ts +161 -0
  107. package/src/d1/{types.tsx → types.ts} +11 -0
  108. package/src/d1/utils.ts +12 -2
  109. package/src/delete.ts +8 -16
  110. package/src/deployments.ts +340 -46
  111. package/src/deprecated/index.ts +8 -302
  112. package/src/dev/dev.tsx +57 -44
  113. package/src/dev/local.tsx +294 -91
  114. package/src/dev/remote.tsx +32 -3
  115. package/src/dev/start-server.ts +89 -68
  116. package/src/dev/use-esbuild.ts +17 -18
  117. package/src/dev/validate-dev-props.ts +9 -0
  118. package/src/{dev-registry.tsx → dev-registry.ts} +2 -1
  119. package/src/dev.tsx +178 -118
  120. package/src/dialogs.ts +136 -0
  121. package/src/dispatch-namespace.ts +21 -17
  122. package/src/docs/index.ts +97 -0
  123. package/src/entry.ts +19 -11
  124. package/src/environment-variables/factory.ts +88 -0
  125. package/src/environment-variables/misc-variables.ts +30 -0
  126. package/src/generate/index.ts +298 -0
  127. package/src/git-client.ts +15 -4
  128. package/src/{index.tsx → index.ts} +137 -63
  129. package/src/init.ts +249 -187
  130. package/src/inspect.ts +16 -1
  131. package/src/jest.d.ts +4 -0
  132. package/src/kv/index.ts +15 -17
  133. package/src/logger.ts +32 -5
  134. package/src/metrics/metrics-config.ts +3 -3
  135. package/src/metrics/send-event.ts +6 -1
  136. package/src/miniflare-cli/assets.ts +64 -28
  137. package/src/miniflare-cli/index.ts +4 -7
  138. package/src/miniflare-cli/tsconfig.json +9 -0
  139. package/src/miniflare-cli/tsconfig.tsbuildinfo +1 -0
  140. package/src/miniflare-cli/types.ts +11 -0
  141. package/src/module-collection.ts +91 -25
  142. package/src/mtls-certificate/cli.ts +155 -0
  143. package/src/pages/build.ts +410 -0
  144. package/src/pages/buildFunctions.ts +140 -0
  145. package/src/pages/deployment-tails.ts +281 -0
  146. package/src/pages/deployments.tsx +11 -33
  147. package/src/pages/{dev.tsx → dev.ts} +147 -124
  148. package/src/pages/errors.ts +1 -0
  149. package/src/pages/functions/buildPlugin.ts +72 -69
  150. package/src/pages/functions/buildWorker.ts +249 -114
  151. package/src/pages/functions/tsconfig.json +8 -0
  152. package/src/pages/functions/tsconfig.tsbuildinfo +1 -0
  153. package/src/pages/{functions.tsx → functions.ts} +8 -4
  154. package/src/pages/{index.tsx → index.ts} +11 -3
  155. package/src/pages/projects.tsx +12 -22
  156. package/src/pages/prompt-select-project.tsx +31 -0
  157. package/src/pages/publish.tsx +45 -214
  158. package/src/pages/types.ts +6 -9
  159. package/src/pages/upload.tsx +58 -14
  160. package/src/pages/utils.ts +14 -3
  161. package/src/parse.ts +23 -1
  162. package/src/paths.ts +1 -1
  163. package/src/proxy.ts +5 -0
  164. package/src/publish/index.ts +20 -16
  165. package/src/publish/publish.ts +72 -47
  166. package/src/pubsub/{pubsub-commands.tsx → pubsub-commands.ts} +19 -20
  167. package/src/queues/cli/commands/consumer/add.ts +24 -24
  168. package/src/queues/cli/commands/consumer/index.ts +3 -6
  169. package/src/queues/cli/commands/consumer/remove.ts +11 -18
  170. package/src/queues/cli/commands/create.ts +8 -8
  171. package/src/queues/cli/commands/delete.ts +8 -8
  172. package/src/queues/cli/commands/index.ts +6 -4
  173. package/src/queues/cli/commands/list.ts +8 -8
  174. package/src/queues/client.ts +1 -0
  175. package/src/queues/utils.ts +18 -0
  176. package/src/r2/constants.ts +4 -0
  177. package/src/r2/helpers.ts +1 -0
  178. package/src/r2/index.ts +40 -28
  179. package/src/secret/index.ts +14 -17
  180. package/src/{sites.tsx → sites.ts} +1 -1
  181. package/src/tail/createTail.ts +94 -3
  182. package/src/tail/filters.ts +1 -1
  183. package/src/tail/index.ts +8 -11
  184. package/src/tail/printing.ts +15 -0
  185. package/src/traverse-module-graph.ts +53 -0
  186. package/src/tsconfig-sanity.ts +16 -0
  187. package/src/type-generation.ts +79 -57
  188. package/src/user/access.ts +0 -1
  189. package/src/user/auth-variables.ts +113 -0
  190. package/src/user/choose-account.tsx +1 -31
  191. package/src/user/index.ts +0 -1
  192. package/src/user/{user.tsx → user.ts} +123 -82
  193. package/src/utils/render.ts +93 -0
  194. package/src/{whoami.tsx → whoami.ts} +37 -71
  195. package/src/worker.ts +35 -3
  196. package/src/yargs-types.ts +22 -5
  197. package/templates/__tests__/tsconfig-sanity.ts +12 -0
  198. package/templates/__tests__/tsconfig.json +8 -0
  199. package/templates/__tests__/tsconfig.tsbuildinfo +1 -0
  200. package/templates/checked-fetch.js +9 -3
  201. package/templates/d1-beta-facade.js +95 -25
  202. package/templates/facade.d.ts +14 -0
  203. package/templates/first-party-worker-module-facade.ts +4 -3
  204. package/templates/format-dev-errors.ts +8 -6
  205. package/templates/gitignore +1 -0
  206. package/templates/init-tests/test-jest-new-worker.js +3 -5
  207. package/templates/init-tests/test-vitest-new-worker.js +3 -5
  208. package/templates/init-tests/test-vitest-new-worker.ts +25 -0
  209. package/templates/middleware/loader-modules.ts +2 -2
  210. package/templates/middleware/loader-sw.ts +51 -31
  211. package/templates/middleware/middleware-miniflare3-json-error.ts +32 -0
  212. package/templates/new-worker.ts +3 -0
  213. package/templates/pages-dev-pipeline.ts +4 -1
  214. package/templates/pages-shim.ts +4 -1
  215. package/templates/pages-template-plugin.ts +19 -10
  216. package/templates/serve-static-assets.ts +17 -14
  217. package/templates/service-bindings-module-facade.js +1 -0
  218. package/templates/tsconfig-sanity.ts +11 -0
  219. package/templates/tsconfig.init.json +105 -0
  220. package/templates/tsconfig.json +5 -103
  221. package/templates/tsconfig.tsbuildinfo +1 -0
  222. package/wrangler-dist/cli.d.ts +826 -77
  223. package/wrangler-dist/cli.js +130734 -140838
  224. package/wrangler-dist/wasm-sync.wasm +0 -0
  225. package/src/__tests__/api-devregistry.test.js +0 -57
  226. package/src/__tests__/d1.test.ts +0 -72
  227. package/src/__tests__/dialogs.test.tsx +0 -40
  228. package/src/__tests__/helpers/mock-cfetch.ts +0 -333
  229. package/src/__tests__/pages.test.ts +0 -2223
  230. package/src/dialogs.tsx +0 -163
  231. package/src/environment-variables.ts +0 -50
  232. package/src/pages/build.tsx +0 -238
  233. package/src/user/env-vars.ts +0 -46
  234. package/templates/experimental-local-cache-stubs.js +0 -27
  235. /package/src/{bundle-reporter.tsx → bundle-reporter.ts} +0 -0
  236. /package/src/dev/{get-local-persistence-path.tsx → get-local-persistence-path.ts} +0 -0
  237. /package/src/pages/{hash.tsx → hash.ts} +0 -0
package/src/dev/local.tsx CHANGED
@@ -1,12 +1,15 @@
1
1
  import assert from "node:assert";
2
2
  import { fork } from "node:child_process";
3
3
  import { realpathSync } from "node:fs";
4
- import { readFile, writeFile } from "node:fs/promises";
4
+ import { readFile } from "node:fs/promises";
5
5
  import path from "node:path";
6
6
  import chalk from "chalk";
7
+ import getPort from "get-port";
7
8
  import { npxImport } from "npx-import";
8
9
  import { useState, useEffect, useRef } from "react";
9
10
  import onExit from "signal-exit";
11
+ import { fetch } from "undici";
12
+ import { performApiFetch } from "../cfetch/internal";
10
13
  import { registerWorker } from "../dev-registry";
11
14
  import useInspector from "../inspect";
12
15
  import { logger } from "../logger";
@@ -16,9 +19,11 @@ import {
16
19
  } from "../module-collection";
17
20
  import { getBasePath } from "../paths";
18
21
  import { waitForPortToBeAvailable } from "../proxy";
22
+ import { requireAuth } from "../user";
19
23
  import type { Config } from "../config";
20
24
  import type { WorkerRegistry } from "../dev-registry";
21
- import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli";
25
+ import type { LoggerLevel } from "../logger";
26
+ import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli/types";
22
27
  import type { AssetPaths } from "../sites";
23
28
  import type {
24
29
  CfWorkerInit,
@@ -37,6 +42,8 @@ import type { EsbuildBundle } from "./use-esbuild";
37
42
  import type {
38
43
  Miniflare as Miniflare3Type,
39
44
  MiniflareOptions as Miniflare3Options,
45
+ Log as Miniflare3LogType,
46
+ CloudflareFetch,
40
47
  } from "@miniflare/tre";
41
48
  import type { MiniflareOptions } from "miniflare";
42
49
  import type { ChildProcess } from "node:child_process";
@@ -51,8 +58,8 @@ export interface LocalProps {
51
58
  bindings: CfWorkerInit["bindings"];
52
59
  workerDefinitions: WorkerRegistry | undefined;
53
60
  assetPaths: AssetPaths | undefined;
54
- port: number;
55
- ip: string;
61
+ initialPort: number;
62
+ initialIp: string;
56
63
  rules: Config["rules"];
57
64
  inspectorPort: number;
58
65
  localPersistencePath: string | null;
@@ -63,18 +70,31 @@ export interface LocalProps {
63
70
  localUpstream: string | undefined;
64
71
  inspect: boolean;
65
72
  onReady: ((ip: string, port: number) => void) | undefined;
66
- logPrefix?: string;
67
73
  enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
68
74
  testScheduled?: boolean;
69
- experimentalLocal?: boolean;
75
+ experimentalLocal: boolean | undefined;
76
+ accountId: string | undefined; // Account ID? In local mode??? :exploding_head:
77
+ experimentalLocalRemoteKv: boolean | undefined;
70
78
  }
71
79
 
80
+ type InspectorJSON = {
81
+ id: string;
82
+ title: string;
83
+ type: "node";
84
+ description: string;
85
+ webSocketDebuggerUrl: string;
86
+ devtoolsFrontendUrl: string;
87
+ devtoolsFrontendUrlCompat: string;
88
+ faviconUrl: string;
89
+ url: string;
90
+ }[];
91
+
72
92
  export function Local(props: LocalProps) {
73
93
  const { inspectorUrl } = useLocalWorker(props);
74
94
  useInspector({
75
95
  inspectorUrl,
76
96
  port: props.inspectorPort,
77
- logToTerminal: false,
97
+ logToTerminal: props.experimentalLocal ?? false,
78
98
  });
79
99
  return null;
80
100
  }
@@ -89,28 +109,34 @@ function useLocalWorker({
89
109
  bindings,
90
110
  workerDefinitions,
91
111
  assetPaths,
92
- port,
93
- inspectorPort,
112
+ initialPort,
94
113
  rules,
95
114
  localPersistencePath,
96
115
  liveReload,
97
- ip,
116
+ initialIp,
98
117
  crons,
99
118
  queueConsumers,
100
119
  localProtocol,
101
120
  localUpstream,
102
121
  inspect,
103
122
  onReady,
104
- logPrefix,
105
123
  enablePagesAssetsServiceBinding,
106
124
  experimentalLocal,
125
+ accountId,
126
+ experimentalLocalRemoteKv,
107
127
  }: LocalProps) {
108
128
  // TODO: pass vars via command line
109
129
  const local = useRef<ChildProcess>();
110
130
  const experimentalLocalRef = useRef<Miniflare3Type>();
111
131
  const removeSignalExitListener = useRef<() => void>();
132
+ const removeExperimentalLocalSignalExitListener = useRef<() => void>();
112
133
  const [inspectorUrl, setInspectorUrl] = useState<string | undefined>();
113
134
 
135
+ // Our inspector proxy server will be binding to `LocalProps`'s `inspectorPort`.
136
+ // If we attempted to bind Node.js/workerd to the same inspector port, we'd get a port already in use error.
137
+ // Therefore, generate a new random port for our runtime's to bind their inspector service to.
138
+ const runtimeInspectorPortRef = useRef<number>();
139
+
114
140
  useEffect(() => {
115
141
  if (bindings.services && bindings.services.length > 0) {
116
142
  logger.warn(
@@ -136,22 +162,6 @@ function useLocalWorker({
136
162
  async function startLocalWorker() {
137
163
  if (!bundle || !format) return;
138
164
 
139
- // port for the worker
140
- await waitForPortToBeAvailable(port, {
141
- retryPeriod: 200,
142
- timeout: 2000,
143
- abortSignal: abortController.signal,
144
- });
145
-
146
- // In local mode, we want to copy all referenced modules into
147
- // the output bundle directory before starting up
148
- for (const module of bundle.modules) {
149
- await writeFile(
150
- path.join(path.dirname(bundle.path), module.name),
151
- module.content
152
- );
153
- }
154
-
155
165
  const scriptPath = realpathSync(bundle.path);
156
166
 
157
167
  const upstream =
@@ -174,12 +184,15 @@ function useLocalWorker({
174
184
  bundle,
175
185
  });
176
186
 
177
- const { forkOptions, miniflareCLIPath } = setupMiniflareOptions({
187
+ runtimeInspectorPortRef.current ??= await getPort();
188
+ const runtimeInspectorPort = runtimeInspectorPortRef.current;
189
+
190
+ const { forkOptions, miniflareCLIPath, options } = setupMiniflareOptions({
178
191
  workerName,
179
- port,
192
+ port: initialPort,
180
193
  scriptPath,
181
194
  localProtocol,
182
- ip,
195
+ ip: initialIp,
183
196
  format,
184
197
  rules,
185
198
  compatibilityDate,
@@ -201,35 +214,102 @@ function useLocalWorker({
201
214
  dataBlobBindings,
202
215
  crons,
203
216
  upstream,
204
- logPrefix,
205
217
  workerDefinitions,
206
218
  enablePagesAssetsServiceBinding,
207
219
  });
208
220
 
209
221
  if (experimentalLocal) {
210
- // TODO: refactor setupMiniflareOptions so we don't need to parse here
211
- const mf2Options: MiniflareOptions = JSON.parse(forkOptions[0]);
212
- const mf = await setupExperimentalLocal(mf2Options, format, bundle);
213
- await mf.ready;
214
- experimentalLocalRef.current = mf;
215
- removeSignalExitListener.current = onExit(() => {
216
- logger.log("⎔ Shutting down experimental local server.");
217
- mf.dispose();
218
- experimentalLocalRef.current = undefined;
222
+ const log = await buildMiniflare3Logger();
223
+ const mf3Options = await transformMf2OptionsToMf3Options({
224
+ miniflare2Options: options,
225
+ format,
226
+ bundle,
227
+ log,
228
+ enablePagesAssetsServiceBinding,
229
+ kvNamespaces: bindings?.kv_namespaces,
230
+ r2Buckets: bindings?.r2_buckets,
231
+ d1Databases: bindings?.d1_databases,
232
+ authenticatedAccountId: accountId,
233
+ kvRemote: experimentalLocalRemoteKv,
234
+ inspectorPort: runtimeInspectorPort,
219
235
  });
236
+
237
+ const current = experimentalLocalRef.current;
238
+
239
+ if (current === undefined) {
240
+ // If we don't have an active Miniflare instance, create a new one
241
+ const { Miniflare } = await getMiniflare3();
242
+ if (abortController.signal.aborted) return;
243
+ const mf = new Miniflare(mf3Options);
244
+ experimentalLocalRef.current = mf;
245
+ removeExperimentalLocalSignalExitListener.current = onExit(() => {
246
+ logger.log("⎔ Shutting down experimental local server.");
247
+ void mf.dispose();
248
+ experimentalLocalRef.current = undefined;
249
+ });
250
+ await mf.ready;
251
+ } else {
252
+ // Otherwise, reuse the existing instance with its loopback server
253
+ // and just update the options
254
+ if (abortController.signal.aborted) return;
255
+ logger.log("⎔ Reloading experimental local server.");
256
+ await current.setOptions(mf3Options);
257
+ }
258
+
259
+ try {
260
+ // fetch the inspector JSON response from the DevTools Inspector protocol
261
+ const inspectorJSONArr = (await (
262
+ await fetch(`http://127.0.0.1:${runtimeInspectorPort}/json`)
263
+ ).json()) as InspectorJSON;
264
+
265
+ const foundInspectorURL = inspectorJSONArr?.find((inspectorJSON) =>
266
+ inspectorJSON.id.startsWith("core:user")
267
+ )?.webSocketDebuggerUrl;
268
+ if (foundInspectorURL === undefined) {
269
+ setInspectorUrl(undefined);
270
+ } else {
271
+ const url = new URL(foundInspectorURL);
272
+ // Force inspector URL to be different on each reload so `useEffect`
273
+ // in `useInspector` is re-run to connect to newly restarted
274
+ // `workerd` server when updating options. Can't use a query param
275
+ // here as that seems to cause an infinite connection loop, can't
276
+ // use a hash as those are forbidden by `ws`, so username it is.
277
+ url.username = `${Date.now()}-${Math.floor(
278
+ Math.random() * Number.MAX_SAFE_INTEGER
279
+ )}`;
280
+ setInspectorUrl(url.toString());
281
+ }
282
+ } catch (error: unknown) {
283
+ logger.error("Error attempting to retrieve Debugger URL:", error);
284
+ }
285
+
220
286
  return;
221
287
  }
222
288
 
223
- const nodeOptions = setupNodeOptions({ inspect, ip, inspectorPort });
289
+ // Wait for the Worker port to be available. We don't want to do this in experimental local
290
+ // mode, as we only `dispose()` the Miniflare 3 instance, and shutdown the server when
291
+ // unmounting the component, not when props change. If we did, we'd just timeout every time.
292
+ await waitForPortToBeAvailable(initialPort, {
293
+ retryPeriod: 200,
294
+ timeout: 2000,
295
+ abortSignal: abortController.signal,
296
+ });
297
+
298
+ const nodeOptions = setupNodeOptions({
299
+ inspect,
300
+ inspectorPort: runtimeInspectorPort,
301
+ });
224
302
  logger.log("⎔ Starting a local server...");
225
303
 
304
+ const hasColourSupport =
305
+ chalk.supportsColor.hasBasic && process.env.FORCE_COLOR !== "0";
226
306
  const child = (local.current = fork(miniflareCLIPath, forkOptions, {
227
307
  cwd: path.dirname(scriptPath),
228
308
  execArgv: nodeOptions,
229
309
  stdio: "pipe",
230
310
  env: {
231
311
  ...process.env,
232
- FORCE_COLOR: chalk.supportsColor.hasBasic ? "1" : undefined,
312
+ FORCE_COLOR: hasColourSupport ? "1" : undefined,
233
313
  },
234
314
  }));
235
315
 
@@ -241,21 +321,21 @@ function useLocalWorker({
241
321
  await registerWorker(workerName, {
242
322
  protocol: localProtocol,
243
323
  mode: "local",
244
- port,
245
- host: ip,
324
+ port: message.port,
325
+ host: initialIp,
246
326
  durableObjects: internalDurableObjects.map((binding) => ({
247
327
  name: binding.name,
248
328
  className: binding.class_name,
249
329
  })),
250
330
  ...(message.durableObjectsPort
251
331
  ? {
252
- durableObjectsHost: ip,
332
+ durableObjectsHost: initialIp,
253
333
  durableObjectsPort: message.durableObjectsPort,
254
334
  }
255
335
  : {}),
256
336
  });
257
337
  }
258
- onReady?.(ip, message.mfPort);
338
+ onReady?.(initialIp, message.port);
259
339
  }
260
340
  });
261
341
 
@@ -307,7 +387,13 @@ function useLocalWorker({
307
387
  }
308
388
 
309
389
  startLocalWorker().catch((err) => {
310
- logger.error("local worker:", err);
390
+ if (err.code === "ERR_RUNTIME_FAILURE") {
391
+ // Don't log a full verbose stack-trace when Miniflare 3's workerd instance fails to start.
392
+ // workerd will log its own errors, and our stack trace won't have any useful information.
393
+ logger.error(err.message);
394
+ } else {
395
+ logger.error("local worker:", err);
396
+ }
311
397
  });
312
398
 
313
399
  return () => {
@@ -317,13 +403,6 @@ function useLocalWorker({
317
403
  local.current?.kill();
318
404
  local.current = undefined;
319
405
  }
320
- if (experimentalLocalRef.current) {
321
- logger.log("⎔ Shutting down experimental local server.");
322
- // Initialisation errors are also thrown asynchronously by dispose().
323
- // The catch() above should've caught them though.
324
- experimentalLocalRef.current?.dispose().catch(() => {});
325
- experimentalLocalRef.current = undefined;
326
- }
327
406
  removeSignalExitListener.current?.();
328
407
  removeSignalExitListener.current = undefined;
329
408
  };
@@ -331,9 +410,8 @@ function useLocalWorker({
331
410
  bundle,
332
411
  workerName,
333
412
  format,
334
- port,
335
- inspectorPort,
336
- ip,
413
+ initialPort,
414
+ initialIp,
337
415
  queueConsumers,
338
416
  bindings.queues,
339
417
  bindings.durable_objects,
@@ -357,11 +435,32 @@ function useLocalWorker({
357
435
  localProtocol,
358
436
  localUpstream,
359
437
  inspect,
360
- logPrefix,
361
438
  onReady,
362
439
  enablePagesAssetsServiceBinding,
363
440
  experimentalLocal,
441
+ accountId,
442
+ experimentalLocalRemoteKv,
364
443
  ]);
444
+
445
+ // Rather than disposing the Miniflare instance on every reload, only dispose
446
+ // it if local mode is disabled and the `Local` component is unmounted. This
447
+ // allows us to use the more efficient `Miniflare#setOptions` on reload which
448
+ // retains internal state (e.g. the Miniflare loopback server).
449
+ useEffect(
450
+ () => () => {
451
+ if (experimentalLocalRef.current) {
452
+ logger.log("⎔ Shutting down experimental local server.");
453
+ // Initialisation errors are also thrown asynchronously by dispose().
454
+ // The catch() above should've caught them though.
455
+ experimentalLocalRef.current?.dispose().catch(() => {});
456
+ experimentalLocalRef.current = undefined;
457
+ }
458
+ removeExperimentalLocalSignalExitListener.current?.();
459
+ removeExperimentalLocalSignalExitListener.current = undefined;
460
+ },
461
+ []
462
+ );
463
+
365
464
  return { inspectorUrl };
366
465
  }
367
466
 
@@ -474,7 +573,6 @@ interface SetupMiniflareOptionsProps {
474
573
  dataBlobBindings: Record<string, string>;
475
574
  crons: Config["triggers"]["crons"];
476
575
  upstream: string | undefined;
477
- logPrefix: string | undefined;
478
576
  workerDefinitions: WorkerRegistry | undefined;
479
577
  enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
480
578
  }
@@ -506,15 +604,15 @@ export function setupMiniflareOptions({
506
604
  dataBlobBindings,
507
605
  crons,
508
606
  upstream,
509
- logPrefix,
510
607
  workerDefinitions,
511
608
  enablePagesAssetsServiceBinding,
512
609
  }: SetupMiniflareOptionsProps): {
513
610
  miniflareCLIPath: string;
514
611
  forkOptions: string[];
612
+ options: MiniflareOptions;
515
613
  } {
516
614
  // It's now getting _really_ messy now with Pages ASSETS binding outside and the external Durable Objects inside.
517
- const options = {
615
+ const options: MiniflareOptions = {
518
616
  name: workerName,
519
617
  port,
520
618
  scriptPath,
@@ -618,7 +716,6 @@ export function setupMiniflareOptions({
618
716
  crons,
619
717
  upstream,
620
718
  logLevel: logger.loggerLevel,
621
- logOptions: logPrefix ? { prefix: logPrefix } : undefined,
622
719
  enablePagesAssetsServiceBinding,
623
720
  };
624
721
  // The path to the Miniflare CLI assumes that this file is being run from
@@ -633,16 +730,14 @@ export function setupMiniflareOptions({
633
730
  if (enablePagesAssetsServiceBinding) {
634
731
  forkOptions.push(JSON.stringify(enablePagesAssetsServiceBinding));
635
732
  }
636
- return { miniflareCLIPath, forkOptions };
733
+ return { miniflareCLIPath, forkOptions, options };
637
734
  }
638
735
 
639
736
  export function setupNodeOptions({
640
737
  inspect,
641
- ip,
642
738
  inspectorPort,
643
739
  }: {
644
740
  inspect: boolean;
645
- ip: string;
646
741
  inspectorPort: number;
647
742
  }) {
648
743
  const nodeOptions = [
@@ -651,33 +746,123 @@ export function setupNodeOptions({
651
746
  // "--log=VERBOSE", // uncomment this to Miniflare to log "everything"!
652
747
  ];
653
748
  if (inspect) {
654
- nodeOptions.push("--inspect=" + `${ip}:${inspectorPort}`); // start Miniflare listening for a debugger to attach
749
+ nodeOptions.push("--inspect=" + `127.0.0.1:${inspectorPort}`); // start Miniflare listening for a debugger to attach
655
750
  }
656
751
  return nodeOptions;
657
752
  }
658
753
 
659
- // Caching of the `npx-import`ed `@miniflare/tre` package
660
- // eslint-disable-next-line @typescript-eslint/consistent-type-imports
661
- let Miniflare: typeof import("@miniflare/tre").Miniflare;
754
+ export interface SetupMiniflare3Options {
755
+ // Regular Miniflare 2 options to transform
756
+ miniflare2Options: MiniflareOptions;
757
+ // Miniflare 3 requires all modules to be manually specified
758
+ format: CfScriptFormat;
759
+ bundle: EsbuildBundle;
760
+
761
+ // Miniflare's logger
762
+ log: Miniflare3LogType;
763
+
764
+ enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
765
+
766
+ // Miniflare 3 accepts namespace/bucket names in addition to binding names.
767
+ // This means multiple workers persisting to the same location can have
768
+ // different binding names for the same namespace/bucket. Therefore, we need
769
+ // the full KV/R2 arrays. This is also required for remote KV storage, as
770
+ // we need actual namespace IDs to connect to.
771
+ kvNamespaces: CfKvNamespace[] | undefined;
772
+ r2Buckets: CfR2Bucket[] | undefined;
773
+ d1Databases: CfD1Database[] | undefined;
774
+
775
+ // Account ID to use for authenticated Cloudflare fetch. If true, prompt
776
+ // user for ID if multiple available.
777
+ authenticatedAccountId: string | true | undefined;
778
+ // Whether to read/write from/to real KV namespaces
779
+ kvRemote: boolean | undefined;
780
+
781
+ // Port to start DevTools inspector server on
782
+ inspectorPort: number;
783
+ }
784
+
785
+ export async function buildMiniflare3Logger(): Promise<Miniflare3LogType> {
786
+ const { Log, NoOpLog, LogLevel } = await getMiniflare3();
662
787
 
663
- function arrayToObject(values: string[] = []): Record<string, string> {
664
- return Object.fromEntries(values.map((value) => [value, value]));
788
+ let level = logger.loggerLevel.toUpperCase() as Uppercase<LoggerLevel>;
789
+ if (level === "LOG") level = "INFO";
790
+ const logLevel = LogLevel[level];
791
+
792
+ return logLevel === LogLevel.NONE ? new NoOpLog() : new Log(logLevel);
665
793
  }
666
794
 
667
- export async function setupExperimentalLocal(
668
- mf2Options: MiniflareOptions,
669
- format: CfScriptFormat,
670
- bundle: EsbuildBundle
671
- ): Promise<Miniflare3Type> {
795
+ export async function transformMf2OptionsToMf3Options({
796
+ miniflare2Options,
797
+ format,
798
+ bundle,
799
+ log,
800
+ enablePagesAssetsServiceBinding,
801
+ kvNamespaces,
802
+ r2Buckets,
803
+ d1Databases,
804
+ authenticatedAccountId,
805
+ kvRemote,
806
+ inspectorPort,
807
+ }: SetupMiniflare3Options): Promise<Miniflare3Options> {
808
+ // Build authenticated Cloudflare API fetch function if required
809
+ let cloudflareFetch: CloudflareFetch | undefined;
810
+ if (kvRemote && authenticatedAccountId !== undefined) {
811
+ const { Response: Miniflare3Response } = await getMiniflare3();
812
+ const preferredAccountId =
813
+ authenticatedAccountId === true ? undefined : authenticatedAccountId;
814
+ const accountId = await requireAuth({ account_id: preferredAccountId });
815
+ cloudflareFetch = async (resource, searchParams, init) => {
816
+ resource = `/accounts/${accountId}/${resource}`;
817
+ const response = await performApiFetch(resource, init, searchParams);
818
+ return new Miniflare3Response(response.body, response);
819
+ };
820
+ }
821
+
672
822
  const options: Miniflare3Options = {
673
- ...mf2Options,
823
+ ...miniflare2Options,
674
824
  // Miniflare 3 distinguishes between binding name and namespace/bucket IDs.
675
- // For now, just use the same value as we did in Miniflare 2.
676
- // TODO: use defined KV preview ID if any
677
- kvNamespaces: arrayToObject(mf2Options.kvNamespaces),
678
- r2Buckets: arrayToObject(mf2Options.r2Buckets),
825
+ kvNamespaces: Object.fromEntries(
826
+ kvNamespaces?.map(({ binding, id }) => [binding, id]) ?? []
827
+ ),
828
+ r2Buckets: Object.fromEntries(
829
+ r2Buckets?.map(({ binding, bucket_name }) => [binding, bucket_name]) ?? []
830
+ ),
831
+ d1Databases: Object.fromEntries(
832
+ d1Databases?.map(({ binding, database_id, preview_database_id }) => [
833
+ binding,
834
+ preview_database_id ?? database_id,
835
+ ]) ?? []
836
+ ),
837
+ inspectorPort,
838
+ verbose: true,
839
+ cloudflareFetch,
840
+ log,
679
841
  };
680
842
 
843
+ if (enablePagesAssetsServiceBinding !== undefined) {
844
+ // `../miniflare-cli/assets` dynamically imports`@cloudflare/pages-shared/environment-polyfills`.
845
+ // `@cloudflare/pages-shared/environment-polyfills/types.ts` defines `global`
846
+ // augmentations that pollute the `import`-site's typing environment.
847
+ //
848
+ // We `require` instead of `import`ing here to avoid polluting the main
849
+ // `wrangler` TypeScript project with the `global` augmentations. This
850
+ // relies on the fact that `require` is untyped.
851
+ //
852
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
853
+ const generateASSETSBinding = require("../miniflare-cli/assets").default;
854
+ options.serviceBindings = {
855
+ ...options.serviceBindings,
856
+ ASSETS: (await generateASSETSBinding({
857
+ log,
858
+ ...enablePagesAssetsServiceBinding,
859
+ tre: true,
860
+ // We can get rid of this `any` easily once we do experimental-local/tre by default
861
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
862
+ })) as any,
863
+ };
864
+ }
865
+
681
866
  if (format === "modules") {
682
867
  // Manually specify all modules from the bundle. If we didn't do this,
683
868
  // Miniflare 3 would try collect them automatically again itself.
@@ -688,30 +873,48 @@ export async function setupExperimentalLocal(
688
873
  const root = path.dirname(bundle.path);
689
874
 
690
875
  assert.strictEqual(bundle.type, "esm");
876
+ // Required for source mapped paths to resolve correctly
877
+ options.modulesRoot = root;
691
878
  options.modules = [
692
879
  // Entrypoint
693
880
  {
694
881
  type: "ESModule",
695
- path: path.relative(root, bundle.path),
882
+ path: bundle.path,
696
883
  contents: await readFile(bundle.path, "utf-8"),
697
884
  },
698
885
  // Misc (WebAssembly, etc, ...)
699
886
  ...bundle.modules.map((module) => ({
700
887
  type: ModuleTypeToRuleType[module.type ?? "esm"],
701
- path: module.name,
888
+ path: path.resolve(root, module.name),
702
889
  contents: module.content,
703
890
  })),
704
891
  ];
705
892
  }
706
893
 
707
- logger.log("⎔ Starting an experimental local server...");
708
-
709
- if (Miniflare === undefined) {
710
- ({ Miniflare } = await npxImport<
711
- // eslint-disable-next-line @typescript-eslint/consistent-type-imports
712
- typeof import("@miniflare/tre")
713
- >("@miniflare/tre@next"));
894
+ if (kvRemote) {
895
+ // `kvPersist` is always assigned a truthy value in `setupMiniflareOptions`
896
+ assert(options.kvPersist);
897
+ const kvRemoteCache =
898
+ options.kvPersist === true
899
+ ? // If storing in temporary directory, find this path from the bundle
900
+ // output path
901
+ path.join(path.dirname(bundle.path), ".mf", "kv-remote")
902
+ : // Otherwise, `kvPersist` looks like `.../kv`, so rewrite it to
903
+ // `kv-remote` since the expected metadata format for remote storage
904
+ // is different to local
905
+ path.join(path.dirname(options.kvPersist), "kv-remote");
906
+ options.kvPersist = `remote:?cache=${encodeURIComponent(kvRemoteCache)}`;
714
907
  }
715
908
 
716
- return new Miniflare(options);
909
+ return options;
910
+ }
911
+
912
+ // Caching of the `npx-import`ed `@miniflare/tre` package
913
+ // eslint-disable-next-line @typescript-eslint/consistent-type-imports
914
+ let miniflare3Module: typeof import("@miniflare/tre");
915
+ export async function getMiniflare3(): Promise<
916
+ // eslint-disable-next-line @typescript-eslint/consistent-type-imports
917
+ typeof import("@miniflare/tre")
918
+ > {
919
+ return (miniflare3Module ??= await npxImport("@miniflare/tre@3.0.0-next.12"));
717
920
  }