wrangler 0.0.0-eee5c78 → 0.0.0-ef9dac8

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 (72) hide show
  1. package/bin/wrangler.js +61 -4
  2. package/package.json +5 -4
  3. package/src/__tests__/configuration.test.ts +61 -0
  4. package/src/__tests__/dev.test.tsx +156 -2
  5. package/src/__tests__/helpers/mock-cfetch.ts +2 -2
  6. package/src/__tests__/helpers/mock-console.ts +11 -1
  7. package/src/__tests__/helpers/mock-dialogs.ts +36 -13
  8. package/src/__tests__/helpers/mock-istty.ts +8 -3
  9. package/src/__tests__/helpers/mock-oauth-flow.ts +19 -24
  10. package/src/__tests__/helpers/mock-process.ts +39 -0
  11. package/src/__tests__/helpers/mock-stdin.ts +5 -0
  12. package/src/__tests__/index.test.ts +2 -1
  13. package/src/__tests__/init.test.ts +789 -434
  14. package/src/__tests__/jest.setup.ts +30 -0
  15. package/src/__tests__/kv.test.ts +148 -10
  16. package/src/__tests__/logger.test.ts +26 -0
  17. package/src/__tests__/pages.test.ts +514 -14
  18. package/src/__tests__/publish.test.ts +748 -150
  19. package/src/__tests__/secret.test.ts +30 -25
  20. package/src/__tests__/user.test.ts +1 -0
  21. package/src/bundle-reporter.tsx +29 -0
  22. package/src/cfetch/internal.ts +2 -2
  23. package/src/config/config.ts +9 -0
  24. package/src/config/validation.ts +32 -0
  25. package/src/config-cache.ts +0 -1
  26. package/src/dev/dev.tsx +38 -38
  27. package/src/dev/local.tsx +28 -19
  28. package/src/dev/remote.tsx +64 -19
  29. package/src/dev/use-esbuild.ts +1 -7
  30. package/src/dev.tsx +469 -0
  31. package/src/entry.ts +7 -1
  32. package/src/generate.ts +33 -0
  33. package/src/git-client.ts +42 -0
  34. package/src/index.tsx +79 -1090
  35. package/src/init.ts +549 -0
  36. package/src/kv.ts +2 -2
  37. package/src/logger.ts +8 -1
  38. package/src/open-in-browser.ts +1 -3
  39. package/src/pages/build.tsx +202 -0
  40. package/src/pages/constants.ts +7 -0
  41. package/src/pages/deployments.tsx +101 -0
  42. package/src/pages/dev.tsx +957 -0
  43. package/{pages → src/pages}/functions/buildPlugin.ts +1 -1
  44. package/{pages → src/pages}/functions/buildWorker.ts +1 -1
  45. package/{pages → src/pages}/functions/filepath-routing.test.ts +3 -3
  46. package/{pages → src/pages}/functions/filepath-routing.ts +2 -2
  47. package/{pages → src/pages}/functions/identifiers.ts +0 -0
  48. package/{pages → src/pages}/functions/routes.ts +1 -6
  49. package/src/pages/index.tsx +84 -0
  50. package/src/pages/projects.tsx +157 -0
  51. package/src/pages/publish.tsx +335 -0
  52. package/src/pages/types.ts +40 -0
  53. package/src/pages/upload.tsx +384 -0
  54. package/src/pages/utils.ts +12 -0
  55. package/src/parse.ts +19 -0
  56. package/src/preview.tsx +149 -0
  57. package/src/proxy.ts +7 -9
  58. package/src/publish.ts +28 -9
  59. package/src/sites.tsx +52 -6
  60. package/src/user/choose-account.tsx +60 -0
  61. package/src/user/env-vars.ts +17 -0
  62. package/src/user/generate-auth-url.ts +33 -0
  63. package/src/user/generate-random-state.ts +16 -0
  64. package/src/user/index.ts +3 -0
  65. package/src/{user.tsx → user/user.tsx} +54 -135
  66. package/src/zones.ts +11 -8
  67. package/templates/no-op-worker.js +10 -0
  68. package/{pages/functions/template-plugin.ts → templates/pages-template-plugin.ts} +9 -1
  69. package/{pages/functions/template-worker.ts → templates/pages-template-worker.ts} +26 -8
  70. package/templates/static-asset-facade.js +1 -1
  71. package/wrangler-dist/cli.js +84632 -84250
  72. package/src/pages.tsx +0 -2093
