wrangler 2.0.24 → 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 (39) hide show
  1. package/miniflare-dist/index.mjs +130 -16
  2. package/package.json +1 -1
  3. package/src/__tests__/configuration.test.ts +1 -1
  4. package/src/__tests__/dev.test.tsx +26 -4
  5. package/src/__tests__/helpers/mock-cfetch.ts +2 -2
  6. package/src/__tests__/r2.test.ts +18 -0
  7. package/src/__tests__/tail.test.ts +93 -39
  8. package/src/api/dev.ts +6 -0
  9. package/src/bundle.ts +3 -2
  10. package/src/config/config.ts +1 -1
  11. package/src/config/validation.ts +1 -1
  12. package/src/dev/dev.tsx +12 -2
  13. package/src/dev/local.tsx +69 -5
  14. package/src/dev/use-esbuild.ts +3 -0
  15. package/src/dev-registry.tsx +3 -0
  16. package/src/dev.tsx +26 -17
  17. package/src/index.tsx +51 -21
  18. package/src/inspect.ts +1 -4
  19. package/src/miniflare-cli/assets.ts +19 -16
  20. package/src/miniflare-cli/index.ts +121 -2
  21. package/src/pages/build.tsx +36 -28
  22. package/src/pages/constants.ts +3 -0
  23. package/src/pages/deployments.tsx +9 -9
  24. package/src/pages/dev.tsx +85 -27
  25. package/src/pages/functions/buildPlugin.ts +4 -0
  26. package/src/pages/functions/buildWorker.ts +4 -0
  27. package/src/pages/functions/routes-consolidation.test.ts +66 -0
  28. package/src/pages/functions/routes-consolidation.ts +29 -0
  29. package/src/pages/functions/routes-transformation.test.ts +271 -0
  30. package/src/pages/functions/routes-transformation.ts +125 -0
  31. package/src/pages/projects.tsx +9 -3
  32. package/src/pages/publish.tsx +56 -14
  33. package/src/pages/types.ts +9 -0
  34. package/src/pages/upload.tsx +6 -8
  35. package/src/r2.ts +13 -0
  36. package/src/tail/index.ts +15 -2
  37. package/src/tail/printing.ts +41 -3
  38. package/wrangler-dist/cli.d.ts +6 -0
  39. package/wrangler-dist/cli.js +385 -89
@@ -105,7 +105,18 @@ var require_mime = __commonJS({
105
105
  });
106
106
 
107
107
  // src/miniflare-cli/index.ts
108
- import { Log, LogLevel, Miniflare } from "miniflare";
108
+ import { fetch } from "@miniflare/core";
109
+ import {
110
+ DurableObjectNamespace,
111
+ DurableObjectStub
112
+ } from "@miniflare/durable-objects";
113
+ import {
114
+ Log,
115
+ LogLevel,
116
+ Miniflare,
117
+ Response as MiniflareResponse,
118
+ Request as MiniflareRequest
119
+ } from "miniflare";
109
120
 
110
121
  // ../../node_modules/yargs/lib/platform-shims/esm.mjs
111
122
  import { notStrictEqual, strictEqual } from "assert";
