wrangler 0.0.0-e88a54e → 0.0.0-e95e5a0

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 (88) hide show
  1. package/README.md +25 -14
  2. package/bin/wrangler.js +13 -1
  3. package/miniflare-dist/index.mjs +4828 -21
  4. package/package.json +29 -24
  5. package/pages/functions/buildPlugin.ts +92 -0
  6. package/pages/functions/buildWorker.ts +80 -4
  7. package/pages/functions/filepath-routing.test.ts +11 -0
  8. package/pages/functions/filepath-routing.ts +8 -0
  9. package/pages/functions/routes.ts +6 -1
  10. package/pages/functions/template-plugin.ts +146 -0
  11. package/pages/functions/template-worker.ts +21 -8
  12. package/src/__tests__/config-cache.test.ts +36 -0
  13. package/src/__tests__/configuration.test.ts +530 -201
  14. package/src/__tests__/dev.test.tsx +284 -18
  15. package/src/__tests__/guess-worker-format.test.ts +28 -1
  16. package/src/__tests__/helpers/mock-cfetch.ts +5 -1
  17. package/src/__tests__/helpers/mock-console.ts +20 -3
  18. package/src/__tests__/helpers/mock-dialogs.ts +26 -1
  19. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  20. package/src/__tests__/helpers/mock-istty.ts +27 -0
  21. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  22. package/src/__tests__/helpers/run-in-tmp.ts +7 -0
  23. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  24. package/src/__tests__/https-options.test.ts +5 -2
  25. package/src/__tests__/index.test.ts +27 -769
  26. package/src/__tests__/init.test.ts +1516 -0
  27. package/src/__tests__/jest.setup.ts +4 -4
  28. package/src/__tests__/kv.test.ts +133 -61
  29. package/src/__tests__/logger.test.ts +115 -0
  30. package/src/__tests__/pages.test.ts +311 -26
  31. package/src/__tests__/publish.test.ts +2789 -618
  32. package/src/__tests__/r2.test.ts +26 -10
  33. package/src/__tests__/route.test.ts +4 -4
  34. package/src/__tests__/secret.test.ts +55 -34
  35. package/src/__tests__/tail.test.ts +146 -20
  36. package/src/__tests__/user.test.ts +131 -0
  37. package/src/__tests__/whoami.test.tsx +14 -9
  38. package/src/bundle.ts +30 -3
  39. package/src/cfetch/internal.ts +0 -2
  40. package/src/cli.ts +5 -11
  41. package/src/config/README.md +1 -1
  42. package/src/config/config.ts +20 -4
  43. package/src/config/environment.ts +32 -4
  44. package/src/config/index.ts +4 -3
  45. package/src/config/validation-helpers.ts +96 -7
  46. package/src/config/validation.ts +388 -54
  47. package/src/config-cache.ts +45 -0
  48. package/src/create-worker-preview.ts +22 -13
  49. package/src/create-worker-upload-form.ts +32 -9
  50. package/src/dev/dev-vars.ts +36 -0
  51. package/src/dev/dev.tsx +132 -78
  52. package/src/dev/local.tsx +101 -21
  53. package/src/dev/remote.tsx +23 -13
  54. package/src/dev/use-esbuild.ts +23 -5
  55. package/src/dialogs.tsx +16 -6
  56. package/src/entry.ts +83 -24
  57. package/src/environment-variables.ts +3 -1
  58. package/src/errors.ts +11 -0
  59. package/src/https-options.ts +3 -2
  60. package/src/index.tsx +689 -394
  61. package/src/inspect.ts +17 -11
  62. package/src/intl-polyfill.d.ts +139 -0
  63. package/src/logger.ts +66 -0
  64. package/src/miniflare-cli/index.ts +4 -0
  65. package/src/miniflare-cli/request-context.ts +40 -0
  66. package/src/module-collection.ts +7 -7
  67. package/src/open-in-browser.ts +5 -4
  68. package/src/package-manager.ts +9 -8
  69. package/src/pages.tsx +875 -56
  70. package/src/parse.ts +1 -1
  71. package/src/proxy.ts +28 -19
  72. package/src/publish.ts +263 -141
  73. package/src/sites.tsx +27 -14
  74. package/src/tail/index.ts +2 -2
  75. package/src/tail/printing.ts +13 -8
  76. package/src/update-check.ts +19 -0
  77. package/src/user.tsx +116 -41
  78. package/src/whoami.tsx +2 -1
  79. package/src/worker.ts +15 -6
  80. package/templates/gitignore +171 -0
  81. package/templates/static-asset-facade.js +1 -1
  82. package/wrangler-dist/cli.js +21162 -50227
  83. package/miniflare-dist/index.mjs.map +0 -7
  84. package/src/__tests__/dev2.test.tsx +0 -154
  85. package/src/__tests__/logout.test.ts +0 -52
  86. package/src/__tests__/sentry.test.ts +0 -146
  87. package/src/reporting.ts +0 -131
  88. package/wrangler-dist/cli.js.map +0 -7
