wrangler 0.0.0-e95e5a0 → 0.0.0-e978986

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 (52) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +3 -1
  3. package/bin/wrangler.js +36 -4
  4. package/package.json +11 -7
  5. package/pages/functions/buildPlugin.ts +13 -0
  6. package/pages/functions/buildWorker.ts +14 -1
  7. package/pages/functions/template-plugin.ts +3 -2
  8. package/src/__tests__/configuration.test.ts +336 -87
  9. package/src/__tests__/dev.test.tsx +243 -25
  10. package/src/__tests__/helpers/mock-dialogs.ts +41 -1
  11. package/src/__tests__/index.test.ts +79 -72
  12. package/src/__tests__/init.test.ts +182 -131
  13. package/src/__tests__/jest.setup.ts +8 -0
  14. package/src/__tests__/kv.test.ts +173 -146
  15. package/src/__tests__/package-manager.test.ts +154 -7
  16. package/src/__tests__/pages.test.ts +381 -53
  17. package/src/__tests__/parse.test.ts +8 -0
  18. package/src/__tests__/publish.test.ts +682 -89
  19. package/src/__tests__/r2.test.ts +55 -36
  20. package/src/__tests__/secret.test.ts +39 -4
  21. package/src/abort.d.ts +3 -0
  22. package/src/bundle.ts +33 -2
  23. package/src/cfetch/index.ts +21 -4
  24. package/src/cfetch/internal.ts +17 -9
  25. package/src/config/config.ts +1 -1
  26. package/src/config/environment.ts +40 -14
  27. package/src/config/index.ts +162 -0
  28. package/src/config/validation.ts +180 -65
  29. package/src/create-worker-preview.ts +17 -7
  30. package/src/create-worker-upload-form.ts +22 -8
  31. package/src/dev/dev.tsx +7 -6
  32. package/src/dev/local.tsx +6 -0
  33. package/src/dev/remote.tsx +15 -1
  34. package/src/dialogs.tsx +48 -0
  35. package/src/durable.ts +102 -0
  36. package/src/entry.ts +64 -9
  37. package/src/index.tsx +357 -177
  38. package/src/inspect.ts +39 -0
  39. package/src/kv.ts +114 -25
  40. package/src/open-in-browser.ts +5 -12
  41. package/src/package-manager.ts +50 -3
  42. package/src/pages.tsx +271 -110
  43. package/src/parse.ts +23 -4
  44. package/src/proxy.ts +38 -22
  45. package/src/publish.ts +227 -113
  46. package/src/sites.tsx +19 -17
  47. package/src/user.tsx +11 -1
  48. package/src/worker.ts +8 -0
  49. package/templates/new-worker-scheduled.js +17 -0
  50. package/templates/new-worker-scheduled.ts +32 -0
  51. package/templates/new-worker.ts +16 -1
  52. package/wrangler-dist/cli.js +36017 -22687
