wrangler 2.0.24 → 2.0.27

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 (63) hide show
  1. package/miniflare-dist/index.mjs +142 -16
  2. package/package.json +3 -3
  3. package/src/__tests__/configuration.test.ts +7 -3
  4. package/src/__tests__/dev.test.tsx +26 -4
  5. package/src/__tests__/generate.test.ts +2 -4
  6. package/src/__tests__/helpers/mock-cfetch.ts +35 -2
  7. package/src/__tests__/init.test.ts +537 -359
  8. package/src/__tests__/jest.setup.ts +7 -0
  9. package/src/__tests__/metrics.test.ts +1 -1
  10. package/src/__tests__/pages.test.ts +14 -0
  11. package/src/__tests__/r2.test.ts +22 -3
  12. package/src/__tests__/tail.test.ts +112 -42
  13. package/src/__tests__/user.test.ts +11 -0
  14. package/src/api/dev.ts +7 -0
  15. package/src/bundle.ts +3 -2
  16. package/src/cfetch/internal.ts +56 -0
  17. package/src/config/config.ts +1 -1
  18. package/src/config/validation-helpers.ts +19 -6
  19. package/src/config/validation.ts +9 -3
  20. package/src/config-cache.ts +2 -1
  21. package/src/dev/dev.tsx +16 -2
  22. package/src/dev/local.tsx +69 -5
  23. package/src/dev/use-esbuild.ts +3 -0
  24. package/src/dev-registry.tsx +3 -0
  25. package/src/dev.tsx +28 -19
  26. package/src/generate.ts +1 -1
  27. package/src/index.tsx +51 -21
  28. package/src/init.ts +111 -38
  29. package/src/inspect.ts +1 -4
  30. package/src/{metrics/is-ci.ts → is-ci.ts} +0 -0
  31. package/src/metrics/metrics-config.ts +1 -1
  32. package/src/miniflare-cli/assets.ts +27 -16
  33. package/src/miniflare-cli/index.ts +124 -2
  34. package/src/pages/build.tsx +75 -41
  35. package/src/pages/constants.ts +4 -0
  36. package/src/pages/deployments.tsx +9 -9
  37. package/src/pages/dev.tsx +178 -64
  38. package/src/pages/errors.ts +22 -0
  39. package/src/pages/functions/buildPlugin.ts +4 -0
  40. package/src/pages/functions/buildWorker.ts +4 -0
  41. package/src/pages/functions/routes-consolidation.test.ts +250 -0
  42. package/src/pages/functions/routes-consolidation.ts +73 -0
  43. package/src/pages/functions/routes-transformation.test.ts +271 -0
  44. package/src/pages/functions/routes-transformation.ts +122 -0
  45. package/src/pages/functions.tsx +96 -0
  46. package/src/pages/index.tsx +65 -55
  47. package/src/pages/projects.tsx +9 -3
  48. package/src/pages/publish.tsx +75 -22
  49. package/src/pages/types.ts +9 -0
  50. package/src/pages/upload.tsx +6 -8
  51. package/src/proxy.ts +10 -0
  52. package/src/r2.ts +17 -4
  53. package/src/tail/filters.ts +3 -1
  54. package/src/tail/index.ts +15 -2
  55. package/src/tail/printing.ts +43 -3
  56. package/src/user/user.tsx +6 -4
  57. package/src/whoami.tsx +5 -5
  58. package/templates/pages-template-plugin.ts +16 -4
  59. package/templates/pages-template-worker.ts +16 -5
  60. package/templates/service-bindings-module-facade.js +10 -7
  61. package/templates/service-bindings-sw-facade.js +10 -7
  62. package/wrangler-dist/cli.d.ts +7 -0
  63. package/wrangler-dist/cli.js +1681 -1091
@@ -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,19 @@ async function generateAssetsFetch(directory, log) {
5252
5276
  );
5253
5277
  return deconstructedResponse;
5254
5278
  }
5255
- } else if (hasFileExtension(url.pathname)) {
5279
+ } else if (hasFileExtension(assetName)) {
5280
+ if (asset = getAsset(assetName + ".html")) {
5281
+ deconstructedResponse.body = serveAsset(asset);
5282
+ deconstructedResponse.headers.set(
5283
+ "Content-Type",
5284
+ (0, import_mime.getType)(asset) || "application/octet-stream"
5285
+ );
5286
+ return deconstructedResponse;
5287
+ }
5256
5288
  notFound();
5257
5289
  return deconstructedResponse;
5258
5290
  }