@@ -1,6 +1,6 @@
1
1
  import * as fs from "node:fs";
2
- import { readFileSync } from "node:fs";
3
2
  import patchConsole from "patch-console";
3
+ import dedent from "ts-dedent";
4
4
  import Dev from "../dev/dev";
5
5
  import { mockAccountId, mockApiToken } from "./helpers/mock-account-id";
6
6
  import { setMockResponse, unsetAllMocks } from "./helpers/mock-cfetch";
@@ -9,7 +9,7 @@ import { runInTempDir } from "./helpers/run-in-tmp";
9
9
  import { runWrangler } from "./helpers/run-wrangler";
10
10
  import writeWranglerToml from "./helpers/write-wrangler-toml";
11
11
 
12
- describe("Dev component", () => {
12
+ describe("wrangler dev", () => {
13
13
  mockAccountId();
14
14
  mockApiToken();
15
15
  runInTempDir();
@@ -20,6 +20,56 @@ describe("Dev component", () => {
20
20
  unsetAllMocks();
21
21
  });
22
22
 
23
+ describe("compatibility-date", () => {
24
+ it("should not warn if there is no wrangler.toml and no compatibility-date specified", async () => {
25
+ fs.writeFileSync("index.js", `export default {};`);
26
+ await runWrangler("dev index.js");
27
+ expect(std.out).toMatchInlineSnapshot(`""`);
28
+ expect(std.warn).toMatchInlineSnapshot(`""`);
29
+ expect(std.err).toMatchInlineSnapshot(`""`);
30
+ });
31
+
32
+ it("should warn if there is a wrangler.toml but no compatibility-date", async () => {
33
+ writeWranglerToml({
34
+ main: "index.js",
35
+ compatibility_date: undefined,
36
+ });
37
+ fs.writeFileSync("index.js", `export default {};`);
38
+ await runWrangler("dev");
39
+ const currentDate = new Date().toISOString().substring(0, 10);
40
+ expect(std.out).toMatchInlineSnapshot(`""`);
41
+ expect(std.warn.replaceAll(currentDate, "<current-date>"))
42
+ .toMatchInlineSnapshot(`
43
+ "▲ [WARNING] No compatibility_date was specified. Using today's date: <current-date>.
44
+
45
+ Add one to your wrangler.toml file:
46
+ \`\`\`
47
+ compatibility_date = \\"<current-date>\\"
48
+ \`\`\`
49
+ or pass it in your terminal:
50
+ \`\`\`
51
+ --compatibility-date=<current-date>
52
+ \`\`\`
53
+ See https://developers.cloudflare.com/workers/platform/compatibility-dates for more information.
54
+
55
+ "
56
+ `);
57
+ expect(std.err).toMatchInlineSnapshot(`""`);
58
+ });
59
+
60
+ it("should not warn if there is a wrangler.toml but compatibility-date is specified at the command line", async () => {
61
+ writeWranglerToml({
62
+ main: "index.js",
63
+ compatibility_date: undefined,
64
+ });
65
+ fs.writeFileSync("index.js", `export default {};`);
66
+ await runWrangler("dev --compatibility-date=2020-01-01");
67
+ expect(std.out).toMatchInlineSnapshot(`""`);
68
+ expect(std.warn).toMatchInlineSnapshot(`""`);
69
+ expect(std.err).toMatchInlineSnapshot(`""`);
70
+ });
71
+ });
72
+
23
73
  describe("entry-points", () => {
24
74
  it("should error if there is no entry-point specified", async () => {
25
75
  writeWranglerToml();
@@ -30,11 +80,14 @@ describe("Dev component", () => {
30
80
  `"Missing entry-point: The entry-point should be specified via the command line (e.g. \`wrangler dev path/to/script\`) or the \`main\` config field."`
31
81
  );
32
82
 
33
- expect(std.out).toMatchInlineSnapshot(`""`);
83
+ expect(std.out).toMatchInlineSnapshot(`
84
+ "
85
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
86
+ `);
34
87
  expect(std.err).toMatchInlineSnapshot(`
35
- "Missing entry-point: The entry-point should be specified via the command line (e.g. \`wrangler dev path/to/script\`) or the \`main\` config field.
88
+ "X [ERROR] Missing entry-point: The entry-point should be specified via the command line (e.g. \`wrangler dev path/to/script\`) or the \`main\` config field.
36
89
 
37
- %s If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
90
+ "
38
91
  `);
39
92
  });
40
93
 
@@ -171,6 +224,38 @@ describe("Dev component", () => {
171
224
  );
172
225
  });
173
226
 
227
+ it("should, when provided, use a configured zone_id", async () => {
228
+ writeWranglerToml({
229
+ main: "index.js",
230
+ routes: [
231
+ { pattern: "https://some-domain.com/*", zone_id: "some-zone-id" },
232
+ ],
233
+ });
234
+ fs.writeFileSync("index.js", `export default {};`);
235
+ await runWrangler("dev");
236
+ expect((Dev as jest.Mock).mock.calls[0][0].zone).toEqual({
237
+ host: "some-domain.com",
238
+ id: "some-zone-id",
239
+ });
240
+ });
241
+
242
+ it("should, when provided, use a zone_name to get a zone_id", async () => {
243
+ writeWranglerToml({
244
+ main: "index.js",
245
+ routes: [
246
+ { pattern: "https://some-domain.com/*", zone_name: "some-zone.com" },
247
+ ],
248
+ });
249
+ fs.writeFileSync("index.js", `export default {};`);
250
+ mockGetZones("some-zone.com", [{ id: "a-zone-id" }]);
251
+ await runWrangler("dev");
252
+ expect((Dev as jest.Mock).mock.calls[0][0].zone).toEqual({
253
+ // note that it uses the provided zone_name as a host too
254
+ host: "some-zone.com",
255
+ id: "a-zone-id",
256
+ });
257
+ });
258
+
174
259
  it("given a long host, it should use the longest subdomain that resolves to a zone", async () => {
175
260
  writeWranglerToml({
176
261
  main: "index.js",
@@ -277,6 +362,15 @@ describe("Dev component", () => {
277
362
  `"Could not find zone for some-host.com"`
278
363
  );
279
364
  });
365
+
366
+ it("should not try to resolve a zone when starting in local mode", async () => {
367
+ writeWranglerToml({
368
+ main: "index.js",
369
+ });
370
+ fs.writeFileSync("index.js", `export default {};`);
371
+ await runWrangler("dev --host some-host.com --local");
372
+ expect((Dev as jest.Mock).mock.calls[0][0].zone).toEqual(undefined);
373
+ });
280
374
  });
281
375
 
282
376
  describe("custom builds", () => {
@@ -289,19 +383,19 @@ describe("Dev component", () => {
289
383
 
290
384
  await runWrangler("dev index.js");
291
385
 
292
- expect(readFileSync("index.js", "utf-8")).toMatchInlineSnapshot(
386
+ expect(fs.readFileSync("index.js", "utf-8")).toMatchInlineSnapshot(
293
387
  `"export default { fetch(){ return new Response(123) } }"`
294
388
  );
295
389
 
296
390
  // and the command would pass through
297
- expect((Dev as jest.Mock).mock.calls[0][0].buildCommand).toEqual({
391
+ expect((Dev as jest.Mock).mock.calls[0][0].build).toEqual({
298
392
  command:
299
393
  "node -e \"console.log('custom build'); require('fs').writeFileSync('index.js', 'export default { fetch(){ return new Response(123) } }')\"",
300
394
  cwd: undefined,
301
395
  watch_dir: "src",
302
396
  });
303
397
  expect(std.out).toMatchInlineSnapshot(
304
- `"running: node -e \\"console.log('custom build'); require('fs').writeFileSync('index.js', 'export default { fetch(){ return new Response(123) } }')\\""`
398
+ `"Running custom build: node -e \\"console.log('custom build'); require('fs').writeFileSync('index.js', 'export default { fetch(){ return new Response(123) } }')\\""`
305
399
  );
306
400
  expect(std.err).toMatchInlineSnapshot(`""`);
307
401
  expect(std.warn).toMatchInlineSnapshot(`""`);
@@ -317,13 +411,13 @@ describe("Dev component", () => {
317
411
 
318
412
  await runWrangler("dev index.js");
319
413
 
320
- expect(readFileSync("index.js", "utf-8")).toMatchInlineSnapshot(`
414
+ expect(fs.readFileSync("index.js", "utf-8")).toMatchInlineSnapshot(`
321
415
  "export default { fetch(){ return new Response(123) } }
322
416
  "
323
417
  `);
324
418
 
325
419
  expect(std.out).toMatchInlineSnapshot(
326
- `"running: echo \\"custom build\\" && echo \\"export default { fetch(){ return new Response(123) } }\\" > index.js"`
420
+ `"Running custom build: echo \\"custom build\\" && echo \\"export default { fetch(){ return new Response(123) } }\\" > index.js"`
327
421
  );
328
422
  expect(std.err).toMatchInlineSnapshot(`""`);
329
423
  expect(std.warn).toMatchInlineSnapshot(`""`);
@@ -343,13 +437,15 @@ describe("Dev component", () => {
343
437
  ).rejects.toThrowErrorMatchingInlineSnapshot(
344
438
  `"Could not resolve \\"index.js\\" after running custom build: node -e \\"console.log('custom build');\\""`
345
439
  );
346
- expect(std.out).toMatchInlineSnapshot(
347
- `"running: node -e \\"console.log('custom build');\\""`
348
- );
440
+ expect(std.out).toMatchInlineSnapshot(`
441
+ "Running custom build: node -e \\"console.log('custom build');\\"
442
+
443
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
444
+ `);
349
445
  expect(std.err).toMatchInlineSnapshot(`
350
- "Could not resolve \\"index.js\\" after running custom build: node -e \\"console.log('custom build');\\"
446
+ "X [ERROR] Could not resolve \\"index.js\\" after running custom build: node -e \\"console.log('custom build');\\"
351
447
 
352
- %s If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
448
+ "
353
449
  `);
354
450
  expect(std.warn).toMatchInlineSnapshot(`""`);
355
451
  });
@@ -381,9 +477,12 @@ describe("Dev component", () => {
381
477
  );
382
478
  expect(std.out).toMatchInlineSnapshot(`""`);
383
479
  expect(std.warn).toMatchInlineSnapshot(`
384
- "Setting upstream-protocol to http is not currently implemented.
385
- If this is required in your project, please add your use case to the following issue:
386
- https://github.com/cloudflare/wrangler2/issues/583."
480
+ "▲ [WARNING] Setting upstream-protocol to http is not currently implemented.
481
+
482
+ If this is required in your project, please add your use case to the following issue:
483
+ https://github.com/cloudflare/wrangler2/issues/583.
484
+
485
+ "
387
486
  `);
388
487
  expect(std.err).toMatchInlineSnapshot(`""`);
389
488
  });
@@ -480,6 +579,173 @@ describe("Dev component", () => {
480
579
  expect(std.err).toMatchInlineSnapshot(`""`);
481
580
  });
482
581
  });
582
+
583
+ describe("durable_objects", () => {
584
+ it("should warn if there is one or more remote Durable Object", async () => {
585
+ writeWranglerToml({
586
+ main: "index.js",
587
+ durable_objects: {
588
+ bindings: [
589
+ { name: "NAME_1", class_name: "CLASS_1" },
590
+ {
591
+ name: "NAME_2",
592
+ class_name: "CLASS_2",
593
+ script_name: "SCRIPT_A",
594
+ },
595
+ { name: "NAME_3", class_name: "CLASS_3" },
596
+ {
597
+ name: "NAME_4",
598
+ class_name: "CLASS_4",
599
+ script_name: "SCRIPT_B",
600
+ },
601
+ ],
602
+ },
603
+ });
604
+ fs.writeFileSync("index.js", `export default {};`);
605
+ await runWrangler("dev");
606
+ expect((Dev as jest.Mock).mock.calls[0][0].ip).toEqual("localhost");
607
+ expect(std.out).toMatchInlineSnapshot(`""`);
608
+ expect(std.warn).toMatchInlineSnapshot(`
609
+ "▲ [WARNING] WARNING: You have Durable Object bindings that are not defined locally in the worker being developed.
610
+
611
+ Be aware that changes to the data stored in these Durable Objects will be permanent and affect the
612
+ live instances.
613
+ Remote Durable Objects that are affected:
614
+ - {\\"name\\":\\"NAME_2\\",\\"class_name\\":\\"CLASS_2\\",\\"script_name\\":\\"SCRIPT_A\\"}
615
+ - {\\"name\\":\\"NAME_4\\",\\"class_name\\":\\"CLASS_4\\",\\"script_name\\":\\"SCRIPT_B\\"}
616
+
617
+ "
618
+ `);
619
+ expect(std.err).toMatchInlineSnapshot(`""`);
620
+ });
621
+ });
622
+
623
+ describe(".dev.vars", () => {
624
+ it("should override `vars` bindings from `wrangler.toml` with values in `.dev.vars`", async () => {
625
+ fs.writeFileSync("index.js", `export default {};`);
626
+
627
+ const localVarsEnvContent = dedent`
628
+ # Preceding comment
629
+ VAR_1="var #1 value" # End of line comment
630
+ VAR_3="var #3 value"
631
+ VAR_MULTI_LINE_1="A: line 1
632
+ line 2"
633
+ VAR_MULTI_LINE_2="B: line 1\\nline 2"
634
+ EMPTY=
635
+ UNQUOTED= unquoted value
636
+ `;
637
+ fs.writeFileSync(".dev.vars", localVarsEnvContent, "utf8");
638
+
639
+ writeWranglerToml({
640
+ main: "index.js",
641
+ vars: {
642
+ VAR_1: "original value 1",
643
+ VAR_2: "original value 2", // should not get overridden
644
+ VAR_3: "original value 3",
645
+ VAR_MULTI_LINE_1: "original multi-line 1",
646
+ VAR_MULTI_LINE_2: "original multi-line 2",
647
+ EMPTY: "original empty",
648
+ UNQUOTED: "original unquoted",
649
+ },
650
+ });
651
+ await runWrangler("dev");
652
+ const varBindings: Record<string, unknown> = (Dev as jest.Mock).mock
653
+ .calls[0][0].bindings.vars;
654
+
655
+ expect(varBindings).toEqual({
656
+ VAR_1: "var #1 value",
657
+ VAR_2: "original value 2",
658
+ VAR_3: "var #3 value",
659
+ VAR_MULTI_LINE_1: "A: line 1\nline 2",
660
+ VAR_MULTI_LINE_2: "B: line 1\nline 2",
661
+ EMPTY: "",
662
+ UNQUOTED: "unquoted value", // Note that whitespace is trimmed
663
+ });
664
+ expect(std.out).toMatchInlineSnapshot(
665
+ `"Using vars defined in .dev.vars"`
666
+ );
667
+ expect(std.warn).toMatchInlineSnapshot(`""`);
668
+ expect(std.err).toMatchInlineSnapshot(`""`);
669
+ });
670
+ });
671
+
672
+ describe("site", () => {
673
+ it("should error if --site is used with no value", async () => {
674
+ await expect(
675
+ runWrangler("dev --site")
676
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
677
+ `"Not enough arguments following: site"`
678
+ );
679
+
680
+ expect(std).toMatchInlineSnapshot(`
681
+ Object {
682
+ "debug": "",
683
+ "err": "wrangler dev [script]
684
+
685
+ 👂 Start a local server for developing your worker
686
+
687
+ Positionals:
688
+ script The path to an entry point for your worker [string]
689
+
690
+ Flags:
691
+ -c, --config Path to .toml configuration file [string]
692
+ -h, --help Show help [boolean]
693
+ -v, --version Show version number [boolean]
694
+ --legacy-env Use legacy environments [boolean]
695
+
696
+ Options:
697
+ --name Name of the worker [string]
698
+ --format Choose an entry type [deprecated] [choices: \\"modules\\", \\"service-worker\\"]
699
+ -e, --env Perform on a specific environment [string]
700
+ --compatibility-date Date to use for compatibility checks [string]
701
+ --compatibility-flags, --compatibility-flag Flags to use for compatibility checks [array]
702
+ --latest Use the latest version of the worker runtime [boolean] [default: true]
703
+ --ip IP address to listen on, defaults to \`localhost\` [string]
704
+ --port Port to listen on [number]
705
+ --inspector-port Port for devtools to connect to [number]
706
+ --routes, --route Routes to upload [array]
707
+ --host Host to forward requests to, defaults to the zone of project [string]
708
+ --local-protocol Protocol to listen to requests on, defaults to http. [choices: \\"http\\", \\"https\\"]
709
+ --experimental-public Static assets to be served [string]
710
+ --site Root folder of static assets for Workers Sites [string]
711
+ --site-include Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded. [array]
712
+ --site-exclude Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded. [array]
713
+ --upstream-protocol Protocol to forward requests to host on, defaults to https. [choices: \\"http\\", \\"https\\"]
714
+ --jsx-factory The function that is called for each JSX element [string]
715
+ --jsx-fragment The function that is called for each JSX fragment [string]
716
+ --tsconfig Path to a custom tsconfig.json file [string]
717
+ -l, --local Run on my machine [boolean] [default: false]
718
+ --minify Minify the script [boolean]
719
+ --node-compat Enable node.js compatibility [boolean]
720
+ --experimental-enable-local-persistence Enable persistence for this session (only for local mode) [boolean]
721
+ --inspect Enable dev tools [deprecated] [boolean]
722
+ X [ERROR] Not enough arguments following: site
723
+
724
+ ",
725
+ "out": "
726
+ ",
727
+ "warn": "",
728
+ }
729
+ `);
730
+ });
731
+ });
732
+
733
+ describe("--inspect", () => {
734
+ it("should warn if --inspect is used", async () => {
735
+ fs.writeFileSync("index.js", `export default {};`);
736
+ await runWrangler("dev index.js --inspect");
737
+ expect(std).toMatchInlineSnapshot(`
738
+ Object {
739
+ "debug": "",
740
+ "err": "",
741
+ "out": "",
742
+ "warn": "▲ [WARNING] Passing --inspect is unnecessary, now you can always connect to devtools.
743
+
744
+ ",
745
+ }
746
+ `);
747
+ });
748
+ });
483
749
  });
484
750
 
485
751
  function mockGetZones(domain: string, zones: { id: string }[] = []) {
@@ -1,10 +1,12 @@
1
1
  import { writeFile } from "fs/promises";
2
2
  import path from "path";
3
3
  import guessWorkerFormat from "../entry";
4
+ import { mockConsoleMethods } from "./helpers/mock-console";
4
5
  import { runInTempDir } from "./helpers/run-in-tmp";
5
6
 
6
7
  describe("guess worker format", () => {
7
8
  runInTempDir();
9
+ const std = mockConsoleMethods();
8
10
  it('should detect a "modules" worker', async () => {
9
11
  await writeFile("./index.ts", "export default {};");
10
12
  // Note that this isn't actually a valid worker, because it's missing
@@ -39,7 +41,7 @@ describe("guess worker format", () => {
39
41
  "service-worker"
40
42
  )
41
43
  ).rejects.toThrow(
42
- "You configured this worker to be a 'service-worker', but the file you are trying to build appears to have ES module exports. Please pass `--format modules`, or simply remove the configuration."
44
+ "You configured this worker to be a 'service-worker', but the file you are trying to build appears to have a `default` export like a module worker. Please pass `--format modules`, or simply remove the configuration."
43
45
  );
44
46
  });
45
47
 
@@ -55,4 +57,29 @@ describe("guess worker format", () => {
55
57
  "You configured this worker to be 'modules', but the file you are trying to build doesn't export a handler. Please pass `--format service-worker`, or simply remove the configuration."
56
58
  );
57
59
  });
60
+
61
+ it("should not error if a .js entry point has jsx", async () => {
62
+ await writeFile("./index.js", "console.log(<div/>)");
63
+ const guess = await guessWorkerFormat(
64
+ path.join(process.cwd(), "./index.js"),
65
+ process.cwd(),
66
+ undefined
67
+ );
68
+ expect(guess).toBe("service-worker");
69
+ });
70
+
71
+ it("logs a warning when a worker has exports, but not a default one", async () => {
72
+ await writeFile("./index.ts", "export const foo = 1;");
73
+ const guess = await guessWorkerFormat(
74
+ path.join(process.cwd(), "./index.ts"),
75
+ process.cwd(),
76
+ undefined
77
+ );
78
+ expect(guess).toBe("service-worker");
79
+ expect(std.warn).toMatchInlineSnapshot(`
80
+ "▲ [WARNING] The entrypoint index.ts has exports like an ES Module, but hasn't defined a default export like a module worker normally would. Building the worker using \\"service-worker\\" format...
81
+
82
+ "
83
+ `);
84
+ });
58
85
  });
@@ -181,6 +181,9 @@ export function unsetAllMocks() {
181
181
 
182
182
  const kvGetMocks = new Map<string, string>();
183
183
 
184
+ /**
185
+ * @mocked typeof fetchKVGetValue
186
+ */
184
187
  export function mockFetchKVGetValue(
185
188
  accountId: string,
186
189
  namespaceId: string,
@@ -188,7 +191,8 @@ export function mockFetchKVGetValue(
188
191
  ) {
189
192
  const mapKey = `${accountId}/${namespaceId}/${key}`;
190
193
  if (kvGetMocks.has(mapKey)) {
191
- return kvGetMocks.get(mapKey);
194
+ const value = kvGetMocks.get(mapKey);
195
+ if (value !== undefined) return Promise.resolve(value);
192
196
  }
193
197
  throw new Error(`no mock value found for \`kv:key get\` - ${mapKey}`);
194
198
  }
@@ -1,15 +1,20 @@
1
1
  import * as util from "node:util";
2
+ import { logger } from "../../logger";
2
3
 
3
4
  /**
4
5
  * We use this module to mock console methods, and optionally
5
6
  * assert on the values they're called with in our tests.
6
7
  */
7
8
 
8
- let logSpy: jest.SpyInstance,
9
+ let debugSpy: jest.SpyInstance,
10
+ logSpy: jest.SpyInstance,
9
11
  errorSpy: jest.SpyInstance,
10
12
  warnSpy: jest.SpyInstance;
11
13
 
12
14
  const std = {
15
+ get debug() {
16
+ return normalizeOutput(debugSpy);
17
+ },
13
18
  get out() {
14
19
  return normalizeOutput(logSpy);
15
20
  },
@@ -22,8 +27,8 @@ const std = {
22
27
  };
23
28
 
24
29
  function normalizeOutput(spy: jest.SpyInstance): string {
25
- return stripTrailingWhitespace(
26
- normalizeSlashes(stripTimings(captureCalls(spy)))
30
+ return normalizeErrorMarkers(
31
+ stripTrailingWhitespace(normalizeSlashes(stripTimings(captureCalls(spy))))
27
32
  );
28
33
  }
29
34
 
@@ -35,11 +40,14 @@ function captureCalls(spy: jest.SpyInstance): string {
35
40
 
36
41
  export function mockConsoleMethods() {
37
42
  beforeEach(() => {
43
+ logger.columns = 100;
44
+ debugSpy = jest.spyOn(console, "debug").mockImplementation();
38
45
  logSpy = jest.spyOn(console, "log").mockImplementation();
39
46
  errorSpy = jest.spyOn(console, "error").mockImplementation();
40
47
  warnSpy = jest.spyOn(console, "warn").mockImplementation();
41
48
  });
42
49
  afterEach(() => {
50
+ debugSpy.mockRestore();
43
51
  logSpy.mockRestore();
44
52
  errorSpy.mockRestore();
45
53
  warnSpy.mockRestore();
@@ -47,6 +55,15 @@ export function mockConsoleMethods() {
47
55
  return std;
48
56
  }
49
57
 
58
+ /**
59
+ * Normalize error `X` markers.
60
+ *
61
+ * Windows gets a different character.
62
+ */
63
+ function normalizeErrorMarkers(str: string): string {
64
+ return str.replaceAll("✘", "X");
65
+ }
66
+
50
67
  /**
51
68
  * Ensure slashes in the `str` are OS file-system agnostic.
52
69
  *
@@ -1,4 +1,5 @@
1
1
  import { confirm, prompt } from "../../dialogs";
2
+ import { normalizeSlashes } from "./mock-console";
2
3
 
3
4
  /**
4
5
  * The expected values for a confirmation request.
@@ -20,7 +21,7 @@ export interface ConfirmExpectation {
20
21
  export function mockConfirm(...expectations: ConfirmExpectation[]) {
21
22
  (confirm as jest.Mock).mockImplementation((text: string) => {
22
23
  for (const { text: expectedText, result } of expectations) {
23
- if (text === expectedText) {
24
+ if (normalizeSlashes(text) === normalizeSlashes(expectedText)) {
24
25
  return Promise.resolve(result);
25
26
  }
26
27
  }
@@ -28,6 +29,18 @@ export function mockConfirm(...expectations: ConfirmExpectation[]) {
28
29
  });
29
30
  }
30
31
 
32
+ export function clearConfirmMocks() {
33
+ (confirm as jest.Mock).mockReset();
34
+ // Because confirm was originally a spy, calling mockReset will simply reset
35
+ // it as a function with no return value (!), so we need to accitionally reset
36
+ // the mock implementation to the one that throws (from jest.setup.js).
37
+ (confirm as jest.Mock).mockImplementation((text: string) => {
38
+ throw new Error(
39
+ `Unexpected call to \`confirm("${text}")\`.\nYou should use \`mockConfirm()\` to mock calls to \`confirm()\` with expectations. Search the codebase for \`mockConfirm\` to learn more.`
40
+ );
41
+ });
42
+ }
43
+
31
44
  /**
32
45
  * The expected values for a prompt request.
33
46
  */
@@ -63,3 +76,15 @@ export function mockPrompt(...expectations: PromptExpectation[]) {
63
76
  }
64
77
  );
65
78
  }
79
+
80
+ export function clearPromptMocks() {
81
+ (prompt as jest.Mock).mockReset();
82
+ // Because prompt was originally a spy, calling mockReset will simply reset
83
+ // it as a function with no return value (!), so we need to accitionally reset
84
+ // the mock implementation to the one that throws (from jest.setup.js).
85
+ (prompt as jest.Mock).mockImplementation((text: string) => {
86
+ throw new Error(
87
+ `Unexpected call to \`prompt(${text}, ...)\`.\nYou should use \`mockPrompt()\` to mock calls to \`prompt()\` with expectations. Search the codebase for \`mockPrompt\` to learn more.`
88
+ );
89
+ });
90
+ }
@@ -0,0 +1,46 @@
1
+ import http from "node:http";
2
+ import type { Request } from "undici";
3
+
4
+ /**
5
+ * Setup a mock HTTP server that can be triggered directly without interacting with any network.
6
+ *
7
+ * @returns a `fetch`-like function that will trigger the mock server to handle the request.
8
+ */
9
+ export function mockHttpServer() {
10
+ let listener: http.RequestListener;
11
+
12
+ beforeEach(() => {
13
+ jest
14
+ .spyOn(http, "createServer")
15
+ .mockImplementation((...args: unknown[]) => {
16
+ listener = args.pop() as http.RequestListener;
17
+ return {
18
+ listen: jest.fn(),
19
+ close(callback?: (err?: Error) => void) {
20
+ callback?.();
21
+ return this;
22
+ },
23
+ } as unknown as http.Server;
24
+ });
25
+ });
26
+
27
+ return async (req: Request) => {
28
+ const resp = new http.ServerResponse(
29
+ // If you squint you can just about see that an `IncomingMessages` is like a `Request`!
30
+ req as unknown as http.IncomingMessage
31
+ );
32
+
33
+ // The listener will attache a callback to the response by calling `resp.end(callback)`.
34
+ // We want to capture that so that we can trigger it after the listener has completed its work.
35
+ const endSpy = jest.spyOn(resp, "end");
36
+
37
+ // The `await` here is important to allow the listener to complete its async work before we end the response.
38
+ await listener(req as unknown as http.IncomingMessage, resp);
39
+
40
+ // Now trigger the end callback.
41
+ const endCallback = endSpy.mock.calls[0].pop();
42
+ endCallback?.();
43
+
44
+ return resp;
45
+ };
46
+ }
@@ -0,0 +1,27 @@
1
+ const ORIGINAL_STDOUT_ISTTY = process.stdout.isTTY;
2
+ const ORIGINAL_STDIN_ISTTY = process.stdin.isTTY;
3
+
4
+ /**
5
+ * Mock `process.stdout.isTTY`
6
+ */
7
+ export function useMockIsTTY() {
8
+ /**
9
+ * Explicitly set `process.stdout.isTTY` to a given value
10
+ */
11
+ const setIsTTY = (isTTY: boolean) => {
12
+ process.stdout.isTTY = isTTY;
13
+ process.stdin.isTTY = isTTY;
14
+ };
15
+
16
+ beforeEach(() => {
17
+ process.stdout.isTTY = ORIGINAL_STDOUT_ISTTY;
18
+ process.stdin.isTTY = ORIGINAL_STDIN_ISTTY;
19
+ });
20
+
21
+ afterEach(() => {
22
+ process.stdout.isTTY = ORIGINAL_STDOUT_ISTTY;
23
+ process.stdin.isTTY = ORIGINAL_STDIN_ISTTY;
24
+ });
25
+
26
+ return { setIsTTY };
27
+ }