wrangler 2.0.23 → 2.0.26

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 (80) hide show
  1. package/README.md +20 -2
  2. package/bin/wrangler.js +1 -1
  3. package/miniflare-dist/index.mjs +235 -47
  4. package/package.json +11 -6
  5. package/src/__tests__/configuration.test.ts +89 -17
  6. package/src/__tests__/dev.test.tsx +29 -4
  7. package/src/__tests__/generate.test.ts +93 -0
  8. package/src/__tests__/helpers/mock-cfetch.ts +87 -2
  9. package/src/__tests__/index.test.ts +10 -27
  10. package/src/__tests__/init.test.ts +537 -359
  11. package/src/__tests__/jest.setup.ts +34 -1
  12. package/src/__tests__/kv.test.ts +2 -2
  13. package/src/__tests__/metrics.test.ts +5 -0
  14. package/src/__tests__/pages.test.ts +14 -0
  15. package/src/__tests__/publish.test.ts +497 -254
  16. package/src/__tests__/r2.test.ts +173 -71
  17. package/src/__tests__/tail.test.ts +112 -42
  18. package/src/__tests__/user.test.ts +1 -0
  19. package/src/__tests__/validate-dev-props.test.ts +56 -0
  20. package/src/__tests__/whoami.test.tsx +60 -1
  21. package/src/api/dev.ts +7 -0
  22. package/src/bundle.ts +279 -44
  23. package/src/cfetch/internal.ts +73 -2
  24. package/src/config/config.ts +8 -3
  25. package/src/config/environment.ts +40 -8
  26. package/src/config/index.ts +13 -0
  27. package/src/config/validation.ts +102 -8
  28. package/src/create-worker-upload-form.ts +25 -0
  29. package/src/dev/dev.tsx +121 -28
  30. package/src/dev/local.tsx +88 -14
  31. package/src/dev/remote.tsx +39 -8
  32. package/src/dev/use-esbuild.ts +28 -0
  33. package/src/dev/validate-dev-props.ts +31 -0
  34. package/src/dev-registry.tsx +160 -0
  35. package/src/dev.tsx +107 -80
  36. package/src/generate.ts +112 -14
  37. package/src/index.tsx +212 -4
  38. package/src/init.ts +111 -38
  39. package/src/inspect.ts +90 -5
  40. package/src/metrics/index.ts +1 -0
  41. package/src/metrics/metrics-dispatcher.ts +1 -0
  42. package/src/metrics/metrics-usage-headers.ts +24 -0
  43. package/src/metrics/send-event.ts +2 -2
  44. package/src/miniflare-cli/assets.ts +27 -16
  45. package/src/miniflare-cli/index.ts +124 -2
  46. package/src/module-collection.ts +3 -3
  47. package/src/pages/build.tsx +75 -41
  48. package/src/pages/constants.ts +5 -0
  49. package/src/pages/deployments.tsx +10 -10
  50. package/src/pages/dev.tsx +177 -52
  51. package/src/pages/errors.ts +22 -0
  52. package/src/pages/functions/buildPlugin.ts +4 -0
  53. package/src/pages/functions/buildWorker.ts +4 -0
  54. package/src/pages/functions/routes-consolidation.test.ts +250 -0
  55. package/src/pages/functions/routes-consolidation.ts +73 -0
  56. package/src/pages/functions/routes-transformation.test.ts +271 -0
  57. package/src/pages/functions/routes-transformation.ts +122 -0
  58. package/src/pages/functions.tsx +96 -0
  59. package/src/pages/index.tsx +65 -55
  60. package/src/pages/projects.tsx +9 -3
  61. package/src/pages/publish.tsx +76 -23
  62. package/src/pages/types.ts +9 -0
  63. package/src/pages/upload.tsx +38 -21
  64. package/src/publish.ts +126 -112
  65. package/src/r2.ts +81 -0
  66. package/src/tail/filters.ts +3 -1
  67. package/src/tail/index.ts +15 -2
  68. package/src/tail/printing.ts +43 -3
  69. package/src/user/user.tsx +20 -2
  70. package/src/whoami.tsx +79 -1
  71. package/src/worker.ts +12 -0
  72. package/templates/first-party-worker-module-facade.ts +18 -0
  73. package/templates/format-dev-errors.ts +32 -0
  74. package/templates/pages-template-plugin.ts +16 -4
  75. package/templates/pages-template-worker.ts +16 -5
  76. package/templates/{static-asset-facade.js → serve-static-assets.ts} +21 -7
  77. package/templates/service-bindings-module-facade.js +54 -0
  78. package/templates/service-bindings-sw-facade.js +42 -0
  79. package/wrangler-dist/cli.d.ts +7 -0
  80. package/wrangler-dist/cli.js +40851 -15332
