wrangler 2.0.12 → 2.0.16

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 (149) hide show
  1. package/README.md +7 -1
  2. package/bin/wrangler.js +111 -57
  3. package/miniflare-dist/index.mjs +9 -2
  4. package/package.json +156 -154
  5. package/src/__tests__/config-cache-without-cache-dir.test.ts +38 -0
  6. package/src/__tests__/config-cache.test.ts +30 -24
  7. package/src/__tests__/configuration.test.ts +3935 -3476
  8. package/src/__tests__/dev.test.tsx +1128 -979
  9. package/src/__tests__/guess-worker-format.test.ts +68 -68
  10. package/src/__tests__/helpers/cmd-shim.d.ts +6 -6
  11. package/src/__tests__/helpers/faye-websocket.d.ts +4 -4
  12. package/src/__tests__/helpers/mock-account-id.ts +24 -24
  13. package/src/__tests__/helpers/mock-bin.ts +20 -20
  14. package/src/__tests__/helpers/mock-cfetch.ts +92 -92
  15. package/src/__tests__/helpers/mock-console.ts +49 -39
  16. package/src/__tests__/helpers/mock-dialogs.ts +94 -71
  17. package/src/__tests__/helpers/mock-http-server.ts +30 -30
  18. package/src/__tests__/helpers/mock-istty.ts +65 -18
  19. package/src/__tests__/helpers/mock-kv.ts +26 -26
  20. package/src/__tests__/helpers/mock-oauth-flow.ts +223 -228
  21. package/src/__tests__/helpers/mock-process.ts +39 -0
  22. package/src/__tests__/helpers/mock-stdin.ts +82 -77
  23. package/src/__tests__/helpers/mock-web-socket.ts +21 -21
  24. package/src/__tests__/helpers/run-in-tmp.ts +27 -27
  25. package/src/__tests__/helpers/run-wrangler.ts +8 -8
  26. package/src/__tests__/helpers/write-worker-source.ts +16 -16
  27. package/src/__tests__/helpers/write-wrangler-toml.ts +9 -9
  28. package/src/__tests__/https-options.test.ts +104 -104
  29. package/src/__tests__/index.test.ts +239 -234
  30. package/src/__tests__/init.test.ts +1605 -1250
  31. package/src/__tests__/jest.setup.ts +63 -33
  32. package/src/__tests__/kv.test.ts +1128 -1011
  33. package/src/__tests__/logger.test.ts +100 -74
  34. package/src/__tests__/package-manager.test.ts +303 -303
  35. package/src/__tests__/pages.test.ts +1152 -652
  36. package/src/__tests__/parse.test.ts +252 -252
  37. package/src/__tests__/publish.test.ts +6371 -5622
  38. package/src/__tests__/pubsub.test.ts +367 -0
  39. package/src/__tests__/r2.test.ts +133 -133
  40. package/src/__tests__/route.test.ts +18 -18
  41. package/src/__tests__/secret.test.ts +382 -377
  42. package/src/__tests__/tail.test.ts +530 -530
  43. package/src/__tests__/user.test.ts +123 -111
  44. package/src/__tests__/whoami.test.tsx +198 -117
  45. package/src/__tests__/worker-namespace.test.ts +327 -0
  46. package/src/abort.d.ts +1 -1
  47. package/src/api/dev.ts +49 -0
  48. package/src/api/index.ts +1 -0
  49. package/src/bundle-reporter.tsx +29 -0
  50. package/src/bundle.ts +157 -149
  51. package/src/cfetch/index.ts +80 -80
  52. package/src/cfetch/internal.ts +90 -83
  53. package/src/cli.ts +21 -7
  54. package/src/config/config.ts +204 -195
  55. package/src/config/diagnostics.ts +61 -61
  56. package/src/config/environment.ts +390 -357
  57. package/src/config/index.ts +206 -193
  58. package/src/config/validation-helpers.ts +366 -366
  59. package/src/config/validation.ts +1573 -1376
  60. package/src/config-cache.ts +79 -41
  61. package/src/create-worker-preview.ts +206 -136
  62. package/src/create-worker-upload-form.ts +247 -238
  63. package/src/dev/dev-vars.ts +13 -13
  64. package/src/dev/dev.tsx +329 -307
  65. package/src/dev/local.tsx +304 -275
  66. package/src/dev/remote.tsx +366 -224
  67. package/src/dev/use-esbuild.ts +126 -91
  68. package/src/dev.tsx +538 -0
  69. package/src/dialogs.tsx +97 -97
  70. package/src/durable.ts +87 -87
  71. package/src/entry.ts +234 -228
  72. package/src/environment-variables.ts +23 -23
  73. package/src/errors.ts +6 -6
  74. package/src/generate.ts +33 -0
  75. package/src/git-client.ts +42 -0
  76. package/src/https-options.ts +79 -79
  77. package/src/index.tsx +1775 -2763
  78. package/src/init.ts +549 -0
  79. package/src/inspect.ts +593 -593
  80. package/src/intl-polyfill.d.ts +123 -123
  81. package/src/is-interactive.ts +12 -0
  82. package/src/kv.ts +277 -277
  83. package/src/logger.ts +46 -39
  84. package/src/miniflare-cli/enum-keys.ts +8 -8
  85. package/src/miniflare-cli/index.ts +42 -31
  86. package/src/miniflare-cli/request-context.ts +18 -18
  87. package/src/module-collection.ts +212 -212
  88. package/src/open-in-browser.ts +4 -6
  89. package/src/package-manager.ts +123 -123
  90. package/src/pages/build.tsx +202 -0
  91. package/src/pages/constants.ts +7 -0
  92. package/src/pages/deployments.tsx +101 -0
  93. package/src/pages/dev.tsx +964 -0
  94. package/src/pages/functions/buildPlugin.ts +105 -0
  95. package/src/pages/functions/buildWorker.ts +151 -0
  96. package/{pages → src/pages}/functions/filepath-routing.test.ts +113 -113
  97. package/src/pages/functions/filepath-routing.ts +189 -0
  98. package/src/pages/functions/identifiers.ts +78 -0
  99. package/src/pages/functions/routes.ts +151 -0
  100. package/src/pages/index.tsx +84 -0
  101. package/src/pages/projects.tsx +157 -0
  102. package/src/pages/publish.tsx +335 -0
  103. package/src/pages/types.ts +40 -0
  104. package/src/pages/upload.tsx +384 -0
  105. package/src/pages/utils.ts +12 -0
  106. package/src/parse.ts +202 -138
  107. package/src/paths.ts +6 -6
  108. package/src/preview.ts +31 -0
  109. package/src/proxy.ts +400 -402
  110. package/src/publish.ts +667 -621
  111. package/src/pubsub/index.ts +286 -0
  112. package/src/pubsub/pubsub-commands.tsx +577 -0
  113. package/src/r2.ts +19 -19
  114. package/src/selfsigned.d.ts +23 -23
  115. package/src/sites.tsx +271 -225
  116. package/src/tail/filters.ts +108 -108
  117. package/src/tail/index.ts +217 -217
  118. package/src/tail/printing.ts +45 -45
  119. package/src/update-check.ts +11 -11
  120. package/src/user/choose-account.tsx +60 -0
  121. package/src/user/env-vars.ts +46 -0
  122. package/src/user/generate-auth-url.ts +33 -0
  123. package/src/user/generate-random-state.ts +16 -0
  124. package/src/user/index.ts +3 -0
  125. package/src/user/user.tsx +1161 -0
  126. package/src/whoami.tsx +61 -42
  127. package/src/worker-namespace.ts +190 -0
  128. package/src/worker.ts +110 -100
  129. package/src/zones.ts +39 -36
  130. package/templates/checked-fetch.js +17 -0
  131. package/templates/new-worker-scheduled.js +3 -3
  132. package/templates/new-worker-scheduled.ts +15 -15
  133. package/templates/new-worker.js +3 -3
  134. package/templates/new-worker.ts +15 -15
  135. package/templates/no-op-worker.js +10 -0
  136. package/templates/pages-template-plugin.ts +155 -0
  137. package/templates/pages-template-worker.ts +161 -0
  138. package/templates/static-asset-facade.js +31 -31
  139. package/templates/tsconfig.json +95 -95
  140. package/wrangler-dist/cli.js +55383 -54138
  141. package/pages/functions/buildPlugin.ts +0 -105
  142. package/pages/functions/buildWorker.ts +0 -151
  143. package/pages/functions/filepath-routing.ts +0 -189
  144. package/pages/functions/identifiers.ts +0 -78
  145. package/pages/functions/routes.ts +0 -156
  146. package/pages/functions/template-plugin.ts +0 -147
  147. package/pages/functions/template-worker.ts +0 -143
  148. package/src/pages.tsx +0 -2093
  149. package/src/user.tsx +0 -1214
