wrangler 0.0.0-e2b7352 → 0.0.0-e2f9bb2

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 (86) hide show
  1. package/README.md +24 -13
  2. package/bin/wrangler.js +3 -0
  3. package/miniflare-dist/index.mjs +4828 -21
  4. package/package.json +29 -22
  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 +386 -81
  14. package/src/__tests__/dev.test.tsx +242 -21
  15. package/src/__tests__/guess-worker-format.test.ts +18 -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 +24 -0
  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 +25 -759
  26. package/src/__tests__/init.test.ts +1042 -0
  27. package/src/__tests__/jest.setup.ts +4 -4
  28. package/src/__tests__/kv.test.ts +130 -56
  29. package/src/__tests__/logger.test.ts +115 -0
  30. package/src/__tests__/pages.test.ts +290 -43
  31. package/src/__tests__/publish.test.ts +1699 -333
  32. package/src/__tests__/r2.test.ts +24 -8
  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 +97 -45
  36. package/src/__tests__/user.test.ts +131 -0
  37. package/src/__tests__/whoami.test.tsx +14 -9
  38. package/src/bundle.ts +15 -1
  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 +26 -3
  44. package/src/config/index.ts +2 -1
  45. package/src/config/validation-helpers.ts +26 -5
  46. package/src/config/validation.ts +233 -41
  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 +60 -34
  52. package/src/dev/local.tsx +101 -21
  53. package/src/dev/remote.tsx +23 -13
  54. package/src/dev/use-esbuild.ts +19 -5
  55. package/src/dialogs.tsx +16 -6
  56. package/src/entry.ts +38 -11
  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 +529 -303
  61. package/src/inspect.ts +17 -11
  62. package/src/logger.ts +66 -0
  63. package/src/miniflare-cli/index.ts +4 -0
  64. package/src/miniflare-cli/request-context.ts +40 -0
  65. package/src/module-collection.ts +7 -7
  66. package/src/open-in-browser.ts +5 -4
  67. package/src/package-manager.ts +9 -8
  68. package/src/pages.tsx +860 -55
  69. package/src/parse.ts +1 -1
  70. package/src/proxy.ts +25 -11
  71. package/src/publish.ts +260 -141
  72. package/src/sites.tsx +27 -14
  73. package/src/tail/index.ts +1 -1
  74. package/src/tail/printing.ts +13 -8
  75. package/src/update-check.ts +19 -0
  76. package/src/user.tsx +104 -38
  77. package/src/whoami.tsx +2 -1
  78. package/src/worker.ts +15 -6
  79. package/templates/gitignore +171 -0
  80. package/templates/static-asset-facade.js +1 -1
  81. package/wrangler-dist/cli.js +20948 -50169
  82. package/miniflare-dist/index.mjs.map +0 -7
  83. package/src/__tests__/logout.test.ts +0 -52
  84. package/src/__tests__/sentry.test.ts +0 -146
  85. package/src/reporting.ts +0 -131
  86. 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";
