wrangler 2.0.22 → 2.0.25

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 (75) hide show
  1. package/README.md +20 -2
  2. package/bin/wrangler.js +1 -1
  3. package/miniflare-dist/index.mjs +643 -7
  4. package/package.json +17 -5
  5. package/src/__tests__/configuration.test.ts +89 -17
  6. package/src/__tests__/dev.test.tsx +121 -8
  7. package/src/__tests__/generate.test.ts +93 -0
  8. package/src/__tests__/helpers/mock-cfetch.ts +54 -2
  9. package/src/__tests__/index.test.ts +10 -27
  10. package/src/__tests__/jest.setup.ts +31 -1
  11. package/src/__tests__/kv.test.ts +82 -61
  12. package/src/__tests__/metrics.test.ts +5 -0
  13. package/src/__tests__/publish.test.ts +573 -254
  14. package/src/__tests__/r2.test.ts +173 -71
  15. package/src/__tests__/tail.test.ts +93 -39
  16. package/src/__tests__/user.test.ts +1 -0
  17. package/src/__tests__/validate-dev-props.test.ts +56 -0
  18. package/src/__tests__/version.test.ts +35 -0
  19. package/src/__tests__/whoami.test.tsx +60 -1
  20. package/src/api/dev.ts +49 -9
  21. package/src/bundle.ts +298 -37
  22. package/src/cfetch/internal.ts +34 -2
  23. package/src/config/config.ts +15 -3
  24. package/src/config/environment.ts +40 -8
  25. package/src/config/index.ts +13 -0
  26. package/src/config/validation.ts +111 -9
  27. package/src/create-worker-preview.ts +3 -1
  28. package/src/create-worker-upload-form.ts +25 -0
  29. package/src/dev/dev.tsx +145 -31
  30. package/src/dev/local.tsx +116 -24
  31. package/src/dev/remote.tsx +39 -12
  32. package/src/dev/use-esbuild.ts +28 -0
  33. package/src/dev/validate-dev-props.ts +31 -0
  34. package/src/dev-registry.tsx +160 -0
  35. package/src/dev.tsx +148 -67
  36. package/src/generate.ts +112 -14
  37. package/src/index.tsx +252 -7
  38. package/src/inspect.ts +90 -5
  39. package/src/metrics/index.ts +1 -0
  40. package/src/metrics/metrics-dispatcher.ts +1 -0
  41. package/src/metrics/metrics-usage-headers.ts +24 -0
  42. package/src/metrics/send-event.ts +2 -2
  43. package/src/miniflare-cli/assets.ts +546 -0
  44. package/src/miniflare-cli/index.ts +157 -6
  45. package/src/module-collection.ts +3 -3
  46. package/src/pages/build.tsx +36 -28
  47. package/src/pages/constants.ts +4 -0
  48. package/src/pages/deployments.tsx +10 -10
  49. package/src/pages/dev.tsx +155 -651
  50. package/src/pages/functions/buildPlugin.ts +4 -0
  51. package/src/pages/functions/buildWorker.ts +4 -0
  52. package/src/pages/functions/routes-consolidation.test.ts +66 -0
  53. package/src/pages/functions/routes-consolidation.ts +29 -0
  54. package/src/pages/functions/routes-transformation.test.ts +271 -0
  55. package/src/pages/functions/routes-transformation.ts +125 -0
  56. package/src/pages/projects.tsx +9 -3
  57. package/src/pages/publish.tsx +57 -15
  58. package/src/pages/types.ts +9 -0
  59. package/src/pages/upload.tsx +38 -21
  60. package/src/publish.ts +139 -112
  61. package/src/r2.ts +81 -0
  62. package/src/tail/index.ts +15 -2
  63. package/src/tail/printing.ts +41 -3
  64. package/src/user/choose-account.tsx +20 -11
  65. package/src/user/user.tsx +20 -2
  66. package/src/whoami.tsx +79 -1
  67. package/src/worker.ts +12 -0
  68. package/templates/first-party-worker-module-facade.ts +18 -0
  69. package/templates/format-dev-errors.ts +32 -0
  70. package/templates/pages-shim.ts +9 -0
  71. package/templates/{static-asset-facade.js → serve-static-assets.ts} +21 -7
  72. package/templates/service-bindings-module-facade.js +51 -0
  73. package/templates/service-bindings-sw-facade.js +39 -0
  74. package/wrangler-dist/cli.d.ts +38 -3
  75. package/wrangler-dist/cli.js +45244 -25199