@@ -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
 
@@ -11,245 +29,222 @@ let oauthGrantResponse: GrantResponseOptions | "timeout" = {};
11
29
  * Functions to help with mocking various parts of the OAuth Flow
12
30
  */
13
31
  export const mockOAuthFlow = () => {
14
- const fetchHttp = mockHttpServer();
15
-
16
- afterEach(() => {
17
- fetchMock.resetMocks();
18
- });
19
-
20
- /**
21
- * Mock out the callback from a browser to our HttpServer.
22
- *
23
- * This function will override `openInBrowser()` so that instead of opening a browser window
24
- * at the OAuth URL, it will automatically trigger the callback URL on the mock HttpServer that
25
- * we have created as part of the call to `mockOAuthFlow()`.
26
- */
27
- const mockOAuthServerCallback = () => {
28
- (
29
- openInBrowser as jest.MockedFunction<typeof openInBrowser>
30
- ).mockImplementation(async (url: string) => {
31
- // We don't support the grant response timing out.
32
- if (oauthGrantResponse === "timeout") {
33
- throw "unimplemented";
34
- }
35
-
36
- // Create a fake callback request that would be created by the OAuth server
37
- const { searchParams } = new URL(url);
38
- const queryParams = toQueryParams(oauthGrantResponse, searchParams);
39
- const request = new Request(
40
- `${searchParams.get("redirect_uri")}?${queryParams}`
41
- );
42
-
43
- // Trigger the mock HttpServer to handle this fake request to continue the OAuth flow.
44
- fetchHttp(request).catch((e) => {
45
- throw new Error(
46
- "Failed to send OAuth Grant to wrangler, maybe the server was closed?",
47
- e as Error
48
- );
49
- });
50
- });
51
- };
52
-
53
- const mockGrantAuthorization = ({
54
- respondWith,
55
- }: {
56
- respondWith: "timeout" | "success" | "failure" | GrantResponseOptions;
57
- }) => {
58
- if (respondWith === "failure") {
59
- oauthGrantResponse = {
60
- error: "access_denied",
61
- };
62
- } else if (respondWith === "success") {
63
- oauthGrantResponse = {
64
- code: "test-oauth-code",
65
- };
66
- } else if (respondWith === "timeout") {
67
- oauthGrantResponse = "timeout";
68
- } else {
69
- oauthGrantResponse = respondWith;
70
- }
71
- };
72
-
73
- const mockRevokeAuthorization = () => {
74
- const outcome = {
75
- actual: new Request("https://example.org"),
76
- expected: new Request("https://dash.cloudflare.com/oauth2/revoke", {
77
- method: "POST",
78
- }),
79
- };
80
-
81
- fetchMock.mockIf(outcome.expected.url, async (req) => {
82
- outcome.actual = req;
83
- return "";
84
- });
85
-
86
- return outcome;
87
- };
88
-
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
- const mockGrantAccessToken = ({
112
- respondWith,
113
- }: {
114
- respondWith: MockTokenResponse;
115
- }) => {
116
- const outcome = {
117
- actual: new Request("https://example.org"),
118
- expected: new Request("https://dash.cloudflare.com/oauth2/token", {
119
- method: "POST",
120
- }),
121
- };
122
-
123
- fetchMock.mockOnceIf(outcome.expected.url, async (req) => {
124
- outcome.actual = req;
125
- return makeTokenResponse(respondWith);
126
- });
127
-
128
- return outcome;
129
- };
130
-
131
- const mockExchangeRefreshTokenForAccessToken = ({
132
- respondWith,
133
- }: {
134
- respondWith: "refreshSuccess" | "refreshError" | "badResponse";
135
- }) => {
136
- fetchMock.mockOnceIf(
137
- "https://dash.cloudflare.com/oauth2/token",
138
- async () => {
139
- switch (respondWith) {
140
- case "refreshSuccess":
141
- return {
142
- status: 200,
143
- body: JSON.stringify({
144
- access_token: "access_token_success_mock",
145
- expires_in: 1701,
146
- refresh_token: "refresh_token_sucess_mock",
147
- scope: "scope_success_mock",
148
- token_type: "bearer",
149
- }),
150
- };
151
- case "refreshError":
152
- return {
153
- status: 400,
154
- body: JSON.stringify({
155
- error: "invalid_request",
156
- error_description: "error_description_mock",
157
- error_hint: "error_hint_mock",
158
- error_verbose: "error_verbose_mock",
159
- status_code: 400,
160
- }),
161
- };
162
- case "badResponse":
163
- return {
164
- status: 400,
165
- body: `<html> <body> This shouldn't be sent, but should be handled </body> </html>`,
166
- };
167
-
168
- default:
169
- return "Not a respondWith option for `mockExchangeRefreshTokenForAccessToken`";
170
- }
171
- }
172
- );
173
- };
174
-
175
- return {
176
- mockGetMemberships,
177
- mockGrantAccessToken,
178
- mockGrantAuthorization,
179
- mockOAuthServerCallback,
180
- mockRevokeAuthorization,
181
- mockExchangeRefreshTokenForAccessToken,
182
- };
32
+ const fetchHttp = mockHttpServer();
33
+
34
+ afterEach(() => {
35
+ fetchMock.resetMocks();
36
+ });
37
+
38
+ /**
39
+ * Mock out the callback from a browser to our HttpServer.
40
+ *
41
+ * This function will override `openInBrowser()` so that instead of opening a browser window
42
+ * at the OAuth URL, it will automatically trigger the callback URL on the mock HttpServer that
43
+ * we have created as part of the call to `mockOAuthFlow()`.
44
+ */
45
+ const mockOAuthServerCallback = () => {
46
+ (
47
+ openInBrowser as jest.MockedFunction<typeof openInBrowser>
48
+ ).mockImplementation(async (url: string) => {
49
+ // We don't support the grant response timing out.
50
+ if (oauthGrantResponse === "timeout") {
51
+ throw "unimplemented";
52
+ }
53
+
54
+ // Create a fake callback request that would be created by the OAuth server
55
+ const { searchParams } = new URL(url);
56
+ const queryParams = toQueryParams(oauthGrantResponse, searchParams);
57
+ const request = new Request(
58
+ `${searchParams.get("redirect_uri")}?${queryParams}`
59
+ );
60
+
61
+ // Trigger the mock HttpServer to handle this fake request to continue the OAuth flow.
62
+ fetchHttp(request).catch((e) => {
63
+ throw new Error(
64
+ "Failed to send OAuth Grant to wrangler, maybe the server was closed?",
65
+ e as Error
66
+ );
67
+ });
68
+ });
69
+ };
70
+
71
+ const mockGrantAuthorization = ({
72
+ respondWith,
73
+ }: {
74
+ respondWith: "timeout" | "success" | "failure" | GrantResponseOptions;
75
+ }) => {
76
+ if (respondWith === "failure") {
77
+ oauthGrantResponse = {
78
+ error: "access_denied",
79
+ };
80
+ } else if (respondWith === "success") {
81
+ oauthGrantResponse = {
82
+ code: "test-oauth-code",
83
+ };
84
+ } else if (respondWith === "timeout") {
85
+ oauthGrantResponse = "timeout";
86
+ } else {
87
+ oauthGrantResponse = respondWith;
88
+ }
89
+ };
90
+
91
+ const mockRevokeAuthorization = () => {
92
+ const outcome = {
93
+ actual: new Request("https://example.org"),
94
+ expected: new Request("https://dash.cloudflare.com/oauth2/revoke", {
95
+ method: "POST",
96
+ }),
97
+ };
98
+
99
+ fetchMock.mockIf(outcome.expected.url, async (req) => {
100
+ outcome.actual = req;
101
+ return "";
102
+ });
103
+
104
+ return outcome;
105
+ };
106
+
107
+ const mockGrantAccessToken = ({
108
+ respondWith,
109
+ }: {
110
+ respondWith: MockTokenResponse;
111
+ }) => {
112
+ const outcome = {
113
+ actual: new Request("https://example.org"),
114
+ expected: new Request("https://dash.cloudflare.com/oauth2/token", {
115
+ method: "POST",
116
+ }),
117
+ };
118
+
119
+ fetchMock.mockOnceIf(outcome.expected.url, async (req) => {
120
+ outcome.actual = req;
121
+ return makeTokenResponse(respondWith);
122
+ });
123
+
124
+ return outcome;
125
+ };
126
+
127
+ const mockExchangeRefreshTokenForAccessToken = ({
128
+ respondWith,
129
+ }: {
130
+ respondWith: "refreshSuccess" | "refreshError" | "badResponse";
131
+ }) => {
132
+ fetchMock.mockOnceIf(
133
+ "https://dash.cloudflare.com/oauth2/token",
134
+ async () => {
135
+ switch (respondWith) {
136
+ case "refreshSuccess":
137
+ return {
138
+ status: 200,
139
+ body: JSON.stringify({
140
+ access_token: "access_token_success_mock",
141
+ expires_in: 1701,
142
+ refresh_token: "refresh_token_sucess_mock",
143
+ scope: "scope_success_mock",
144
+ token_type: "bearer",
145
+ }),
146
+ };
147
+ case "refreshError":
148
+ return {
149
+ status: 400,
150
+ body: JSON.stringify({
151
+ error: "invalid_request",
152
+ error_description: "error_description_mock",
153
+ error_hint: "error_hint_mock",
154
+ error_verbose: "error_verbose_mock",
155
+ status_code: 400,
156
+ }),
157
+ };
158
+ case "badResponse":
159
+ return {
160
+ status: 400,
161
+ body: `<html> <body> This shouldn't be sent, but should be handled </body> </html>`,
162
+ };
163
+
164
+ default:
165
+ return "Not a respondWith option for `mockExchangeRefreshTokenForAccessToken`";
166
+ }
167
+ }
168
+ );
169
+ };
170
+
171
+ return {
172
+ mockGrantAccessToken,
173
+ mockGrantAuthorization,
174
+ mockOAuthServerCallback,
175
+ mockRevokeAuthorization,
176
+ mockExchangeRefreshTokenForAccessToken,
177
+ };
183
178
  };
