wrangler 2.0.29 → 2.1.2

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 (45) hide show
  1. package/miniflare-dist/index.mjs +1136 -372
  2. package/package.json +3 -2
  3. package/src/__tests__/helpers/mock-cfetch.ts +39 -19
  4. package/src/__tests__/helpers/mock-console.ts +11 -2
  5. package/src/__tests__/helpers/msw/handlers/index.ts +13 -0
  6. package/src/__tests__/helpers/msw/handlers/namespaces.ts +104 -0
  7. package/src/__tests__/helpers/msw/handlers/oauth.ts +36 -0
  8. package/src/__tests__/helpers/msw/handlers/r2.ts +80 -0
  9. package/src/__tests__/helpers/msw/handlers/user.ts +63 -0
  10. package/src/__tests__/helpers/msw/index.ts +4 -0
  11. package/src/__tests__/index.test.ts +9 -7
  12. package/src/__tests__/init.test.ts +356 -5
  13. package/src/__tests__/jest.setup.ts +16 -0
  14. package/src/__tests__/middleware.test.ts +768 -0
  15. package/src/__tests__/pages.test.ts +11 -12
  16. package/src/__tests__/publish.test.ts +516 -438
  17. package/src/__tests__/r2.test.ts +128 -93
  18. package/src/__tests__/secret.test.ts +78 -0
  19. package/src/__tests__/tail.test.ts +47 -74
  20. package/src/__tests__/whoami.test.tsx +49 -64
  21. package/src/api/dev.ts +23 -4
  22. package/src/bundle.ts +225 -1
  23. package/src/dev/dev.tsx +3 -1
  24. package/src/dev/local.tsx +2 -2
  25. package/src/dev/remote.tsx +6 -3
  26. package/src/dev/start-server.ts +11 -7
  27. package/src/dev/use-esbuild.ts +4 -0
  28. package/src/dev.tsx +6 -16
  29. package/src/dialogs.tsx +12 -0
  30. package/src/index.tsx +95 -4
  31. package/src/init.ts +286 -11
  32. package/src/miniflare-cli/assets.ts +130 -415
  33. package/src/miniflare-cli/index.ts +3 -1
  34. package/src/pages/dev.tsx +5 -1
  35. package/src/pages/hash.tsx +13 -0
  36. package/src/pages/upload.tsx +3 -18
  37. package/src/publish.ts +38 -4
  38. package/src/tail/filters.ts +1 -5
  39. package/src/tail/index.ts +6 -3
  40. package/templates/middleware/common.ts +62 -0
  41. package/templates/middleware/loader-modules.ts +84 -0
  42. package/templates/middleware/loader-sw.ts +213 -0
  43. package/templates/middleware/middleware-pretty-error.ts +40 -0
  44. package/templates/middleware/middleware-scheduled.ts +14 -0
  45. package/wrangler-dist/cli.js +65900 -65432
@@ -1,15 +1,11 @@
1
1
  import { render } from "ink-testing-library";
2
+ import { rest } from "msw";
2
3
  import React from "react";
3
4
  import { writeAuthConfigFile } from "../user";
4
5
  import { getUserInfo, WhoAmI } from "../whoami";
5
- import {
6
- createFetchResult,
7
- setMockRawResponse,
8
- setMockResponse,
9
- unsetAllMocks,
10
- } from "./helpers/mock-cfetch";
11
6
  import { mockConsoleMethods } from "./helpers/mock-console";
12
7
  import { useMockIsTTY } from "./helpers/mock-istty";
8
+ import { msw } from "./helpers/msw";
13
9
  import { runInTempDir } from "./helpers/run-in-tmp";
14
10
  import type { UserInfo } from "../whoami";
15
11
 