@@ -20,6 +20,56 @@ describe("wrangler dev", () => {
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("wrangler dev", () => {
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("wrangler dev", () => {
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",
@@ -298,7 +383,7 @@ describe("wrangler dev", () => {
298
383
 
299
384
  await runWrangler("dev index.js");
300
385
 
301
- expect(readFileSync("index.js", "utf-8")).toMatchInlineSnapshot(
386
+ expect(fs.readFileSync("index.js", "utf-8")).toMatchInlineSnapshot(
302
387
  `"export default { fetch(){ return new Response(123) } }"`
303
388
  );
304
389
 
@@ -310,7 +395,7 @@ describe("wrangler dev", () => {
310
395
  watch_dir: "src",
311
396
  });
312
397
  expect(std.out).toMatchInlineSnapshot(
313
- `"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) } }')\\""`
314
399
  );
315
400
  expect(std.err).toMatchInlineSnapshot(`""`);
316
401
  expect(std.warn).toMatchInlineSnapshot(`""`);
@@ -326,13 +411,13 @@ describe("wrangler dev", () => {
326
411
 
327
412
  await runWrangler("dev index.js");
328
413
 
329
- expect(readFileSync("index.js", "utf-8")).toMatchInlineSnapshot(`
414
+ expect(fs.readFileSync("index.js", "utf-8")).toMatchInlineSnapshot(`
330
415
  "export default { fetch(){ return new Response(123) } }
331
416
  "
332
417
  `);
333
418
 
334
419
  expect(std.out).toMatchInlineSnapshot(
335
- `"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"`
336
421
  );
337
422
  expect(std.err).toMatchInlineSnapshot(`""`);
338
423
  expect(std.warn).toMatchInlineSnapshot(`""`);
@@ -352,13 +437,15 @@ describe("wrangler dev", () => {
352
437
  ).rejects.toThrowErrorMatchingInlineSnapshot(
353
438
  `"Could not resolve \\"index.js\\" after running custom build: node -e \\"console.log('custom build');\\""`
354
439
  );
355
- expect(std.out).toMatchInlineSnapshot(
356
- `"running: node -e \\"console.log('custom build');\\""`
357
- );
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
+ `);
358
445
  expect(std.err).toMatchInlineSnapshot(`
359
- "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');\\"
360
447
 
361
- %s If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
448
+ "
362
449
  `);
363
450
  expect(std.warn).toMatchInlineSnapshot(`""`);
364
451
  });
@@ -390,9 +477,12 @@ describe("wrangler dev", () => {
390
477
  );
391
478
  expect(std.out).toMatchInlineSnapshot(`""`);
392
479
  expect(std.warn).toMatchInlineSnapshot(`
393
- "Setting upstream-protocol to http is not currently implemented.
394
- If this is required in your project, please add your use case to the following issue:
395
- 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
+ "
396
486
  `);
397
487
  expect(std.err).toMatchInlineSnapshot(`""`);
398
488
  });
@@ -516,15 +606,146 @@ describe("wrangler dev", () => {
516
606
  expect((Dev as jest.Mock).mock.calls[0][0].ip).toEqual("localhost");
517
607
  expect(std.out).toMatchInlineSnapshot(`""`);
518
608
  expect(std.warn).toMatchInlineSnapshot(`
519
- "WARNING: You have Durable Object bindings, which are not defined locally in the worker being developed.
520
- Be aware that changes to the data stored in these Durable Objects will be permanent and affect the live instances.
521
- Remote Durable Objects that are affected:
522
- - {\\"name\\":\\"NAME_2\\",\\"class_name\\":\\"CLASS_2\\",\\"script_name\\":\\"SCRIPT_A\\"}
523
- - {\\"name\\":\\"NAME_4\\",\\"class_name\\":\\"CLASS_4\\",\\"script_name\\":\\"SCRIPT_B\\"}"
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
+ "
524
618
  `);
525
619
  expect(std.err).toMatchInlineSnapshot(`""`);
526
620
  });
527
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
+ });
528
749
  });
529
750
 
530
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
 
@@ -65,4 +67,19 @@ describe("guess worker format", () => {
65
67
  );
66
68
  expect(guess).toBe("service-worker");
67
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
+ });
68
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
  *
@@ -28,6 +28,18 @@ export function mockConfirm(...expectations: ConfirmExpectation[]) {
28
28
  });
29
29
  }
30
30
 
31
+ export function clearConfirmMocks() {
32
+ (confirm as jest.Mock).mockReset();
33
+ // Because confirm was originally a spy, calling mockReset will simply reset
34
+ // it as a function with no return value (!), so we need to accitionally reset
35
+ // the mock implementation to the one that throws (from jest.setup.js).
36
+ (confirm as jest.Mock).mockImplementation((text: string) => {
37
+ throw new Error(
38
+ `Unexpected call to \`confirm("${text}")\`.\nYou should use \`mockConfirm()\` to mock calls to \`confirm()\` with expectations. Search the codebase for \`mockConfirm\` to learn more.`
39
+ );
40
+ });
41
+ }
42
+
31
43
  /**
32
44
  * The expected values for a prompt request.
33
45
  */
@@ -63,3 +75,15 @@ export function mockPrompt(...expectations: PromptExpectation[]) {
63
75
  }
64
76
  );
65
77
  }
78
+
79
+ export function clearPromptMocks() {
80
+ (prompt as jest.Mock).mockReset();
81
+ // Because prompt was originally a spy, calling mockReset will simply reset
82
+ // it as a function with no return value (!), so we need to accitionally reset
83
+ // the mock implementation to the one that throws (from jest.setup.js).
84
+ (prompt as jest.Mock).mockImplementation((text: string) => {
85
+ throw new Error(
86
+ `Unexpected call to \`prompt(${text}, ...)\`.\nYou should use \`mockPrompt()\` to mock calls to \`prompt()\` with expectations. Search the codebase for \`mockPrompt\` to learn more.`
87
+ );
88
+ });
89
+ }
@@ -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
+ }