wrangler 2.0.6 → 2.0.7

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.
@@ -1,11 +1,35 @@
1
1
  import { mkdirSync, writeFileSync } from "node:fs";
2
2
  import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
3
- import { setMockResponse, unsetAllMocks } from "./helpers/mock-cfetch";
3
+ import {
4
+ createFetchResult,
5
+ setMockRawResponse,
6
+ setMockResponse,
7
+ unsetAllMocks,
8
+ } from "./helpers/mock-cfetch";
4
9
  import { mockConsoleMethods } from "./helpers/mock-console";
5
10
  import { runInTempDir } from "./helpers/run-in-tmp";
6
11
  import { runWrangler } from "./helpers/run-wrangler";
7
- import type { Project, Deployment } from "../pages";
8
- import type { File, FormData } from "undici";
12
+ import type { Deployment, Project, UploadPayloadFile } from "../pages";
13
+ import type { FormData, RequestInit } from "undici";
14
+
15
+ // Asserting within mock responses get swallowed, so run them out-of-band
16
+ const outOfBandTests: (() => void)[] = [];
17
+ function assertLater(fn: () => void) {
18
+ outOfBandTests.push(fn);
19
+ }
20
+
21
+ function mockGetToken(jwt: string) {
22
+ setMockResponse(
23
+ "/accounts/:accountId/pages/projects/foo/upload-token",
24
+ async ([_url, accountId]) => {
25
+ assertLater(() => {
26
+ expect(accountId).toEqual("some-account-id");
27
+ });
28
+
29
+ return { jwt };
30
+ }
31
+ );
32
+ }
9
33
 
