wrangler 2.0.29 → 2.1.2

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 (45) hide show
  1. package/miniflare-dist/index.mjs +1136 -372
  2. package/package.json +3 -2
  3. package/src/__tests__/helpers/mock-cfetch.ts +39 -19
  4. package/src/__tests__/helpers/mock-console.ts +11 -2
  5. package/src/__tests__/helpers/msw/handlers/index.ts +13 -0
  6. package/src/__tests__/helpers/msw/handlers/namespaces.ts +104 -0
  7. package/src/__tests__/helpers/msw/handlers/oauth.ts +36 -0
  8. package/src/__tests__/helpers/msw/handlers/r2.ts +80 -0
  9. package/src/__tests__/helpers/msw/handlers/user.ts +63 -0
  10. package/src/__tests__/helpers/msw/index.ts +4 -0
  11. package/src/__tests__/index.test.ts +9 -7
  12. package/src/__tests__/init.test.ts +356 -5
  13. package/src/__tests__/jest.setup.ts +16 -0
  14. package/src/__tests__/middleware.test.ts +768 -0
  15. package/src/__tests__/pages.test.ts +11 -12
  16. package/src/__tests__/publish.test.ts +516 -438
  17. package/src/__tests__/r2.test.ts +128 -93
  18. package/src/__tests__/secret.test.ts +78 -0
  19. package/src/__tests__/tail.test.ts +47 -74
  20. package/src/__tests__/whoami.test.tsx +49 -64
  21. package/src/api/dev.ts +23 -4
  22. package/src/bundle.ts +225 -1
  23. package/src/dev/dev.tsx +3 -1
  24. package/src/dev/local.tsx +2 -2
  25. package/src/dev/remote.tsx +6 -3
  26. package/src/dev/start-server.ts +11 -7
  27. package/src/dev/use-esbuild.ts +4 -0
  28. package/src/dev.tsx +6 -16
  29. package/src/dialogs.tsx +12 -0
  30. package/src/index.tsx +95 -4
  31. package/src/init.ts +286 -11
  32. package/src/miniflare-cli/assets.ts +130 -415
  33. package/src/miniflare-cli/index.ts +3 -1
  34. package/src/pages/dev.tsx +5 -1
  35. package/src/pages/hash.tsx +13 -0
  36. package/src/pages/upload.tsx +3 -18
  37. package/src/publish.ts +38 -4
  38. package/src/tail/filters.ts +1 -5
  39. package/src/tail/index.ts +6 -3
  40. package/templates/middleware/common.ts +62 -0
  41. package/templates/middleware/loader-modules.ts +84 -0
  42. package/templates/middleware/loader-sw.ts +213 -0
  43. package/templates/middleware/middleware-pretty-error.ts +40 -0
  44. package/templates/middleware/middleware-scheduled.ts +14 -0
  45. package/wrangler-dist/cli.js +65900 -65432
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wrangler",
3
- "version": "2.0.29",
3
+ "version": "2.1.2",
4
4
  "description": "Command-line interface for all things Cloudflare Workers",
