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
@@ -1,14 +1,5 @@
1
1
  import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
2
- import {
3
- basename,
4
- dirname,
5
- extname,
6
- join,
7
- relative,
8
- resolve,
9
- sep,
10
- } from "node:path";
11
- import { hash as blake3hash } from "blake3-wasm";
2
+ import { dirname, join, relative, resolve, sep } from "node:path";
12
3
  import { render, Text } from "ink";
13
4
  import Spinner from "ink-spinner";
14
5
  import { getType } from "mime";
@@ -25,6 +16,7 @@ import {
25
16
  MAX_CHECK_MISSING_ATTEMPTS,
26
17
  MAX_UPLOAD_ATTEMPTS,
27
18
  } from "./constants";
19
+ import { hashFile } from "./hash";
28
20
  import { pagesBetaWarning } from "./utils";
29
21
  import type { UploadPayloadFile, YargsOptionsToInterface } from "./types";
30
22
  import type { Argv } from "yargs";
@@ -144,11 +136,6 @@ export const upload = async (
144
136
  } else {
145
137
  const name = relative(startingDir, filepath).split(sep).join("/");
146
138
 
147
- const fileContent = await readFile(filepath);
148
-
149
- const base64Content = fileContent.toString("base64");
150
- const extension = extname(basename(name)).substring(1);
151
-
152
139
  if (filestat.size > 25 * 1024 * 1024) {
153
140
  throw new FatalError(
154
141
  `Error: Pages only supports files up to ${prettyBytes(
@@ -163,9 +150,7 @@ export const upload = async (
163
150
  path: filepath,
164
151
  contentType: getType(name) || "application/octet-stream",
165
152
  sizeInBytes: filestat.size,
166
- hash: blake3hash(base64Content + extension)
167
- .toString("hex")
168
- .slice(0, 32),
153
+ hash: hashFile(filepath),
169
154
  });
170
155
  }
171
156
  })
package/src/publish.ts CHANGED
@@ -8,7 +8,7 @@ import { printBundleSize } from "./bundle-reporter";
8
8
  import { fetchListResult, fetchResult } from "./cfetch";
9
9
  import { printBindings } from "./config";
10
10
  import { createWorkerUploadForm } from "./create-worker-upload-form";
11
- import { confirm } from "./dialogs";
11
+ import { confirm, fromDashMessagePrompt } from "./dialogs";
12
12
  import { getMigrationsToUpload } from "./durable";
13
13
  import { logger } from "./logger";
14
14
  import { getMetricsUsageHeaders } from "./metrics";
@@ -218,11 +218,38 @@ Update them to point to this script instead?`;
218
218
 
219
219
  export default async function publish(props: Props): Promise<void> {
220
220
  // TODO: warn if git/hg has uncommitted changes
221
- const { config, accountId } = props;
221
+ const { config, accountId, name } = props;
222
+ if (accountId && name) {
223
+ try {
224
+ const serviceMetaData = await fetchResult(
225
+ `/accounts/${accountId}/workers/services/${name}`
226
+ );
227
+ const { default_environment } = serviceMetaData as {
228
+ default_environment: {
229
+ script: { last_deployed_from: "dash" | "wrangler" | "api" };
230
+ };
231
+ };
232
+
233
+ if (
234
+ (await fromDashMessagePrompt(
235
+ default_environment.script.last_deployed_from
236
+ )) === false
237
+ )
238
+ return;
239
+ } catch (e) {
240
+ // code: 10090, message: workers.api.error.service_not_found
241
+ // is thrown from the above fetchResult on the first publish of a Worker
242
+ if ((e as { code?: number }).code !== 10090) {
243
+ logger.error(e);
244
+ }
245
+ }
246
+ }
222
247
 
223
248
  if (!(props.compatibilityDate || config.compatibility_date)) {
224
249
  const compatibilityDateStr = `${new Date().getFullYear()}-${(
225
- new Date().getMonth() + ""
250
+ new Date().getMonth() +
251
+ 1 +
252
+ ""
226
253
  ).padStart(2, "0")}-${(new Date().getDate() + "").padStart(2, "0")}`;
227
254
 
228
255
  throw new Error(`A compatibility_date is required when publishing. Add the following to your wrangler.toml file:.
@@ -397,6 +424,9 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
397
424
  // facades on top of it
398
425
  workerDefinitions: undefined,
399
426
  firstPartyWorkerDevFacade: false,
427
+ // We want to know if the build is for development or publishing
428
+ // This could potentially cause issues as we no longer have identical behaviour between dev and publish?
429
+ targetConsumer: "publish",
400
430
  }
401
431
  );
402
432
 
@@ -475,10 +505,14 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
475
505
  keep_bindings: true,
476
506
  };
477
507
 
478
- void printBundleSize(
508
+ // As this is not deterministic for testing, we detect if in a jest environment and run asynchronously
509
+ // We do not care about the timing outside of testing
510
+ const bundleSizePromise = printBundleSize(
479
511
  { name: path.basename(resolvedEntryPointPath), content: content },
480
512
  modules
481
513
  );
514
+ if (process.env.JEST_WORKER_ID !== undefined) await bundleSizePromise;
515
+ else void bundleSizePromise;
482
516
 
483
517
  const withoutStaticAssets = {
484
518
  ...bindings,
@@ -122,7 +122,6 @@ type QueryFilter = {
122
122
  */
123
123
  export type TailFilterMessage = {
124
124
  filters: TailAPIFilter[];
125
- debug: boolean;
126
125
  };
127
126
 
128
127
  /**
@@ -130,12 +129,10 @@ export type TailFilterMessage = {
130
129
  * into a message that we can send to the tail worker.
131
130
  *
132
131
  * @param cliFilters An object containing all the filters passed in from the CLI
133
- * @param debug Whether or not we should be in debug mode
134
132
  * @returns A filter message ready to be sent to the tail worker
135
133
  */
136
134
  export function translateCLICommandToFilterMessage(
137
- cliFilters: TailCLIFilters,
138
- debug: boolean
135
+ cliFilters: TailCLIFilters
139
136
  ): TailFilterMessage {
140
137
  const apiFilters: TailAPIFilter[] = [];
141
138
 
@@ -165,7 +162,6 @@ export function translateCLICommandToFilterMessage(
165
162
 
166
163
  return {
167
164
  filters: apiFilters,
168
- debug,
169
165
  };
170
166
  }
171
167
 
package/src/tail/index.ts CHANGED
@@ -69,13 +69,15 @@ function makeDeleteTailUrl(
69
69
  *
70
70
  * @param accountId the account ID associated with the worker to tail
71
71
  * @param workerName the name of the worker to tail
72
- * @param message a `TailFilterMessage` to send up to the tail worker
72
+ * @param filters A list of `TailAPIFilters` given to the tail
73
+ * @param debug Flag to run tail in debug mode
73
74
  * @returns a websocket connection, an expiration, and a function to call to delete the tail
74
75
  */
75
76
  export async function createTail(
76
77
  accountId: string,
77
78
  workerName: string,
78
- message: TailFilterMessage,
79
+ filters: TailFilterMessage,
80
+ debug: boolean,
79
81
  env: string | undefined
80
82
  ): Promise<{
81
83
  tail: WebSocket;
@@ -90,6 +92,7 @@ export async function createTail(
90
92
  expires_at: expiration,
91
93
  } = await fetchResult<TailCreationApiResponse>(createTailUrl, {
92
94
  method: "POST",
95
+ body: JSON.stringify(filters),
93
96
  });
94
97
 
95
98
  // delete the tail (not yet!)
@@ -109,7 +112,7 @@ export async function createTail(
109
112
  // send filters when we open up
110
113
  tail.on("open", function () {
111
114
  tail.send(
112
- JSON.stringify(message),
115
+ JSON.stringify({ debug: debug }),
113
116
  { binary: false, compress: false, mask: false, fin: true },
114
117
  (err) => {
115
118
  if (err) {
@@ -0,0 +1,62 @@
1
+ export type Awaitable<T> = T | Promise<T>;
2
+ // TODO: allow dispatching more events?
3
+ export type Dispatcher = (
4
+ type: "scheduled",
5
+ init: { cron?: string }
6
+ ) => Awaitable<void>;
7
+
8
+ export interface MiddlewareContext {
9
+ dispatch: Dispatcher;
10
+ next(request: Request, env: any): Awaitable<Response>;
11
+ }
12
+
13
+ export type Middleware = (
14
+ request: Request,
15
+ env: any,
16
+ ctx: ExecutionContext,
17
+ middlewareCtx: MiddlewareContext
18
+ ) => Awaitable<Response>;
19
+
20
+ const __facade_middleware__: Middleware[] = [];
21
+
22
+ // The register functions allow for the insertion of one or many middleware,
23
+ // We register internal middleware first in the stack, but have no way of controlling
24
+ // the order that addMiddleware is run in service workers so need an internal function.
25
+ export function __facade_register__(...args: (Middleware | Middleware[])[]) {
26
+ __facade_middleware__.push(...args.flat());
27
+ }
28
+ export function __facade_registerInternal__(
29
+ ...args: (Middleware | Middleware[])[]
30
+ ) {
31
+ __facade_middleware__.unshift(...args.flat());
32
+ }
33
+
34
+ function __facade_invokeChain__(
35
+ request: Request,
36
+ env: any,
37
+ ctx: ExecutionContext,
38
+ dispatch: Dispatcher,
39
+ middlewareChain: Middleware[]
40
+ ): Awaitable<Response> {
41
+ const [head, ...tail] = middlewareChain;
42
+ const middlewareCtx: MiddlewareContext = {
43
+ dispatch,
44
+ next(newRequest, newEnv) {
45
+ return __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail);
46
+ },
47
+ };
48
+ return head(request, env, ctx, middlewareCtx);
49
+ }
50
+
51
+ export function __facade_invoke__(
52
+ request: Request,
53
+ env: any,
54
+ ctx: ExecutionContext,
55
+ dispatch: Dispatcher,
56
+ finalMiddleware: Middleware
57
+ ): Awaitable<Response> {
58
+ return __facade_invokeChain__(request, env, ctx, dispatch, [
59
+ ...__facade_middleware__,
60
+ finalMiddleware,
61
+ ]);
62
+ }
@@ -0,0 +1,84 @@
1
+ // // This loads all middlewares exposed on the middleware object
2
+ // // and then starts the invocation chain.
3
+ // // The big idea is that we can add these to the middleware export dynamically
4
+ // // through wrangler, or we can potentially let users directly add them as a sort
5
+ // // of "plugin" system.
6
+
7
+ import {
8
+ Dispatcher,
9
+ Middleware,
10
+ __facade_invoke__,
11
+ __facade_register__,
12
+ } from "./common";
13
+
14
+ // @ts-expect-error We'll swap in the entry point during build
15
+ import worker from "__ENTRY_POINT__";
16
+
17
+ // We need to preserve all of the exports from the worker
18
+ // @ts-expect-error We'll swap in the entry point during build
19
+ export * from "__ENTRY_POINT__";
20
+
21
+ class __Facade_ScheduledController__ implements ScheduledController {
22
+ #noRetry: ScheduledController["noRetry"];
23
+
24
+ constructor(
25
+ readonly scheduledTime: number,
26
+ readonly cron: string,
27
+ noRetry: ScheduledController["noRetry"]
28
+ ) {
29
+ this.#noRetry = noRetry;
30
+ }
31
+
32
+ noRetry() {
33
+ if (!(this instanceof __Facade_ScheduledController__)) {
34
+ throw new TypeError("Illegal invocation");
35
+ }
36
+ // Need to call native method immediately in case uncaught error thrown
37
+ this.#noRetry();
38
+ }
39
+ }
40
+
41
+ const __facade_modules_fetch__: Middleware = function (request, env, ctx) {
42
+ if (worker.fetch === undefined) throw new Error("No fetch handler!"); // TODO: proper error message
43
+ return worker.fetch(request, env, ctx);
44
+ };
45
+
46
+ const facade: ExportedHandler<unknown> = {
47
+ fetch(request, env, ctx) {
48
+ // Get the chain of middleware from the worker object
49
+ if (worker.middleware && worker.middleware.length > 0) {
50
+ for (const middleware of worker.middleware) {
51
+ __facade_register__(middleware);
52
+ }
53
+
54
+ const __facade_modules_dispatch__: Dispatcher = function (type, init) {
55
+ if (type === "scheduled" && worker.scheduled !== undefined) {
56
+ const controller = new __Facade_ScheduledController__(
57
+ Date.now(),
58
+ init.cron ?? "",
59
+ () => {}
60
+ );
61
+ return worker.scheduled(controller, env, ctx);
62
+ }
63
+ };
64
+
65
+ return __facade_invoke__(
66
+ request,
67
+ env,
68
+ ctx,
69
+ __facade_modules_dispatch__,
70
+ __facade_modules_fetch__
71
+ );
72
+ } else {
73
+ // We didn't have any middleware so we can skip the invocation chain,
74
+ // and just call the fetch handler directly
75
+
76
+ // We "don't care" if this is undefined as we want to have the same behaviour
77
+ // as if the worker completely bypassed middleware.
78
+ return worker.fetch!(request, env, ctx);
79
+ }
80
+ },
81
+ scheduled: worker.scheduled,
82
+ };
83
+
84
+ export default facade;
@@ -0,0 +1,213 @@
1
+ import { Awaitable, Dispatcher, Middleware, __facade_invoke__ } from "./common";
2
+ export { __facade_register__, __facade_registerInternal__ } from "./common";
3
+
4
+ // Miniflare's `EventTarget` follows the spec and doesn't allow exceptions to
5
+ // be caught by `dispatchEvent`. Instead it has a custom`ThrowingEventTarget`
6
+ // class that rethrows errors from event listeners in `dispatchEvent`.
7
+ // We'd like errors to be propagated to the top-level `addEventListener`, so
8
+ // we'd like to use `ThrowingEventTarget`. Unfortunately, `ThrowingEventTarget`
9
+ // isn't exposed on the global scope, but `WorkerGlobalScope` (which extends
10
+ // `ThrowingEventTarget`) is. Therefore, we get at it in this nasty way.
11
+ let __FACADE_EVENT_TARGET__: EventTarget;
12
+ if ((globalThis as any).MINIFLARE) {
13
+ __FACADE_EVENT_TARGET__ = new (Object.getPrototypeOf(WorkerGlobalScope))();
14
+ } else {
15
+ __FACADE_EVENT_TARGET__ = new EventTarget();
16
+ }
17
+
18
+ declare global {
19
+ var __facade_addEventListener__: (
20
+ type: string,
21
+ listener: EventListenerOrEventListenerObject,
22
+ options?: EventTargetAddEventListenerOptions | boolean
23
+ ) => void;
24
+ var __facade_removeEventListener__: (
25
+ type: string,
26
+ listener: EventListenerOrEventListenerObject,
27
+ options?: EventTargetEventListenerOptions | boolean
28
+ ) => void;
29
+ var __facade_dispatchEvent__: (event: Event) => void;
30
+ }
31
+
32
+ function __facade_isSpecialEvent__(type: string) {
33
+ return type === "fetch" || type === "scheduled";
34
+ }
35
+ globalThis.__facade_addEventListener__ = function (type, listener, options) {
36
+ if (__facade_isSpecialEvent__(type)) {
37
+ __FACADE_EVENT_TARGET__.addEventListener(type, listener, options);
38
+ } else {
39
+ globalThis.addEventListener(type as any, listener, options);
40
+ }
41
+ };
42
+ globalThis.__facade_removeEventListener__ = function (type, listener, options) {
43
+ if (__facade_isSpecialEvent__(type)) {
44
+ __FACADE_EVENT_TARGET__.removeEventListener(type, listener, options);
45
+ } else {
46
+ globalThis.removeEventListener(type as any, listener, options);
47
+ }
48
+ };
49
+ globalThis.__facade_dispatchEvent__ = function (event) {
50
+ if (__facade_isSpecialEvent__(event.type)) {
51
+ __FACADE_EVENT_TARGET__.dispatchEvent(event);
52
+ } else {
53
+ globalThis.dispatchEvent(event as any);
54
+ }
55
+ };
56
+
57
+ const __facade_waitUntil__ = Symbol("__facade_waitUntil__");
58
+ const __facade_response__ = Symbol("__facade_response__");
59
+ const __facade_dispatched__ = Symbol("__facade_dispatched__");
60
+
61
+ class __Facade_ExtendableEvent__ extends Event {
62
+ [__facade_waitUntil__]: Awaitable<unknown>[] = [];
63
+
64
+ waitUntil(promise: Awaitable<any>) {
65
+ if (!(this instanceof __Facade_ExtendableEvent__)) {
66
+ throw new TypeError("Illegal invocation");
67
+ }
68
+ this[__facade_waitUntil__].push(promise);
69
+ }
70
+ }
71
+
72
+ interface FetchEventInit extends EventInit {
73
+ request: Request;
74
+ passThroughOnException: FetchEvent["passThroughOnException"];
75
+ }
76
+
77
+ class __Facade_FetchEvent__ extends __Facade_ExtendableEvent__ {
78
+ #request: Request;
79
+ #passThroughOnException: FetchEvent["passThroughOnException"];
80
+ [__facade_response__]?: Awaitable<Response>;
81
+ [__facade_dispatched__] = false;
82
+
83
+ constructor(type: "fetch", init: FetchEventInit) {
84
+ super(type);
85
+ this.#request = init.request;
86
+ this.#passThroughOnException = init.passThroughOnException;
87
+ }
88
+
89
+ get request() {
90
+ return this.#request;
91
+ }
92
+
93
+ respondWith(response: Awaitable<Response>) {
94
+ if (!(this instanceof __Facade_FetchEvent__)) {
95
+ throw new TypeError("Illegal invocation");
96
+ }
97
+ if (this[__facade_response__] !== undefined) {
98
+ throw new DOMException(
99
+ "FetchEvent.respondWith() has already been called; it can only be called once.",
100
+ "InvalidStateError"
101
+ );
102
+ }
103
+ if (this[__facade_dispatched__]) {
104
+ throw new DOMException(
105
+ "Too late to call FetchEvent.respondWith(). It must be called synchronously in the event handler.",
106
+ "InvalidStateError"
107
+ );
108
+ }
109
+ this.stopImmediatePropagation();
110
+ this[__facade_response__] = response;
111
+ }
112
+
113
+ passThroughOnException() {
114
+ if (!(this instanceof __Facade_FetchEvent__)) {
115
+ throw new TypeError("Illegal invocation");
116
+ }
117
+ // Need to call native method immediately in case uncaught error thrown
118
+ this.#passThroughOnException();
119
+ }
120
+ }
121
+
122
+ interface ScheduledEventInit extends EventInit {
123
+ scheduledTime: number;
124
+ cron: string;
125
+ noRetry: ScheduledEvent["noRetry"];
126
+ }
127
+
128
+ class __Facade_ScheduledEvent__ extends __Facade_ExtendableEvent__ {
129
+ #scheduledTime: number;
130
+ #cron: string;
131
+ #noRetry: ScheduledEvent["noRetry"];
132
+
133
+ constructor(type: "scheduled", init: ScheduledEventInit) {
134
+ super(type);
135
+ this.#scheduledTime = init.scheduledTime;
136
+ this.#cron = init.cron;
137
+ this.#noRetry = init.noRetry;
138
+ }
139
+
140
+ get scheduledTime() {
141
+ return this.#scheduledTime;
142
+ }
143
+
144
+ get cron() {
145
+ return this.#cron;
146
+ }
147
+
148
+ noRetry() {
149
+ if (!(this instanceof __Facade_ScheduledEvent__)) {
150
+ throw new TypeError("Illegal invocation");
151
+ }
152
+ // Need to call native method immediately in case uncaught error thrown
153
+ this.#noRetry();
154
+ }
155
+ }
156
+
157
+ globalThis.addEventListener("fetch", (event) => {
158
+ const ctx: ExecutionContext = {
159
+ waitUntil: event.waitUntil.bind(event),
160
+ passThroughOnException: event.passThroughOnException.bind(event),
161
+ };
162
+
163
+ const __facade_sw_dispatch__: Dispatcher = function (type, init) {
164
+ if (type === "scheduled") {
165
+ const facadeEvent = new __Facade_ScheduledEvent__("scheduled", {
166
+ scheduledTime: Date.now(),
167
+ cron: init.cron ?? "",
168
+ noRetry() {},
169
+ });
170
+
171
+ __FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent);
172
+ event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__]));
173
+ }
174
+ };
175
+
176
+ const __facade_sw_fetch__: Middleware = function (request, _env, ctx) {
177
+ const facadeEvent = new __Facade_FetchEvent__("fetch", {
178
+ request,
179
+ passThroughOnException: ctx.passThroughOnException,
180
+ });
181
+
182
+ __FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent);
183
+ facadeEvent[__facade_dispatched__] = true;
184
+ event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__]));
185
+
186
+ const response = facadeEvent[__facade_response__];
187
+ if (response === undefined) {
188
+ throw new Error("No response!"); // TODO: proper error message
189
+ }
190
+ return response;
191
+ };
192
+
193
+ event.respondWith(
194
+ __facade_invoke__(
195
+ event.request,
196
+ globalThis,
197
+ ctx,
198
+ __facade_sw_dispatch__,
199
+ __facade_sw_fetch__
200
+ )
201
+ );
202
+ });
203
+
204
+ globalThis.addEventListener("scheduled", (event) => {
205
+ const facadeEvent = new __Facade_ScheduledEvent__("scheduled", {
206
+ scheduledTime: event.scheduledTime,
207
+ cron: event.cron,
208
+ noRetry: event.noRetry.bind(event),
209
+ });
210
+
211
+ __FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent);
212
+ event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__]));
213
+ });
@@ -0,0 +1,40 @@
1
+ import type { Middleware } from "./common";
2
+
3
+ // A middleware has to be a function of type Middleware
4
+ const prettyError: Middleware = async (request, env, _ctx, middlewareCtx) => {
5
+ try {
6
+ const response = await middlewareCtx.next(request, env);
7
+ return response;
8
+ } catch (e: any) {
9
+ const html = `
10
+ <!DOCTYPE html>
11
+ <html lang="en">
12
+ <head>
13
+ <meta charset="UTF-8">
14
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
15
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
16
+ <title>Error 🚨</title>
17
+ <style>
18
+ pre {
19
+ margin: 16px auto;
20
+ max-width: 600px;
21
+ background-color: #eeeeee;
22
+ border-radius: 4px;
23
+ padding: 16px;
24
+ }
25
+ </style>
26
+ </head>
27
+ <body>
28
+ <pre>${e.stack}</pre>
29
+ </body>
30
+ </html>
31
+ `;
32
+
33
+ return new Response(html, {
34
+ status: 500,
35
+ headers: { "Content-Type": "text/html;charset=utf-8" },
36
+ });
37
+ }
38
+ };
39
+
40
+ export default prettyError;
@@ -0,0 +1,14 @@
1
+ import type { Middleware } from "./common";
2
+
3
+ // A middleware has to be a function of type Middleware
4
+ const scheduled: Middleware = async (request, env, _ctx, middlewareCtx) => {
5
+ const url = new URL(request.url);
6
+ if (url.pathname === "/__scheduled") {
7
+ const cron = url.searchParams.get("cron") ?? "";
8
+ await middlewareCtx.dispatch("scheduled", { cron });
9
+ return new Response("OK");
10
+ }
11
+ return middlewareCtx.next(request, env);
12
+ };
13
+
14
+ export default scheduled;