5259
- if (asset = getAsset(`${url.pathname}.html`)) {
5291
+ if (asset = getAsset(`${assetName}.html`)) {
5260
5292
  deconstructedResponse.body = serveAsset(asset);
5261
5293
  deconstructedResponse.headers.set(
5262
5294
  "Content-Type",
@@ -5264,11 +5296,11 @@ async function generateAssetsFetch(directory, log) {
5264
5296
  );
5265
5297
  return deconstructedResponse;
5266
5298
  }
5267
- if (asset = getAsset(`${url.pathname}/index.html`)) {
5299
+ if (asset = getAsset(`${assetName}/index.html`)) {
5268
5300
  deconstructedResponse.status = 301;
5269
5301
  deconstructedResponse.headers.set(
5270
5302
  "Location",
5271
- `${url.pathname}/${url.search}`
5303
+ `${assetName}/${url.search}`
5272
5304
  );
5273
5305
  return deconstructedResponse;
5274
5306
  } else {
@@ -5369,7 +5401,44 @@ async function main() {
5369
5401
  if (logLevel > LogLevel.INFO) {
5370
5402
  console.log("OPTIONS:\n", JSON.stringify(config, null, 2));
5371
5403
  }
5404
+ config.bindings = {
5405
+ ...config.bindings,
5406
+ ...Object.fromEntries(
5407
+ Object.entries(
5408
+ config.externalDurableObjects
5409
+ ).map(([binding, { name, host, port }]) => {
5410
+ const factory = () => {
5411
+ throw new FatalError(
5412
+ "An external Durable Object instance's state has somehow been attempted to be accessed.",
5413
+ 1
5414
+ );
5415
+ };
5416
+ const namespace = new DurableObjectNamespace(name, factory);
5417
+ namespace.get = (id) => {
5418
+ const stub = new DurableObjectStub(factory, id);
5419
+ stub.fetch = (...reqArgs) => {
5420
+ const requestFromArgs = new MiniflareRequest(...reqArgs);
5421
+ const url = new URL(requestFromArgs.url);
5422
+ url.host = host;
5423
+ if (port !== void 0)
5424
+ url.port = port.toString();
5425
+ const request = new MiniflareRequest(
5426
+ url.toString(),
5427
+ requestFromArgs
5428
+ );
5429
+ request.headers.set("x-miniflare-durable-object-name", name);
5430
+ request.headers.set("x-miniflare-durable-object-id", id.toString());
5431
+ return fetch(request);
5432
+ };
5433
+ return stub;
5434
+ };
5435
+ return [binding, namespace];
5436
+ })
5437
+ )
5438
+ };
5372
5439
  let mf;
5440
+ let durableObjectsMf = void 0;
5441
+ let durableObjectsMfPort = void 0;
5373
5442
  try {
5374
5443
  if (args._[1]) {
5375
5444
  const opts = JSON.parse(
@@ -5393,11 +5462,68 @@ async function main() {
5393
5462
  mf = new Miniflare(config);
5394
5463
  await mf.startServer();
5395
5464
  await mf.startScheduler();
5396
- process.send && process.send("ready");
5465
+ const internalDurableObjectClassNames = Object.values(
5466
+ config.durableObjects
5467
+ );
5468
+ if (internalDurableObjectClassNames.length > 0) {
5469
+ durableObjectsMf = new Miniflare({
5470
+ host: config.host,
5471
+ port: 0,
5472
+ script: `
5473
+ export default {
5474
+ fetch(request, env) {
5475
+ return env.DO.fetch(request)
5476
+ }
5477
+ }`,
5478
+ serviceBindings: {
5479
+ DO: async (request) => {
5480
+ request = new MiniflareRequest(request);
5481
+ const name = request.headers.get("x-miniflare-durable-object-name");
5482
+ const idString = request.headers.get(
5483
+ "x-miniflare-durable-object-id"
5484
+ );
5485
+ request.headers.delete("x-miniflare-durable-object-name");
5486
+ request.headers.delete("x-miniflare-durable-object-id");
5487
+ if (!name || !idString) {
5488
+ return new MiniflareResponse(
5489
+ "[durable-object-proxy-err] Missing `x-miniflare-durable-object-name` or `x-miniflare-durable-object-id` headers.",
5490
+ { status: 400 }
5491
+ );
5492
+ }
5493
+ const namespace = await mf?.getDurableObjectNamespace(name);
5494
+ const id = namespace?.idFromString(idString);
5495
+ if (!id) {
5496
+ return new MiniflareResponse(
5497
+ "[durable-object-proxy-err] Could not generate an ID. Possibly due to a mismatched DO name and ID?",
5498
+ { status: 500 }
5499
+ );
5500
+ }
5501
+ const stub = namespace?.get(id);
5502
+ if (!stub) {
5503
+ return new MiniflareResponse(
5504
+ "[durable-object-proxy-err] Could not generate a stub. Possibly due to a mismatched DO name and ID?",
5505
+ { status: 500 }
5506
+ );
5507
+ }
5508
+ return stub.fetch(request);
5509
+ }
5510
+ },
5511
+ modules: true
5512
+ });
5513
+ const server = await durableObjectsMf.startServer();
5514
+ durableObjectsMfPort = server.address().port;
5515
+ }
5516
+ process.send && process.send(
5517
+ JSON.stringify({
5518
+ ready: true,
5519
+ durableObjectsPort: durableObjectsMfPort
5520
+ })
5521
+ );
5397
5522
  } catch (e) {
5398
5523
  mf?.log.error(e);
5399
5524
  process.exitCode = 1;
5400
5525
  await mf?.dispose();
5526
+ await durableObjectsMf?.dispose();
5401
5527
  }
5402
5528
  }
5403
5529
  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.27",
4
4
  "description": "Command-line interface for all things Cloudflare Workers",
5
5
  "keywords": [
6
6
  "wrangler",
@@ -52,11 +52,11 @@
52
52
  "build": "npm run clean && npm run bundle && npm run emit-types",
53
53
  "bundle": "node -r esbuild-register scripts/bundle.ts",
54
54
  "check:type": "tsc",
55
- "clean": "rm -rf wrangler-dist miniflare-dist emitted-types",
55
+ "clean": "rimraf wrangler-dist miniflare-dist emitted-types",
56
56
  "dev": "npm run clean && concurrently -c black,blue --kill-others-on-fail false 'npm run bundle -- --watch' 'npm run check:type -- --watch --preserveWatchOutput'",
57
57
  "emit-types": "tsc -p tsconfig.emit.json && node -r esbuild-register scripts/emit-types.ts",
58
58
  "prepublishOnly": "SOURCEMAPS=false npm run build",
59
- "start": "npm run bundle && NODE_OPTIONS=--enable-source-maps ./bin/wrangler.js",
59
+ "start": "npm run bundle && cross-env NODE_OPTIONS=--enable-source-maps ./bin/wrangler.js",
60
60
  "test": "jest --silent=false --verbose=true",
61
61
  "test-watch": "npm run test -- --runInBand --testTimeout=50000 --watch",
62
62
  "test:ci": "npm run test -- --verbose=true --coverage"
@@ -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",
@@ -1635,6 +1635,7 @@ describe("normalizeAndValidateConfig()", () => {
1635
1635
  id: "KV_ID_2",
1636
1636
  preview_id: 2222,
1637
1637
  },
1638
+ { binding: "VALID", id: "" },
1638
1639
  ],
1639
1640
  } as unknown as RawConfig,
1640
1641
  undefined,
@@ -1654,7 +1655,8 @@ describe("normalizeAndValidateConfig()", () => {
1654
1655
  - \\"kv_namespaces[1]\\" bindings should have a string \\"id\\" field but got {\\"binding\\":\\"VALID\\"}.
1655
1656
  - \\"kv_namespaces[2]\\" bindings should have a string \\"binding\\" field but got {\\"binding\\":2000,\\"id\\":2111}.
1656
1657
  - \\"kv_namespaces[2]\\" bindings should have a string \\"id\\" field but got {\\"binding\\":2000,\\"id\\":2111}.
1657
- - \\"kv_namespaces[3]\\" bindings should, optionally, have a string \\"preview_id\\" field but got {\\"binding\\":\\"KV_BINDING_2\\",\\"id\\":\\"KV_ID_2\\",\\"preview_id\\":2222}."
1658
+ - \\"kv_namespaces[3]\\" bindings should, optionally, have a string \\"preview_id\\" field but got {\\"binding\\":\\"KV_BINDING_2\\",\\"id\\":\\"KV_ID_2\\",\\"preview_id\\":2222}.
1659
+ - \\"kv_namespaces[4]\\" bindings should have a string \\"id\\" field but got {\\"binding\\":\\"VALID\\",\\"id\\":\\"\\"}."
1658
1660
  `);
1659
1661
  });
1660
1662
  });
@@ -1740,6 +1742,7 @@ describe("normalizeAndValidateConfig()", () => {
1740
1742
  bucket_name: "R2_BUCKET_2",
1741
1743
  preview_bucket_name: 2555,
1742
1744
  },
1745
+ { binding: "R2_BINDING_1", bucket_name: "" },
1743
1746
  ],
1744
1747
  } as unknown as RawConfig,
1745
1748
  undefined,
@@ -1759,7 +1762,8 @@ describe("normalizeAndValidateConfig()", () => {
1759
1762
  - \\"r2_buckets[1]\\" bindings should have a string \\"bucket_name\\" field but got {\\"binding\\":\\"R2_BINDING_1\\"}.
1760
1763
  - \\"r2_buckets[2]\\" bindings should have a string \\"binding\\" field but got {\\"binding\\":2333,\\"bucket_name\\":2444}.
1761
1764
  - \\"r2_buckets[2]\\" bindings should have a string \\"bucket_name\\" field but got {\\"binding\\":2333,\\"bucket_name\\":2444}.
1762
- - \\"r2_buckets[3]\\" bindings should, optionally, have a string \\"preview_bucket_name\\" field but got {\\"binding\\":\\"R2_BINDING_2\\",\\"bucket_name\\":\\"R2_BUCKET_2\\",\\"preview_bucket_name\\":2555}."
1765
+ - \\"r2_buckets[3]\\" bindings should, optionally, have a string \\"preview_bucket_name\\" field but got {\\"binding\\":\\"R2_BINDING_2\\",\\"bucket_name\\":\\"R2_BUCKET_2\\",\\"preview_bucket_name\\":2555}.
1766
+ - \\"r2_buckets[4]\\" bindings should have a string \\"bucket_name\\" field but got {\\"binding\\":\\"R2_BINDING_1\\",\\"bucket_name\\":\\"\\"}."
1763
1767
  `);
1764
1768
  });
1765
1769
  });
@@ -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]
@@ -1,6 +1,4 @@
1
1
  import fs from "node:fs";
2
- import path from "node:path";
3
- import process from "node:process";
4
2
  import { setup } from "create-cloudflare";
5
3
  import { mockConsoleMethods } from "./helpers/mock-console";
6
4
  import { mockConfirm, clearConfirmMocks } from "./helpers/mock-dialogs";
@@ -73,7 +71,7 @@ describe("generate", () => {
73
71
  ).resolves.toBeUndefined();
74
72
 
75
73
  expect(createCloudflareMock).lastCalledWith(
76
- path.resolve(process.cwd(), "my-worker-1"),
74
+ "my-worker-1",
77
75
  "some-template",
78
76
  { debug: false, force: false, init: true }
79
77
  );
@@ -85,7 +83,7 @@ describe("generate", () => {
85
83
  ).resolves.toBeUndefined();
86
84
 
87
85
  expect(createCloudflareMock).lastCalledWith(
88
- path.resolve(process.cwd(), "my-worker-2"),
86
+ "my-worker-2",
89
87
  "some-template",
90
88
  { debug: false, force: false, init: true }
91
89
  );
@@ -183,6 +183,7 @@ export function unsetAllMocks() {
183
183
 
184
184
  const kvGetMocks = new Map<string, string | Buffer>();
185
185
  const r2GetMocks = new Map<string, string | undefined>();
186
+ const dashScriptMocks = new Map<string, string | undefined>();
186
187
 
187
188
  /**
188
189
  * @mocked typeof fetchKVGetValue
@@ -217,7 +218,7 @@ export async function mockFetchR2Objects(
217
218
  bodyInit: {
218
219
  body: BodyInit | Readable;
219
220
  headers: HeadersInit | undefined;
220
- method: "PUT" | "GET";
221
+ method: "PUT" | "GET" | "DELETE";
221
222
  }
222
223
  ): Promise<Response> {
223
224
  /**
@@ -234,7 +235,7 @@ export async function mockFetchR2Objects(
234
235
 
235
236
  return new Response(value);
236
237
  }
237
- throw new Error(`no expected mock found for \`r2 object get\` - ${resource}`);
238
+ throw new Error(`no mock found for \`r2 object\` - ${resource}`);
238
239
  }
239
240
 
240
241
  /**
@@ -260,4 +261,36 @@ export function setMockFetchR2Objects({
260
261
  export function unsetSpecialMockFns() {
261
262
  kvGetMocks.clear();
262
263
  r2GetMocks.clear();
264
+ dashScriptMocks.clear();
265
+ }
266
+
267
+ /**
268
+ * @mocked typeof fetchDashScript
269
+ * multipart/form-data is the response for modules and raw text for the Script endpoint.
270
+ */
271
+ export async function mockFetchDashScript(resource: string): Promise<string> {
272
+ if (dashScriptMocks.has(resource)) {
273
+ const value = dashScriptMocks.get(resource) ?? "";
274
+
275
+ return value;
276
+ }
277
+ throw new Error(`no mock found for \`init from-dash\` - ${resource}`);
278
+ }
279
+
280
+ /**
281
+ * Mock setter for usage within test blocks, companion helper to `mockFetchDashScript`
282
+ */
283
+ export function setMockFetchDashScript({
284
+ accountId,
285
+ fromDashScriptName,
286
+ mockResponse,
287
+ }: {
288
+ accountId: string;
289
+ fromDashScriptName: string;
290
+ mockResponse?: string;
291
+ }) {
292
+ dashScriptMocks.set(
293
+ `/accounts/${accountId}/workers/scripts/${fromDashScriptName}`,
294
+ mockResponse
295
+ );
263
296
  }