@@ -1,11 +1,35 @@
1
- import { writeFileSync } from "node:fs";
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");
@@ -24,16 +54,15 @@ describe("pages", () => {
24
54
  ⚡️ Configure Cloudflare Pages
25
55
 
26
56
  Commands:
27
- wrangler pages dev [directory] [-- command] 🧑‍💻 Develop your full-stack Pages application locally
28
- wrangler pages project ⚡️ Interact with your Pages projects
29
- wrangler pages deployment 🚀 Interact with the deployments of a project
30
- wrangler pages publish [directory] 🆙 Publish a directory of static assets as a Pages deployment
57
+ wrangler pages dev [directory] [-- command..] 🧑‍💻 Develop your full-stack Pages application locally
58
+ wrangler pages project ⚡️ Interact with your Pages projects
59
+ wrangler pages deployment 🚀 Interact with the deployments of a project
60
+ wrangler pages publish [directory] 🆙 Publish a directory of static assets as a Pages deployment
31
61
 
32
62
  Flags:
33
- -c, --config Path to .toml configuration file [string]
34
- -h, --help Show help [boolean]
35
- -v, --version Show version number [boolean]
36
- --legacy-env Use legacy environments [boolean]
63
+ -c, --config Path to .toml configuration file [string]
64
+ -h, --help Show help [boolean]
65
+ -v, --version Show version number [boolean]
37
66
 
38
67
  🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose"
39
68
  `);
@@ -50,7 +79,7 @@ describe("pages", () => {
50
79
  expect(std.out).toMatchInlineSnapshot(`
51
80
  "🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose
52
81
 
53
- If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
82
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
54
83
  `);
55
84
  });
56
85
 
@@ -60,7 +89,7 @@ describe("pages", () => {
60
89
  expect(std.out).toMatchInlineSnapshot(`
61
90
  "🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose
62
91
 
63
- If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
92
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
64
93
  `);
65
94
  });
66
95
  });
@@ -79,12 +108,16 @@ describe("pages", () => {
79
108
  "/accounts/:accountId/pages/projects",
80
109
  ([_url, accountId], init, query) => {
81
110
  requests.count++;
82
- expect(accountId).toEqual("some-account-id");
83
- expect(query.get("per_page")).toEqual("10");
84
- expect(query.get("page")).toEqual(`${requests.count}`);
85
- expect(init).toEqual({});
86
111
  const pageSize = Number(query.get("per_page"));
87
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
+ });
88
121
  return projects.slice((page - 1) * pageSize, page * pageSize);
89
122
  }
90
123
  );
@@ -159,12 +192,14 @@ describe("pages", () => {
159
192
  setMockResponse(
160
193
  "/accounts/:accountId/pages/projects",
161
194
  ([_url, accountId], init) => {
162
- expect(accountId).toEqual("some-account-id");
163
- expect(init.method).toEqual("POST");
164
195
  const body = JSON.parse(init.body as string);
165
- expect(body).toEqual({
166
- name: "a-new-project",
167
- 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
+ });
168
203
  });
169
204
  return {
170
205
  name: "a-new-project",
@@ -196,8 +231,10 @@ describe("pages", () => {
196
231
  "/accounts/:accountId/pages/projects/:project/deployments",
197
232
  ([_url, accountId, project]) => {
198
233
  requests.count++;
199
- expect(project).toEqual("images");
200
- expect(accountId).toEqual("some-account-id");
234
+ assertLater(() => {
235
+ expect(project).toEqual("images");
236
+ expect(accountId).toEqual("some-account-id");
237
+ });
201
238
  return deployments;
202
239
  }
203
240
  );
@@ -258,20 +295,19 @@ describe("pages", () => {
258
295
  🆙 Publish a directory of static assets as a Pages deployment
259
296
 
260
297
  Positionals:
261
- directory The directory of Pages Functions [string] [default: \\"functions\\"]
298
+ directory The directory of static files to upload [string]
262
299
 
263
300
  Flags:
264
- -c, --config Path to .toml configuration file [string]
265
- -h, --help Show help [boolean]
266
- -v, --version Show version number [boolean]
267
- --legacy-env Use legacy environments [boolean]
301
+ -c, --config Path to .toml configuration file [string]
302
+ -h, --help Show help [boolean]
303
+ -v, --version Show version number [boolean]
268
304
 
269
305
  Options:
270
- --project-name The name of the project you want to list deployments for [string]
271
- --branch The branch of the project you want to list deployments for [string]
272
- --commit-hash The branch of the project you want to list deployments for [string]
273
- --commit-message The branch of the project you want to list deployments for [string]
274
- --commit-dirty The branch of the project you want to list deployments for [boolean]
306
+ --project-name The name of the project you want to deploy to [string]
307
+ --branch The name of the branch you want to deploy to [string]
308
+ --commit-hash The SHA to attach to this deployment [string]
309
+ --commit-message The commit message to attach to this deployment [string]
310
+ --commit-dirty Whether or not the workspace should be considered dirty for this deployment [boolean]
275
311
 
276
312
  🚧 'wrangler pages <command>' is a beta command. Please report any issues to https://github.com/cloudflare/wrangler2/issues/new/choose"
277
313
  `);
@@ -281,33 +317,68 @@ describe("pages", () => {
281
317
  writeFileSync("logo.png", "foobar");
282
318
 
283
319
  setMockResponse(
284
- "/accounts/:accountId/pages/projects/foo/file",
285
- async ([_url, accountId], init) => {
286
- expect(accountId).toEqual("some-account-id");
287
- expect(init.method).toEqual("POST");
288
- const body = init.body as FormData;
289
- const logoPNGFile = body.get("file") as File;
290
- expect(await logoPNGFile.text()).toEqual("foobar");
291
- 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
+ });
292
325
 
293
326
  return {
294
- id: "2082190357cfd3617ccfe04f340c6247",
327
+ jwt: "<<funfetti-auth-jwt>>",
295
328
  };
296
329
  }
297
330
  );
298
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
+
299
368
  setMockResponse(
300
369
  "/accounts/:accountId/pages/projects/foo/deployments",
301
370
  async ([_url, accountId], init) => {
302
- expect(accountId).toEqual("some-account-id");
303
- expect(init.method).toEqual("POST");
304
- const body = init.body as FormData;
305
- const manifest = JSON.parse(body.get("manifest") as string);
306
- expect(manifest).toMatchInlineSnapshot(`
307
- Object {
308
- "/logo.png": "2082190357cfd3617ccfe04f340c6247",
309
- }
310
- `);
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(`
377
+ Object {
378
+ "/logo.png": "2082190357cfd3617ccfe04f340c6247",
379
+ }
380
+ `);
381
+ });
311
382
 
312
383
  return {
313
384
  url: "https://abcxyz.foo.pages.dev/",
@@ -325,5 +396,262 @@ describe("pages", () => {
325
396
  // ✨ Deployment complete! Take a peek over at https://abcxyz.foo.pages.dev/"
326
397
  // `);
327
398
  });
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
+
601
+ it("should not error when directory names contain periods and houses a extensionless file", async () => {
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
605
+ writeFileSync(".well-known/foobar", "foobar");
606
+
607
+ mockGetToken("<<funfetti-auth-jwt>>");
608
+
609
+ setMockResponse(
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
+ }
624
+ );
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
+
645
+ setMockResponse(
646
+ "/accounts/:accountId/pages/projects/foo/deployments",
647
+ async () => ({
648
+ url: "https://abcxyz.foo.pages.dev/",
649
+ })
650
+ );
651
+
652
+ await runWrangler("pages publish . --project-name=foo");
653
+
654
+ expect(std.err).toMatchInlineSnapshot(`""`);
655
+ });
328
656
  });
329
657
  });
@@ -165,6 +165,14 @@ describe("parseTOML", () => {
165
165
  });
166
166
  }
167
167
  });
168
+
169
+ it("should cope with Windows line-endings", () => {
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({});
175
+ });
168
176
  });
169
177
 
170
178
  describe("parseJSON", () => {