package/src/publish.ts CHANGED
@@ -11,6 +11,7 @@ import { createWorkerUploadForm } from "./create-worker-upload-form";
11
11
  import { confirm } from "./dialogs";
12
12
  import { getMigrationsToUpload } from "./durable";
13
13
  import { logger } from "./logger";
14
+ import { getMetricsUsageHeaders } from "./metrics";
14
15
  import { ParseError } from "./parse";
15
16
  import { syncAssets } from "./sites";
16
17
  import { getZoneForRoute } from "./zones";
@@ -51,6 +52,27 @@ type Props = {
51
52
 
52
53
  type RouteObject = ZoneIdRoute | ZoneNameRoute | CustomDomainRoute;
53
54
 
55
+ export type CustomDomain = {
56
+ id: string;
57
+ zone_id: string;
58
+ zone_name: string;
59
+ hostname: string;
60
+ service: string;
61
+ environment: string;
62
+ };
63
+ type UpdatedCustomDomain = CustomDomain & { modified: boolean };
64
+ type ConflictingCustomDomain = CustomDomain & {
65
+ external_dns_record_id?: string;
66
+ external_cert_id?: string;
67
+ };
68
+
69
+ export type CustomDomainChangeset = {
70
+ added: CustomDomain[];
71
+ removed: CustomDomain[];
72
+ updated: UpdatedCustomDomain[];
73
+ conflicting: ConflictingCustomDomain[];
74
+ };
75
+
54
76
  function sleep(ms: number) {
55
77
  return new Promise((resolve) => setTimeout(resolve, ms));
56
78
  }
@@ -79,58 +101,27 @@ function renderRoute(route: Route): string {
79
101
  return result;
80
102
  }
81
103
 
82
- // this function takes a string with quotes in it
83
- // (i.e. `hello "world", if that really is your name`)
84
- // and peels out the first instance of a substring
85
- // bounded by quotes (so, in the example above, `world`)
86
- //
87
- // this is useful because the /domains api will return
88
- // which domains conflicted in an error message, bounded
89
- // by a string, which we can use to provide helpful
90
- // messages to a user
91
- function getQuoteBoundedSubstring(content: string) {
92
- const matches = content.split('"');
93
- return matches[1] ?? "";
94
- }
95
-
96
- function isOriginConflictError(
97
- e: unknown
98
- ): e is { code: 100116; message: string; notes: Array<{ text: string }> } {
99
- return (
100
- typeof e === "object" &&
101
- e !== null &&
102
- (e as { code: number }).code === 100116
103
- );
104
- }
105
-
106
- function isDNSConflictError(
107
- e: unknown
108
- ): e is { code: 100117; message: string; notes: Array<{ text: string }> } {
109
- return (
110
- typeof e === "object" &&
111
- e !== null &&
112
- (e as { code: number }).code === 100117
113
- );
114
- }
115
-
116
- // empty error class to throw and then explicitly catch via `instanceof`
117
- class CustomDomainOverrideRejected extends Error {}
118
-
119
104
  // publishing to custom domains involves a few more steps than just updating
120
105
  // the routing table, and thus the api implementing it is fairly defensive -
121
106
  // it will error eagerly on conflicts against existing domains or existing
122
107
  // managed DNS records
123
- //
124
- // however, you can pass params to override the errors. we start on the
125
- // defensive path, and if one of these errors occur, we prompt the user
126
- // for confirmation that they do indeed want to override the conflicts, and
127
- // then retry the request with the right override added
108
+
109
+ // however, you can pass params to override the errors. to know if we should
110
+ // override the current state, we generate a "changeset" of required actions
111
+ // to get to the state we want (specified by the list of custom domains). the
112
+ // changeset returns an "updated" collection (existing custom domains
113
+ // connected to other scripts) and a "conflicting" collection (the requested
114
+ // custom domains that have a managed, conflicting DNS record preventing the
115
+ // host's use as a custom domain). with this information, we can prompt to
116
+ // the user what will occur if we create the custom domains requested, and
117
+ // add the override param if they confirm the action
128
118
  //
129
119
  // if a user does not confirm that they want to override, we skip publishing
130
120
  // to these custom domains, but continue on through the rest of the
131
121
  // publish stage
132
- function publishCustomDomains(
122
+ async function publishCustomDomains(
133
123
  workerUrl: string,
124
+ accountId: string,
134
125
  domains: Array<RouteObject>
135
126
  ): Promise<string[]> {
136
127
  const config = {
@@ -146,79 +137,81 @@ function publishCustomDomains(
146
137
  };
147
138
  });
148
139
 
140
+ const fail = () => {
141
+ return [
142
+ domains.length > 1
143
+ ? `Publishing to ${domains.length} Custom Domains was skipped, fix conflicts and try again`
144
+ : `Publishing to Custom Domain "${domains[0].pattern}" was skipped, fix conflict and try again`,
145
+ ];
146
+ };
147
+
149
148
  if (!process.stdout.isTTY) {
150
149
  // running in non-interactive mode.
151
150
  // existing origins / dns records are not indicative of errors,
152
151
  // so we aggressively update rather than aggressively fail
153
152
  config.override_existing_origin = true;
154
153
  config.override_existing_dns_record = true;
154
+ } else {
155
+ // get a changeset for operations required to achieve a state with the requested domains
156
+ const changeset = await fetchResult<CustomDomainChangeset>(
157
+ `${workerUrl}/domains/changeset?replace_state=true`,
158
+ {
159
+ method: "POST",
160
+ body: JSON.stringify(origins),
161
+ headers: {
162
+ "Content-Type": "application/json",
163
+ },
164
+ }
165
+ );
166
+
167
+ const updatesRequired = changeset.updated.filter(
168
+ (domain) => domain.modified
169
+ );
170
+ if (updatesRequired.length > 0) {
171
+ // find out which scripts the conflict domains are already attached to
172
+ // so we can provide that in the confirmation prompt
173
+ const existing = await Promise.all(
174
+ updatesRequired.map((domain) =>
175
+ fetchResult<CustomDomain>(
176
+ `/accounts/${accountId}/workers/domains/records/${domain.id}`
177
+ )
178
+ )
179
+ );
180
+ const existingRendered = existing
181
+ .map(
182
+ (domain) =>
183
+ `\t• ${domain.hostname} (used as a domain for "${domain.service}")`
184
+ )
185
+ .join("\n");
186
+ const message = `Custom Domains already exist for these domains:
187
+ ${existingRendered}
188
+ Update them to point to this script instead?`;
189
+ if (!(await confirm(message))) return fail();
190
+ config.override_existing_origin = true;
191
+ }
192
+
193
+ if (changeset.conflicting.length > 0) {
194
+ const conflicitingRendered = changeset.conflicting
195
+ .map((domain) => `\t• ${domain.hostname}`)
196
+ .join("\n");
197
+ const message = `You already have DNS records that conflict for these Custom Domains:
198
+ ${conflicitingRendered}
199
+ Update them to point to this script instead?`;
200
+ if (!(await confirm(message))) return fail();
201
+ config.override_existing_dns_record = true;
202
+ }
155
203
  }
156
204
 
157
- // Mixing promise chains with async/await is funky, but it allows us to keep related
158
- // logic synchronous-looking (i.e. prompting for confirmation and then re-requesting)
159
- // while retaining the flexibility of promise chain fall-throughs. We can group error
160
- // handling logic in dedicated catch calls, and all we have to do is re-throw an
161
- // error and it will pass down to the next catch call
162
- return fetchResult(`${workerUrl}/domains`, {
205
+ // publish to domains
206
+ await fetchResult(`${workerUrl}/domains/records`, {
163
207
  method: "PUT",
164
208
  body: JSON.stringify({ ...config, origins }),
165
209
  headers: {
166
210
  "Content-Type": "application/json",
167
211
  },
168
- })
169
- .catch(async (err) => {
170
- if (isOriginConflictError(err)) {
171
- const conflictingOrigins = getQuoteBoundedSubstring(err.notes[0].text);
172
- const shouldContinue = await confirm(
173
- `Custom Domains already exist for these domains: "${conflictingOrigins}"\nUpdate them to point to this script instead?`
174
- );
175
- if (!shouldContinue) {
176
- throw new CustomDomainOverrideRejected();
177
- }
178
- config.override_existing_origin = true;
179
- await fetchResult(`${workerUrl}/domains`, {
180
- method: "PUT",
181
- body: JSON.stringify({ ...config, origins }),
182
- headers: {
183
- "Content-Type": "application/json",
184
- },
185
- });
186
- } else {
187
- throw err;
188
- }
189
- })
190
- .catch(async (err) => {
191
- if (isDNSConflictError(err)) {
192
- const conflictingOrigins = getQuoteBoundedSubstring(err.notes[0].text);
193
- const shouldContinue = await confirm(
194
- `You already have conflicting DNS records for these domains: "${conflictingOrigins}"\nUpdate them to point to this script instead?`
195
- );
196
- if (!shouldContinue) {
197
- throw new CustomDomainOverrideRejected();
198
- }
199
- config.override_existing_dns_record = true;
200
- await fetchResult(`${workerUrl}/domains`, {
201
- method: "PUT",
202
- body: JSON.stringify({ ...config, origins }),
203
- headers: {
204
- "Content-Type": "application/json",
205
- },
206
- });
207
- } else {
208
- throw err;
209
- }
210
- })
211
- .then(() => domains.map((domain) => renderRoute(domain)))
212
- .catch((err) => {
213
- if (err instanceof CustomDomainOverrideRejected) {
214
- return [
215
- domains.length > 1
216
- ? `Publishing to ${domains.length} Custom Domains was skipped, fix conflicts and try again`
217
- : `Publishing to Custom Domain "${domains[0].pattern}" was skipped, fix conflict and try again`,
218
- ];
219
- }
220
- throw err;
221
- });
212
+ });
213
+
214
+ return domains.map((domain) => renderRoute(domain));
222
215
  }
223
216
 
224
217
  export default async function publish(props: Props): Promise<void> {
@@ -391,6 +384,17 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
391
384
  nodeCompat,
392
385
  define: config.define,
393
386
  checkFetch: false,
387
+ assets: config.assets && {
388
+ ...config.assets,
389
+ // enable the cache when publishing
390
+ bypassCache: false,
391
+ },
392
+ services: config.services,
393
+ // We don't set workerDefinitions here,
394
+ // because we don't want to apply the dev-time
395
+ // facades on top of it
396
+ workerDefinitions: undefined,
397
+ firstPartyWorkerDevFacade: false,
394
398
  }
395
399
  );