@@ -4914,6 +4925,14 @@ function isYargsInstance(y) {
4914
4925
  var Yargs = YargsFactory(esm_default);
4915
4926
  var yargs_default = Yargs;
4916
4927
 
4928
+ // src/errors.ts
4929
+ var FatalError = class extends Error {
4930
+ constructor(message, code) {
4931
+ super(message);
4932
+ this.code = code;
4933
+ }
4934
+ };
4935
+
4917
4936
  // src/miniflare-cli/assets.ts
4918
4937
  var import_mime = __toESM(require_mime());
4919
4938
  import { existsSync, lstatSync, readFileSync as readFileSync4 } from "node:fs";
@@ -5147,6 +5166,11 @@ async function generateAssetsFetch(directory, log) {
5147
5166
  };
5148
5167
  const generateResponse = (request) => {
5149
5168
  const url = new URL(request.url);
5169
+ let assetName = url.pathname;
5170
+ try {
5171
+ assetName = decodeURIComponent(url.pathname);
5172
+ } catch {
5173
+ }
5150
5174
  const deconstructedResponse = {
5151
5175
  status: 200,
5152
5176
  headers: new Headers(),
@@ -5176,7 +5200,7 @@ async function generateAssetsFetch(directory, log) {
5176
5200
  return deconstructedResponse;
5177
5201
  }
5178
5202
  const notFound = () => {
5179
- let cwd = url.pathname;
5203
+ let cwd = assetName;
5180
5204
  while (cwd) {
5181
5205
  cwd = cwd.slice(0, cwd.lastIndexOf("/"));
5182
5206
  if (asset = getAsset(`${cwd}/404.html`)) {
@@ -5201,34 +5225,34 @@ async function generateAssetsFetch(directory, log) {
5201
5225
  return deconstructedResponse;
5202
5226
  };
5203
5227
  let asset;
5204
- if (url.pathname.endsWith("/")) {
5205
- if (asset = getAsset(`${url.pathname}/index.html`)) {
5228
+ if (assetName.endsWith("/")) {
5229
+ if (asset = getAsset(`${assetName}/index.html`)) {
5206
5230
  deconstructedResponse.body = serveAsset(asset);
5207
5231
  deconstructedResponse.headers.set(
5208
5232
  "Content-Type",
5209
5233
  (0, import_mime.getType)(asset) || "application/octet-stream"
5210
5234
  );
5211
5235
  return deconstructedResponse;
5212
- } else if (asset = getAsset(`${url.pathname.replace(/\/$/, ".html")}`)) {
5236
+ } else if (asset = getAsset(`${assetName.replace(/\/$/, ".html")}`)) {
5213
5237
  deconstructedResponse.status = 301;
5214
5238
  deconstructedResponse.headers.set(
5215
5239
  "Location",
5216
- `${url.pathname.slice(0, -1)}${url.search}`
5240
+ `${assetName.slice(0, -1)}${url.search}`
5217
5241
  );
5218
5242
  return deconstructedResponse;
5219
5243
  }
5220
5244
  }
5221
- if (url.pathname.endsWith("/index")) {
5245
+ if (assetName.endsWith("/index")) {
5222
5246
  deconstructedResponse.status = 301;
5223
5247
  deconstructedResponse.headers.set(
5224
5248
  "Location",
5225
- `${url.pathname.slice(0, -"index".length)}${url.search}`
5249
+ `${assetName.slice(0, -"index".length)}${url.search}`
5226
5250
  );
5227
5251
  return deconstructedResponse;
5228
5252
  }
5229
- if (asset = getAsset(url.pathname)) {
5230
- if (url.pathname.endsWith(".html")) {
5231
- const extensionlessPath = url.pathname.slice(0, -".html".length);
5253
+ if (asset = getAsset(assetName)) {
5254
+ if (assetName.endsWith(".html")) {
5255
+ const extensionlessPath = assetName.slice(0, -".html".length);
5232
5256
  if (getAsset(extensionlessPath) || extensionlessPath === "/") {
5233
5257
  deconstructedResponse.body = serveAsset(asset);
5234
5258
  deconstructedResponse.headers.set(
@@ -5252,11 +5276,11 @@ async function generateAssetsFetch(directory, log) {
5252
5276
  );
5253
5277
  return deconstructedResponse;
5254
5278
  }
5255
- } else if (hasFileExtension(url.pathname)) {
5279
+ } else if (hasFileExtension(assetName)) {
5256
5280
  notFound();
5257
5281
  return deconstructedResponse;
5258
5282
  }
5259
- if (asset = getAsset(`${url.pathname}.html`)) {
5283
+ if (asset = getAsset(`${assetName}.html`)) {
5260
5284
  deconstructedResponse.body = serveAsset(asset);
5261
5285
  deconstructedResponse.headers.set(
5262
5286
  "Content-Type",
@@ -5264,11 +5288,11 @@ async function generateAssetsFetch(directory, log) {
5264
5288
  );
5265
5289
  return deconstructedResponse;
5266
5290
  }
5267
- if (asset = getAsset(`${url.pathname}/index.html`)) {
5291
+ if (asset = getAsset(`${assetName}/index.html`)) {
5268
5292
  deconstructedResponse.status = 301;
5269
5293
  deconstructedResponse.headers.set(
5270
5294
  "Location",
5271
- `${url.pathname}/${url.search}`
5295
+ `${assetName}/${url.search}`
5272
5296
  );
5273
5297
  return deconstructedResponse;
5274
5298
  } else {
@@ -5369,7 +5393,40 @@ async function main() {
5369
5393
  if (logLevel > LogLevel.INFO) {
5370
5394
  console.log("OPTIONS:\n", JSON.stringify(config, null, 2));
5371
5395
  }
5396
+ config.bindings = {
5397
+ ...config.bindings,
5398
+ ...Object.fromEntries(
5399
+ Object.entries(
5400
+ config.externalDurableObjects
5401
+ ).map(([binding, { name, host, port }]) => {
5402
+ const factory = () => {
5403
+ throw new FatalError(
5404
+ "An external Durable Object instance's state has somehow been attempted to be accessed.",
5405
+ 1
5406
+ );
5407
+ };
5408
+ const namespace = new DurableObjectNamespace(name, factory);
5409
+ namespace.get = (id) => {
5410
+ const stub = new DurableObjectStub(factory, id);
5411
+ stub.fetch = (...reqArgs) => {
5412
+ const url = `http://${host}${port ? `:${port}` : ""}`;
5413
+ const request = new MiniflareRequest(
5414
+ url,
5415
+ new MiniflareRequest(...reqArgs)
5416
+ );
5417
+ request.headers.set("x-miniflare-durable-object-name", name);
5418
+ request.headers.set("x-miniflare-durable-object-id", id.toString());
5419
+ return fetch(request);
5420
+ };
5421
+ return stub;
5422
+ };
5423
+ return [binding, namespace];
5424
+ })
5425
+ )
5426
+ };
5372
5427
  let mf;
5428
+ let durableObjectsMf = void 0;
5429
+ let durableObjectsMfPort = void 0;
5373
5430
  try {
5374
5431
  if (args._[1]) {
5375
5432
  const opts = JSON.parse(
@@ -5393,11 +5450,68 @@ async function main() {
5393
5450
  mf = new Miniflare(config);
5394
5451
  await mf.startServer();
5395
5452
  await mf.startScheduler();
5396
- process.send && process.send("ready");
5453
+ const internalDurableObjectClassNames = Object.values(
5454
+ config.durableObjects
5455
+ );
5456
+ if (internalDurableObjectClassNames.length > 0) {
5457
+ durableObjectsMf = new Miniflare({
5458
+ host: config.host,
5459
+ port: 0,
5460
+ script: `
5461
+ export default {
5462
+ fetch(request, env) {
5463
+ return env.DO.fetch(request)
5464
+ }
5465
+ }`,
5466
+ serviceBindings: {
5467
+ DO: async (request) => {
5468
+ request = new MiniflareRequest(request);
5469
+ const name = request.headers.get("x-miniflare-durable-object-name");
5470
+ const idString = request.headers.get(
5471
+ "x-miniflare-durable-object-id"
5472
+ );
5473
+ request.headers.delete("x-miniflare-durable-object-name");
5474
+ request.headers.delete("x-miniflare-durable-object-id");
5475
+ if (!name || !idString) {
5476
+ return new MiniflareResponse(
5477
+ "[durable-object-proxy-err] Missing `x-miniflare-durable-object-name` or `x-miniflare-durable-object-id` headers.",
5478
+ { status: 400 }
5479
+ );
5480
+ }
5481
+ const namespace = await mf?.getDurableObjectNamespace(name);
5482
+ const id = namespace?.idFromString(idString);
5483
+ if (!id) {
5484
+ return new MiniflareResponse(
5485
+ "[durable-object-proxy-err] Could not generate an ID. Possibly due to a mismatched DO name and ID?",
5486
+ { status: 500 }
5487
+ );
5488
+ }
5489
+ const stub = namespace?.get(id);
5490
+ if (!stub) {
5491
+ return new MiniflareResponse(
5492
+ "[durable-object-proxy-err] Could not generate a stub. Possibly due to a mismatched DO name and ID?",
5493
+ { status: 500 }
5494
+ );
5495
+ }
5496
+ return stub.fetch(request);
5497
+ }
5498
+ },
5499
+ modules: true
5500
+ });
5501
+ const server = await durableObjectsMf.startServer();
5502
+ durableObjectsMfPort = server.address().port;
5503
+ }
5504
+ process.send && process.send(
5505
+ JSON.stringify({
5506
+ ready: true,
5507
+ durableObjectsPort: durableObjectsMfPort
5508
+ })
5509
+ );
5397
5510
  } catch (e) {
5398
5511
  mf?.log.error(e);
5399
5512
  process.exitCode = 1;
5400
5513
  await mf?.dispose();
5514
+ await durableObjectsMf?.dispose();
5401
5515
  }
5402
5516
  }
5403
5517
  await main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wrangler",
3
- "version": "2.0.24",
3
+ "version": "2.0.25",
4
4
  "description": "Command-line interface for all things Cloudflare Workers",
5
5
  "keywords": [
6
6
  "wrangler",
@@ -26,7 +26,7 @@ describe("normalizeAndValidateConfig()", () => {
26
26
  compatibility_flags: [],
27
27
  configPath: undefined,
28
28
  dev: {
29
- ip: "localhost",
29
+ ip: "0.0.0.0",
30
30
  local_protocol: "http",
31
31
  port: undefined, // the default of 8787 is set at runtime
32
32
  upstream_protocol: "https",
@@ -68,6 +68,28 @@ describe("wrangler dev", () => {
68
68
  });
69
69
  });
70
70
 
71
+ describe("usage-model", () => {
72
+ it("should read wrangler.toml's usage_model", async () => {
73
+ writeWranglerToml({
74
+ main: "index.js",
75
+ usage_model: "unbound",
76
+ });
77
+ fs.writeFileSync("index.js", `export default {};`);
78
+ await runWrangler("dev");
79
+ expect((Dev as jest.Mock).mock.calls[0][0].usageModel).toEqual("unbound");
80
+ });
81
+
82
+ it("should read wrangler.toml's usage_model in local mode", async () => {
83
+ writeWranglerToml({
84
+ main: "index.js",
85
+ usage_model: "unbound",
86
+ });
87
+ fs.writeFileSync("index.js", `export default {};`);
88
+ await runWrangler("dev --local");
89
+ expect((Dev as jest.Mock).mock.calls[0][0].usageModel).toEqual("unbound");
90
+ });
91
+ });
92
+
71
93
  describe("entry-points", () => {
72
94
  it("should error if there is no entry-point specified", async () => {
73
95
  writeWranglerToml();
@@ -630,13 +652,13 @@ describe("wrangler dev", () => {
630
652
  });
631
653
 
632
654
  describe("ip", () => {
633
- it("should default ip to localhost", async () => {
655
+ it("should default ip to 0.0.0.0", async () => {
634
656
  writeWranglerToml({
635
657
  main: "index.js",
636
658
  });
637
659
  fs.writeFileSync("index.js", `export default {};`);
638
660
  await runWrangler("dev");
639
- expect((Dev as jest.Mock).mock.calls[0][0].ip).toEqual("localhost");
661
+ expect((Dev as jest.Mock).mock.calls[0][0].ip).toEqual("0.0.0.0");
640
662
  expect(std.out).toMatchInlineSnapshot(`""`);
641
663
  expect(std.warn).toMatchInlineSnapshot(`""`);
642
664
  expect(std.err).toMatchInlineSnapshot(`""`);
@@ -849,7 +871,7 @@ describe("wrangler dev", () => {
849
871
  });
850
872
  fs.writeFileSync("index.js", `export default {};`);
851
873
  await runWrangler("dev");
852
- expect((Dev as jest.Mock).mock.calls[0][0].ip).toEqual("localhost");
874
+ expect((Dev as jest.Mock).mock.calls[0][0].ip).toEqual("0.0.0.0");
853
875
  expect(std.out).toMatchInlineSnapshot(`
854
876
  "Your worker has access to the following bindings:
855
877
  - Durable Objects:
@@ -983,7 +1005,7 @@ describe("wrangler dev", () => {
983
1005
  --compatibility-date Date to use for compatibility checks [string]
984
1006
  --compatibility-flags, --compatibility-flag Flags to use for compatibility checks [array]
985
1007
  --latest Use the latest version of the worker runtime [boolean] [default: true]
986
- --ip IP address to listen on, defaults to \`localhost\` [string]
1008
+ --ip IP address to listen on [string] [default: \\"0.0.0.0\\"]
987
1009
  --port Port to listen on [number]
988
1010
  --inspector-port Port for devtools to connect to [number]
989
1011
  --routes, --route Routes to upload [array]
@@ -217,7 +217,7 @@ export async function mockFetchR2Objects(
217
217
  bodyInit: {
218
218
  body: BodyInit | Readable;
219
219
  headers: HeadersInit | undefined;
220
- method: "PUT" | "GET";
220
+ method: "PUT" | "GET" | "DELETE";
221
221
  }
222
222
  ): Promise<Response> {
223
223
  /**
@@ -234,7 +234,7 @@ export async function mockFetchR2Objects(
234
234
 
235
235
  return new Response(value);
236
236
  }
237
- throw new Error(`no expected mock found for \`r2 object get\` - ${resource}`);
237
+ throw new Error(`no mock found for \`r2 object\` - ${resource}`);
238
238
  }
239
239
 
240
240
  /**
@@ -290,6 +290,24 @@ describe("wrangler", () => {
290
290
  Upload complete."
291
291
  `);
292
292
  });
293
+
294
+ it("should delete R2 object from bucket", async () => {
295
+ setMockFetchR2Objects({
296
+ accountId: "some-account-id",
297
+ bucketName: "bucketName-object-test",
298
+ objectName: "wormhole-img.png",
299
+ });
300
+
301
+ await runWrangler(
302
+ `r2 object delete bucketName-object-test/wormhole-img.png`
303
+ );
304
+
305
+ expect(std.out).toMatchInlineSnapshot(`
306
+ "Deleting object \\"wormhole-img.png\\" from bucket \\"bucketName-object-test\\".
307
+ Delete complete."
308
+ `);
309
+ });
310
+
293
311
  it("should not allow `--pipe` & `--file` to run together", async () => {
294
312
  fs.writeFileSync("wormhole-img.png", "passageway");
295
313
  setMockFetchR2Objects({
@@ -6,7 +6,12 @@ import { mockConsoleMethods } from "./helpers/mock-console";
6
6
  import { useMockIsTTY } from "./helpers/mock-istty";
7
7
  import { runInTempDir } from "./helpers/run-in-tmp";
8
8
  import { runWrangler } from "./helpers/run-wrangler";
9
- import type { TailEventMessage, RequestEvent, ScheduledEvent } from "../tail";
9
+ import type {
10
+ TailEventMessage,
11
+ RequestEvent,
12
+ ScheduledEvent,
13
+ AlarmEvent,
14
+ } from "../tail";
10
15
  import type WebSocket from "ws";
11
16
 
12
17
  describe("tail", () => {
@@ -251,6 +256,18 @@ describe("tail", () => {
251
256
  expect(std.out).toMatch(deserializeToJson(serializedMessage));
252
257
  });
253
258
 
259
+ it("logs alarm messages in json format", async () => {
260
+ const api = mockWebsocketAPIs();
261
+ await runWrangler("tail test-worker --format json");
262
+
263
+ const event = generateMockAlarmEvent();
264
+ const message = generateMockEventMessage({ event });
265
+ const serializedMessage = serialize(message);
266
+
267
+ api.ws.send(serializedMessage);
268
+ expect(std.out).toMatch(deserializeToJson(serializedMessage));
269
+ });
270
+
254
271
  it("logs request messages in pretty format", async () => {
255
272
  const api = mockWebsocketAPIs();
256
273
  await runWrangler("tail test-worker --format pretty");
@@ -271,10 +288,10 @@ describe("tail", () => {
271
288
  "[mock expiration date]"
272
289
  )
273
290
  ).toMatchInlineSnapshot(`
274
- "Successfully created tail, expires at [mock expiration date]
275
- Connected to test-worker, waiting for logs...
276
- GET https://example.org/ - Ok @ [mock event timestamp]"
277
- `);
291
+ "Successfully created tail, expires at [mock expiration date]
292
+ Connected to test-worker, waiting for logs...
293
+ GET https://example.org/ - Ok @ [mock event timestamp]"
294
+ `);
278
295
  });
279
296
 
280
297
  it("logs scheduled messages in pretty format", async () => {
@@ -297,35 +314,61 @@ describe("tail", () => {
297
314
  "[mock expiration date]"
298
315
  )
299
316
  ).toMatchInlineSnapshot(`
300
- "Successfully created tail, expires at [mock expiration date]
301
- Connected to test-worker, waiting for logs...
302
- \\"* * * * *\\" @ [mock timestamp string] - Ok"
303
- `);
317
+ "Successfully created tail, expires at [mock expiration date]
318
+ Connected to test-worker, waiting for logs...
319
+ \\"* * * * *\\" @ [mock timestamp string] - Ok"
320
+ `);
304
321
  });
305
322
 
306
- it("should not crash when the tail message has a void event", async () => {
323
+ it("logs alarm messages in pretty format", async () => {
307
324
  const api = mockWebsocketAPIs();
308
325
  await runWrangler("tail test-worker --format pretty");
309
326
 
310
- const message = generateMockEventMessage({ event: null });
327
+ const event = generateMockAlarmEvent();
328
+ const message = generateMockEventMessage({ event });
311
329
  const serializedMessage = serialize(message);
312
330
 
313
331
  api.ws.send(serializedMessage);
314
332
  expect(
315
333
  std.out
316
334
  .replace(
317
- new Date(mockEventTimestamp).toLocaleString(),
318
- "[mock timestamp string]"
335
+ new Date(mockEventScheduledTime).toLocaleString(),
336
+ "[mock scheduled time]"
319
337
  )
320
338
  .replace(
321
339
  mockTailExpiration.toLocaleString(),
322
340
  "[mock expiration date]"
323
341
  )
324
342
  ).toMatchInlineSnapshot(`
325
- "Successfully created tail, expires at [mock expiration date]
326
- Connected to test-worker, waiting for logs...
327
- [missing request] - Ok @ [mock timestamp string]"
328
- `);
343
+ "Successfully created tail, expires at [mock expiration date]
344
+ Connected to test-worker, waiting for logs...
345
+ Alarm @ [mock scheduled time] - Ok"
346
+ `);
347
+ });
348
+
349
+ it("should not crash when the tail message has a void event", async () => {
350
+ const api = mockWebsocketAPIs();
351
+ await runWrangler("tail test-worker --format pretty");
352
+
353
+ const message = generateMockEventMessage({ event: null });
354
+ const serializedMessage = serialize(message);
355
+
356
+ api.ws.send(serializedMessage);
357
+ expect(
358
+ std.out
359
+ .replace(
360
+ mockTailExpiration.toLocaleString(),
361
+ "[mock expiration date]"
362
+ )
363
+ .replace(
364
+ new Date(mockEventTimestamp).toLocaleString(),
365
+ "[mock timestamp string]"
366
+ )
367
+ ).toMatchInlineSnapshot(`
368
+ "Successfully created tail, expires at [mock expiration date]
369
+ Connected to test-worker, waiting for logs...
370
+ Unknown Event - Ok @ [mock timestamp string]"
371
+ `);
329
372
  });
330
373
 
331
374
  it("defaults to logging in pretty format when the output is a TTY", async () => {
@@ -349,10 +392,10 @@ describe("tail", () => {
349
392
  "[mock expiration date]"
350
393
  )
351
394
  ).toMatchInlineSnapshot(`
352
- "Successfully created tail, expires at [mock expiration date]
353
- Connected to test-worker, waiting for logs...
354
- GET https://example.org/ - Ok @ [mock event timestamp]"
355
- `);
395
+ "Successfully created tail, expires at [mock expiration date]
396
+ Connected to test-worker, waiting for logs...
397
+ GET https://example.org/ - Ok @ [mock event timestamp]"
398
+ `);
356
399
  });
357
400
 
358
401
  it("defaults to logging in json format when the output is not a TTY", async () => {
@@ -405,21 +448,21 @@ describe("tail", () => {
405
448
  "[mock expiration date]"
406
449
  )
407
450
  ).toMatchInlineSnapshot(`
408
- "Successfully created tail, expires at [mock expiration date]
409
- Connected to test-worker, waiting for logs...
410
- GET https://example.org/ - Ok @ [mock event timestamp]
411
- (log) some string
412
- (log) { complex: 'object' }
413
- (error) 1234"
414
- `);
451
+ "Successfully created tail, expires at [mock expiration date]
452
+ Connected to test-worker, waiting for logs...
453
+ GET https://example.org/ - Ok @ [mock event timestamp]
454
+ (log) some string
455
+ (log) { complex: 'object' }
456
+ (error) 1234"
457
+ `);
415
458
  expect(std.err).toMatchInlineSnapshot(`
416
- "X [ERROR]  Error: some error
459
+ "X [ERROR]  Error: some error
417
460
 
418
461
 
419
- X [ERROR]  Error: { complex: 'error' }
462
+ X [ERROR]  Error: { complex: 'error' }
420
463
 
421
- "
422
- `);
464
+ "
465
+ `);
423
466
  expect(std.warn).toMatchInlineSnapshot(`""`);
424
467
  });
425
468
  });
@@ -437,8 +480,8 @@ describe("tail", () => {
437
480
  * @returns the same type we expect when deserializing in wrangler
438
481
  */
439
482
  function serialize(message: TailEventMessage): WebSocket.RawData {
440
- if (isScheduled(message.event)) {
441
- // `ScheduledEvent`s work just fine
483
+ if (!isRequest(message.event)) {
484
+ // `ScheduledEvent`s and `TailEvent`s work just fine
442
485
  const stringified = JSON.stringify(message);
443
486
  return Buffer.from(stringified, "utf-8");
444
487
  } else {
@@ -470,12 +513,12 @@ function serialize(message: TailEventMessage): WebSocket.RawData {
470
513
  * Small helper to disambiguate the event types possible in a `TailEventMessage`
471
514
  *
472
515
  * @param event A TailEvent
473
- * @returns whether event is a ScheduledEvent (true) or a RequestEvent
516
+ * @returns true if `event` is a RequestEvent
474
517
  */
475
- function isScheduled(
476
- event: ScheduledEvent | RequestEvent | undefined | null
477
- ): event is ScheduledEvent {
478
- return Boolean(event && "cron" in event);
518
+ function isRequest(
519
+ event: ScheduledEvent | RequestEvent | AlarmEvent | undefined | null
520
+ ): event is RequestEvent {
521
+ return Boolean(event && "request" in event);
479
522
  }
480
523
 
481
524
  /**
@@ -559,6 +602,11 @@ const mockTailExpiration = new Date(3005, 1);
559
602
  */
560
603
  const mockEventTimestamp = 1645454470467;
561
604
 
605
+ /**
606
+ * Default value for event time ISO strings
607
+ */
608
+ const mockEventScheduledTime = new Date(mockEventTimestamp).toISOString();
609
+
562
610
  /**
563
611
  * Mock out the API hit during Tail deletion
564
612
  *
@@ -696,3 +744,9 @@ function generateMockScheduledEvent(
696
744
  scheduledTime: opts?.scheduledTime || mockEventTimestamp,
697
745
  };
698
746
  }
747
+
748
+ function generateMockAlarmEvent(opts?: Partial<AlarmEvent>): AlarmEvent {
749
+ return {
750
+ scheduledTime: opts?.scheduledTime || mockEventScheduledTime,
751
+ };
752
+ }
package/src/api/dev.ts CHANGED
@@ -8,6 +8,7 @@ interface DevOptions {
8
8
  env?: string;
9
9
  ip?: string;
10
10
  port?: number;
11
+ inspectorPort?: number;
11
12
  localProtocol?: "http" | "https";
12
13
  assets?: string;
13
14
  site?: string;
@@ -32,6 +33,11 @@ interface DevOptions {
32
33
  script_name?: string | undefined;
33
34
  environment?: string | undefined;
34
35
  }[];
36
+ r2?: {
37
+ binding: string;
38
+ bucket_name: string;
39
+ preview_bucket_name?: string;
40
+ }[];
35
41
  showInteractiveDevSession?: boolean;
36
42
  logLevel?: "none" | "error" | "log" | "warn" | "debug";
37
43
  logPrefix?: string;
package/src/bundle.ts CHANGED
@@ -192,8 +192,9 @@ export async function bundleWorker(
192
192
  format: entry.format === "modules" ? "esm" : "iife",
193
193
  target: "es2020",
194
194
  sourcemap: true,
195
- // The root included, as the sources are relative paths to tmpDir
196
- sourceRoot: entryDirectory,
195
+ // Include a reference to the output folder in the sourcemap.
196
+ // This is omitted by default, but we need it to properly resolve source paths in error output.
197
+ sourceRoot: destination,
197
198
  minify,
198
199
  metafile: true,
199
200
  conditions: ["worker", "browser"],
@@ -174,7 +174,7 @@ export interface DevConfig {
174
174
  /**
175
175
  * IP address for the local dev server to listen on,
176
176
  *
177
- * @default `localhost`
177
+ * @default `0.0.0.0`
178
178
  */
179
179
  ip: string;
180
180
 
@@ -357,7 +357,7 @@ function normalizeAndValidateDev(
357
357
  rawDev: RawDevConfig
358
358
  ): DevConfig {
359
359
  const {
360
- ip = "localhost",
360
+ ip = "0.0.0.0",
361
361
  port,
362
362
  inspector_port,
363
363
  local_protocol = "http",