wrangler 2.0.24 → 2.0.27

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 (63) hide show
  1. package/miniflare-dist/index.mjs +142 -16
  2. package/package.json +3 -3
  3. package/src/__tests__/configuration.test.ts +7 -3
  4. package/src/__tests__/dev.test.tsx +26 -4
  5. package/src/__tests__/generate.test.ts +2 -4
  6. package/src/__tests__/helpers/mock-cfetch.ts +35 -2
  7. package/src/__tests__/init.test.ts +537 -359
  8. package/src/__tests__/jest.setup.ts +7 -0
  9. package/src/__tests__/metrics.test.ts +1 -1
  10. package/src/__tests__/pages.test.ts +14 -0
  11. package/src/__tests__/r2.test.ts +22 -3
  12. package/src/__tests__/tail.test.ts +112 -42
  13. package/src/__tests__/user.test.ts +11 -0
  14. package/src/api/dev.ts +7 -0
  15. package/src/bundle.ts +3 -2
  16. package/src/cfetch/internal.ts +56 -0
  17. package/src/config/config.ts +1 -1
  18. package/src/config/validation-helpers.ts +19 -6
  19. package/src/config/validation.ts +9 -3
  20. package/src/config-cache.ts +2 -1
  21. package/src/dev/dev.tsx +16 -2
  22. package/src/dev/local.tsx +69 -5
  23. package/src/dev/use-esbuild.ts +3 -0
  24. package/src/dev-registry.tsx +3 -0
  25. package/src/dev.tsx +28 -19
  26. package/src/generate.ts +1 -1
  27. package/src/index.tsx +51 -21
  28. package/src/init.ts +111 -38
  29. package/src/inspect.ts +1 -4
  30. package/src/{metrics/is-ci.ts → is-ci.ts} +0 -0
  31. package/src/metrics/metrics-config.ts +1 -1
  32. package/src/miniflare-cli/assets.ts +27 -16
  33. package/src/miniflare-cli/index.ts +124 -2
  34. package/src/pages/build.tsx +75 -41
  35. package/src/pages/constants.ts +4 -0
  36. package/src/pages/deployments.tsx +9 -9
  37. package/src/pages/dev.tsx +178 -64
  38. package/src/pages/errors.ts +22 -0
  39. package/src/pages/functions/buildPlugin.ts +4 -0
  40. package/src/pages/functions/buildWorker.ts +4 -0
  41. package/src/pages/functions/routes-consolidation.test.ts +250 -0
  42. package/src/pages/functions/routes-consolidation.ts +73 -0
  43. package/src/pages/functions/routes-transformation.test.ts +271 -0
  44. package/src/pages/functions/routes-transformation.ts +122 -0
  45. package/src/pages/functions.tsx +96 -0
  46. package/src/pages/index.tsx +65 -55
  47. package/src/pages/projects.tsx +9 -3
  48. package/src/pages/publish.tsx +75 -22
  49. package/src/pages/types.ts +9 -0
  50. package/src/pages/upload.tsx +6 -8
  51. package/src/proxy.ts +10 -0
  52. package/src/r2.ts +17 -4
  53. package/src/tail/filters.ts +3 -1
  54. package/src/tail/index.ts +15 -2
  55. package/src/tail/printing.ts +43 -3
  56. package/src/user/user.tsx +6 -4
  57. package/src/whoami.tsx +5 -5
  58. package/templates/pages-template-plugin.ts +16 -4
  59. package/templates/pages-template-worker.ts +16 -5
  60. package/templates/service-bindings-module-facade.js +10 -7
  61. package/templates/service-bindings-sw-facade.js +10 -7
  62. package/wrangler-dist/cli.d.ts +7 -0
  63. package/wrangler-dist/cli.js +1681 -1091