184
179
 
185
180
  type GrantResponseOptions = {
186
- code?: string;
187
- error?: ErrorType | ErrorType[];
181
+ code?: string;
182
+ error?: ErrorType | ErrorType[];
188
183
  };
189
184
 
190
185
  const toQueryParams = (
191
- { code, error }: GrantResponseOptions,
192
- wranglerRequestParams: URLSearchParams
186
+ { code, error }: GrantResponseOptions,
187
+ wranglerRequestParams: URLSearchParams
193
188
  ): string => {
194
- const queryParams = [];
195
- if (code) {
196
- queryParams.push(`code=${code}`);
197
- }
198
- if (error) {
199
- const stringifiedErr = Array.isArray(error) ? error.join(",") : error;
200
- queryParams.push(`error=${stringifiedErr}`);
201
- }
202
-
203
- queryParams.push(`state=${wranglerRequestParams.get("state")}`);
204
-
205
- return queryParams.join("&");
189
+ const queryParams = [];
190
+ if (code) {
191
+ queryParams.push(`code=${code}`);
192
+ }
193
+ if (error) {
194
+ const stringifiedErr = Array.isArray(error) ? error.join(",") : error;
195
+ queryParams.push(`error=${stringifiedErr}`);
196
+ }
197
+
198
+ queryParams.push(`state=${wranglerRequestParams.get("state")}`);
199
+
200
+ return queryParams.join("&");
206
201
  };