396
400
 
@@ -440,6 +444,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
440
444
  r2_buckets: config.r2_buckets,
441
445
  services: config.services,
442
446
  worker_namespaces: config.worker_namespaces,
447
+ logfwdr: config.logfwdr,
443
448
  unsafe: config.unsafe?.bindings,
444
449
  };
445
450
 
@@ -492,6 +497,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
492
497
  {
493
498
  method: "PUT",
494
499
  body: createWorkerUploadForm(worker),
500
+ headers: await getMetricsUsageHeaders(config.send_metrics),
495
501
  },
496
502
  new URLSearchParams({
497
503
  include_subdomain_availability: "true",
@@ -503,13 +509,15 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
503
509
 
504
510
  available_on_subdomain = result.available_on_subdomain;
505
511
 
506
- // Print some useful information returned after publishing
507
- // Not all fields will be populated for every worker
508
- // These fields are likely to be scraped by tools, so do not rename
509
- if (result.id) logger.log("Worker ID: ", result.id);
510
- if (result.etag) logger.log("Worker ETag: ", result.etag);
511
- if (result.pipeline_hash)
512
- logger.log("Worker PipelineHash: ", result.pipeline_hash);
512
+ if (config.first_party_worker) {
513
+ // Print some useful information returned after publishing
514
+ // Not all fields will be populated for every worker
515
+ // These fields are likely to be scraped by tools, so do not rename
516
+ if (result.id) logger.log("Worker ID: ", result.id);
517
+ if (result.etag) logger.log("Worker ETag: ", result.etag);
518
+ if (result.pipeline_hash)
519
+ logger.log("Worker PipelineHash: ", result.pipeline_hash);
520
+ }
513
521
  }
514
522
  } finally {
515
523
  if (typeof destination !== "string") {
@@ -590,7 +598,9 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
590
598
 
591
599
  // Update custom domains for the script
592
600
  if (customDomainsOnly.length > 0) {
593
- deployments.push(publishCustomDomains(workerUrl, customDomainsOnly));
601
+ deployments.push(
602
+ publishCustomDomains(workerUrl, accountId, customDomainsOnly)
603
+ );
594
604
  }
595
605
 
596
606
  // Configure any schedules for the script.
@@ -614,7 +624,11 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
614
624
  if (deployments.length > 0) {
615
625
  logger.log("Published", workerName, formatTime(deployMs));
616
626
  for (const target of targets.flat()) {
617
- logger.log(" ", target);
627
+ // Append protocol only on workers.dev domains
628
+ logger.log(
629
+ " ",
630
+ (target.endsWith("workers.dev") ? "https://" : "") + target
631
+ );
618
632
  }
619
633
  } else {
620
634
  logger.log("No publish targets for", workerName, formatTime(deployMs));
package/src/r2.ts CHANGED
@@ -1,4 +1,7 @@
1
+ import { Readable } from "node:stream";
1
2
  import { fetchResult } from "./cfetch";
3
+ import { fetchR2Objects } from "./cfetch/internal";
4
+ import type { HeadersInit } from "undici";
2
5
 
3
6
  /**
4
7
  * Information about a bucket, returned from `listR2Buckets()`.
@@ -48,3 +51,81 @@ export async function deleteR2Bucket(
48
51
  { method: "DELETE" }
49
52
  );
50
53
  }
54
+
55
+ export function bucketAndKeyFromObjectPath(objectPath = ""): {
56
+ bucket: string;
57
+ key: string;
58
+ } {
59
+ const match = /^([^/]+)\/(.*)/.exec(objectPath);
60
+ if (match === null) {
61
+ throw new Error(
62
+ `The object path must be in the form of {bucket}/{key} you provided ${objectPath}`
63
+ );
64
+ }
65
+
66
+ return { bucket: match[1], key: match[2] };
67
+ }
68
+
69
+ /**
70
+ * Downloads an object
71
+ */
72
+ export async function getR2Object(
73
+ accountId: string,
74
+ bucketName: string,
75
+ objectName: string
76
+ ): Promise<Readable> {
77
+ const response = await fetchR2Objects(
78
+ `/accounts/${accountId}/r2/buckets/${bucketName}/objects/${objectName}`,
79
+ { method: "GET" }
80
+ );
81
+
82
+ return Readable.from(response.body);
83
+ }
84
+
85
+ /**
86
+ * Uploads an object
87
+ */
88
+ export async function putR2Object(
89
+ accountId: string,
90
+ bucketName: string,
91
+ objectName: string,
92
+ object: Readable | Buffer,
93
+ options: Record<string, unknown>
94
+ ): Promise<void> {
95
+ const headerKeys = [
96
+ "content-length",
97
+ "content-type",
98
+ "content-disposition",
99
+ "content-encoding",
100
+ "content-language",
101
+ "cache-control",
102
+ "expires",
103
+ ];
104
+ const headers: HeadersInit = {};
105
+ for (const key of headerKeys) {
106
+ const value = options[key] || "";
107
+ if (value && typeof value === "string") headers[key] = value;
108
+ }
109
+
110
+ await fetchR2Objects(
111
+ `/accounts/${accountId}/r2/buckets/${bucketName}/objects/${objectName}`,
112
+ {
113
+ body: object,
114
+ headers,
115
+ method: "PUT",
116
+ }
117
+ );
118
+ }
119
+ /**
120
+ * Delete an Object
121
+ */
122
+ export async function deleteR2Object(
123
+ accountId: string,
124
+ bucketName: string,
125
+ objectName: string
126
+ ): Promise<void> {
127
+ await fetchR2Objects(
128
+ `/accounts/${accountId}/r2/buckets/${bucketName}/objects/${objectName}`,
129
+ { method: "DELETE" }
130
+ );
131
+ }
@@ -58,13 +58,14 @@ type OutcomeFilter = {
58
58
 
59
59
  /**
60
60
  * There are five possible outcomes we can get, three of which
61
- * (exception, exceededCpu, and unknown) are considered errors
61
+ * (exception, exceededCpu, exceededMemory, and unknown) are considered errors
62
62
  */
63
63
  export type Outcome =
64
64
  | "ok"
65
65
  | "canceled"
66
66
  | "exception"
67
67
  | "exceededCpu"
68
+ | "exceededMemory"
68
69
  | "unknown";
69
70
 
70
71
  /**
@@ -210,6 +211,7 @@ function parseOutcome(
210
211
  case "error":
211
212
  outcomes.add("exception");
212
213
  outcomes.add("exceededCpu");
214
+ outcomes.add("exceededMemory");
213
215
  outcomes.add("unknown");
214
216
  break;
215
217
 
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
@@ -262,6 +262,7 @@ interface State extends AuthTokens {
262
262
  interface AuthTokens {
263
263
  accessToken?: AccessToken;
264
264
  refreshToken?: RefreshToken;
265
+ scopes?: Scope[];
265
266
  /** @deprecated - this field was only provided by the deprecated `wrangler1 config` command. */
266
267
  apiToken?: string;
267
268
  }
@@ -279,6 +280,7 @@ export interface UserAuthConfig {
279
280
  oauth_token?: string;
280
281
  refresh_token?: string;
281
282
  expiration_time?: string;
283
+ scopes?: string[];
282
284
  /** @deprecated - this field was only provided by the deprecated `wrangler1 config` command. */
283
285
  api_token?: string;
284
286
  }
@@ -355,7 +357,7 @@ function getAuthTokens(config?: UserAuthConfig): AuthTokens | undefined {
355
357
  if (getAuthFromEnv()) return;
356
358
 
357
359
  // otherwise try loading from the user auth config file.
358
- const { oauth_token, refresh_token, expiration_time, api_token } =
360
+ const { oauth_token, refresh_token, expiration_time, scopes, api_token } =
359
361
  config || readAuthConfigFile();
360
362
 
361
363
  if (oauth_token) {
@@ -366,6 +368,7 @@ function getAuthTokens(config?: UserAuthConfig): AuthTokens | undefined {
366
368
  expiry: expiration_time ?? "2000-01-01:00:00:00+00:00",
367
369
  },
368
370
  refreshToken: { value: refresh_token ?? "" },
371
+ scopes: scopes as Scope[],
369
372
  };
370
373
  } else if (api_token) {
371
374
  logger.warn(
@@ -959,6 +962,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
959
962
  oauth_token: exchange.token?.value ?? "",
960
963
  expiration_time: exchange.token?.expiry,
961
964
  refresh_token: exchange.refreshToken?.value,
965
+ scopes: exchange.scopes,
962
966
  });
963
967
  res.writeHead(307, {
964
968
  Location:
@@ -1003,8 +1007,14 @@ async function refreshToken(): Promise<boolean> {
1003
1007
  expiry: "",
1004
1008
  },
1005
1009
  refreshToken: { value: refresh_token } = {},
1010
+ scopes,
1006
1011
  } = await exchangeRefreshTokenForAccessToken();
1007
- writeAuthConfigFile({ oauth_token, expiration_time, refresh_token });
1012
+ writeAuthConfigFile({
1013
+ oauth_token,
1014
+ expiration_time,
1015
+ refresh_token,
1016
+ scopes,
1017
+ });
1008
1018
  return true;
1009
1019
  } catch (err) {
1010
1020
  return false;
@@ -1169,3 +1179,11 @@ export function getAccountFromCache():
1169
1179
  "wrangler-account.json"
1170
1180
  ).account;
1171
1181
  }
1182
+
1183
+ /**
1184
+ * Get the scopes of the following token, will only return scopes
1185
+ * if the token is an OAuth token.
1186
+ */
1187
+ export function getScopes(): Scope[] | undefined {
1188
+ return LocalState.scopes;
1189
+ }