10
34
  describe("pages", () => {
11
35
  runInTempDir();
@@ -13,6 +37,12 @@ describe("pages", () => {
13
37
  function endEventLoop() {
14
38
  return new Promise((resolve) => setImmediate(resolve));
15
39
  }
40
+ beforeEach(() => {
41
+ outOfBandTests.length = 0;
42
+ });
43
+ afterEach(() => {
44
+ outOfBandTests.forEach((fn) => fn());
45
+ });
16
46
 
17
47
  it("should should display a list of available subcommands, for pages with no subcommand", async () => {
18
48
  await runWrangler("pages");
@@ -78,12 +108,16 @@ describe("pages", () => {
78
108
  "/accounts/:accountId/pages/projects",
79
109
  ([_url, accountId], init, query) => {
80
110
  requests.count++;
81
- expect(accountId).toEqual("some-account-id");
82
- expect(query.get("per_page")).toEqual("10");
83
- expect(query.get("page")).toEqual(`${requests.count}`);
84
- expect(init).toEqual({});
85
111
  const pageSize = Number(query.get("per_page"));
86
112
  const page = Number(query.get("page"));
113
+ const expectedPageSize = 10;
114
+ const expectedPage = requests.count;
115
+ assertLater(() => {
116
+ expect(accountId).toEqual("some-account-id");
117
+ expect(pageSize).toEqual(expectedPageSize);
118
+ expect(page).toEqual(expectedPage);
119
+ expect(init).toEqual({});
120
+ });
87
121
  return projects.slice((page - 1) * pageSize, page * pageSize);
88
122
  }
89
123
  );
@@ -158,12 +192,14 @@ describe("pages", () => {
158
192
  setMockResponse(
159
193
  "/accounts/:accountId/pages/projects",
160
194
  ([_url, accountId], init) => {
161
- expect(accountId).toEqual("some-account-id");
162
- expect(init.method).toEqual("POST");
163
195
  const body = JSON.parse(init.body as string);
164
- expect(body).toEqual({
165
- name: "a-new-project",
166
- production_branch: "main",
196
+ assertLater(() => {
197
+ expect(accountId).toEqual("some-account-id");
198
+ expect(init.method).toEqual("POST");
199
+ expect(body).toEqual({
200
+ name: "a-new-project",
201
+ production_branch: "main",
202
+ });
167
203
  });
168
204
  return {
169
205
  name: "a-new-project",
@@ -195,8 +231,10 @@ describe("pages", () => {
195
231
  "/accounts/:accountId/pages/projects/:project/deployments",
196
232
  ([_url, accountId, project]) => {
197
233
  requests.count++;
198
- expect(project).toEqual("images");
199
- expect(accountId).toEqual("some-account-id");
234
+ assertLater(() => {
235
+ expect(project).toEqual("images");
236
+ expect(accountId).toEqual("some-account-id");
237
+ });
200
238
  return deployments;
201
239
  }
202
240
  );
@@ -279,33 +317,68 @@ describe("pages", () => {
279
317
  writeFileSync("logo.png", "foobar");
280
318
 
281
319
  setMockResponse(
282
- "/accounts/:accountId/pages/projects/foo/file",
283
- async ([_url, accountId], init) => {
284
- expect(accountId).toEqual("some-account-id");
285
- expect(init.method).toEqual("POST");
286
- const body = init.body as FormData;
287
- const logoPNGFile = body.get("file") as File;
288
- expect(await logoPNGFile.text()).toEqual("foobar");
289
- expect(logoPNGFile.name).toEqual("logo.png");
320
+ "/accounts/:accountId/pages/projects/foo/upload-token",
321
+ async ([_url, accountId]) => {
322
+ assertLater(() => {
323
+ expect(accountId).toEqual("some-account-id");
324
+ });
290
325
 
291
326
  return {
292
- id: "2082190357cfd3617ccfe04f340c6247",
327
+ jwt: "<<funfetti-auth-jwt>>",
293
328
  };
294
329
  }
295
330
  );
296
331
 
332
+ setMockResponse(
333
+ "/pages/assets/check-missing",
334
+ "POST",
335
+ async (_, init) => {
336
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
337
+ assertLater(() => {
338
+ expect(init.headers).toMatchObject({
339
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
340
+ });
341
+ expect(body).toMatchObject({
342
+ hashes: ["2082190357cfd3617ccfe04f340c6247"],
343
+ });
344
+ });
345
+ return body.hashes;
346
+ }
347
+ );
348
+
349
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
350
+ assertLater(() => {
351
+ expect(init.headers).toMatchObject({
352
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
353
+ });
354
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
355
+ expect(body).toMatchObject([
356
+ {
357
+ key: "2082190357cfd3617ccfe04f340c6247",
358
+ value: Buffer.from("foobar").toString("base64"),
359
+ metadata: {
360
+ contentType: "image/png",
361
+ },
362
+ base64: true,
363
+ },
364
+ ]);
365
+ });
366
+ });
367
+
297
368
  setMockResponse(
298
369
  "/accounts/:accountId/pages/projects/foo/deployments",
299
370
  async ([_url, accountId], init) => {
300
- expect(accountId).toEqual("some-account-id");
301
- expect(init.method).toEqual("POST");
302
- const body = init.body as FormData;
303
- const manifest = JSON.parse(body.get("manifest") as string);
304
- expect(manifest).toMatchInlineSnapshot(`
371
+ assertLater(() => {
372
+ expect(accountId).toEqual("some-account-id");
373
+ expect(init.method).toEqual("POST");
374
+ const body = init.body as FormData;
375
+ const manifest = JSON.parse(body.get("manifest") as string);
376
+ expect(manifest).toMatchInlineSnapshot(`
305
377
  Object {
306
378
  "/logo.png": "2082190357cfd3617ccfe04f340c6247",
307
379
  }
308
380
  `);
381
+ });
309
382
 
310
383
  return {
311
384
  url: "https://abcxyz.foo.pages.dev/",
@@ -324,17 +397,251 @@ describe("pages", () => {
324
397
  // `);
325
398
  });
326
399
 
400
+ it("should retry uploads", async () => {
401
+ writeFileSync("logo.txt", "foobar");
402
+
403
+ mockGetToken("<<funfetti-auth-jwt>>");
404
+
405
+ setMockResponse(
406
+ "/pages/assets/check-missing",
407
+ "POST",
408
+ async (_, init) => {
409
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
410
+ assertLater(() => {
411
+ expect(init.headers).toMatchObject({
412
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
413
+ });
414
+ expect(body).toMatchObject({
415
+ hashes: ["1a98fb08af91aca4a7df1764a2c4ddb0"],
416
+ });
417
+ });
418
+ return body.hashes;
419
+ }
420
+ );
421
+
422
+ // Accumulate multiple requests then assert afterwards
423
+ const requests: RequestInit[] = [];
424
+ setMockRawResponse("/pages/assets/upload", "POST", async (_, init) => {
425
+ requests.push(init);
426
+
427
+ if (requests.length < 2) {
428
+ return createFetchResult(null, false, [
429
+ {
430
+ code: 800000,
431
+ message: "Something exploded, please retry",
432
+ },
433
+ ]);
434
+ } else {
435
+ return createFetchResult(null, true);
436
+ }
437
+ });
438
+
439
+ setMockResponse(
440
+ "/accounts/:accountId/pages/projects/foo/deployments",
441
+ async ([_url, accountId], init) => {
442
+ assertLater(() => {
443
+ expect(accountId).toEqual("some-account-id");
444
+ expect(init.method).toEqual("POST");
445
+ const body = init.body as FormData;
446
+ const manifest = JSON.parse(body.get("manifest") as string);
447
+ expect(manifest).toMatchInlineSnapshot(`
448
+ Object {
449
+ "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
450
+ }
451
+ `);
452
+ });
453
+
454
+ return {
455
+ url: "https://abcxyz.foo.pages.dev/",
456
+ };
457
+ }
458
+ );
459
+
460
+ await runWrangler("pages publish . --project-name=foo");
461
+
462
+ // Assert two identical requests
463
+ expect(requests.length).toBe(2);
464
+ for (const init of requests) {
465
+ assertLater(() => {
466
+ expect(init.headers).toMatchObject({
467
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
468
+ });
469
+
470
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
471
+ expect(body).toMatchObject([
472
+ {
473
+ key: "1a98fb08af91aca4a7df1764a2c4ddb0",
474
+ value: Buffer.from("foobar").toString("base64"),
475
+ metadata: {
476
+ contentType: "text/plain",
477
+ },
478
+ base64: true,
479
+ },
480
+ ]);
481
+ });
482
+ }
483
+
484
+ expect(std.out).toMatchInlineSnapshot(`
485
+ "✨ Success! Uploaded 1 files (TIMINGS)
486
+
487
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
488
+ `);
489
+ });
490
+
491
+ it("should try to use multiple buckets (up to the max concurrency)", async () => {
492
+ writeFileSync("logo.txt", "foobar");
493
+ writeFileSync("logo.png", "foobar");
494
+ writeFileSync("logo.html", "foobar");
495
+ writeFileSync("logo.js", "foobar");
496
+
497
+ mockGetToken("<<funfetti-auth-jwt>>");
498
+
499
+ setMockResponse(
500
+ "/pages/assets/check-missing",
501
+ "POST",
502
+ async (_, init) => {
503
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
504
+ assertLater(() => {
505
+ expect(init.headers).toMatchObject({
506
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
507
+ });
508
+ expect(body).toMatchObject({
509
+ hashes: expect.arrayContaining([
510
+ "d96fef225537c9f5e44a3cb27fd0b492",
511
+ "2082190357cfd3617ccfe04f340c6247",
512
+ "6be321bef99e758250dac034474ddbb8",
513
+ "1a98fb08af91aca4a7df1764a2c4ddb0",
514
+ ]),
515
+ });
516
+ });
517
+ return body.hashes;
518
+ }
519
+ );
520
+
521
+ // Accumulate multiple requests then assert afterwards
522
+ const requests: RequestInit[] = [];
523
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
524
+ requests.push(init);
525
+ });
526
+
527
+ setMockResponse(
528
+ "/accounts/:accountId/pages/projects/foo/deployments",
529
+ async ([_url, accountId], init) => {
530
+ assertLater(() => {
531
+ expect(accountId).toEqual("some-account-id");
532
+ expect(init.method).toEqual("POST");
533
+ const body = init.body as FormData;
534
+ const manifest = JSON.parse(body.get("manifest") as string);
535
+ expect(manifest).toMatchInlineSnapshot(`
536
+ Object {
537
+ "/logo.html": "d96fef225537c9f5e44a3cb27fd0b492",
538
+ "/logo.js": "6be321bef99e758250dac034474ddbb8",
539
+ "/logo.png": "2082190357cfd3617ccfe04f340c6247",
540
+ "/logo.txt": "1a98fb08af91aca4a7df1764a2c4ddb0",
541
+ }
542
+ `);
543
+ });
544
+
545
+ return {
546
+ url: "https://abcxyz.foo.pages.dev/",
547
+ };
548
+ }
549
+ );
550
+
551
+ await runWrangler("pages publish . --project-name=foo");
552
+
553
+ // We have 3 buckets, so expect 3 uploads
554
+ expect(requests.length).toBe(3);
555
+ const bodies: UploadPayloadFile[][] = [];
556
+ for (const init of requests) {
557
+ expect(init.headers).toMatchObject({
558
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
559
+ });
560
+ bodies.push(JSON.parse(init.body as string) as UploadPayloadFile[]);
561
+ }
562
+ // First bucket should end up with 2 files
563
+ expect(bodies.map((b) => b.length)).toEqual([2, 1, 1]);
564
+ // But we don't know the order, so flatten and test without ordering
565
+ expect(bodies.flatMap((b) => b)).toEqual(
566
+ expect.arrayContaining([
567
+ {
568
+ base64: true,
569
+ key: "d96fef225537c9f5e44a3cb27fd0b492",
570
+ metadata: { contentType: "text/html" },
571
+ value: "Zm9vYmFy",
572
+ },
573
+ {
574
+ base64: true,
575
+ key: "1a98fb08af91aca4a7df1764a2c4ddb0",
576
+ metadata: { contentType: "text/plain" },
577
+ value: "Zm9vYmFy",
578
+ },
579
+ {
580
+ base64: true,
581
+ key: "6be321bef99e758250dac034474ddbb8",
582
+ metadata: { contentType: "application/javascript" },
583
+ value: "Zm9vYmFy",
584
+ },
585
+ {
586
+ base64: true,
587
+ key: "2082190357cfd3617ccfe04f340c6247",
588
+ metadata: { contentType: "image/png" },
589
+ value: "Zm9vYmFy",
590
+ },
591
+ ])
592
+ );
593
+
594
+ expect(std.out).toMatchInlineSnapshot(`
595
+ "✨ Success! Uploaded 4 files (TIMINGS)
596
+
597
+ ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
598
+ `);
599
+ });
600
+
327
601
  it("should not error when directory names contain periods and houses a extensionless file", async () => {
328
602
  mkdirSync(".well-known");
603
+ // Note: same content as previous test, but since it's a different extension,
604
+ // it hashes to a different value
329
605
  writeFileSync(".well-known/foobar", "foobar");
330
606
 
607
+ mockGetToken("<<funfetti-auth-jwt>>");
608
+
331
609
  setMockResponse(
332
- "/accounts/:accountId/pages/projects/foo/file",
333
- async () => ({
334
- id: "7b764dacfd211bebd8077828a7ddefd7",
335
- })
610
+ "/pages/assets/check-missing",
611
+ "POST",
612
+ async (_, init) => {
613
+ const body = JSON.parse(init.body as string) as { hashes: string[] };
614
+ assertLater(() => {
615
+ expect(init.headers).toMatchObject({
616
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
617
+ });
618
+ expect(body).toMatchObject({
619
+ hashes: ["7b764dacfd211bebd8077828a7ddefd7"],
620
+ });
621
+ });
622
+ return body.hashes;
623
+ }
336
624
  );
337
625
 
626
+ setMockResponse("/pages/assets/upload", "POST", async (_, init) => {
627
+ assertLater(() => {
628
+ expect(init.headers).toMatchObject({
629
+ Authorization: "Bearer <<funfetti-auth-jwt>>",
630
+ });
631
+ const body = JSON.parse(init.body as string) as UploadPayloadFile[];
632
+ expect(body).toMatchObject([
633
+ {
634
+ key: "7b764dacfd211bebd8077828a7ddefd7",
635
+ value: Buffer.from("foobar").toString("base64"),
636
+ metadata: {
637
+ contentType: "application/octet-stream",
638
+ },
639
+ base64: true,
640
+ },
641
+ ]);
642
+ });
643
+ });
644
+
338
645
  setMockResponse(
339
646
  "/accounts/:accountId/pages/projects/foo/deployments",
340
647
  async () => ({
@@ -167,7 +167,11 @@ describe("parseTOML", () => {
167
167
  });
168
168
 
169
169
  it("should cope with Windows line-endings", () => {
170
- expect(parseTOML("# A comment with a Windows line-ending\r\n")).toEqual({});
170
+ expect(
171
+ parseTOML(
172
+ "# A comment with a Windows line-ending\r\n# Another comment with a Windows line-ending\r\n"
173
+ )
174
+ ).toEqual({});
171
175
  });
172
176
  });
173
177