@@ -26,7 +22,6 @@ describe("getUserInfo()", () => {
26
22
 
27
23
  afterEach(() => {
28
24
  process.env = ENV_COPY;
29
- unsetAllMocks();
30
25
  });
31
26
 
32
27
  it("should return undefined if there is no config file", async () => {
@@ -44,12 +39,47 @@ describe("getUserInfo()", () => {
44
39
  process.env = {
45
40
  CLOUDFLARE_API_TOKEN: "123456789",
46
41
  };
47
- setMockRawResponse("/user", () =>
48
- createFetchResult(undefined, false, [
49
- { code: 9109, message: "Uauthorized to access requested resource" },
50
- ])
42
+ msw.use(
43
+ rest.get("*/user", (_, res, ctx) => {
44
+ return res.once(
45
+ ctx.status(200),
46
+ ctx.json({
47
+ success: false,
48
+ errors: [
49
+ {
50
+ code: 9109,
51
+ message: "Uauthorized to access requested resource",
52
+ },
53
+ ],
54
+ messages: [],
55
+ result: {},
56
+ })
57
+ );
58
+ }),
59
+ rest.get("*/accounts", (request, res, ctx) => {
60
+ const headersObject = request.headers.all();
61
+ delete headersObject["user-agent"];
62
+
63
+ expect(headersObject).toMatchInlineSnapshot(`
64
+ Object {
65
+ "accept": "*/*",
66
+ "accept-encoding": "gzip,deflate",
67
+ "authorization": "Bearer 123456789",
68
+ "connection": "close",
69
+ "host": "api.cloudflare.com",
70
+ }
71
+ `);
72
+ return res.once(
73
+ ctx.status(200),
74
+ ctx.json({
75
+ success: true,
76
+ errors: [],
77
+ messages: [],
78
+ result: [],
79
+ })
80
+ );
81
+ })
51
82
  );
52
- setMockResponse("/accounts", () => []);
53
83
  const userInfo = await getUserInfo();
54
84
  expect(userInfo?.email).toBeUndefined();
55
85
  });
@@ -58,16 +88,6 @@ describe("getUserInfo()", () => {
58
88
  process.env = {
59
89
  CLOUDFLARE_API_TOKEN: "123456789",
60
90
  };
61
- setMockResponse("/user", () => {
62
- return { email: "user@example.com" };
63
- });
64
- setMockResponse("/accounts", () => {
65
- return [
66
- { name: "Account One", id: "account-1" },
67
- { name: "Account Two", id: "account-2" },
68
- { name: "Account Three", id: "account-3" },
69
- ];
70
- });
71
91
 
72
92
  const userInfo = await getUserInfo();
73
93
  expect(userInfo).toEqual({
@@ -87,13 +107,6 @@ describe("getUserInfo()", () => {
87
107
  CLOUDFLARE_API_KEY: "123456789",
88
108
  CLOUDFLARE_EMAIL: "user@example.com",
89
109
  };
90
- setMockResponse("/accounts", () => {
91
- return [
92
- { name: "Account One", id: "account-1" },
93
- { name: "Account Two", id: "account-2" },
94
- { name: "Account Three", id: "account-3" },
95
- ];
96
- });
97
110
 
98
111
  const userInfo = await getUserInfo();
99
112
  expect(userInfo).toEqual({
@@ -114,13 +127,6 @@ describe("getUserInfo()", () => {
114
127
  CLOUDFLARE_EMAIL: "user@example.com",
115
128
  CLOUDFLARE_API_TOKEN: "123456789",
116
129
  };
117
- setMockResponse("/accounts", () => {
118
- return [
119
- { name: "Account One", id: "account-1" },
120
- { name: "Account Two", id: "account-2" },
121
- { name: "Account Three", id: "account-3" },
122
- ];
123
- });
124
130
 
125
131
  const userInfo = await getUserInfo();
126
132
  expect(userInfo).toEqual({
@@ -145,18 +151,6 @@ describe("getUserInfo()", () => {
145
151
 
146
152
  it("should return the user's email and accounts if authenticated via config token", async () => {
147
153
  writeAuthConfigFile({ oauth_token: "some-oauth-token" });
148
-
149
- setMockResponse("/user", () => {
150
- return { email: "user@example.com" };
151
- });
152
- setMockResponse("/accounts", () => {
153
- return [
154
- { name: "Account One", id: "account-1" },
155
- { name: "Account Two", id: "account-2" },
156
- { name: "Account Three", id: "account-3" },
157
- ];
158
- });
159
-
160
154
  const userInfo = await getUserInfo();
161
155
 
162
156
  expect(userInfo).toEqual({
@@ -173,26 +167,17 @@ describe("getUserInfo()", () => {
173
167
 
174
168
  it("should display a warning message if the config file contains a legacy api_token field", async () => {
175
169
  writeAuthConfigFile({ api_token: "API_TOKEN" });
176
- setMockResponse("/user", () => {
177
- return { email: "user@example.com" };
178
- });
179
- setMockResponse("/accounts", () => {
180
- return [
181
- { name: "Account One", id: "account-1" },
182
- { name: "Account Two", id: "account-2" },
183
- { name: "Account Three", id: "account-3" },
184
- ];
185
- });
186
170
  await getUserInfo();
171
+
187
172
  expect(std.warn).toMatchInlineSnapshot(`
188
- "▲ [WARNING] It looks like you have used Wrangler 1's \`config\` command to login with an API token.
173
+ "▲ [WARNING] It looks like you have used Wrangler 1's \`config\` command to login with an API token.
189
174
 
190
- This is no longer supported in the current version of Wrangler.
191
- If you wish to authenticate via an API token then please set the \`CLOUDFLARE_API_TOKEN\`
192
- environment variable.
175
+ This is no longer supported in the current version of Wrangler.
176
+ If you wish to authenticate via an API token then please set the \`CLOUDFLARE_API_TOKEN\`
177
+ environment variable.
193
178
 
194
- "
195
- `);
179
+ "
180
+ `);
196
181
  });
197
182
  });
198
183
 
package/src/api/dev.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { fetch } from "undici";
1
2
  import { startApiDev, startDev } from "../dev";
2
3
  import { logger } from "../logger";
3
4
 
@@ -79,6 +80,8 @@ export async function unstable_dev(
79
80
  `unstable_dev() is experimental\nunstable_dev()'s behaviour will likely change in future releases`
80
81
  );
81
82
  }
83
+ let readyPort: number;
84
+ let readyAddress: string;
82
85
  //due to Pages adoption of unstable_dev, we can't *just* disable rebuilds and watching. instead, we'll have two versions of startDev, which will converge.
83
86
  if (testMode) {
84
87
  //in testMode, we can run multiple wranglers in parallel, but rebuilds might not work out of the box
@@ -94,16 +97,24 @@ export async function unstable_dev(
94
97
  showInteractiveDevSession: false,
95
98
  _: [],
96
99
  $0: "",
100
+ port: options?.port ?? 0,
97
101
  ...options,
98
102
  local: true,
99
- onReady: () => ready(devServer),
103
+ onReady: (address, port) => {
104
+ readyPort = port;
105
+ readyAddress = address;
106
+ ready(devServer);
107
+ },
100
108
  });
101
109
  }).then((devServer) => {
102
110
  // now that the inner promise has resolved, we can resolve the outer promise
103
111
  // with an object that lets you fetch and stop the dev server
104
112
  resolve({
105
113
  stop: devServer.stop,
106
- fetch: devServer.fetch,
114
+ fetch: async (init?: RequestInit) => {
115
+ const urlToFetch = `http://${readyAddress}:${readyPort}/`;
116
+ return await fetch(urlToFetch, init);
117
+ },
107
118
  //no-op, does nothing in tests
108
119
  waitUntilExit: async () => {
109
120
  return;
@@ -113,6 +124,7 @@ export async function unstable_dev(
113
124
  });
114
125
  } else {
115
126
  //outside of test mode, rebuilds work fine, but only one instance of wrangler will work at a time
127
+
116
128
  return new Promise<UnstableDev>((resolve) => {
117
129
  //lmao
118
130
  return new Promise<Awaited<ReturnType<typeof startDev>>>((ready) => {
@@ -125,12 +137,19 @@ export async function unstable_dev(
125
137
  $0: "",
126
138
  ...options,
127
139
  local: true,
128
- onReady: () => ready(devServer),
140
+ onReady: (address, port) => {
141
+ readyPort = port;
142
+ readyAddress = address;
143
+ ready(devServer);
144
+ },
129
145
  });
130
146
  }).then((devServer) => {
131
147
  resolve({
132
148
  stop: devServer.stop,
133
- fetch: devServer.fetch,
149
+ fetch: async (init?: RequestInit) => {
150
+ const urlToFetch = `http://${readyAddress}:${readyPort}/`;
151
+ return await fetch(urlToFetch, init);
152
+ },
134
153
  waitUntilExit: devServer.devReactElement.waitUntilExit,
135
154
  });
136
155
  });
package/src/bundle.ts CHANGED
@@ -75,6 +75,7 @@ export async function bundleWorker(
75
75
  services: Config["services"] | undefined;
76
76
  workerDefinitions: WorkerRegistry | undefined;
77
77
  firstPartyWorkerDevFacade: boolean | undefined;
78
+ targetConsumer: "dev" | "publish";
78
79
  }
79
80
  ): Promise<BundleResult> {
80
81
  const {
@@ -91,6 +92,7 @@ export async function bundleWorker(
91
92
  workerDefinitions,
92
93
  services,
93
94
  firstPartyWorkerDevFacade,
95
+ targetConsumer,
94
96
  } = options;
95
97
 
96
98
  // We create a temporary directory for any oneoff files we
@@ -140,6 +142,19 @@ export async function bundleWorker(
140
142
  // a new entry point, that we call "middleware" or "facades".
141
143
  // Look at implementations of these functions to learn more.
142
144
 
145
+ // We also have middleware that uses a more "traditional" middleware stack,
146
+ // which is all loaded as one in a stack.
147
+ const middlewareToLoad: MiddlewareLoader[] = [
148
+ // {
149
+ // path: "templates/middleware/middleware-pretty-error.ts",
150
+ // publish: true,
151
+ // dev: false,
152
+ // },
153
+ // {
154
+ // path: "../templates/middleware/middleware-scheduled.ts",
155
+ // },
156
+ ];
157
+
143
158
  type MiddlewareFn = (arg0: Entry) => Promise<Entry>;
144
159
  const middleware: (false | undefined | MiddlewareFn)[] = [
145
160
  // serve static assets
@@ -173,6 +188,32 @@ export async function bundleWorker(
173
188
  ((currentEntry: Entry) => {
174
189
  return applyFirstPartyWorkerDevFacade(currentEntry, tmpDir.path);
175
190
  }),
191
+
192
+ // Middleware loader: to add middleware, we add the path to the middleware
193
+ // Currently for demonstration purposes we have two example middlewares
194
+ // Middlewares are togglable by changing the `publish` (default=false) and `dev` (default=true) options
195
+ // As we are not yet supporting user created middlewares yet, if no wrangler applied middleware
196
+ // are found, we will not load any middleware. We also need to check if there are middlewares compatible with
197
+ // the target consumer (dev / publish).
198
+ (middlewareToLoad.filter(
199
+ (m) =>
200
+ (m.publish && targetConsumer === "publish") ||
201
+ (m.dev !== false && targetConsumer === "dev")
202
+ ).length > 0 ||
203
+ process.env.EXPERIMENTAL_MIDDLEWARE === "true") &&
204
+ ((currentEntry: Entry) => {
205
+ return applyMiddlewareLoaderFacade(
206
+ currentEntry,
207
+ tmpDir.path,
208
+ middlewareToLoad.filter(
209
+ // We dynamically filter the middleware depending on where we are bundling for
210
+ (m) =>
211
+ (targetConsumer === "dev" && m.dev !== false) ||
212
+ (m.publish && targetConsumer === "publish")
213
+ ),
214
+ moduleCollector.plugin
215
+ );
216
+ }),
176
217
  ].filter(Boolean);
177
218
 
178
219
  let inputEntry = entry;
@@ -215,7 +256,11 @@ export async function bundleWorker(
215
256
  ".cjs": "jsx",
216
257
  },
217
258
  plugins: [
218
- moduleCollector.plugin,
259
+ // We run the moduleCollector plugin for service workers as part of the middleware loader
260
+ // so we only run here for modules or with no middleware to load
261
+ ...(entry.format === "modules" || middlewareToLoad.length === 0
262
+ ? [moduleCollector.plugin]
263
+ : []),
219
264
  ...(nodeCompat
220
265
  ? [NodeGlobalsPolyfills({ buffer: true }), NodeModulesPolyfills()]
221
266
  : // we use checkForNodeBuiltinsPlugin to throw a nicer error
@@ -323,6 +368,185 @@ async function applyFormatDevErrorsFacade(
323
368
  };
324
369
  }
325
370
 
371
+ /**
372
+ * A facade that acts as a "middleware loader".
373
+ * Instead of needing to apply a facade for each individual middleware, this allows
374
+ * middleware to be written in a more traditional manner and then be applied all
375
+ * at once, requiring just two esbuild steps, rather than 1 per middleware.
376
+ */
377
+
378
+ interface MiddlewareLoader {
379
+ path: string;
380
+ // By default all middleware will run on dev, but will not be run when published
381
+ publish?: boolean;
382
+ dev?: boolean;
383
+ }
384
+
385
+ async function applyMiddlewareLoaderFacade(
386
+ entry: Entry,
387
+ tmpDirPath: string,
388
+ middleware: MiddlewareLoader[], // a list of paths to middleware files
389
+ moduleCollectorPlugin: esbuild.Plugin
390
+ ): Promise<Entry> {
391
+ // Firstly we need to insert the middleware array into the project,
392
+ // and then we load the middleware - this insertion and loading is
393
+ // different for each format.
394
+
395
+ // STEP 1: Insert the middleware
396
+ const targetPathInsertion = path.join(
397
+ tmpDirPath,
398
+ "middleware-insertion.entry.js"
399
+ );
400
+
401
+ // We need to import each of the middlewares, so we need to generate a
402
+ // random, unique identifier that we can use for the import.
403
+ // Middlewares are required to be default exports so we can import to any name.
404
+ const middlewareIdentifiers = middleware.map(
405
+ (_, index) => `__MIDDLEWARE_${index}__`
406
+ );
407
+
408
+ const dynamicFacadePath = path.join(
409
+ tmpDirPath,
410
+ "middleware-insertion-facade.js"
411
+ );
412
+
413
+ if (entry.format === "modules") {
414
+ // We use a facade to expose the required middleware alongside any user defined
415
+ // middleware on the worker object
416
+
417
+ const imports = middlewareIdentifiers
418
+ .map((m) => `import ${m} from "${m}";`)
419
+ .join("\n");
420
+
421
+ // write a file with all of the imports required
422
+ fs.writeFileSync(
423
+ dynamicFacadePath,
424
+ `import worker from "__ENTRY_POINT__";
425
+ ${imports}
426
+ const facade = {
427
+ ...worker,
428
+ middleware: [
429
+ ${middlewareIdentifiers.join(",")}${middlewareIdentifiers.length > 0 ? "," : ""}
430
+ ...(worker.middleware ? worker.middleware : []),
431
+ ]
432
+ }
433
+ export * from "__ENTRY_POINT__";
434
+ export default facade;`
435
+ );
436
+
437
+ await esbuild.build({
438
+ entryPoints: [path.resolve(getBasePath(), dynamicFacadePath)],
439
+ bundle: true,
440
+ sourcemap: true,
441
+ format: "esm",
442
+ plugins: [
443
+ esbuildAliasExternalPlugin({
444
+ __ENTRY_POINT__: entry.file,
445
+ ...Object.fromEntries(
446
+ middleware.map((val, index) => [
447
+ middlewareIdentifiers[index],
448
+ path.resolve(getBasePath(), val.path),
449
+ ])
450
+ ),
451
+ }),
452
+ ],
453
+ outfile: targetPathInsertion,
454
+ });
455
+ } else {
456
+ // We handle service workers slightly differently as we have to overwrite
457
+ // the event listeners and reimplement them
458
+
459
+ await esbuild.build({
460
+ entryPoints: [entry.file],
461
+ bundle: true,
462
+ sourcemap: true,
463
+ define: {
464
+ "process.env.NODE_ENV": `"${process.env["NODE_ENV" + ""]}"`,
465
+ },
466
+ format: "esm",
467
+ outfile: targetPathInsertion,
468
+ plugins: [moduleCollectorPlugin],
469
+ });
470
+
471
+ const imports = middlewareIdentifiers
472
+ .map(
473
+ (m, i) =>
474
+ `import ${m} from "${path.resolve(
475
+ getBasePath(),
476
+ middleware[i].path
477
+ )}";`
478
+ )
479
+ .join("\n");
480
+
481
+ // We add the new modules with imports and then register using the
482
+ // addMiddleware function (which gets rewritten in the next build step)
483
+
484
+ // We choose to run middleware inserted in wrangler before user inserted
485
+ // middleware in the stack
486
+ // To do this, we either need to execute the addMiddleware function first
487
+ // before any user middleware, or use a separate handling function.
488
+ // We choose to do the latter as to prepend, we would have to load the entire
489
+ // script into memory as a prepend function doesn't exist or work in the same
490
+ // way that an append function does.
491
+
492
+ fs.copyFileSync(targetPathInsertion, dynamicFacadePath);
493
+ fs.appendFileSync(
494
+ dynamicFacadePath,
495
+ `
496
+ ${imports}
497
+ addMiddlewareInternal([${middlewareIdentifiers.join(",")}])
498
+ `
499
+ );
500
+ }
501
+
502
+ // STEP 2: Load the middleware
503
+ // We want to get the filename of the orginal entry point
504
+ let targetPathLoader = path.join(tmpDirPath, path.basename(entry.file));
505
+ if (path.extname(entry.file) === "") targetPathLoader += ".js";
506
+
507
+ const loaderPath =
508
+ entry.format === "modules"
509
+ ? path.resolve(getBasePath(), "templates/middleware/loader-modules.ts")
510
+ : dynamicFacadePath;
511
+
512
+ await esbuild.build({
513
+ entryPoints: [loaderPath],
514
+ bundle: true,
515
+ sourcemap: true,
516
+ format: "esm",
517
+ ...(entry.format === "service-worker"
518
+ ? {
519
+ inject: [
520
+ path.resolve(getBasePath(), "templates/middleware/loader-sw.ts"),
521
+ ],
522
+ define: {
523
+ addEventListener: "__facade_addEventListener__",
524
+ removeEventListener: "__facade_removeEventListener__",
525
+ dispatchEvent: "__facade_dispatchEvent__",
526
+ addMiddleware: "__facade_register__",
527
+ addMiddlewareInternal: "__facade_registerInternal__",
528
+ },
529
+ }
530
+ : {
531
+ plugins: [
532
+ esbuildAliasExternalPlugin({
533
+ __ENTRY_POINT__: targetPathInsertion,
534
+ "./common": path.resolve(
535
+ getBasePath(),
536
+ "templates/middleware/common.ts"
537
+ ),
538
+ }),
539
+ ],
540
+ }),
541
+ outfile: targetPathLoader,
542
+ });
543
+
544
+ return {
545
+ ...entry,
546
+ file: targetPathLoader,
547
+ };
548
+ }
549
+
326
550
  /**
327
551
  * A middleware that serves static assets from a worker.
328
552
  * This powers --assets / config.assets
package/src/dev/dev.tsx CHANGED
@@ -158,7 +158,7 @@ export type DevProps = {
158
158
  inspect: boolean;
159
159
  logLevel: "none" | "error" | "log" | "warn" | "debug" | undefined;
160
160
  logPrefix?: string;
161
- onReady: (() => void) | undefined;
161
+ onReady: ((ip: string, port: number) => void) | undefined;
162
162
  showInteractiveDevSession: boolean | undefined;
163
163
  forceLocal: boolean | undefined;
164
164
  enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
@@ -257,6 +257,8 @@ function DevSession(props: DevSessionProps) {
257
257
  services: props.bindings.services,
258
258
  durableObjects: props.bindings.durable_objects || { bindings: [] },
259
259
  firstPartyWorkerDevFacade: props.firstPartyWorker,
260
+ // Enable the bundling to know whether we are using dev or publish
261
+ targetConsumer: "dev",
260
262
  });
261
263
 
262
264
  return props.local ? (
package/src/dev/local.tsx CHANGED
@@ -48,7 +48,7 @@ export interface LocalProps {
48
48
  localProtocol: "http" | "https";
49
49
  localUpstream: string | undefined;
50
50
  inspect: boolean;
51
- onReady: (() => void) | undefined;
51
+ onReady: ((ip: string, port: number) => void) | undefined;
52
52
  logLevel: "none" | "error" | "log" | "warn" | "debug" | undefined;
53
53
  logPrefix?: string;
54
54
  enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
@@ -228,7 +228,7 @@ function useLocalWorker({
228
228
  : {}),
229
229
  });
230
230
  }
231
- onReady?.();
231
+ onReady?.(ip, message.mfPort);
232
232
  }
233
233
  });
234
234
 
@@ -54,7 +54,7 @@ export function Remote(props: {
54
54
  zone: string | undefined;
55
55
  host: string | undefined;
56
56
  routes: Route[] | undefined;
57
- onReady?: (() => void) | undefined;
57
+ onReady?: ((ip: string, port: number) => void) | undefined;
58
58
  sourceMapPath: string | undefined;
59
59
  sendMetrics: boolean | undefined;
60
60
  }) {
@@ -81,6 +81,7 @@ export function Remote(props: {
81
81
  routes: props.routes,
82
82
  onReady: props.onReady,
83
83
  sendMetrics: props.sendMetrics,
84
+ port: props.port,
84
85
  });
85
86
 
86
87
  usePreviewServer({
@@ -164,8 +165,9 @@ export function useWorker(props: {
164
165
  zone: string | undefined;
165
166
  host: string | undefined;
166
167
  routes: Route[] | undefined;
167
- onReady: (() => void) | undefined;
168
+ onReady: ((ip: string, port: number) => void) | undefined;
168
169
  sendMetrics: boolean | undefined;
170
+ port: number;
169
171
  }): CfPreviewToken | undefined {
170
172
  const {
171
173
  name,
@@ -363,7 +365,7 @@ export function useWorker(props: {
363
365
  }
364
366
  */
365
367
 
366
- onReady?.();
368
+ onReady?.(props.host || "localhost", props.port);
367
369
  }
368
370
  start().catch((err) => {
369
371
  // we want to log the error, but not end the process
@@ -417,6 +419,7 @@ export function useWorker(props: {
417
419
  session,
418
420
  onReady,
419
421
  props.sendMetrics,
422
+ props.port,
420
423
  ]);
421
424
  return token;
422
425
  }
@@ -177,6 +177,7 @@ async function runEsbuild({
177
177
  services: undefined,
178
178
  workerDefinitions: undefined,
179
179
  firstPartyWorkerDevFacade: undefined,
180
+ targetConsumer: "dev", // We are starting a dev server
180
181
  });
181
182
 
182
183
  return {
@@ -237,11 +238,14 @@ export async function startLocalServer({
237
238
  if (!bundle || !format) return;
238
239
 
239
240
  // port for the worker
240
- await waitForPortToBeAvailable(port, {
241
- retryPeriod: 200,
242
- timeout: 2000,
243
- abortSignal: abortController.signal,
244
- });
241
+
242
+ if (port !== 0) {
243
+ await waitForPortToBeAvailable(port, {
244
+ retryPeriod: 200,
245
+ timeout: 2000,
246
+ abortSignal: abortController.signal,
247
+ });
248
+ }
245
249
 
246
250
  if (bindings.services && bindings.services.length > 0) {
247
251
  throw new Error(
@@ -327,7 +331,7 @@ export async function startLocalServer({
327
331
  await registerWorker(workerName, {
328
332
  protocol: localProtocol,
329
333
  mode: "local",
330
- port,
334
+ port: message.mfPort,
331
335
  host: ip,
332
336
  durableObjects: internalDurableObjects.map((binding) => ({
333
337
  name: binding.name,
@@ -341,7 +345,7 @@ export async function startLocalServer({
341
345
  : {}),
342
346
  });
343
347
  }
344
- onReady?.();
348
+ onReady?.(ip, message.mfPort);
345
349
  }
346
350
  });
347
351
 
@@ -36,6 +36,7 @@ export function useEsbuild({
36
36
  services,
37
37
  durableObjects,
38
38
  firstPartyWorkerDevFacade,
39
+ targetConsumer,
39
40
  }: {
40
41
  entry: Entry;
41
42
  destination: string | undefined;
@@ -53,6 +54,7 @@ export function useEsbuild({
53
54
  workerDefinitions: WorkerRegistry;
54
55
  durableObjects: Config["durable_objects"];
55
56
  firstPartyWorkerDevFacade: boolean | undefined;
57
+ targetConsumer: "dev" | "publish";
56
58
  }): EsbuildBundle | undefined {
57
59
  const [bundle, setBundle] = useState<EsbuildBundle>();
58
60
  const { exit } = useApp();
@@ -116,6 +118,7 @@ export function useEsbuild({
116
118
  workerDefinitions,
117
119
  services,
118
120
  firstPartyWorkerDevFacade,
121
+ targetConsumer,
119
122
  });
120
123
 
121
124
  // Capture the `stop()` method to use as the `useEffect()` destructor.
@@ -173,6 +176,7 @@ export function useEsbuild({
173
176
  durableObjects,
174
177
  workerDefinitions,
175
178
  firstPartyWorkerDevFacade,
179
+ targetConsumer,
176
180
  ]);
177
181
  return bundle;
178
182
  }