207
202
 
208
203
  type ErrorType =
209
- | "invalid_request"
210
- | "invalid_grant"
211
- | "unauthorized_client"
212
- | "access_denied"
213
- | "unsupported_response_type"
214
- | "invalid_scope"
215
- | "server_error"
216
- | "temporarily_unavailable"
217
- | "invalid_client"
218
- | "unsupported_grant_type"
219
- | "invalid_json"
220
- | "invalid_token"
221
- | "test-token-grant-error";
204
+ | "invalid_request"
205
+ | "invalid_grant"
206
+ | "unauthorized_client"
207
+ | "access_denied"
208
+ | "unsupported_response_type"
209
+ | "invalid_scope"
210
+ | "server_error"
211
+ | "temporarily_unavailable"
212
+ | "invalid_client"
213
+ | "unsupported_grant_type"
214
+ | "invalid_json"
215
+ | "invalid_token"
216
+ | "test-token-grant-error";
222
217
 
223
218
  type MockTokenResponse =
224
- | "ok"
225
- | "error"
226
- | {
227
- access_token: string;
228
- expires_in: number;
229
- refresh_token: string;
230
- scope: string;
231
- }
232
- | {
233
- error: ErrorType;
234
- };
219
+ | "ok"
220
+ | "error"
221
+ | {
222
+ access_token: string;
223
+ expires_in: number;
224
+ refresh_token: string;
225
+ scope: string;
226
+ }
227
+ | {
228
+ error: ErrorType;
229
+ };
235
230
 
236
231
  const makeTokenResponse = (partialResponse: MockTokenResponse): string => {
237
- let fullResponse: MockTokenResponse;
238
-
239
- if (partialResponse === "ok") {
240
- fullResponse = {
241
- access_token: "test-access-token",
242
- expires_in: 100000,
243
- refresh_token: "test-refresh-token",
244
- scope: "account:read",
245
- };
246
- } else if (partialResponse === "error") {
247
- fullResponse = {
248
- error: "test-token-grant-error",
249
- };
250
- } else {
251
- fullResponse = partialResponse;
252
- }
253
-
254
- return JSON.stringify(fullResponse);
232
+ let fullResponse: MockTokenResponse;
233
+
234
+ if (partialResponse === "ok") {
235
+ fullResponse = {
236
+ access_token: "test-access-token",
237
+ expires_in: 100000,
238
+ refresh_token: "test-refresh-token",
239
+ scope: "account:read",
240
+ };
241
+ } else if (partialResponse === "error") {
242
+ fullResponse = {
243
+ error: "test-token-grant-error",
244
+ };
245
+ } else {
246
+ fullResponse = partialResponse;
247
+ }
248
+
249
+ return JSON.stringify(fullResponse);
255
250
  };
@@ -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
+ }