package/src/tail/index.ts CHANGED
@@ -174,12 +174,12 @@ export type TailEventMessage = {
174
174
  /**
175
175
  * The event that triggered the worker. In the case of an HTTP request,
176
176
  * this will be a RequestEvent. If it's a cron trigger, it'll be a
177
- * ScheduledEvent.
177
+ * ScheduledEvent. If it's a durable object alarm, it's an AlarmEvent.
178
178
  *
179
179
  * Until workers-types exposes individual types for export, we'll have
180
180
  * to just re-define these types ourselves.
181
181
  */
182
- event: RequestEvent | ScheduledEvent | undefined | null;
182
+ event: RequestEvent | ScheduledEvent | AlarmEvent | undefined | null;
183
183
  };
184
184
 
185
185
  /**
@@ -297,3 +297,16 @@ export type ScheduledEvent = {
297
297
  */
298
298
  scheduledTime: number;
299
299
  };
300
+
301
+ /**
302
+ * A event that was triggered from a durable object alarm
303
+ */
304
+ export type AlarmEvent = {
305
+ /**
306
+ * The datetime the alarm was scheduled for.
307
+ *
308
+ * This is sent as an ISO timestamp string (different than ScheduledEvent.scheduledTime),
309
+ * you should parse it later on on your own.
310
+ */
311
+ scheduledTime: string;
312
+ };
@@ -1,6 +1,11 @@
1
1
  import { logger } from "../logger";
2
- import type { RequestEvent, ScheduledEvent, TailEventMessage } from ".";
3
2
  import type { Outcome } from "./filters";
3
+ import type {
4
+ AlarmEvent,
5
+ RequestEvent,
6
+ ScheduledEvent,
7
+ TailEventMessage,
8
+ } from "./index";
4
9
  import type WebSocket from "ws";
5
10
 