package/bin/wrangler.js CHANGED
@@ -9,7 +9,10 @@ const MIN_NODE_VERSION = "16.7.0";
9
9
 
10
10
  let wranglerProcess;
11
11
 
12
- async function main() {
12
+ /**
13
+ * Executes ../wrangler-dist/cli.js
14
+ */
15
+ function runWrangler() {
13
16
  if (semiver(process.versions.node, MIN_NODE_VERSION) < 0) {
14
17
  // Note Volta and nvm are also recommended in the official docs:
15
18
  // https://developers.cloudflare.com/workers/get-started/guide#2-install-the-workers-cli
@@ -46,7 +49,7 @@ Consider using a Node.js version manager such as https://volta.sh/ or https://gi
46
49
  pathToCACerts = certPath;
47
50
  }
48
51
 
49
- wranglerProcess = spawn(
52
+ return spawn(
50
53
  process.execPath,
51
54
  [
52
55
  ...(semiver(process.versions.node, "18.0.0") >= 0
@@ -70,11 +73,65 @@ Consider using a Node.js version manager such as https://volta.sh/ or https://gi
70
73
  );
71
74
  }
72
75
 
76
+ /**
77
+ * Runs a locally-installed version of wrangler, delegating from this version.
78
+ * @throws {MODULE_NOT_FOUND} if there isn't a locally installed version of wrangler.
79
+ */
80
+ function runDelegatedWrangler() {
81
+ const packageJsonPath = require.resolve("wrangler/package.json", {
82
+ paths: [process.cwd()],
83
+ });
84
+ const {
85
+ bin: { wrangler: binaryPath },
86
+ version,
87
+ } = JSON.parse(fs.readFileSync(packageJsonPath));
88
+ const resolvedBinaryPath = path.resolve(packageJsonPath, "..", binaryPath);
89
+
90
+ console.log(
91
+ `Delegating to locally-installed version of wrangler @ v${version}`
92
+ );
93
+ // this call to `spawn` is simpler because the delegated version will do all
94
+ // of the other work.
95
+ return spawn(
96
+ process.execPath,
97
+ [resolvedBinaryPath, ...process.argv.slice(2)],
98
+ {
99
+ stdio: "inherit",
100
+ }
101
+ ).on("exit", (code) =>
102
+ process.exit(code === undefined || code === null ? 0 : code)
103
+ );
104
+ }
105
+
106
+ /**
107
+ * Indicates if this invocation of `wrangler` should delegate
108
+ * to a locally-installed version.
109
+ */
110
+ function shouldDelegate() {
111
+ try {
112
+ // `require.resolve` will throw if it can't find
113
+ // a locally-installed version of `wrangler`
114
+ const delegatedPackageJson = require.resolve("wrangler/package.json", {
115
+ paths: [process.cwd()],
116
+ });
117
+ const thisPackageJson = path.resolve(__dirname, "..", "package.json");
118
+ // if it's the same path, then we're already a local install -- no need to delegate
119
+ return thisPackageJson !== delegatedPackageJson;
120
+ } catch (e) {
121
+ // there's no local version to delegate to -- `require.resolve` threw
122
+ return false;
123
+ }
124
+ }
125
+
126
+ async function main() {
127
+ wranglerProcess = shouldDelegate() ? runDelegatedWrangler() : runWrangler();
128
+ }
129
+
73
130
  process.on("SIGINT", () => {
74
- wranglerProcess.kill();
131
+ wranglerProcess?.kill();
75
132
  });
76
133
  process.on("SIGTERM", () => {
77
- wranglerProcess.kill();
134
+ wranglerProcess?.kill();
78
135
  });
79
136
 
80
137
  void main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wrangler",
3
- "version": "0.0.0-eee5c78",
3
+ "version": "0.0.0-ef9dac8",
4
4
  "author": "wrangler@cloudflare.com",
5
5
  "description": "Command-line interface for all things Cloudflare Workers",
6
6
  "bin": {
@@ -41,7 +41,7 @@
41
41
  "@esbuild-plugins/node-modules-polyfill": "^0.1.4",
42
42
  "blake3-wasm": "^2.1.5",
43
43
  "esbuild": "0.14.34",
44
- "miniflare": "^2.5.0",
44
+ "miniflare": "^2.5.1",
45
45
  "nanoid": "^3.3.3",
46
46
  "path-to-regexp": "^6.2.0",
47
47
  "selfsigned": "^2.0.1",
@@ -68,6 +68,7 @@
68
68
  "clipboardy": "^3.0.0",
69
69
  "cmd-shim": "^4.1.0",
70
70
  "command-exists": "^1.2.9",
71
+ "concurrently": "^7.2.2",
71
72
  "devtools-protocol": "^0.0.955664",
72
73
  "dotenv": "^16.0.0",
73
74
  "execa": "^6.1.0",
@@ -99,7 +100,7 @@
99
100
  "timeago.js": "^4.0.2",
100
101
  "tmp-promise": "^3.0.3",
101
102
  "ts-dedent": "^2.2.0",
102
- "undici": "^5.3.0",
103
+ "undici": "^5.5.1",
103
104
  "update-check": "^1.5.4",
104
105
  "ws": "^8.5.0",
105
106
  "yargs": "^17.4.1"
@@ -107,7 +108,6 @@
107
108
  "files": [
108
109
  "src",
109
110
  "bin",
110
- "pages",
111
111
  "miniflare-config-stubs",
112
112
  "miniflare-dist",
113
113
  "wrangler-dist",
@@ -122,6 +122,7 @@
122
122
  "check:type": "tsc",
123
123
  "bundle": "node -r esbuild-register scripts/bundle.ts",
124
124
  "build": "npm run clean && npm run bundle",
125
+ "dev": "npm run clean && concurrently -c black,blue 'npm run bundle -- --watch' 'npm run check:type -- --watch --preserveWatchOutput'",
125
126
  "prepublishOnly": "SOURCEMAPS=false npm run build",
126
127
  "start": "npm run bundle && NODE_OPTIONS=--enable-source-maps ./bin/wrangler.js",
127
128
  "test": "jest --silent=false --verbose=true",
@@ -412,6 +412,67 @@ describe("normalizeAndValidateConfig()", () => {
412
412
  });
413
413
  });
414
414
 
415
+ describe("assets", () => {
416
+ it("should error if `assets` config is missing `bucket`", () => {
417
+ const expectedConfig: RawConfig = {
418
+ // @ts-expect-error we're intentionally passing an invalid configuration here
419
+ assets: {
420
+ include: ["INCLUDE_1", "INCLUDE_2"],
421
+ exclude: ["EXCLUDE_1", "EXCLUDE_2"],
422
+ },
423
+ };
424
+
425
+ const { config, diagnostics } = normalizeAndValidateConfig(
426
+ expectedConfig,
427
+ undefined,
428
+ { env: undefined }
429
+ );
430
+
431
+ expect(config).toEqual(expect.objectContaining(expectedConfig));
432
+ expect(diagnostics.hasWarnings()).toBe(true);
433
+ expect(diagnostics.hasErrors()).toBe(true);
434
+
435
+ expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(`
436
+ "Processing wrangler configuration:
437
+ - \\"assets\\" fields are experimental and may change or break at any time."
438
+ `);
439
+ expect(diagnostics.renderErrors()).toMatchInlineSnapshot(`
440
+ "Processing wrangler configuration:
441
+ - \\"assets.bucket\\" is a required field."
442
+ `);
443
+ });
444
+
445
+ it("should error on invalid `assets` values", () => {
446
+ const expectedConfig = {
447
+ assets: {
448
+ bucket: "BUCKET",
449
+ include: [222, 333],
450
+ exclude: [444, 555],
451
+ },
452
+ };
453
+
454
+ const { config, diagnostics } = normalizeAndValidateConfig(
455
+ expectedConfig as unknown as RawConfig,
456
+ undefined,
457
+ { env: undefined }
458
+ );
459
+
460
+ expect(config).toEqual(expect.objectContaining(expectedConfig));
461
+ expect(diagnostics.hasWarnings()).toBe(true);
462
+ expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(`
463
+ "Processing wrangler configuration:
464
+ - \\"assets\\" fields are experimental and may change or break at any time."
465
+ `);
466
+ expect(diagnostics.renderErrors()).toMatchInlineSnapshot(`
467
+ "Processing wrangler configuration:
468
+ - Expected \\"assets.include.[0]\\" to be of type string but got 222.
469
+ - Expected \\"assets.include.[1]\\" to be of type string but got 333.
470
+ - Expected \\"assets.exclude.[0]\\" to be of type string but got 444.
471
+ - Expected \\"assets.exclude.[1]\\" to be of type string but got 555."
472
+ `);
473
+ });
474
+ });
475
+
415
476
  it("should map `wasm_module` paths from relative to the config path to relative to the cwd", () => {
416
477
  const expectedConfig: RawConfig = {
417
478
  wasm_modules: {
@@ -403,6 +403,46 @@ describe("wrangler dev", () => {
403
403
  });
404
404
  });
405
405
 
406
+ describe("local upstream", () => {
407
+ it("should use dev.host from toml by default", async () => {
408
+ writeWranglerToml({
409
+ main: "index.js",
410
+ dev: {
411
+ host: `2.some-host.com`,
412
+ },
413
+ });
414
+ fs.writeFileSync("index.js", `export default {};`);
415
+ await runWrangler("dev --local");
416
+ expect((Dev as jest.Mock).mock.calls[0][0].localUpstream).toEqual(
417
+ "2.some-host.com"
418
+ );
419
+ });
420
+
421
+ it("should use route from toml by default", async () => {
422
+ writeWranglerToml({
423
+ main: "index.js",
424
+ route: "https://4.some-host.com/some/path/*",
425
+ });
426
+ fs.writeFileSync("index.js", `export default {};`);
427
+ await runWrangler("dev --local");
428
+ expect((Dev as jest.Mock).mock.calls[0][0].host).toEqual(
429
+ "4.some-host.com"
430
+ );
431
+ });
432
+
433
+ it("should respect the option when provided", async () => {
434
+ writeWranglerToml({
435
+ main: "index.js",
436
+ route: `2.some-host.com`,
437
+ });
438
+ fs.writeFileSync("index.js", `export default {};`);
439
+ await runWrangler("dev --local-upstream some-host.com --local");
440
+ expect((Dev as jest.Mock).mock.calls[0][0].localUpstream).toEqual(
441
+ "some-host.com"
442
+ );
443
+ });
444
+ });
445
+
406
446
  describe("custom builds", () => {
407
447
  it("should run a custom build before starting `dev`", async () => {
408
448
  writeWranglerToml({
@@ -799,7 +839,7 @@ describe("wrangler dev", () => {
799
839
  });
800
840
  });
801
841
 
802
- describe("site", () => {
842
+ describe("serve static assets", () => {
803
843
  it("should error if --site is used with no value", async () => {
804
844
  await expect(
805
845
  runWrangler("dev --site")
@@ -839,7 +879,8 @@ describe("wrangler dev", () => {
839
879
  --routes, --route Routes to upload [array]
840
880
  --host Host to forward requests to, defaults to the zone of project [string]
841
881
  --local-protocol Protocol to listen to requests on, defaults to http. [choices: \\"http\\", \\"https\\"]
842
- --experimental-public Static assets to be served [string]
882
+ --local-upstream Host to act as origin in local mode, defaults to dev.host or route [string]
883
+ --assets Static assets to be served [string]
843
884
  --site Root folder of static assets for Workers Sites [string]
844
885
  --site-include Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded. [array]
845
886
  --site-exclude Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded. [array]
@@ -856,6 +897,119 @@ describe("wrangler dev", () => {
856
897
  }
857
898
  `);
858
899
  });
900
+
901
+ it("should error if --assets and --site are used together", async () => {
902
+ writeWranglerToml({
903
+ main: "./index.js",
904
+ });
905
+ fs.writeFileSync("index.js", `export default {};`);
906
+ await expect(
907
+ runWrangler("dev --assets abc --site xyz")
908
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
909
+ `"Cannot use Assets and Workers Sites in the same Worker."`
910
+ );
911
+ });
912
+
913
+ it("should error if --assets and config.site are used together", async () => {
914
+ writeWranglerToml({
915
+ main: "./index.js",
916
+ site: {
917
+ bucket: "xyz",
918
+ },
919
+ });
920
+ fs.writeFileSync("index.js", `export default {};`);
921
+ await expect(
922
+ runWrangler("dev --assets abc")
923
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
924
+ `"Cannot use Assets and Workers Sites in the same Worker."`
925
+ );
926
+ });
927
+
928
+ it("should error if config.assets and --site are used together", async () => {
929
+ writeWranglerToml({
930
+ main: "./index.js",
931
+ assets: "abc",
932
+ });
933
+ fs.writeFileSync("index.js", `export default {};`);
934
+ await expect(
935
+ runWrangler("dev --site xyz")
936
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
937
+ `"Cannot use Assets and Workers Sites in the same Worker."`
938
+ );
939
+ });
940
+
941
+ it("should error if config.assets and config.site are used together", async () => {
942
+ writeWranglerToml({
943
+ main: "./index.js",
944
+ assets: "abc",
945
+ site: {
946
+ bucket: "xyz",
947
+ },
948
+ });
949
+ fs.writeFileSync("index.js", `export default {};`);
950
+ await expect(
951
+ runWrangler("dev --assets abc")
952
+ ).rejects.toThrowErrorMatchingInlineSnapshot(
953
+ `"Cannot use Assets and Workers Sites in the same Worker."`
954
+ );
955
+ });
956
+
957
+ it("should indicate whether Sites is being used", async () => {
958
+ writeWranglerToml({
959
+ main: "index.js",
960
+ });
961
+ fs.writeFileSync("index.js", `export default {};`);
962
+
963
+ await runWrangler("dev");
964
+ expect((Dev as jest.Mock).mock.calls[0][0].isWorkersSite).toEqual(false);
965
+
966
+ await runWrangler("dev --site abc");
967
+ expect((Dev as jest.Mock).mock.calls[1][0].isWorkersSite).toEqual(true);
968
+
969
+ await runWrangler("dev --assets abc");
970
+ expect((Dev as jest.Mock).mock.calls[2][0].isWorkersSite).toEqual(false);
971
+ });
972
+
973
+ it("should warn if --assets is used", async () => {
974
+ writeWranglerToml({
975
+ main: "./index.js",
976
+ });
977
+ fs.writeFileSync("index.js", `export default {};`);
978
+
979
+ await runWrangler('dev --assets "./assets"');
980
+ expect(std).toMatchInlineSnapshot(`
981
+ Object {
982
+ "debug": "",
983
+ "err": "",
984
+ "out": "",
985
+ "warn": "▲ [WARNING] The --assets argument is experimental and may change or break at any time
986
+
987
+ ",
988
+ }
989
+ `);
990
+ });
991
+
992
+ it("should warn if config.assets is used", async () => {
993
+ writeWranglerToml({
994
+ main: "./index.js",
995
+ assets: "./assets",
996
+ });
997
+ fs.writeFileSync("index.js", `export default {};`);
998
+
999
+ await runWrangler("dev");
1000
+ expect(std).toMatchInlineSnapshot(`
1001
+ Object {
1002
+ "debug": "",
1003
+ "err": "",
1004
+ "out": "",
1005
+ "warn": "▲ [WARNING] Processing wrangler.toml configuration:
1006
+
1007
+ - \\"assets\\" fields are experimental and may change or break at any time.
1008
+
1009
+ ",
1010
+ }
1011
+ `);
1012
+ });
859
1013
  });
860
1014
 
861
1015
  describe("--inspect", () => {
@@ -179,7 +179,7 @@ export function unsetAllMocks() {
179
179
  * special-case mocking it here.
180
180
  */
181
181
 
182
- const kvGetMocks = new Map<string, string>();
182
+ const kvGetMocks = new Map<string, string | Buffer>();
183
183
 
184
184
  /**
185
185
  * @mocked typeof fetchKVGetValue
@@ -201,7 +201,7 @@ export function setMockFetchKVGetValue(
201
201
  accountId: string,
202
202
  namespaceId: string,
203
203
  key: string,
204
- value: string
204
+ value: string | Buffer
205
205
  ) {
206
206
  kvGetMocks.set(`${accountId}/${namespaceId}/${key}`, value);
207
207
  }
@@ -28,7 +28,9 @@ const std = {
28
28
 
29
29
  function normalizeOutput(spy: jest.SpyInstance): string {
30
30
  return normalizeErrorMarkers(
31
- stripTrailingWhitespace(normalizeSlashes(stripTimings(captureCalls(spy))))
31
+ replaceByte(
32
+ stripTrailingWhitespace(normalizeSlashes(stripTimings(captureCalls(spy))))
33
+ )
32
34
  );
33
35
  }
34
36
 
@@ -85,3 +87,11 @@ export function stripTimings(stdout: string): string {
85
87
  export function stripTrailingWhitespace(str: string): string {
86
88
  return str.replace(/[^\S\n]+\n/g, "\n");
87
89
  }
90
+
91
+ /**
92
+ * Removing leading kilobit (tenth of a byte) from test output due to
93
+ * variation causing every few tests the value to change by ± .01
94
+ */
95
+ function replaceByte(stdout: string): string {
96
+ return stdout.replaceAll(/.[0-9][0-9] KiB/g, "xx KiB");
97
+ }
@@ -20,13 +20,22 @@ export interface ConfirmExpectation {
20
20
  */
21
21
  export function mockConfirm(...expectations: ConfirmExpectation[]) {
22
22
  (confirm as jest.Mock).mockImplementation((text: string) => {
23
- for (const { text: expectedText, result } of expectations) {
24
- if (normalizeSlashes(text) === normalizeSlashes(expectedText)) {
25
- return Promise.resolve(result);
23
+ for (const expectation of expectations) {
24
+ if (normalizeSlashes(text) === normalizeSlashes(expectation.text)) {
25
+ expectations = expectations.filter((e) => e !== expectation);
26
+ return Promise.resolve(expectation.result);
26
27
  }
27
28
  }
28
29
  throw new Error(`Unexpected confirmation message: ${text}`);
29
30
  });
31
+ return () => {
32
+ if (expectations.length > 0) {
33
+ throw new Error(
34
+ "The following expected confirmation dialogs were not used:\n" +
35
+ expectations.map((e) => `- "${e.text}"`).join("\n")
36
+ );
37
+ }
38
+ };
30
39
  }
31
40
 
32
41
  export function clearConfirmMocks() {
@@ -63,18 +72,23 @@ export interface PromptExpectation {
63
72
  export function mockPrompt(...expectations: PromptExpectation[]) {
64
73
  (prompt as jest.Mock).mockImplementation(
65
74
  (text: string, type: "text" | "password") => {
66
- for (const {
67
- text: expectedText,
68
- type: expectedType,
69
- result,
70
- } of expectations) {
71
- if (text === expectedText && type == expectedType) {
72
- return Promise.resolve(result);
75
+ for (const expectation of expectations) {
76
+ if (text === expectation.text && type == expectation.type) {
77
+ expectations = expectations.filter((e) => e !== expectation);
78
+ return Promise.resolve(expectation.result);
73
79
  }
74
80
  }
75
81
  throw new Error(`Unexpected confirmation message: ${text}`);
76
82
  }
77
83
  );
84
+ return () => {
85
+ if (expectations.length > 0) {
86
+ throw new Error(
87
+ "The following expected prompt dialogs were not used:\n" +
88
+ expectations.map((e) => `- "${e.text}"`).join("\n")
89
+ );
90
+ }
91
+ };
78
92
  }
79
93
 
80
94
  export function clearPromptMocks() {
@@ -108,13 +122,22 @@ export interface SelectExpectation {
108
122
  */
109
123
  export function mockSelect(...expectations: SelectExpectation[]) {
110
124
  (select as jest.Mock).mockImplementation((text: string) => {
111
- for (const { text: expectedText, result } of expectations) {
112
- if (normalizeSlashes(text) === normalizeSlashes(expectedText)) {
113
- return Promise.resolve(result);
125
+ for (const expectation of expectations) {
126
+ if (normalizeSlashes(text) === normalizeSlashes(expectation.text)) {
127
+ expectations = expectations.filter((e) => e !== expectation);
128
+ return Promise.resolve(expectation.result);
114
129
  }
115
130
  }
116
131
  throw new Error(`Unexpected select message: ${text}`);
117
132
  });
133
+ return () => {
134
+ if (expectations.length > 0) {
135
+ throw new Error(
136
+ "The following expected select dialogs were not used:\n" +
137
+ expectations.map((e) => `- "${e.text}"`).join("\n")
138
+ );
139
+ }
140
+ };
118
141
  }
119
142
 
120
143
  export function clearSelectMocks() {
@@ -8,9 +8,14 @@ export function useMockIsTTY() {
8
8
  /**
9
9
  * Explicitly set `process.stdout.isTTY` to a given value
10
10
  */
11
- const setIsTTY = (isTTY: boolean) => {
12
- process.stdout.isTTY = isTTY;
13
- process.stdin.isTTY = isTTY;
11
+ const setIsTTY = (isTTY: boolean | { stdin: boolean; stdout: boolean }) => {
12
+ if (typeof isTTY === "boolean") {
13
+ process.stdout.isTTY = isTTY;
14
+ process.stdin.isTTY = isTTY;
15
+ } else {
16
+ process.stdin.isTTY = isTTY.stdin;
17
+ process.stdout.isTTY = isTTY.stdout;
18
+ }
14
19
  };
15
20
 
16
21
  beforeEach(() => {
@@ -1,9 +1,27 @@
1
1
  import fetchMock from "jest-fetch-mock";
2
2
  import { Request } from "undici";
3
- import { getCloudflareApiBaseUrl } from "../../cfetch";
4
3
  import openInBrowser from "../../open-in-browser";
4
+ import {
5
+ createFetchResult,
6
+ setMockRawResponse,
7
+ setMockResponse,
8
+ } from "./mock-cfetch";
5
9
  import { mockHttpServer } from "./mock-http-server";
6
10
 
11
+ export function mockGetMemberships(
12
+ accounts: { id: string; account: { id: string; name: string } }[]
13
+ ) {
14
+ setMockResponse("/memberships", "GET", () => {
15
+ return accounts;
16
+ });
17
+ }
18
+
19
+ export function mockGetMembershipsFail() {
20
+ setMockRawResponse("/memberships", () => {
21
+ return createFetchResult([], false);
22
+ });
23
+ }
24
+
7
25
  // the response to send when wrangler wants an oauth grant
8
26
  let oauthGrantResponse: GrantResponseOptions | "timeout" = {};
9
27
 
@@ -86,28 +104,6 @@ export const mockOAuthFlow = () => {
86
104
  return outcome;
87
105
  };
88
106
 
89
- const mockGetMemberships = (args: {
90
- success: boolean;
91
- result: { id: string; account: { id: string; name: string } }[];
92
- }) => {
93
- const outcome = {
94
- actual: new Request("https://example.org"),
95
- expected: new Request(`${getCloudflareApiBaseUrl()}/memberships`, {
96
- method: "GET",
97
- }),
98
- };
99
-
100
- fetchMock.mockIf(outcome.expected.url, async (req) => {
101
- outcome.actual = req;
102
- return {
103
- status: 200,
104
- body: JSON.stringify(args),
105
- };
106
- });
107
-
108
- return outcome;
109
- };
110
-
111
107
  const mockGrantAccessToken = ({
112
108
  respondWith,
113
109
  }: {
@@ -173,7 +169,6 @@ export const mockOAuthFlow = () => {
173
169
  };
174
170
 
175
171
  return {
176
- mockGetMemberships,
177
172
  mockGrantAccessToken,
178
173
  mockGrantAuthorization,
179
174
  mockOAuthServerCallback,
@@ -0,0 +1,39 @@
1
+ /**
2
+ * We use this module to mock process methods (write only for now),
3
+ * and optionally assert on the values they're called with in our tests.
4
+ */
5
+
6
+ let writeSpy: jest.SpyInstance;
7
+
8
+ function captureLastWriteCall(spy: jest.SpyInstance): Buffer {
9
+ const calls = spy.mock.calls;
10
+ if (calls.length > 1) {
11
+ throw new Error(
12
+ "Unexpected calls to `stdout.write()`: " + JSON.stringify(calls)
13
+ );
14
+ }
15
+ const buffer = calls[0]?.[0] ?? Buffer.alloc(0);
16
+ if (buffer instanceof Buffer) {
17
+ return buffer;
18
+ } else {
19
+ throw new Error(
20
+ `Unexpected non-Buffer passed to \`stdout.write()\`: "${JSON.stringify(
21
+ buffer
22
+ )}"`
23
+ );
24
+ }
25
+ }
26
+
27
+ export function mockProcess() {
28
+ beforeEach(() => {
29
+ writeSpy = jest.spyOn(process.stdout, "write").mockImplementation();
30
+ });
31
+ afterEach(() => {
32
+ writeSpy.mockRestore();
33
+ });
34
+ return {
35
+ get write() {
36
+ return captureLastWriteCall(writeSpy);
37
+ },
38
+ };
39
+ }
@@ -100,4 +100,9 @@ class MockStdIn {
100
100
  }
101
101
  return this.chunks.shift() ?? null;
102
102
  }
103
+
104
+ /**
105
+ * Used by Ink.js to set the encoding of the stdout.
106
+ */
107
+ setEncoding() {}
103
108
  }
@@ -247,7 +247,8 @@ describe("wrangler", () => {
247
247
  Attempting to run \`wrangler publish --dry-run --outdir=dist\` for you instead:
248
248
 
249
249
 
250
- --dry-run: exiting now."
250
+ --dry-run: exiting now.
251
+ Total Upload: 0xx KiB / gzip: 0xx KiB"
251
252
  `);
252
253
  });
253
254
  });