5
5
  "keywords": [
6
6
  "wrangler",
@@ -97,7 +97,7 @@
97
97
  "blake3-wasm": "^2.1.5",
98
98
  "chokidar": "^3.5.3",
99
99
  "esbuild": "0.14.51",
100
- "miniflare": "^2.7.1",
100
+ "miniflare": "^2.8.1",
101
101
  "nanoid": "^3.3.3",
102
102
  "path-to-regexp": "^6.2.0",
103
103
  "selfsigned": "^2.0.1",
@@ -148,6 +148,7 @@
148
148
  "jest-fetch-mock": "^3.0.3",
149
149
  "jest-websocket-mock": "^2.3.0",
150
150
  "mime": "^3.0.0",
151
+ "msw": "^0.47.1",
151
152
  "open": "^8.4.0",
152
153
  "p-queue": "^7.2.0",
153
154
  "pretty-bytes": "^6.0.0",
@@ -4,8 +4,24 @@ import { pathToRegexp } from "path-to-regexp";
4
4
  import { Response } from "undici";
5
5
  import { getCloudflareApiBaseUrl } from "../../cfetch";
6
6
  import type { FetchResult, FetchError } from "../../cfetch";
7
+ import type { fetchInternal, fetchR2Objects } from "../../cfetch/internal";
7
8
  import type { RequestInit, BodyInit, HeadersInit } from "undici";
8
9
 
10
+ /**
11
+ * When the custom mocks fallthrough in tests because they aren't set, instead of throwing an error,
12
+ * we use real fetch to make the request which will use Mock Service Workers.
13
+ */
14
+ const {
15
+ fetchInternal: realFetchInternal,
16
+ }: { fetchInternal: typeof fetchInternal } = jest.requireActual(
17
+ "../../cfetch/internal"
18
+ );
19
+ const {
20
+ fetchR2Objects: realFetchR2Objects,
21
+ }: { fetchR2Objects: typeof fetchR2Objects } = jest.requireActual(
22
+ "../../cfetch/internal"
23
+ );
24
+
9
25
  /**
10
26
  * The signature of the function that will handle a mock request.
11
27
  */
@@ -34,7 +50,7 @@ const mocks: MockFetch<unknown>[] = [];
34
50
  export async function mockFetchInternal(
35
51
  resource: string,
36
52
  init: RequestInit = {},
37
- queryParams: URLSearchParams = new URLSearchParams()
53
+ queryParams: URLSearchParams | undefined
38
54
  ) {
39
55
  for (const { regexp, method, handler } of mocks) {
40
56
  const resourcePath = new URL(resource, getCloudflareApiBaseUrl()).pathname;
@@ -43,12 +59,13 @@ export async function mockFetchInternal(
43
59
  if (uri !== null && (!method || method === (init.method ?? "GET"))) {
44
60
  // The `resource` regular expression will extract the labelled groups from the URL.
45
61
  // These are passed through to the `handler` call, to allow it to do additional checks or behaviour.
46
- return await handler(uri, init, queryParams); // TODO: should we have some kind of fallthrough system? we'll see.
62
+ return await handler(uri, init, queryParams ?? new URLSearchParams()); // TODO: should we have some kind of fallthrough system? we'll see.
47
63
  }
48
64
  }
49
- throw new Error(
50
- `no mocks found for ${init.method ?? "any HTTP"} request to ${resource}`
51
- );
65
+
66
+ // let it fall through to mock-service-worker
67
+ // (do a real, unmocked fetch)
68
+ return await realFetchInternal(resource, init, queryParams);
52
69
  }
53
70
 
54
71
  /**
@@ -221,21 +238,24 @@ export async function mockFetchR2Objects(
221
238
  method: "PUT" | "GET" | "DELETE";
222
239
  }
223
240
  ): Promise<Response> {
224
- /**
225
- * Here we destroy & removeListeners to "drain" the stream, for testing purposes
226
- * mimicking the fetch request taking in the stream and draining it.
227
- */
228
- if (bodyInit.body instanceof Readable) {
229
- bodyInit.body.destroy();
230
- bodyInit.body.removeAllListeners();
231
- }
232
-
233
241
  if (r2GetMocks.has(resource)) {
242
+ /**
243
+ * Here we destroy & removeListeners to "drain" the stream, for testing purposes
244
+ * mimicking the fetch request taking in the stream and draining it.
245
+ */
246
+ if (bodyInit.body instanceof Readable) {
247
+ bodyInit.body.destroy();
248
+ bodyInit.body.removeAllListeners();
249
+ }
250
+
234
251
  const value = r2GetMocks.get(resource);
235
252
 
236
253
  return new Response(value);
237
254
  }
238
- throw new Error(`no mock found for \`r2 object\` - ${resource}`);
255
+
256
+ // No mocks found for ${init.method ?? "any HTTP"} request to ${resource}
257
+ // let it fall through to Mock Service Worker with a real fetch.
258
+ return await realFetchR2Objects(resource, bodyInit);
239
259
  }
240
260
 
241
261
  /**
@@ -270,9 +290,7 @@ export function unsetSpecialMockFns() {
270
290
  */
271
291
  export async function mockFetchDashScript(resource: string): Promise<string> {
272
292
  if (dashScriptMocks.has(resource)) {
273
- const value = dashScriptMocks.get(resource) ?? "";
274
-
275
- return value;
293
+ return dashScriptMocks.get(resource) ?? "";
276
294
  }
277
295
  throw new Error(`no mock found for \`init from-dash\` - ${resource}`);
278
296
  }
@@ -283,14 +301,16 @@ export async function mockFetchDashScript(resource: string): Promise<string> {
283
301
  export function setMockFetchDashScript({
284
302
  accountId,
285
303
  fromDashScriptName,
304
+ environment,
286
305
  mockResponse,
287
306
  }: {
288
307
  accountId: string;
289
308
  fromDashScriptName: string;
309
+ environment: string;
290
310
  mockResponse?: string;
291
311
  }) {
292
312
  dashScriptMocks.set(
293
- `/accounts/${accountId}/workers/scripts/${fromDashScriptName}`,
313
+ `/accounts/${accountId}/workers/services/${fromDashScriptName}/environments/${environment}/content`,
294
314
  mockResponse
295
315
  );
296
316
  }
@@ -29,7 +29,9 @@ const std = {
29
29
  function normalizeOutput(spy: jest.SpyInstance): string {
30
30
  return normalizeErrorMarkers(
31
31
  replaceByte(
32
- stripTrailingWhitespace(normalizeSlashes(stripTimings(captureCalls(spy))))
32
+ stripTrailingWhitespace(
33
+ normalizeSlashes(normalizeTempDirs(stripTimings(captureCalls(spy))))
34
+ )
33
35
  )
34
36
  );
35
37
  }
@@ -93,5 +95,12 @@ export function stripTrailingWhitespace(str: string): string {
93
95
  * variation causing every few tests the value to change by ± .01
94
96
  */
95
97
  function replaceByte(stdout: string): string {
96
- return stdout.replaceAll(/.[0-9][0-9] KiB/g, "xx KiB");
98
+ return stdout.replaceAll(/\d+\.\d+ KiB/g, "xx KiB");
99
+ }
100
+
101
+ /**
102
+ * Temp directories are created with random names, so we replace all comments temp dirs in them
103
+ */
104
+ export function normalizeTempDirs(stdout: string): string {
105
+ return stdout.replaceAll(/\/\/.+\/tmp.+/g, "//tmpdir");
97
106
  }
@@ -0,0 +1,13 @@
1
+ // barrel import for msw handlers
2
+ import { mswNamespacesHandlers } from "./namespaces";
3
+ import { mswOauthHandlers } from "./oauth";
4
+ import { mswR2handlers } from "./r2";
5
+ import { mswUserHandlers } from "./user";
6
+
7
+ // All the handlers are used in msw/index for setting up the server
8
+ export const mswDefaultHandlers = [
9
+ ...mswOauthHandlers,
10
+ ...mswR2handlers,
11
+ ...mswUserHandlers,
12
+ ...mswNamespacesHandlers,
13
+ ];
@@ -0,0 +1,104 @@
1
+ import { rest } from "msw";
2
+
3
+ export const mswNamespacesHandlers = [
4
+ rest.post(
5
+ "*/accounts/:accountId/workers/dispatch/namespaces",
6
+ (request, response, context) => {
7
+ return response(
8
+ context.status(200),
9
+ context.json({
10
+ success: true,
11
+ errors: [],
12
+ messages: [],
13
+ result: {
14
+ namespace_id: "some-namespace-id",
15
+ namespace_name: "namespace-name",
16
+ created_on: "2022-06-29T14:30:08.16152Z",
17
+ created_by: "1fc1df98cc4420fe00367c3ab68c1639",
18
+ modified_on: "2022-06-29T14:30:08.16152Z",
19
+ modified_by: "1fc1df98cc4420fe00367c3ab68c1639",
20
+ },
21
+ })
22
+ );
23
+ }
24
+ ),
25
+ rest.delete(
26
+ "*/accounts/:accountId/workers/dispatch/namespaces/:namespaceName",
27
+ (_, response, context) => {
28
+ return response(
29
+ context.status(200),
30
+ context.json({
31
+ success: true,
32
+ errors: [],
33
+ messages: [],
34
+ result: null,
35
+ })
36
+ );
37
+ }
38
+ ),
39
+ rest.put(
40
+ "*/accounts/:accountId/workers/dispatch/namespaces/:namespaceName",
41
+ (_, response, context) => {
42
+ return response(
43
+ context.status(200),
44
+ context.json({
45
+ success: true,
46
+ errors: [],
47
+ messages: [],
48
+ result: {
49
+ namespace_id: "some-namespace-id",
50
+ namespace_name: "namespace-name",
51
+ created_on: "2022-06-29T14:30:08.16152Z",
52
+ created_by: "1fc1df98cc4420fe00367c3ab68c1639",
53
+ modified_on: "2022-06-29T14:30:08.16152Z",
54
+ modified_by: "1fc1df98cc4420fe00367c3ab68c1639",
55
+ },
56
+ })
57
+ );
58
+ }
59
+ ),
60
+ rest.get(
61
+ "*/accounts/:accountId/workers/dispatch/namespaces/:namespaceName",
62
+ (_, response, context) => {
63
+ return response(
64
+ context.status(200),
65
+ context.json({
66
+ success: true,
67
+ errors: [],
68
+ messages: [],
69
+ result: {
70
+ namespace_id: "some-namespace-id",
71
+ namespace_name: "namespace-name",
72
+ created_on: "2022-06-29T14:30:08.16152Z",
73
+ created_by: "1fc1df98cc4420fe00367c3ab68c1639",
74
+ modified_on: "2022-06-29T14:30:08.16152Z",
75
+ modified_by: "1fc1df98cc4420fe00367c3ab68c1639",
76
+ },
77
+ })
78
+ );
79
+ }
80
+ ),
81
+ rest.get(
82
+ "*/accounts/:accountId/workers/dispatch/namespaces",
83
+ (_, response, context) => {
84
+ return response(
85
+ context.status(200),
86
+ context.json({
87
+ success: true,
88
+ errors: [],
89
+ messages: [],
90
+ result: [
91
+ {
92
+ namespace_id: "some-namespace-id",
93
+ namespace_name: "namespace-name",
94
+ created_on: "2022-06-29T14:30:08.16152Z",
95
+ created_by: "1fc1df98cc4420fe00367c3ab68c1639",
96
+ modified_on: "2022-06-29T14:30:08.16152Z",
97
+ modified_by: "1fc1df98cc4420fe00367c3ab68c1639",
98
+ },
99
+ ],
100
+ })
101
+ );
102
+ }
103
+ ),
104
+ ];
@@ -0,0 +1,36 @@
1
+ import { rest } from "msw";
2
+
3
+ export const mswOauthHandlers = [
4
+ rest.all("*/oauth/callback", (_, response, context) => {
5
+ return response(
6
+ context.status(200),
7
+ context.json({
8
+ success: true,
9
+ errors: [],
10
+ messages: [],
11
+ code: "test-oauth-code",
12
+ })
13
+ );
14
+ }),
15
+ // revoke access token
16
+ rest.post(
17
+ "https://dash.cloudflare.com/oauth2/revoke",
18
+ (_, response, context) => response(context.status(200), context.text(""))
19
+ ),
20
+
21
+ // exchange (auth code | refresh token) for access token
22
+ rest.post(
23
+ "https://dash.cloudflare.com/oauth2/token",
24
+ (_, response, context) => {
25
+ return response(
26
+ context.status(200),
27
+ context.json({
28
+ access_token: "test-access-token",
29
+ expires_in: 100000,
30
+ refresh_token: "test-refresh-token",
31
+ scope: "account:read",
32
+ })
33
+ );
34
+ }
35
+ ),
36
+ ];
@@ -0,0 +1,80 @@
1
+ import { rest } from "msw";
2
+
3
+ export const mswR2handlers = [
4
+ // List endpoint r2Buckets
5
+ rest.get("*/accounts/:accountId/r2/buckets", (_, response, context) =>
6
+ response(
7
+ context.status(200),
8
+ context.json({
9
+ success: true,
10
+ errors: [],
11
+ messages: [],
12
+ result: {
13
+ buckets: [
14
+ { name: "bucket-1", creation_date: "01-01-2001" },
15
+ { name: "bucket-2", creation_date: "01-01-2001" },
16
+ ],
17
+ },
18
+ })
19
+ )
20
+ ),
21
+ rest.post("*/accounts/:accountId/r2/buckets", (_, response, context) =>
22
+ response(
23
+ context.status(200),
24
+ context.json({ success: true, errors: [], messages: [], result: {} })
25
+ )
26
+ ),
27
+ rest.put(
28
+ "*/accounts/:accountId/r2/buckets/:bucketName",
29
+ (_, response, context) =>
30
+ response(
31
+ context.status(200),
32
+ context.json({ success: true, errors: [], messages: [], result: {} })
33
+ )
34
+ ),
35
+ rest.delete(
36
+ "*/accounts/:accountId/r2/buckets/:bucketName",
37
+ (_, response, context) =>
38
+ response(
39
+ context.status(200),
40
+ context.json({ success: true, errors: [], messages: [], result: null })
41
+ )
42
+ ),
43
+ rest.get(
44
+ "*/accounts/:accountId/r2/buckets/:bucketName/objects/:objectName",
45
+ (_, response, context) => {
46
+ const imageBuffer = Buffer.from("wormhole-img.png");
47
+ return response(
48
+ context.set("Content-Length", imageBuffer.byteLength.toString()),
49
+ context.set("Content-Type", "image/png"),
50
+ context.status(200),
51
+ context.body(imageBuffer)
52
+ );
53
+ }
54
+ ),
55
+ rest.put(
56
+ "*/accounts/:accountId/r2/buckets/:bucketName/objects/:objectName",
57
+ (_, response, context) =>
58
+ response(
59
+ context.status(200),
60
+ context.json({
61
+ success: true,
62
+ errors: [],
63
+ messages: [],
64
+ result: {
65
+ accountId: "some-account-id",
66
+ bucketName: "bucketName-object-test",
67
+ objectName: "wormhole-img.png",
68
+ },
69
+ })
70
+ )
71
+ ),
72
+ rest.delete(
73
+ "*/accounts/:accountId/r2/buckets/:bucketName/objects/:objectName",
74
+ (_, response, context) =>
75
+ response(
76
+ context.status(200),
77
+ context.json({ success: true, errors: [], messages: [], result: null })
78
+ )
79
+ ),
80
+ ];
@@ -0,0 +1,63 @@
1
+ import { rest } from "msw";
2
+
3
+ export const mswUserHandlers = [
4
+ rest.get("*/user", (_, res, cxt) => {
5
+ return res(
6
+ cxt.status(200),
7
+ cxt.json({
8
+ success: true,
9
+ errors: [],
10
+ messages: [],
11
+ result: {
12
+ id: "7c5dae5552338874e5053f2534d2767a",
13
+ email: "user@example.com",
14
+ first_name: "John",
15
+ last_name: "Appleseed",
16
+ username: "cfuser12345",
17
+ telephone: "+1 123-123-1234",
18
+ country: "US",
19
+ zipcode: "12345",
20
+ created_on: "2014-01-01T05:20:00Z",
21
+ modified_on: "2014-01-01T05:20:00Z",
22
+ two_factor_authentication_enabled: false,
23
+ suspended: false,
24
+ },
25
+ })
26
+ );
27
+ }),
28
+ rest.get("*/accounts", (_, res, cxt) => {
29
+ return res(
30
+ cxt.status(200),
31
+ cxt.json({
32
+ success: true,
33
+ errors: [],
34
+ messages: [],
35
+ result: [
36
+ { name: "Account One", id: "account-1" },
37
+ { name: "Account Two", id: "account-2" },
38
+ { name: "Account Three", id: "account-3" },
39
+ ],
40
+ })
41
+ );
42
+ }),
43
+ rest.get("*/memberships", (_, response, context) => {
44
+ return response(
45
+ context.status(200),
46
+ context.json({
47
+ success: true,
48
+ errors: [],
49
+ messages: [],
50
+ result: [
51
+ {
52
+ id: "membership-id-1",
53
+ account: { id: "account-id-1", name: "My Personal Account" },
54
+ },
55
+ {
56
+ id: "membership-id-2",
57
+ account: { id: "account-id-2", name: "Enterprise Account" },
58
+ },
59
+ ],
60
+ })
61
+ );
62
+ }),
63
+ ];
@@ -0,0 +1,4 @@
1
+ import { setupServer } from "msw/node";
2
+ import { mswDefaultHandlers } from "./handlers";
3
+
4
+ export const msw = setupServer(...mswDefaultHandlers);
@@ -36,6 +36,7 @@ describe("wrangler", () => {
36
36
  wrangler publish [script] 🆙 Publish your Worker to Cloudflare.
37
37
  wrangler tail [worker] 🦚 Starts a log tailing session for a published Worker.
38
38
  wrangler secret 🤫 Generate a secret that can be referenced in a Worker
39
+ wrangler secret:bulk <json> 🗄️ Bulk upload secrets for a Worker
39
40
  wrangler kv:namespace 🗂️ Interact with your Workers KV Namespaces
40
41
  wrangler kv:key 🔑 Individually manage Workers KV key-value pairs
41
42
  wrangler kv:bulk 💪 Interact with multiple Workers KV key-value pairs at once
@@ -75,6 +76,7 @@ describe("wrangler", () => {
75
76
  wrangler publish [script] 🆙 Publish your Worker to Cloudflare.
76
77
  wrangler tail [worker] 🦚 Starts a log tailing session for a published Worker.
77
78
  wrangler secret 🤫 Generate a secret that can be referenced in a Worker
79
+ wrangler secret:bulk <json> 🗄️ Bulk upload secrets for a Worker
78
80
  wrangler kv:namespace 🗂️ Interact with your Workers KV Namespaces
79
81
  wrangler kv:key 🔑 Individually manage Workers KV key-value pairs
80
82
  wrangler kv:bulk 💪 Interact with multiple Workers KV key-value pairs at once
@@ -227,16 +229,16 @@ describe("wrangler", () => {
227
229
  await runWrangler("build");
228
230
  await endEventLoop();
229
231
  expect(std.out).toMatchInlineSnapshot(`
230
- "▲ [WARNING] Deprecation: \`wrangler build\` has been deprecated.
232
+ "▲ [WARNING] Deprecation: \`wrangler build\` has been deprecated.
231
233
 
232
- Please refer to https://developers.cloudflare.com/workers/wrangler/migration/deprecations/#build
233
- for more information.
234
- Attempting to run \`wrangler publish --dry-run --outdir=dist\` for you instead:
234
+ Please refer to https://developers.cloudflare.com/workers/wrangler/migration/deprecations/#build
235
+ for more information.
236
+ Attempting to run \`wrangler publish --dry-run --outdir=dist\` for you instead:
235
237
 
236
238
 
237
- --dry-run: exiting now.
238
- Total Upload: 0xx KiB / gzip: 0xx KiB"
239
- `);
239
+ Total Upload: xx KiB / gzip: xx KiB
240
+ --dry-run: exiting now."
241
+ `);
240
242
  });
241
243
  });
242
244