wrangler 0.0.0-e7a0cd5b → 0.0.0-e87ad068

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