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
@@ -1,5 +1,6 @@
1
1
  import fetchMock from "jest-fetch-mock";
2
2
  import {
3
+ fetchDashboardScript,
3
4
  fetchInternal,
4
5
  fetchKVGetValue,
5
6
  fetchR2Objects,
@@ -7,6 +8,7 @@ import {
7
8
  } from "../cfetch/internal";
8
9
  import { confirm, prompt } from "../dialogs";
9
10
  import {
11
+ mockFetchDashScript,
10
12
  mockFetchInternal,
11
13
  mockFetchKVGetValue,
12
14
  mockFetchR2Objects,
@@ -49,6 +51,7 @@ jest.mock("../cfetch/internal");
49
51
  "https://api.cloudflare.com/client/v4"
50
52
  );
51
53
  (fetchR2Objects as jest.Mock).mockImplementation(mockFetchR2Objects);
54
+ (fetchDashboardScript as jest.Mock).mockImplementation(mockFetchDashScript);
52
55
 
53
56
  jest.mock("../dialogs");
54
57
 
@@ -106,6 +109,10 @@ jest.mock("../user/generate-auth-url", () => {
106
109
  };
107
110
  });
108
111
 
112
+ jest.mock("../is-ci", () => {
113
+ return { CI: { isCI: jest.fn().mockImplementation(() => false) } };
114
+ });
115
+
109
116
  jest.mock("../user/generate-random-state", () => {
110
117
  return {
111
118
  generateRandomState: jest.fn().mockImplementation(() => "MOCK_STATE_PARAM"),
@@ -2,9 +2,9 @@ import { mkdirSync } from "node:fs";
2
2
  import fetchMock from "jest-fetch-mock";
3
3
  import { version as wranglerVersion } from "../../package.json";
4
4
  import { purgeConfigCaches, saveToConfigCache } from "../config-cache";
5
+ import { CI } from "../is-ci";
5
6
  import { logger } from "../logger";
6
7
  import { getMetricsDispatcher, getMetricsConfig } from "../metrics";
7
- import { CI } from "../metrics/is-ci";
8
8
  import {
9
9
  CURRENT_METRICS_DATE,
10
10
  readMetricsConfig,
@@ -97,6 +97,20 @@ describe("pages", () => {
97
97
  expect(std.out).toMatchInlineSnapshot(`
98
98
  "🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose
99
99
 
100
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
101
+ `);
102
+ });
103
+
104
+ it("should display for pages:functions:optimize-routes", async () => {
105
+ await expect(
106
+ runWrangler(
107
+ 'pages functions optimize-routes --routes-path="/build/_routes.json" --output-routes-path="/build/_optimized-routes.json"'
108
+ )
109
+ ).rejects.toThrowError();
110
+
111
+ expect(std.out).toMatchInlineSnapshot(`
112
+ "🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose
113
+
100
114
  If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
101
115
  `);
102
116
  });
@@ -82,10 +82,11 @@ describe("wrangler", () => {
82
82
  function mockCreateRequest(expectedBucketName: string) {
83
83
  const requests = { count: 0 };
84
84
  setMockResponse(
85
- "/accounts/:accountId/r2/buckets/:bucketName",
86
- "PUT",
87
- ([_url, accountId, bucketName]) => {
85
+ "/accounts/:accountId/r2/buckets",
86
+ "POST",
87
+ ([_url, accountId], { body }) => {
88
88
  expect(accountId).toEqual("some-account-id");
89
+ const bucketName = JSON.parse(body as string).name;
89
90
  expect(bucketName).toEqual(expectedBucketName);
90
91
  requests.count += 1;
91
92
  }
@@ -290,6 +291,24 @@ describe("wrangler", () => {
290
291
  Upload complete."
291
292
  `);
292
293
  });
294
+
295
+ it("should delete R2 object from bucket", async () => {
296
+ setMockFetchR2Objects({
297
+ accountId: "some-account-id",
298
+ bucketName: "bucketName-object-test",
299
+ objectName: "wormhole-img.png",
300
+ });
301
+
302
+ await runWrangler(
303
+ `r2 object delete bucketName-object-test/wormhole-img.png`
304
+ );
305
+
306
+ expect(std.out).toMatchInlineSnapshot(`
307
+ "Deleting object \\"wormhole-img.png\\" from bucket \\"bucketName-object-test\\".
308
+ Delete complete."
309
+ `);
310
+ });
311
+
293
312
  it("should not allow `--pipe` & `--file` to run together", async () => {
294
313
  fs.writeFileSync("wormhole-img.png", "passageway");
295
314
  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", () => {
@@ -113,7 +118,7 @@ describe("tail", () => {
113
118
  const api = mockWebsocketAPIs();
114
119
  await runWrangler("tail test-worker --status error");
115
120
  await expect(api.nextMessageJson()).resolves.toHaveProperty("filters", [
116
- { outcome: ["exception", "exceededCpu", "unknown"] },
121
+ { outcome: ["exception", "exceededCpu", "exceededMemory", "unknown"] },
117
122
  ]);
118
123
  });
119
124
 
@@ -121,7 +126,15 @@ describe("tail", () => {
121
126
  const api = mockWebsocketAPIs();
122
127
  await runWrangler("tail test-worker --status error --status canceled");
123
128
  await expect(api.nextMessageJson()).resolves.toHaveProperty("filters", [
124
- { outcome: ["exception", "exceededCpu", "unknown", "canceled"] },
129
+ {
130
+ outcome: [
131
+ "exception",
132
+ "exceededCpu",
133
+ "exceededMemory",
134
+ "unknown",
135
+ "canceled",
136
+ ],
137
+ },
125
138
  ]);
126
139
  });
127
140
 
@@ -208,7 +221,15 @@ describe("tail", () => {
208
221
  const expectedWebsocketMessage = {
209
222
  filters: [
210
223
  { sampling_rate },
211
- { outcome: ["ok", "exception", "exceededCpu", "unknown"] },
224
+ {
225
+ outcome: [
226
+ "ok",
227
+ "exception",
228
+ "exceededCpu",
229
+ "exceededMemory",
230
+ "unknown",
231
+ ],
232
+ },
212
233
  { method },
213
234
  { header: { key: "X-HELLO", query: "world" } },
214
235
  { client_ip },
@@ -251,6 +272,18 @@ describe("tail", () => {
251
272
  expect(std.out).toMatch(deserializeToJson(serializedMessage));
252
273
  });
253
274
 
275
+ it("logs alarm messages in json format", async () => {
276
+ const api = mockWebsocketAPIs();
277
+ await runWrangler("tail test-worker --format json");
278
+
279
+ const event = generateMockAlarmEvent();
280
+ const message = generateMockEventMessage({ event });
281
+ const serializedMessage = serialize(message);
282
+
283
+ api.ws.send(serializedMessage);
284
+ expect(std.out).toMatch(deserializeToJson(serializedMessage));
285
+ });
286
+
254
287
  it("logs request messages in pretty format", async () => {
255
288
  const api = mockWebsocketAPIs();
256
289
  await runWrangler("tail test-worker --format pretty");
@@ -271,10 +304,10 @@ describe("tail", () => {
271
304
  "[mock expiration date]"
272
305
  )
273
306
  ).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
- `);
307
+ "Successfully created tail, expires at [mock expiration date]
308
+ Connected to test-worker, waiting for logs...
309
+ GET https://example.org/ - Ok @ [mock event timestamp]"
310
+ `);
278
311
  });
279
312
 
280
313
  it("logs scheduled messages in pretty format", async () => {
@@ -297,35 +330,61 @@ describe("tail", () => {
297
330
  "[mock expiration date]"
298
331
  )
299
332
  ).toMatchInlineSnapshot(`
300
- "Successfully created tail, expires at [mock expiration date]
301
- Connected to test-worker, waiting for logs...
302
- \\"* * * * *\\" @ [mock timestamp string] - Ok"
303
- `);
333
+ "Successfully created tail, expires at [mock expiration date]
334
+ Connected to test-worker, waiting for logs...
335
+ \\"* * * * *\\" @ [mock timestamp string] - Ok"
336
+ `);
304
337
  });
305
338
 
306
- it("should not crash when the tail message has a void event", async () => {
339
+ it("logs alarm messages in pretty format", async () => {
307
340
  const api = mockWebsocketAPIs();
308
341
  await runWrangler("tail test-worker --format pretty");
309
342
 
310
- const message = generateMockEventMessage({ event: null });
343
+ const event = generateMockAlarmEvent();
344
+ const message = generateMockEventMessage({ event });
311
345
  const serializedMessage = serialize(message);
312
346
 
313
347
  api.ws.send(serializedMessage);
314
348
  expect(
315
349
  std.out
316
350
  .replace(
317
- new Date(mockEventTimestamp).toLocaleString(),
318
- "[mock timestamp string]"
351
+ new Date(mockEventScheduledTime).toLocaleString(),
352
+ "[mock scheduled time]"
319
353
  )
320
354
  .replace(
321
355
  mockTailExpiration.toLocaleString(),
322
356
  "[mock expiration date]"
323
357
  )
324
358
  ).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
- `);
359
+ "Successfully created tail, expires at [mock expiration date]
360
+ Connected to test-worker, waiting for logs...
361
+ Alarm @ [mock scheduled time] - Ok"
362
+ `);
363
+ });
364
+
365
+ it("should not crash when the tail message has a void event", async () => {
366
+ const api = mockWebsocketAPIs();
367
+ await runWrangler("tail test-worker --format pretty");
368
+
369
+ const message = generateMockEventMessage({ event: null });
370
+ const serializedMessage = serialize(message);
371
+
372
+ api.ws.send(serializedMessage);
373
+ expect(
374
+ std.out
375
+ .replace(
376
+ mockTailExpiration.toLocaleString(),
377
+ "[mock expiration date]"
378
+ )
379
+ .replace(
380
+ new Date(mockEventTimestamp).toLocaleString(),
381
+ "[mock timestamp string]"
382
+ )
383
+ ).toMatchInlineSnapshot(`
384
+ "Successfully created tail, expires at [mock expiration date]
385
+ Connected to test-worker, waiting for logs...
386
+ Unknown Event - Ok @ [mock timestamp string]"
387
+ `);
329
388
  });
330
389
 
331
390
  it("defaults to logging in pretty format when the output is a TTY", async () => {
@@ -349,10 +408,10 @@ describe("tail", () => {
349
408
  "[mock expiration date]"
350
409
  )
351
410
  ).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
- `);
411
+ "Successfully created tail, expires at [mock expiration date]
412
+ Connected to test-worker, waiting for logs...
413
+ GET https://example.org/ - Ok @ [mock event timestamp]"
414
+ `);
356
415
  });
357
416
 
358
417
  it("defaults to logging in json format when the output is not a TTY", async () => {
@@ -405,21 +464,21 @@ describe("tail", () => {
405
464
  "[mock expiration date]"
406
465
  )
407
466
  ).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
- `);
467
+ "Successfully created tail, expires at [mock expiration date]
468
+ Connected to test-worker, waiting for logs...
469
+ GET https://example.org/ - Ok @ [mock event timestamp]
470
+ (log) some string
471
+ (log) { complex: 'object' }
472
+ (error) 1234"
473
+ `);
415
474
  expect(std.err).toMatchInlineSnapshot(`
416
- "X [ERROR]  Error: some error
475
+ "X [ERROR]  Error: some error
417
476
 
418
477
 
419
- X [ERROR]  Error: { complex: 'error' }
478
+ X [ERROR]  Error: { complex: 'error' }
420
479
 
421
- "
422
- `);
480
+ "
481
+ `);
423
482
  expect(std.warn).toMatchInlineSnapshot(`""`);
424
483
  });
425
484
  });
@@ -437,8 +496,8 @@ describe("tail", () => {
437
496
  * @returns the same type we expect when deserializing in wrangler
438
497
  */
439
498
  function serialize(message: TailEventMessage): WebSocket.RawData {
440
- if (isScheduled(message.event)) {
441
- // `ScheduledEvent`s work just fine
499
+ if (!isRequest(message.event)) {
500
+ // `ScheduledEvent`s and `TailEvent`s work just fine
442
501
  const stringified = JSON.stringify(message);
443
502
  return Buffer.from(stringified, "utf-8");
444
503
  } else {
@@ -470,12 +529,12 @@ function serialize(message: TailEventMessage): WebSocket.RawData {
470
529
  * Small helper to disambiguate the event types possible in a `TailEventMessage`
471
530
  *
472
531
  * @param event A TailEvent
473
- * @returns whether event is a ScheduledEvent (true) or a RequestEvent
532
+ * @returns true if `event` is a RequestEvent
474
533
  */
475
- function isScheduled(
476
- event: ScheduledEvent | RequestEvent | undefined | null
477
- ): event is ScheduledEvent {
478
- return Boolean(event && "cron" in event);
534
+ function isRequest(
535
+ event: ScheduledEvent | RequestEvent | AlarmEvent | undefined | null
536
+ ): event is RequestEvent {
537
+ return Boolean(event && "request" in event);
479
538
  }
480
539
 
481
540
  /**
@@ -559,6 +618,11 @@ const mockTailExpiration = new Date(3005, 1);
559
618
  */
560
619
  const mockEventTimestamp = 1645454470467;
561
620
 
621
+ /**
622
+ * Default value for event time ISO strings
623
+ */
624
+ const mockEventScheduledTime = new Date(mockEventTimestamp).toISOString();
625
+
562
626
  /**
563
627
  * Mock out the API hit during Tail deletion
564
628
  *
@@ -696,3 +760,9 @@ function generateMockScheduledEvent(
696
760
  scheduledTime: opts?.scheduledTime || mockEventTimestamp,
697
761
  };
698
762
  }
763
+
764
+ function generateMockAlarmEvent(opts?: Partial<AlarmEvent>): AlarmEvent {
765
+ return {
766
+ scheduledTime: opts?.scheduledTime || mockEventScheduledTime,
767
+ };
768
+ }
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import fetchMock from "jest-fetch-mock";
4
4
  import { getGlobalWranglerConfigPath } from "../global-wrangler-config-path";
5
+ import { CI } from "../is-ci";
5
6
  import {
6
7
  loginOrRefreshIfRequired,
7
8
  readAuthConfigFile,
@@ -18,6 +19,7 @@ import type { Config } from "../config";
18
19
  import type { UserAuthConfig } from "../user";
19
20
 
20
21
  describe("User", () => {
22
+ let isCISpy: jest.SpyInstance;
21
23
  runInTempDir();
22
24
  const std = mockConsoleMethods();
23
25
  const {
@@ -30,6 +32,10 @@ describe("User", () => {
30
32
 
31
33
  const { setIsTTY } = useMockIsTTY();
32
34
 
35
+ beforeEach(() => {
36
+ isCISpy = jest.spyOn(CI, "isCI").mockReturnValue(false);
37
+ });
38
+
33
39
  describe("login", () => {
34
40
  it("should login a user when `wrangler login` is run", async () => {
35
41
  mockOAuthServerCallback();
@@ -132,6 +138,11 @@ describe("User", () => {
132
138
  expect(std.err).toContain("");
133
139
  });
134
140
 
141
+ it("should revert to non-interactive mode if in CI", async () => {
142
+ isCISpy.mockReturnValue(true);
143
+ await expect(loginOrRefreshIfRequired()).resolves.toEqual(false);
144
+ });
145
+
135
146
  it("should revert to non-interactive mode if isTTY throws an error", async () => {
136
147
  setIsTTY({
137
148
  stdin() {
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;
@@ -15,6 +16,7 @@ interface DevOptions {
15
16
  siteExclude?: string[];
16
17
  nodeCompat?: boolean;
17
18
  compatibilityDate?: string;
19
+ compatibilityFlags?: string[];
18
20
  experimentalEnableLocalPersistence?: boolean;
19
21
  liveReload?: boolean;
20
22
  watch?: boolean;
@@ -32,6 +34,11 @@ interface DevOptions {
32
34
  script_name?: string | undefined;
33
35
  environment?: string | undefined;
34
36
  }[];
37
+ r2?: {
38
+ binding: string;
39
+ bucket_name: string;
40
+ preview_bucket_name?: string;
41
+ }[];
35
42
  showInteractiveDevSession?: boolean;
36
43
  logLevel?: "none" | "error" | "log" | "warn" | "debug";
37
44
  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"],
@@ -2,6 +2,7 @@ import assert from "node:assert";
2
2
  import { fetch, Headers } from "undici";
3
3
  import { version as wranglerVersion } from "../../package.json";
4
4
  import { getEnvironmentVariableFactory } from "../environment-variables";
5
+ import { logger } from "../logger";
5
6
  import { ParseError, parseJSON } from "../parse";
6
7
  import { loginOrRefreshIfRequired, requireApiToken } from "../user";
7
8
  import type { ApiCredentials } from "../user";
@@ -44,6 +45,13 @@ export async function fetchInternal<ResponseType>(
44
45
 
45
46
  const queryString = queryParams ? `?${queryParams.toString()}` : "";
46
47
  const method = init.method ?? "GET";
48
+
49
+ logger.debug(
50
+ `-- START CF API REQUEST: ${method} ${getCloudflareAPIBaseURL()}${resource}${queryString}`
51
+ );
52
+ logger.debug("HEADERS:", JSON.stringify(headers, null, 2));
53
+ logger.debug("INIT:", JSON.stringify(init, null, 2));
54
+ logger.debug("-- END CF API REQUEST");
47
55
  const response = await fetch(
48
56
  `${getCloudflareAPIBaseURL()}${resource}${queryString}`,
49
57
  {
@@ -54,6 +62,15 @@ export async function fetchInternal<ResponseType>(
54
62
  }
55
63
  );
56
64
  const jsonText = await response.text();
65
+ logger.debug(
66
+ "-- START CF API RESPONSE:",
67
+ response.statusText,
68
+ response.status
69
+ );
70
+ logger.debug("HEADERS:", JSON.stringify(response.headers, null, 2));
71
+ logger.debug("RESPONSE:", jsonText);
72
+ logger.debug("-- END CF API RESPONSE");
73
+
57
74
  try {
58
75
  return parseJSON<ResponseType>(jsonText);
59
76
  } catch (err) {
@@ -179,3 +196,42 @@ export async function fetchR2Objects(
179
196
  );
180
197
  }
181
198
  }
199
+
200
+ /**
201
+ * This is a wrapper STOPGAP for getting the script which returns a raw text response.
202
+ */
203
+ export async function fetchDashboardScript(
204
+ resource: string,
205
+ bodyInit: RequestInit = {}
206
+ ): Promise<string> {
207
+ await requireLoggedIn();
208
+ const auth = requireApiToken();
209
+ const headers = cloneHeaders(bodyInit.headers);
210
+ addAuthorizationHeaderIfUnspecified(headers, auth);
211
+ addUserAgent(headers);
212
+
213
+ const response = await fetch(`${getCloudflareAPIBaseURL()}${resource}`, {
214
+ ...bodyInit,
215
+ headers,
216
+ });
217
+
218
+ if (!response.ok || !response.body) {
219
+ throw new Error(
220
+ `Failed to fetch ${resource} - ${response.status}: ${response.statusText});`
221
+ );
222
+ }
223
+
224
+ const usesModules = response.headers
225
+ .get("content-type")
226
+ ?.startsWith("multipart");
227
+
228
+ if (usesModules) {
229
+ const file = await response.text();
230
+
231
+ // Follow up on issue in Undici about multipart/form-data support & replace the workaround: https://github.com/nodejs/undici/issues/974
232
+ // This should be using a builtin formData() parser pattern.
233
+ return file.split("\n").slice(4, -4).join("\n");
234
+ } else {
235
+ return response.text();
236
+ }
237
+ }
@@ -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
 
@@ -383,7 +383,7 @@ export const validateRequiredProperty = (
383
383
  container: string,
384
384
  key: string,
385
385
  value: unknown,
386
- type: string,
386
+ type: TypeofType,
387
387
  choices?: unknown[]
388
388
  ): boolean => {
389
389
  if (container) {
@@ -417,7 +417,7 @@ export const validateOptionalProperty = (
417
417
  container: string,
418
418
  key: string,
419
419
  value: unknown,
420
- type: string,
420
+ type: TypeofType,
421
421
  choices?: unknown[]
422
422
  ): boolean => {
423
423
  if (value !== undefined) {
@@ -440,7 +440,7 @@ export const validateTypedArray = (
440
440
  diagnostics: Diagnostics,
441
441
  container: string,
442
442
  value: unknown,
443
- type: string
443
+ type: TypeofType
444
444
  ): boolean => {
445
445
  let isValid = true;
446
446
  if (!Array.isArray(value)) {
@@ -472,7 +472,7 @@ export const validateOptionalTypedArray = (
472
472
  diagnostics: Diagnostics,
473
473
  container: string,
474
474
  value: unknown,
475
- type: string
475
+ type: TypeofType
476
476
  ) => {
477
477
  if (value !== undefined) {
478
478
  return validateTypedArray(diagnostics, container, value, type);
@@ -486,7 +486,7 @@ export const validateOptionalTypedArray = (
486
486
  export const isRequiredProperty = <T extends object>(
487
487
  obj: object,
488
488
  prop: keyof T,
489
- type: string,
489
+ type: TypeofType,
490
490
  choices?: unknown[]
491
491
  ): obj is T =>
492
492
  hasProperty<T>(obj, prop) &&
@@ -499,7 +499,7 @@ export const isRequiredProperty = <T extends object>(
499
499
  export const isOptionalProperty = <T extends object>(
500
500
  obj: object,
501
501
  prop: keyof T,
502
- type: string
502
+ type: TypeofType
503
503
  ): obj is T => !hasProperty<T>(obj, prop) || typeof obj[prop] === type;
504
504
 
505
505
  /**
@@ -582,3 +582,16 @@ const isRecord = (
582
582
  value: unknown
583
583
  ): value is Record<string | number | symbol, unknown> =>
584
584
  typeof value === "object" && value !== null && !Array.isArray(value);
585
+
586
+ /**
587
+ * JavaScript `typeof` operator return values.
588
+ */
589
+ type TypeofType =
590
+ | "string"
591
+ | "number"
592
+ | "bigint"
593
+ | "boolean"
594
+ | "symbol"
595
+ | "undefined"
596
+ | "object"
597
+ | "function";
@@ -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",
@@ -1630,7 +1630,10 @@ const validateKVBinding: ValidatorFn = (diagnostics, field, value) => {
1630
1630
  );
1631
1631
  isValid = false;
1632
1632
  }
1633
- if (!isRequiredProperty(value, "id", "string")) {
1633
+ if (
1634
+ !isRequiredProperty(value, "id", "string") ||
1635
+ (value as { id: string }).id.length === 0
1636
+ ) {
1634
1637
  diagnostics.errors.push(
1635
1638
  `"${field}" bindings should have a string "id" field but got ${JSON.stringify(
1636
1639
  value
@@ -1668,7 +1671,10 @@ const validateR2Binding: ValidatorFn = (diagnostics, field, value) => {
1668
1671
  );
1669
1672
  isValid = false;
1670
1673
  }
1671
- if (!isRequiredProperty(value, "bucket_name", "string")) {
1674
+ if (
1675
+ !isRequiredProperty(value, "bucket_name", "string") ||
1676
+ (value as { bucket_name: string }).bucket_name.length === 0
1677
+ ) {
1672
1678
  diagnostics.errors.push(
1673
1679
  `"${field}" bindings should have a string "bucket_name" field but got ${JSON.stringify(
1674
1680
  value
@@ -1,6 +1,7 @@
1
1
  import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
2
2
  import * as path from "path";
3
3
  import { findUpSync } from "find-up";
4
+ import { CI } from "./is-ci";
4
5
  import isInteractive from "./is-interactive";
5
6
  import { logger } from "./logger";
6
7
 
@@ -29,7 +30,7 @@ const arrayFormatter = new Intl.ListFormat("en", {
29
30
  });
30
31
 
31
32
  function showCacheMessage(fields: string[], folder: string) {
32
- if (!cacheMessageShown && isInteractive()) {
33
+ if (!cacheMessageShown && isInteractive() && !CI.isCI()) {
33
34
  if (fields.length > 0) {
34
35
  logger.log(
35
36
  `Retrieving cached values for ${arrayFormatter.format(