6
11
  export function prettyPrintLogs(data: WebSocket.RawData): void {
@@ -14,7 +19,7 @@ export function prettyPrintLogs(data: WebSocket.RawData): void {
14
19
  const outcome = prettifyOutcome(eventMessage.outcome);
15
20
 
16
21
  logger.log(`"${cronPattern}" @ ${datetime} - ${outcome}`);
17
- } else {
22
+ } else if (isRequestEvent(eventMessage.event)) {
18
23
  const requestMethod = eventMessage.event?.request.method.toUpperCase();
19
24
  const url = eventMessage.event?.request.url;
20
25
  const outcome = prettifyOutcome(eventMessage.outcome);
@@ -25,6 +30,19 @@ export function prettyPrintLogs(data: WebSocket.RawData): void {
25
30
  ? `${requestMethod} ${url} - ${outcome} @ ${datetime}`
26
31
  : `[missing request] - ${outcome} @ ${datetime}`
27
32
  );
33
+ } else if (isAlarmEvent(eventMessage.event)) {
34
+ const outcome = prettifyOutcome(eventMessage.outcome);
35
+ const datetime = new Date(
36
+ eventMessage.event.scheduledTime
37
+ ).toLocaleString();
38
+
39
+ logger.log(`Alarm @ ${datetime} - ${outcome}`);
40
+ } else {
41
+ // Unknown event type
42
+ const outcome = prettifyOutcome(eventMessage.outcome);
43
+ const datetime = new Date(eventMessage.eventTimestamp).toLocaleString();
44
+
45
+ logger.log(`Unknown Event - ${outcome} @ ${datetime}`);
28
46
  }
29
47
 
30
48
  if (eventMessage.logs.length > 0) {
@@ -44,12 +62,32 @@ export function jsonPrintLogs(data: WebSocket.RawData): void {
44
62
  console.log(JSON.stringify(JSON.parse(data.toString()), null, 2));
45
63
  }
46
64
 
65
+ function isRequestEvent(
66
+ event: TailEventMessage["event"]
67
+ ): event is RequestEvent {
68
+ return Boolean(event && "request" in event);
69
+ }
70
+
47
71
  function isScheduledEvent(
48
- event: RequestEvent | ScheduledEvent | undefined | null
72
+ event: TailEventMessage["event"]
49
73
  ): event is ScheduledEvent {
50
74
  return Boolean(event && "cron" in event);
51
75
  }
52
76
 
77
+ /**
78
+ * Check to see if an event sent from a worker is an AlarmEvent.
79
+ *
80
+ * Because the only property on `AlarmEvent` is "scheduledTime", which it
81
+ * shares with `ScheduledEvent`, `isAlarmEvent` checks if there's _not_
82
+ * a "cron" property in `event` to confirm it's an alarm event.
83
+ *
84
+ * @param event An event
85
+ * @returns true if the event is an AlarmEvent
86
+ */
87
+ function isAlarmEvent(event: TailEventMessage["event"]): event is AlarmEvent {
88
+ return Boolean(event && "scheduledTime" in event && !("cron" in event));
89
+ }
90
+
53
91
  function prettifyOutcome(outcome: Outcome): string {
54
92
  switch (outcome) {
55
93
  case "ok":
@@ -58,6 +96,8 @@ function prettifyOutcome(outcome: Outcome): string {
58
96
  return "Canceled";
59
97
  case "exceededCpu":
60
98
  return "Exceeded CPU Limit";
99
+ case "exceededMemory":
100
+ return "Exceeded Memory Limit";
61
101
  case "exception":
62
102
  return "Exception Thrown";
63
103
  case "unknown":
package/src/user/user.tsx CHANGED
@@ -224,6 +224,7 @@ import {
224
224
  saveToConfigCache,
225
225
  } from "../config-cache";
226
226
  import { getGlobalWranglerConfigPath } from "../global-wrangler-config-path";
227
+ import { CI } from "../is-ci";
227
228
  import isInteractive from "../is-interactive";
228
229
  import { logger } from "../logger";
229
230
  import openInBrowser from "../open-in-browser";
@@ -878,10 +879,11 @@ type LoginProps = {
878
879
  export async function loginOrRefreshIfRequired(): Promise<boolean> {
879
880
  // TODO: if there already is a token, then try refreshing
880
881
  // TODO: ask permission before opening browser
882
+ const { isCI } = CI;
881
883
  if (!getAPIToken()) {
882
884
  // Not logged in.
883
885
  // If we are not interactive, we cannot ask the user to login
884
- return isInteractive() && (await login());
886
+ return isInteractive() && !isCI() && (await login());
885
887
  } else if (isAccessTokenExpired()) {
886
888
  // We're logged in, but the refresh token seems to have expired,
887
889
  // so let's try to refresh it
@@ -891,7 +893,7 @@ export async function loginOrRefreshIfRequired(): Promise<boolean> {
891
893
  return true;
892
894
  } else {
893
895
  // If the refresh token isn't valid, then we ask the user to login again
894
- return isInteractive() && (await login());
896
+ return isInteractive() && !isCI() && (await login());
895
897
  }
896
898
  } else {
897
899
  return true;
@@ -1088,7 +1090,7 @@ export async function getAccountId(): Promise<string | undefined> {
1088
1090
  return accounts[0].id;
1089
1091
  }
1090
1092
 
1091
- if (isInteractive()) {
1093
+ if (isInteractive() && !CI.isCI()) {
1092
1094
  const account = await new Promise<{ id: string; name: string }>(
1093
1095
  (resolve, reject) => {
1094
1096
  const { unmount } = render(
@@ -1128,7 +1130,7 @@ export async function requireAuth(config: {
1128
1130
  }): Promise<string> {
1129
1131
  const loggedIn = await loginOrRefreshIfRequired();
1130
1132
  if (!loggedIn) {
1131
- if (!isInteractive()) {
1133
+ if (!isInteractive() || CI.isCI()) {
1132
1134
  throw new Error(
1133
1135
  "In a non-interactive environment, it's necessary to set a CLOUDFLARE_API_TOKEN environment variable for wrangler to work. Please go to https://developers.cloudflare.com/api/tokens/create/ for instructions on how to create an api token, and assign its value to CLOUDFLARE_API_TOKEN."
1134
1136
  );
package/src/whoami.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Text, render } from "ink";
2
2
  import Table from "ink-table";
3
- import React from "react";
3
+ import React, { Fragment } from "react";
4
4
  import { fetchListResult, fetchResult } from "./cfetch";
5
5
  import { logger } from "./logger";
6
6
  import { getAPIToken, getAuthFromEnv, getScopes } from "./user";
@@ -64,12 +64,12 @@ function Permissions(props: {
64
64
  and re-login.
65
65
  </Text>
66
66
  <Text>Scope (Access)</Text>
67
- {permissions.map(([type, name]) => (
68
- <>
67
+ {permissions.map(([scope, access], index) => (
68
+ <Fragment key={`${scope}${index}`}>
69
69
  <Text>
70
- - {type} {name && `(${name})`}
70
+ - {scope} {access && `(${access})`}
71
71
  </Text>
72
- </>
72
+ </Fragment>
73
73
  ))}
74
74
  </>
75
75
  ) : null
@@ -1,5 +1,8 @@
1
1
  import { match } from "path-to-regexp";
2
2
 
3
+ //note: this explicitly does not include the * character, as pages requires this
4
+ const escapeRegex = /[.+?^${}()|[\]\\]/g;
5
+
3
6
  type HTTPMethod =
4
7
  | "HEAD"
5
8
  | "OPTIONS"
@@ -67,8 +70,13 @@ function* executeRequest(request: Request, relativePathname: string) {
67
70
  continue;
68
71
  }
69
72
 
70
- const routeMatcher = match(route.routePath, { end: false });
71
- const mountMatcher = match(route.mountPath, { end: false });
73
+ // replaces with "\\$&", this prepends a backslash to the matched string, e.g. "[" becomes "\["
74
+ const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), {
75
+ end: false,
76
+ });
77
+ const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), {
78
+ end: false,
79
+ });
72
80
  const matchResult = routeMatcher(relativePathname);
73
81
  const mountMatchResult = mountMatcher(relativePathname);
74
82
  if (matchResult && mountMatchResult) {
@@ -88,8 +96,12 @@ function* executeRequest(request: Request, relativePathname: string) {
88
96
  continue;
89
97
  }
90
98
 
91
- const routeMatcher = match(route.routePath, { end: true });
92
- const mountMatcher = match(route.mountPath, { end: false });
99
+ const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), {
100
+ end: true,
101
+ });
102
+ const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), {
103
+ end: false,
104
+ });
93
105
  const matchResult = routeMatcher(relativePathname);
94
106
  const mountMatchResult = mountMatcher(relativePathname);
95
107
  if (matchResult && mountMatchResult && route.modules.length) {
@@ -1,5 +1,8 @@
1
1
  import { match } from "path-to-regexp";
2
2
 
3
+ //note: this explicitly does not include the * character, as pages requires this
4
+ const escapeRegex = /[.+?^${}()|[\]\\]/g;
5
+
3
6
  type HTTPMethod =
4
7
  | "HEAD"
5
8
  | "OPTIONS"
@@ -61,8 +64,13 @@ function* executeRequest(request: Request) {
61
64
  continue;
62
65
  }
63
66
 
64
- const routeMatcher = match(route.routePath, { end: false });
65
- const mountMatcher = match(route.mountPath, { end: false });
67
+ // replaces with "\\$&", this prepends a backslash to the matched string, e.g. "[" becomes "\["
68
+ const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), {
69
+ end: false,
70
+ });
71
+ const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), {
72
+ end: false,
73
+ });
66
74
  const matchResult = routeMatcher(requestPath);
67
75
  const mountMatchResult = mountMatcher(requestPath);
68
76
  if (matchResult && mountMatchResult) {
@@ -81,9 +89,12 @@ function* executeRequest(request: Request) {
81
89
  if (route.method && route.method !== request.method) {
82
90
  continue;
83
91
  }
84
-
85
- const routeMatcher = match(route.routePath, { end: true });
86
- const mountMatcher = match(route.mountPath, { end: false });
92
+ const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), {
93
+ end: true,
94
+ });
95
+ const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), {
96
+ end: false,
97
+ });
87
98
  const matchResult = routeMatcher(requestPath);
88
99
  const mountMatchResult = mountMatcher(requestPath);
89
100
  if (matchResult && mountMatchResult && route.modules.length) {
@@ -16,20 +16,23 @@ export default {
16
16
  if (details) {
17
17
  facadeEnv[name] = {
18
18
  async fetch(...reqArgs) {
19
+ const reqFromArgs = new Request(...reqArgs);
19
20
  if (details.headers) {
20
- const req = new Request(...reqArgs);
21
21
  for (const [key, value] of Object.entries(details.headers)) {
22
22
  // In remote mode, you need to add a couple of headers
23
23
  // to make sure it's talking to the 'dev' preview session
24
24
  // (much like wrangler dev already does via proxy.ts)
25
- req.headers.set(key, value);
25
+ reqFromArgs.headers.set(key, value);
26
26
  }
27
- return env[name].fetch(req);
27
+ return env[name].fetch(reqFromArgs);
28
28
  }
29
- const url = `${details.protocol}://${details.host}${
30
- details.port ? `:${details.port}` : ""
31
- }`;
32
- const request = new Request(url, ...reqArgs);
29
+
30
+ const url = new URL(reqFromArgs.url);
31
+ url.protocol = details.protocol;
32
+ url.host = details.host;
33
+ if (details.port !== undefined) url.port = details.port;
34
+
35
+ const request = new Request(url.toString(), reqFromArgs);
33
36
  return fetch(request);
34
37
  },
35
38
  };
@@ -7,20 +7,23 @@ for (const [name, details] of Object.entries(Workers)) {
7
7
  if (details) {
8
8
  globalThis[name] = {
9
9
  async fetch(...reqArgs) {
10
+ const reqFromArgs = new Request(...reqArgs);
10
11
  if (details.headers) {
11
- const req = new Request(...reqArgs);
12
12
  for (const [key, value] of Object.entries(details.headers)) {
13
13
  // In remote mode, you need to add a couple of headers
14
14
  // to make sure it's talking to the 'dev' preview session
15
15
  // (much like wrangler dev already does via proxy.ts)
16
- req.headers.set(key, value);
16
+ reqFromArgs.headers.set(key, value);
17
17
  }
18
- return env[name].fetch(req);
18
+ return env[name].fetch(reqFromArgs);
19
19
  }
20
- const url = `${details.protocol}://${details.host}${
21
- details.port ? `:${details.port}` : ""
22
- }`;
23
- const request = new Request(url, ...reqArgs);
20
+
21
+ const url = new URL(reqFromArgs.url);
22
+ url.protocol = details.protocol;
23
+ url.host = details.host;
24
+ if (details.port !== undefined) url.port = details.port;
25
+
26
+ const request = new Request(url.toString(), reqFromArgs);
24
27
  return fetch(request);
25
28
  },
26
29
  };
@@ -33,6 +33,7 @@ declare interface DevOptions {
33
33
  env?: string;
34
34
  ip?: string;
35
35
  port?: number;
36
+ inspectorPort?: number;
36
37
  localProtocol?: "http" | "https";
37
38
  assets?: string;
38
39
  site?: string;
@@ -40,6 +41,7 @@ declare interface DevOptions {
40
41
  siteExclude?: string[];
41
42
  nodeCompat?: boolean;
42
43
  compatibilityDate?: string;
44
+ compatibilityFlags?: string[];
43
45
  experimentalEnableLocalPersistence?: boolean;
44
46
  liveReload?: boolean;
45
47
  watch?: boolean;
@@ -57,6 +59,11 @@ declare interface DevOptions {
57
59
  script_name?: string | undefined;
58
60
  environment?: string | undefined;
59
61
  }[];
62
+ r2?: {
63
+ binding: string;
64
+ bucket_name: string;
65
+ preview_bucket_name?: string;
66
+ }[];
60
67
  showInteractiveDevSession?: boolean;
61
68
  logLevel?: "none" | "error" | "log" | "warn" | "debug";
62
69
  logPrefix?: string;