package/src/dev/local.tsx CHANGED
@@ -4,11 +4,14 @@ import { writeFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { useState, useEffect, useRef } from "react";
6
6
  import onExit from "signal-exit";
7
+ import { registerWorker } from "../dev-registry";
7
8
  import useInspector from "../inspect";
8
9
  import { logger } from "../logger";
9
10
  import { DEFAULT_MODULE_RULES } from "../module-collection";
10
11
  import { waitForPortToBeAvailable } from "../proxy";
11
12
  import type { Config } from "../config";
13
+ import type { WorkerRegistry } from "../dev-registry";
14
+ import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli";
12
15
  import type { AssetPaths } from "../sites";
13
16
  import type { CfWorkerInit, CfScriptFormat } from "../worker";
14
17
  import type { EsbuildBundle } from "./use-esbuild";
@@ -20,20 +23,24 @@ interface LocalProps {
20
23
  format: CfScriptFormat | undefined;
21
24
  compatibilityDate: string;
22
25
  compatibilityFlags: string[] | undefined;
26
+ usageModel: "bundled" | "unbound" | undefined;
23
27
  bindings: CfWorkerInit["bindings"];
28
+ workerDefinitions: WorkerRegistry;
24
29
  assetPaths: AssetPaths | undefined;
25
- isWorkersSite: boolean;
26
30
  port: number;
27
31
  ip: string;
28
32
  rules: Config["rules"];
29
33
  inspectorPort: number;
30
34
  enableLocalPersistence: boolean;
35
+ liveReload: boolean;
31
36
  crons: Config["triggers"]["crons"];
32
37
  localProtocol: "http" | "https";
33
38
  localUpstream: string | undefined;
34
39
  inspect: boolean;
35
40
  onReady: (() => void) | undefined;
36
41
  logLevel: "none" | "error" | "log" | "warn" | "debug" | undefined;
42
+ logPrefix?: string;
43
+ enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
37
44
  }
38
45
 
39
46
  export function Local(props: LocalProps) {
@@ -52,12 +59,15 @@ function useLocalWorker({
52
59
  format,
53
60
  compatibilityDate,
54
61
  compatibilityFlags,
62
+ usageModel,
55
63
  bindings,
64
+ workerDefinitions,
56
65
  assetPaths,
57
- isWorkersSite,
58
66
  port,
67
+ inspectorPort,
59
68
  rules,
60
69
  enableLocalPersistence,
70
+ liveReload,
61
71
  ip,
62
72
  crons,
63
73
  localProtocol,
@@ -65,6 +75,8 @@ function useLocalWorker({
65
75
  inspect,
66
76
  onReady,
67
77
  logLevel,
78
+ logPrefix,
79
+ enablePagesAssetsServiceBinding,
68
80
  }: LocalProps) {
69
81
  // TODO: pass vars via command line
70
82
  const local = useRef<ChildProcess>();
@@ -80,6 +92,27 @@ function useLocalWorker({
80
92
  // so that it's persisted in the temp dir across a dev session
81
93
  // even when we change source and reload
82
94
  null;
95
+
96
+ useEffect(() => {
97
+ if (bindings.services && bindings.services.length > 0) {
98
+ logger.warn(
99
+ "⎔ Support for service bindings in local mode is experimental and may change."
100
+ );
101
+ }
102
+ }, [bindings.services]);
103
+
104
+ useEffect(() => {
105
+ const externalDurableObjects = (
106
+ bindings.durable_objects?.bindings || []
107
+ ).filter((binding) => binding.script_name);
108
+
109
+ if (externalDurableObjects.length > 0) {
110
+ logger.warn(
111
+ "⎔ Support for external Durable Objects in local mode is experimental and may change."
112
+ );
113
+ }
114
+ }, [bindings.durable_objects?.bindings]);
115
+
83
116
  useEffect(() => {
84
117
  const abortController = new AbortController();
85
118
  async function startLocalWorker() {
@@ -92,12 +125,6 @@ function useLocalWorker({
92
125
  abortSignal: abortController.signal,
93
126
  });
94
127
 
95
- if (bindings.services && bindings.services.length > 0) {
96
- throw new Error(
97
- "⎔ Service bindings are not yet supported in local mode."
98
- );
99
- }
100
-
101
128
  // In local mode, we want to copy all referenced modules into
102
129
  // the output bundle directory before starting up
103
130
  for (const module of bundle.modules) {
@@ -170,6 +197,15 @@ function useLocalWorker({
170
197
  ? `${localProtocol}://${localUpstream}`
171
198
  : undefined;
172
199
 
200
+ const internalDurableObjects = (
201
+ bindings.durable_objects?.bindings || []
202
+ ).filter((binding) => !binding.script_name);
203
+ const externalDurableObjects = (
204
+ bindings.durable_objects?.bindings || []
205
+ ).filter((binding) => binding.script_name);
206
+
207
+ // TODO: This was already messy with the custom `disableLogs` and `logOptions`.
208
+ // It's now getting _really_ messy now with Pages ASSETS binding outside and the external Durable Objects inside.
173
209
  const options: MiniflareOptions = {
174
210
  name: workerName,
175
211
  port,
@@ -186,12 +222,37 @@ function useLocalWorker({
186
222
  })),
187
223
  compatibilityDate,
188
224
  compatibilityFlags,
225
+ usageModel,
189
226
  kvNamespaces: bindings.kv_namespaces?.map((kv) => kv.binding),
190
227
  r2Buckets: bindings.r2_buckets?.map((r2) => r2.binding),
191
228
  durableObjects: Object.fromEntries(
192
- (bindings.durable_objects?.bindings ?? []).map<[string, string]>(
193
- (value) => [value.name, value.class_name]
194
- )
229
+ internalDurableObjects.map((binding) => [
230
+ binding.name,
231
+ binding.class_name,
232
+ ])
233
+ ),
234
+ externalDurableObjects: Object.fromEntries(
235
+ externalDurableObjects
236
+ .map((binding) => {
237
+ const service = workerDefinitions[binding.script_name as string];
238
+ if (!service) return [binding.name, undefined];
239
+
240
+ const name = service.durableObjects.find(
241
+ (durableObject) =>
242
+ durableObject.className === binding.class_name
243
+ )?.name;
244
+ if (!name) return [binding.name, undefined];
245
+
246
+ return [
247
+ binding.name,
248
+ {
249
+ name,
250
+ host: service.durableObjectsHost,
251
+ port: service.durableObjectsPort,
252
+ },
253
+ ];
254
+ })
255
+ .filter(([_, details]) => !!details)
195
256
  ),
196
257
  ...(localPersistencePath
197
258
  ? {
@@ -212,6 +273,7 @@ function useLocalWorker({
212
273
  r2Persist: true,
213
274
  }),
214
275
 
276
+ liveReload,
215
277
  sitePath: assetPaths?.assetDirectory
216
278
  ? path.join(assetPaths.baseDirectory, assetPaths.assetDirectory)
217
279
  : undefined,
@@ -230,6 +292,7 @@ function useLocalWorker({
230
292
  crons,
231
293
  upstream,
232
294
  disableLogs: logLevel === "none",
295
+ logOptions: logPrefix ? { prefix: logPrefix } : undefined,
233
296
  };
234
297
 
235
298
  // The path to the Miniflare CLI assumes that this file is being run from
@@ -248,19 +311,43 @@ function useLocalWorker({
248
311
  // "--log=VERBOSE", // uncomment this to Miniflare to log "everything"!
249
312
  ];
250
313
  if (inspect) {
251
- nodeOptions.push("--inspect"); // start Miniflare listening for a debugger to attach
314
+ nodeOptions.push("--inspect=" + `${ip}:${inspectorPort}`); // start Miniflare listening for a debugger to attach
252
315
  }
253
- const child = (local.current = fork(
254
- miniflareCLIPath,
255
- [miniflareOptions],
256
- {
257
- cwd: path.dirname(scriptPath),
258
- execArgv: nodeOptions,
259
- stdio: "pipe",
260
- }
261
- ));
262
- child.on("message", (message) => {
263
- if (message === "ready") {
316
+
317
+ const forkOptions = [miniflareOptions];
318
+
319
+ if (enablePagesAssetsServiceBinding) {
320
+ forkOptions.push(JSON.stringify(enablePagesAssetsServiceBinding));
321
+ }
322
+
323
+ const child = (local.current = fork(miniflareCLIPath, forkOptions, {
324
+ cwd: path.dirname(scriptPath),
325
+ execArgv: nodeOptions,
326
+ stdio: "pipe",
327
+ }));
328
+
329
+ child.on("message", async (messageString) => {
330
+ const message = JSON.parse(messageString as string);
331
+ if (message.ready) {
332
+ // Let's register our presence in the dev registry
333
+ if (workerName) {
334
+ await registerWorker(workerName, {
335
+ protocol: localProtocol,
336
+ mode: "local",
337
+ port,
338
+ host: ip,
339
+ durableObjects: internalDurableObjects.map((binding) => ({
340
+ name: binding.name,
341
+ className: binding.class_name,
342
+ })),
343
+ ...(message.durableObjectsPort
344
+ ? {
345
+ durableObjectsHost: ip,
346
+ durableObjectsPort: message.durableObjectsPort,
347
+ }
348
+ : {}),
349
+ });
350
+ }
264
351
  onReady?.();
265
352
  }
266
353
  });
@@ -331,17 +418,20 @@ function useLocalWorker({
331
418
  workerName,
332
419
  format,
333
420
  port,
421
+ inspectorPort,
334
422
  ip,
335
423
  bindings.durable_objects?.bindings,
336
424
  bindings.kv_namespaces,
337
425
  bindings.r2_buckets,
338
426
  bindings.vars,
339
427
  bindings.services,
428
+ workerDefinitions,
340
429
  compatibilityDate,
341
430
  compatibilityFlags,
431
+ usageModel,
342
432
  localPersistencePath,
433
+ liveReload,
343
434
  assetPaths,
344
- isWorkersSite,
345
435
  rules,
346
436
  bindings.wasm_modules,
347
437
  bindings.text_blobs,
@@ -351,7 +441,9 @@ function useLocalWorker({
351
441
  localUpstream,
352
442
  inspect,
353
443
  logLevel,
444
+ logPrefix,
354
445
  onReady,
446
+ enablePagesAssetsServiceBinding,
355
447
  ]);
356
448
  return { inspectorUrl };
357
449
  }
@@ -55,6 +55,8 @@ export function Remote(props: {
55
55
  host: string | undefined;
56
56
  routes: Route[] | undefined;
57
57
  onReady?: (() => void) | undefined;
58
+ sourceMapPath: string | undefined;
59
+ sendMetrics: boolean | undefined;
58
60
  }) {
59
61
  const [accountId, setAccountId] = useState(props.accountId);
60
62
  const accountChoicesRef = useRef<Promise<ChooseAccountItem[]>>();
@@ -69,7 +71,6 @@ export function Remote(props: {
69
71
  bindings: props.bindings,
70
72
  assetPaths: props.assetPaths,
71
73
  isWorkersSite: props.isWorkersSite,
72
- port: props.port,
73
74
  compatibilityDate: props.compatibilityDate,
74
75
  compatibilityFlags: props.compatibilityFlags,
75
76
  usageModel: props.usageModel,
@@ -79,6 +80,7 @@ export function Remote(props: {
79
80
  host: props.host,
80
81
  routes: props.routes,
81
82
  onReady: props.onReady,
83
+ sendMetrics: props.sendMetrics,
82
84
  });
83
85
 
84
86
  usePreviewServer({
@@ -98,6 +100,7 @@ export function Remote(props: {
98
100
  : undefined,
99
101
  port: props.inspectorPort,
100
102
  logToTerminal: true,
103
+ sourceMapPath: props.sourceMapPath,
101
104
  });
102
105
 
103
106
  const errorHandler = useErrorHandler();
@@ -153,7 +156,6 @@ export function useWorker(props: {
153
156
  bindings: CfWorkerInit["bindings"];
154
157
  assetPaths: AssetPaths | undefined;
155
158
  isWorkersSite: boolean;
156
- port: number;
157
159
  compatibilityDate: string | undefined;
158
160
  compatibilityFlags: string[] | undefined;
159
161
  usageModel: "bundled" | "unbound" | undefined;
@@ -163,6 +165,7 @@ export function useWorker(props: {
163
165
  host: string | undefined;
164
166
  routes: Route[] | undefined;
165
167
  onReady: (() => void) | undefined;
168
+ sendMetrics: boolean | undefined;
166
169
  }): CfPreviewToken | undefined {
167
170
  const {
168
171
  name,
@@ -175,7 +178,6 @@ export function useWorker(props: {
175
178
  compatibilityDate,
176
179
  compatibilityFlags,
177
180
  usageModel,
178
- port,
179
181
  onReady,
180
182
  } = props;
181
183
  const [session, setSession] = useState<CfPreviewSession | undefined>();
@@ -205,6 +207,7 @@ export function useWorker(props: {
205
207
  zone: props.zone,
206
208
  host: props.host,
207
209
  routes: props.routes,
210
+ sendMetrics: props.sendMetrics,
208
211
  };
209
212
 
210
213
  setSession(
@@ -233,6 +236,7 @@ export function useWorker(props: {
233
236
  props.legacyEnv,
234
237
  props.routes,
235
238
  props.zone,
239
+ props.sendMetrics,
236
240
  ]);
237
241
 
238
242
  // This effect uses the session to upload the worker and create a preview
@@ -323,17 +327,40 @@ export function useWorker(props: {
323
327
  zone: props.zone,
324
328
  host: props.host,
325
329
  routes: props.routes,
330
+ sendMetrics: props.sendMetrics,
326
331
  };
327
332
 
328
- setToken(
329
- await createWorkerPreview(
330
- init,
331
- workerAccount,
332
- workerCtx,
333
- session,
334
- abortController.signal
335
- )
333
+ const workerPreviewToken = await createWorkerPreview(
334
+ init,
335
+ workerAccount,
336
+ workerCtx,
337
+ session,
338
+ abortController.signal
336
339
  );
340
+
341
+ setToken(workerPreviewToken);
342
+
343
+ // TODO: Once we get service bindings working in the
344
+ // edge preview server, we can define remote dev service bindings
345
+ // and you can uncomment this code.
346
+ // https://github.com/cloudflare/wrangler2/issues/1182
347
+
348
+ /*
349
+ if (name) {
350
+ await registerWorker(name, {
351
+ mode: "remote",
352
+ // upstream protocol is always https (https://github.com/cloudflare/wrangler2/issues/583)
353
+ protocol: "https",
354
+ port: undefined,
355
+ host: workerPreviewToken.host,
356
+ headers: {
357
+ "cf-workers-preview-token": workerPreviewToken.value,
358
+ host: workerPreviewToken.host,
359
+ },
360
+ });
361
+ }
362
+ */
363
+
337
364
  onReady?.();
338
365
  }
339
366
  start().catch((err) => {
@@ -366,7 +393,6 @@ export function useWorker(props: {
366
393
  bundle,
367
394
  format,
368
395
  accountId,
369
- port,
370
396
  assetPaths,
371
397
  props.isWorkersSite,
372
398
  compatibilityDate,
@@ -381,6 +407,7 @@ export function useWorker(props: {
381
407
  props.routes,
382
408
  session,
383
409
  onReady,
410
+ props.sendMetrics,
384
411
  ]);
385
412
  return token;
386
413
  }
@@ -5,6 +5,7 @@ import { useState, useEffect } from "react";
5
5
  import { bundleWorker } from "../bundle";
6
6
  import { logger } from "../logger";
7
7
  import type { Config } from "../config";
8
+ import type { WorkerRegistry } from "../dev-registry";
8
9
  import type { Entry } from "../entry";
9
10
  import type { CfModule } from "../worker";
10
11
  import type { WatchMode } from "esbuild";
@@ -15,6 +16,7 @@ export type EsbuildBundle = {
15
16
  entry: Entry;
16
17
  type: "esm" | "commonjs";
17
18
  modules: CfModule[];
19
+ sourceMapPath: string | undefined;
18
20
  };
19
21
 
20
22
  export function useEsbuild({
@@ -23,24 +25,34 @@ export function useEsbuild({
23
25
  jsxFactory,
24
26
  jsxFragment,
25
27
  rules,
28
+ assets,
26
29
  serveAssetsFromWorker,
27
30
  tsconfig,
28
31
  minify,
29
32
  nodeCompat,
30
33
  define,
31
34
  noBundle,
35
+ workerDefinitions,
36
+ services,
37
+ durableObjects,
38
+ firstPartyWorkerDevFacade,
32
39
  }: {
33
40
  entry: Entry;
34
41
  destination: string | undefined;
35
42
  jsxFactory: string | undefined;
36
43
  jsxFragment: string | undefined;
37
44
  rules: Config["rules"];
45
+ assets: Config["assets"];
38
46
  define: Config["define"];
47
+ services: Config["services"];
39
48
  serveAssetsFromWorker: boolean;
40
49
  tsconfig: string | undefined;
41
50
  minify: boolean | undefined;
42
51
  nodeCompat: boolean | undefined;
43
52
  noBundle: boolean;
53
+ workerDefinitions: WorkerRegistry;
54
+ durableObjects: Config["durable_objects"];
55
+ firstPartyWorkerDevFacade: boolean | undefined;
44
56
  }): EsbuildBundle | undefined {
45
57
  const [bundle, setBundle] = useState<EsbuildBundle>();
46
58
  const { exit } = useApp();
@@ -76,12 +88,14 @@ export function useEsbuild({
76
88
  bundleType,
77
89
  modules,
78
90
  stop,
91
+ sourceMapPath,
79
92
  }: Awaited<ReturnType<typeof bundleWorker>> = noBundle
80
93
  ? {
81
94
  modules: [],
82
95
  resolvedEntryPointPath: entry.file,
83
96
  bundleType: entry.format === "modules" ? "esm" : "commonjs",
84
97
  stop: undefined,
98
+ sourceMapPath: undefined,
85
99
  }
86
100
  : await bundleWorker(entry, destination, {
87
101
  serveAssetsFromWorker,
@@ -94,6 +108,14 @@ export function useEsbuild({
94
108
  nodeCompat,
95
109
  define,
96
110
  checkFetch: true,
111
+ assets: assets && {
112
+ ...assets,
113
+ // disable the cache in dev
114
+ bypassCache: true,
115
+ },
116
+ workerDefinitions,
117
+ services,
118
+ firstPartyWorkerDevFacade,
97
119
  });
98
120
 
99
121
  // Capture the `stop()` method to use as the `useEffect()` destructor.
@@ -119,6 +141,7 @@ export function useEsbuild({
119
141
  path: resolvedEntryPointPath,
120
142
  type: bundleType,
121
143
  modules,
144
+ sourceMapPath,
122
145
  });
123
146
  }
124
147
 
@@ -145,6 +168,11 @@ export function useEsbuild({
145
168
  minify,
146
169
  nodeCompat,
147
170
  define,
171
+ assets,
172
+ services,
173
+ durableObjects,
174
+ workerDefinitions,
175
+ firstPartyWorkerDevFacade,
148
176
  ]);
149
177
  return bundle;
150
178
  }
@@ -0,0 +1,31 @@
1
+ import type { DevProps } from "./dev";
2
+
3
+ export function validateDevProps(props: DevProps) {
4
+ if (
5
+ !props.isWorkersSite &&
6
+ props.assetPaths &&
7
+ props.entry.format === "service-worker"
8
+ ) {
9
+ throw new Error(
10
+ "You cannot use the service-worker format with an `assets` directory yet. For information on how to migrate to the module-worker format, see: https://developers.cloudflare.com/workers/learning/migrating-to-module-workers/"
11
+ );
12
+ }
13
+
14
+ if (props.bindings.wasm_modules && props.entry.format === "modules") {
15
+ throw new Error(
16
+ "You cannot configure [wasm_modules] with an ES module worker. Instead, import the .wasm module directly in your code"
17
+ );
18
+ }
19
+
20
+ if (props.bindings.text_blobs && props.entry.format === "modules") {
21
+ throw new Error(
22
+ "You cannot configure [text_blobs] with an ES module worker. Instead, import the file directly in your code, and optionally configure `[rules]` in your wrangler.toml"
23
+ );
24
+ }
25
+
26
+ if (props.bindings.data_blobs && props.entry.format === "modules") {
27
+ throw new Error(
28
+ "You cannot configure [data_blobs] with an ES module worker. Instead, import the file directly in your code, and optionally configure `[rules]` in your wrangler.toml"
29
+ );
30
+ }
31
+ }
@@ -0,0 +1,160 @@
1
+ import http from "http";
2
+ import net from "net";
3
+ import bodyParser from "body-parser";
4
+ import express from "express";
5
+ import { createHttpTerminator } from "http-terminator";
6
+ import { fetch } from "undici";
7
+ import { logger } from "./logger";
8
+ import type { Server } from "http";
9
+ import type { HttpTerminator } from "http-terminator";
10
+
11
+ const DEV_REGISTRY_PORT = "6284";
12
+ const DEV_REGISTRY_HOST = `http://localhost:${DEV_REGISTRY_PORT}`;
13
+
14
+ let server: Server;
15
+ let terminator: HttpTerminator;
16
+
17
+ export type WorkerRegistry = Record<string, WorkerDefinition>;
18
+
19
+ type WorkerDefinition = {
20
+ port: number | undefined;
21
+ protocol: "http" | "https" | undefined;
22
+ host: string | undefined;
23
+ mode: "local" | "remote";
24
+ headers?: Record<string, string>;
25
+ durableObjects: { name: string; className: string }[];
26
+ durableObjectsHost?: string;
27
+ durableObjectsPort?: number;
28
+ };
29
+
30
+ /**
31
+ * A helper function to check whether our service registry is already running
32
+ */
33
+ async function isPortAvailable() {
34
+ return new Promise((resolve, reject) => {
35
+ const netServer = net
36
+ .createServer()
37
+ .once("error", (err) => {
38
+ netServer.close();
39
+ if ((err as unknown as { code: string }).code === "EADDRINUSE") {
40
+ resolve(false);
41
+ } else {
42
+ reject(err);
43
+ }
44
+ })
45
+ .once("listening", () => {
46
+ netServer.close();
47
+ resolve(true);
48
+ });
49
+ netServer.listen(DEV_REGISTRY_PORT);
50
+ });
51
+ }
52
+
53
+ const jsonBodyParser = bodyParser.json();
54
+
55
+ /**
56
+ * Start the service registry. It's a simple server
57
+ * that exposes endpoints for registering and unregistering
58
+ * services, as well as getting the state of the registry.
59
+ */
60
+ export async function startWorkerRegistry() {
61
+ if ((await isPortAvailable()) && !server) {
62
+ const app = express();
63
+
64
+ let workers: WorkerRegistry = {};
65
+ app
66
+ .get("/workers", async (req, res) => {
67
+ res.json(workers);
68
+ })
69
+ .post("/workers/:workerId", jsonBodyParser, async (req, res) => {
70
+ workers[req.params.workerId] = req.body;
71
+ res.json(null);
72
+ })
73
+ .delete(`/workers/:workerId`, async (req, res) => {
74
+ delete workers[req.params.workerId];
75
+ res.json(null);
76
+ })
77
+ .delete("/workers", async (req, res) => {
78
+ workers = {};
79
+ res.json(null);
80
+ });
81
+ server = http.createServer(app);
82
+ terminator = createHttpTerminator({ server });
83
+ server.listen(DEV_REGISTRY_PORT);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Stop the service registry.
89
+ */
90
+ export async function stopWorkerRegistry() {
91
+ await terminator?.terminate();
92
+ }
93
+
94
+ /**
95
+ * Register a worker in the registry.
96
+ */
97
+ export async function registerWorker(
98
+ name: string,
99
+ definition: WorkerDefinition
100
+ ) {
101
+ try {
102
+ return await fetch(`${DEV_REGISTRY_HOST}/workers/${name}`, {
103
+ method: "POST",
104
+ headers: {
105
+ "Content-Type": "application/json",
106
+ },
107
+ body: JSON.stringify(definition),
108
+ });
109
+ } catch (e) {
110
+ if (
111
+ !["ECONNRESET", "ECONNREFUSED"].includes(
112
+ (e as unknown as { cause?: { code?: string } }).cause?.code || "___"
113
+ )
114
+ ) {
115
+ logger.error("Failed to register worker in local service registry", e);
116
+ } else {
117
+ logger.debug("Failed to register worker in local service registry", e);
118
+ }
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Unregister a worker from the registry.
124
+ */
125
+ export async function unregisterWorker(name: string) {
126
+ try {
127
+ await fetch(`${DEV_REGISTRY_HOST}/workers/${name}`, {
128
+ method: "DELETE",
129
+ });
130
+ } catch (e) {
131
+ if (
132
+ !["ECONNRESET", "ECONNREFUSED"].includes(
133
+ (e as unknown as { cause?: { code?: string } }).cause?.code || "___"
134
+ )
135
+ ) {
136
+ throw e;
137
+ // logger.error("failed to unregister worker", e);
138
+ }
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Get the state of the service registry.
144
+ */
145
+ export async function getRegisteredWorkers(): Promise<
146
+ WorkerRegistry | undefined
147
+ > {
148
+ try {
149
+ const response = await fetch(`${DEV_REGISTRY_HOST}/workers`);
150
+ return (await response.json()) as WorkerRegistry;
151
+ } catch (e) {
152
+ if (
153
+ !["ECONNRESET", "ECONNREFUSED"].includes(
154
+ (e as unknown as { cause?: { code?: string } }).cause?.code || "___"
155
+ )
156
+ ) {
157
+ throw e;
158
+ }
159
+ }
160
+ }