diff options
| author | akiyamn | 2023-09-24 23:22:21 +1000 |
|---|---|---|
| committer | akiyamn | 2023-09-24 23:22:21 +1000 |
| commit | 4e87195739f2a5d9a05451b48773c8afdc680765 (patch) | |
| tree | 9cba501844a4a11dcbdffc4050ed8189561c55ed /node_modules/wrangler/templates | |
| download | price-tracker-worker-4e87195739f2a5d9a05451b48773c8afdc680765.tar.gz price-tracker-worker-4e87195739f2a5d9a05451b48773c8afdc680765.zip | |
Initial commit (by create-cloudflare CLI)
Diffstat (limited to 'node_modules/wrangler/templates')
33 files changed, 1801 insertions, 0 deletions
diff --git a/node_modules/wrangler/templates/__tests__/pages-dev-util.test.ts b/node_modules/wrangler/templates/__tests__/pages-dev-util.test.ts new file mode 100644 index 0000000..3dea566 --- /dev/null +++ b/node_modules/wrangler/templates/__tests__/pages-dev-util.test.ts @@ -0,0 +1,128 @@ +import { isRoutingRuleMatch } from "../pages-dev-util"; + +describe("isRoutingRuleMatch", () => { + it("should match rules referencing root level correctly", () => { + const routingRule = "/"; + + expect(isRoutingRuleMatch("/", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo", routingRule)).toBeFalsy(); + expect(isRoutingRuleMatch("/foo/", routingRule)).toBeFalsy(); + expect(isRoutingRuleMatch("/foo/bar", routingRule)).toBeFalsy(); + }); + + it("should match include-all rules correctly", () => { + const routingRule = "/*"; + + expect(isRoutingRuleMatch("/", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo/", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo/bar", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo/bar/", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo/bar/baz", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo/bar/baz/", routingRule)).toBeTruthy(); + }); + + it("should match `/*` suffix-ed rules correctly", () => { + let routingRule = "/foo/*"; + + expect(isRoutingRuleMatch("/foo", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo/", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foobar", routingRule)).toBeFalsy(); + expect(isRoutingRuleMatch("/foo/bar", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo/bar/baz", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/bar/foo", routingRule)).toBeFalsy(); + expect(isRoutingRuleMatch("/bar/foo/baz", routingRule)).toBeFalsy(); + + routingRule = "/foo/bar/*"; + + expect(isRoutingRuleMatch("/foo", routingRule)).toBeFalsy(); + expect(isRoutingRuleMatch("/foo/", routingRule)).toBeFalsy(); + expect(isRoutingRuleMatch("/foo/bar", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo/bar/baz", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo/barfoo", routingRule)).toBeFalsy(); + expect(isRoutingRuleMatch("baz/foo/bar", routingRule)).toBeFalsy(); + expect(isRoutingRuleMatch("baz/foo/bar/", routingRule)).toBeFalsy(); + }); + + it("should match `/` suffix-ed rules correctly", () => { + let routingRule = "/foo/"; + expect(isRoutingRuleMatch("/foo/", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo", routingRule)).toBeTruthy(); + + routingRule = "/foo/bar/"; + expect(isRoutingRuleMatch("/foo/bar/", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo/bar", routingRule)).toBeTruthy(); + }); + + it("should match `*` suffix-ed rules correctly", () => { + let routingRule = "/foo*"; + expect(isRoutingRuleMatch("/foo", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo/", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foobar", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/barfoo", routingRule)).toBeFalsy(); + expect(isRoutingRuleMatch("/foo/bar", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/bar/foo", routingRule)).toBeFalsy(); + expect(isRoutingRuleMatch("/bar/foobar", routingRule)).toBeFalsy(); + expect(isRoutingRuleMatch("/foo/bar/baz", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/bar/foo/baz", routingRule)).toBeFalsy(); + + routingRule = "/foo/bar*"; + expect(isRoutingRuleMatch("/foo/bar", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo/bar/", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo/barfoo", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/bar/foo/barfoo", routingRule)).toBeFalsy(); + expect(isRoutingRuleMatch("/foo/bar/baz", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/bar/foo/bar/baz", routingRule)).toBeFalsy(); + }); + + it("should match rules without wildcards correctly", () => { + let routingRule = "/foo"; + + expect(isRoutingRuleMatch("/foo", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo/", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo/bar", routingRule)).toBeFalsy(); + expect(isRoutingRuleMatch("/bar/foo", routingRule)).toBeFalsy(); + + routingRule = "/foo/bar"; + expect(isRoutingRuleMatch("/foo/bar", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo/bar/", routingRule)).toBeTruthy(); + expect(isRoutingRuleMatch("/foo/bar/baz", routingRule)).toBeFalsy(); + expect(isRoutingRuleMatch("/baz/foo/bar", routingRule)).toBeFalsy(); + }); + + it("should throw an error if pathname or routing rule params are missing", () => { + // MISSING PATHNAME + expect(() => + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore: sanity check + isRoutingRuleMatch(undefined, "/*") + ).toThrow("Pathname is undefined."); + + expect(() => + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore: sanity check + isRoutingRuleMatch(null, "/*") + ).toThrow("Pathname is undefined."); + + expect(() => isRoutingRuleMatch("", "/*")).toThrow( + "Pathname is undefined." + ); + + // MISSING ROUTING RULE + expect(() => + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore: sanity check + isRoutingRuleMatch("/foo", undefined) + ).toThrow("Routing rule is undefined."); + + expect(() => + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore: sanity check + isRoutingRuleMatch("/foo", null) + ).toThrow("Routing rule is undefined."); + + expect(() => isRoutingRuleMatch("/foo", "")).toThrow( + "Routing rule is undefined." + ); + }); +}); diff --git a/node_modules/wrangler/templates/__tests__/tsconfig-sanity.ts b/node_modules/wrangler/templates/__tests__/tsconfig-sanity.ts new file mode 100644 index 0000000..c9f2bcd --- /dev/null +++ b/node_modules/wrangler/templates/__tests__/tsconfig-sanity.ts @@ -0,0 +1,12 @@ +// `@types/node` should be included +Buffer.from("test"); + +// `@types/jest` should be included +test("test"); + +// @ts-expect-error `@cloudflare/workers-types` should NOT be included +const _handler: ExportedHandler = {}; +// @ts-expect-error `@cloudflare/workers-types` should NOT be included +new HTMLRewriter(); + +export {}; diff --git a/node_modules/wrangler/templates/__tests__/tsconfig.json b/node_modules/wrangler/templates/__tests__/tsconfig.json new file mode 100644 index 0000000..683b6d2 --- /dev/null +++ b/node_modules/wrangler/templates/__tests__/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@cloudflare/workers-tsconfig/tsconfig.json", + "compilerOptions": { + "types": ["node", "jest"] + }, + "include": ["**/*.ts"], + "exclude": [] +} diff --git a/node_modules/wrangler/templates/checked-fetch.js b/node_modules/wrangler/templates/checked-fetch.js new file mode 100644 index 0000000..cf630c0 --- /dev/null +++ b/node_modules/wrangler/templates/checked-fetch.js @@ -0,0 +1,30 @@ +const urls = new Set(); + +function checkURL(request, init) { + const url = + request instanceof URL + ? request + : new URL( + (typeof request === "string" + ? new Request(request, init) + : request + ).url + ); + if (url.port && url.port !== "443" && url.protocol === "https:") { + if (!urls.has(url.toString())) { + urls.add(url.toString()); + console.warn( + `WARNING: known issue with \`fetch()\` requests to custom HTTPS ports in published Workers:\n` + + ` - ${url.toString()} - the custom port will be ignored when the Worker is published using the \`wrangler deploy\` command.\n` + ); + } + } +} + +globalThis.fetch = new Proxy(globalThis.fetch, { + apply(target, thisArg, argArray) { + const [request, init] = argArray; + checkURL(request, init); + return Reflect.apply(target, thisArg, argArray); + }, +}); diff --git a/node_modules/wrangler/templates/facade.d.ts b/node_modules/wrangler/templates/facade.d.ts new file mode 100644 index 0000000..bfd2f4c --- /dev/null +++ b/node_modules/wrangler/templates/facade.d.ts @@ -0,0 +1,17 @@ +declare module "__ENTRY_POINT__" { + import type { Middleware } from "./middleware/common"; + const worker: ExportedHandler & { + middleware?: Middleware[]; + envWrappers: ((env: Record<string, unknown>) => Record<string, unknown>)[]; + }; + export default worker; +} + +declare module "__KV_ASSET_HANDLER__" { + export * from "@cloudflare/kv-asset-handler"; +} + +declare module "__STATIC_CONTENT_MANIFEST" { + const manifest: string; + export default manifest; +} diff --git a/node_modules/wrangler/templates/gitignore b/node_modules/wrangler/templates/gitignore new file mode 100644 index 0000000..3b0fe33 --- /dev/null +++ b/node_modules/wrangler/templates/gitignore @@ -0,0 +1,172 @@ +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) + +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# Runtime data + +pids +_.pid +_.seed +\*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover + +lib-cov + +# Coverage directory used by tools like istanbul + +coverage +\*.lcov + +# nyc test coverage + +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) + +.grunt + +# Bower dependency directory (https://bower.io/) + +bower_components + +# node-waf configuration + +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) + +build/Release + +# Dependency directories + +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) + +web_modules/ + +# TypeScript cache + +\*.tsbuildinfo + +# Optional npm cache directory + +.npm + +# Optional eslint cache + +.eslintcache + +# Optional stylelint cache + +.stylelintcache + +# Microbundle cache + +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history + +.node_repl_history + +# Output of 'npm pack' + +\*.tgz + +# Yarn Integrity file + +.yarn-integrity + +# dotenv environment variable files + +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) + +.cache +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +.cache/ + +# Comment in the public line in if your project uses Gatsby and not Next.js + +# https://nextjs.org/blog/next-9-1#public-directory-support + +# public + +# vuepress build output + +.vuepress/dist + +# vuepress v2.x temp and cache directory + +.temp +.cache + +# Docusaurus cache and generated files + +.docusaurus + +# Serverless directories + +.serverless/ + +# FuseBox cache + +.fusebox/ + +# DynamoDB Local files + +.dynamodb/ + +# TernJS port file + +.tern-port + +# Stores VSCode versions used for testing VSCode extensions + +.vscode-test + +# yarn v2 + +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.\* + +# wrangler project + +.dev.vars +.wrangler/ diff --git a/node_modules/wrangler/templates/init-tests/test-jest-new-worker.js b/node_modules/wrangler/templates/init-tests/test-jest-new-worker.js new file mode 100644 index 0000000..d33aa91 --- /dev/null +++ b/node_modules/wrangler/templates/init-tests/test-jest-new-worker.js @@ -0,0 +1,23 @@ +const { unstable_dev } = require("wrangler"); + +describe("Worker", () => { + let worker; + + beforeAll(async () => { + worker = await unstable_dev("src/index.js", { + experimental: { disableExperimentalWarning: true }, + }); + }); + + afterAll(async () => { + await worker.stop(); + }); + + it("should return Hello World", async () => { + const resp = await worker.fetch(); + if (resp) { + const text = await resp.text(); + expect(text).toMatchInlineSnapshot(`"Hello World!"`); + } + }); +}); diff --git a/node_modules/wrangler/templates/init-tests/test-vitest-new-worker.js b/node_modules/wrangler/templates/init-tests/test-vitest-new-worker.js new file mode 100644 index 0000000..9e120f1 --- /dev/null +++ b/node_modules/wrangler/templates/init-tests/test-vitest-new-worker.js @@ -0,0 +1,24 @@ +import { unstable_dev } from "wrangler"; +import { describe, expect, it, beforeAll, afterAll } from "vitest"; + +describe("Worker", () => { + let worker; + + beforeAll(async () => { + worker = await unstable_dev("src/index.js", { + experimental: { disableExperimentalWarning: true }, + }); + }); + + afterAll(async () => { + await worker.stop(); + }); + + it("should return Hello World", async () => { + const resp = await worker.fetch(); + if (resp) { + const text = await resp.text(); + expect(text).toMatchInlineSnapshot(`"Hello World!"`); + } + }); +}); diff --git a/node_modules/wrangler/templates/init-tests/test-vitest-new-worker.ts b/node_modules/wrangler/templates/init-tests/test-vitest-new-worker.ts new file mode 100644 index 0000000..974364b --- /dev/null +++ b/node_modules/wrangler/templates/init-tests/test-vitest-new-worker.ts @@ -0,0 +1,25 @@ +import { unstable_dev } from "wrangler"; +import type { UnstableDevWorker } from "wrangler"; +import { describe, expect, it, beforeAll, afterAll } from "vitest"; + +describe("Worker", () => { + let worker: UnstableDevWorker; + + beforeAll(async () => { + worker = await unstable_dev("src/index.ts", { + experimental: { disableExperimentalWarning: true }, + }); + }); + + afterAll(async () => { + await worker.stop(); + }); + + it("should return Hello World", async () => { + const resp = await worker.fetch(); + if (resp) { + const text = await resp.text(); + expect(text).toMatchInlineSnapshot(`"Hello World!"`); + } + }); +}); diff --git a/node_modules/wrangler/templates/middleware/common.ts b/node_modules/wrangler/templates/middleware/common.ts new file mode 100644 index 0000000..1319e5d --- /dev/null +++ b/node_modules/wrangler/templates/middleware/common.ts @@ -0,0 +1,67 @@ +export type Awaitable<T> = T | Promise<T>; +// TODO: allow dispatching more events? +export type Dispatcher = ( + type: "scheduled", + init: { cron?: string } +) => Awaitable<void>; + +export type IncomingRequest = Request< + unknown, + IncomingRequestCfProperties<unknown> +>; + +export interface MiddlewareContext { + dispatch: Dispatcher; + next(request: IncomingRequest, env: any): Awaitable<Response>; +} + +export type Middleware = ( + request: IncomingRequest, + env: any, + ctx: ExecutionContext, + middlewareCtx: MiddlewareContext +) => Awaitable<Response>; + +const __facade_middleware__: Middleware[] = []; + +// The register functions allow for the insertion of one or many middleware, +// We register internal middleware first in the stack, but have no way of controlling +// the order that addMiddleware is run in service workers so need an internal function. +export function __facade_register__(...args: (Middleware | Middleware[])[]) { + __facade_middleware__.push(...args.flat()); +} +export function __facade_registerInternal__( + ...args: (Middleware | Middleware[])[] +) { + __facade_middleware__.unshift(...args.flat()); +} + +function __facade_invokeChain__( + request: IncomingRequest, + env: any, + ctx: ExecutionContext, + dispatch: Dispatcher, + middlewareChain: Middleware[] +): Awaitable<Response> { + const [head, ...tail] = middlewareChain; + const middlewareCtx: MiddlewareContext = { + dispatch, + next(newRequest, newEnv) { + return __facade_invokeChain__(newRequest, newEnv, ctx, dispatch, tail); + }, + }; + return head(request, env, ctx, middlewareCtx); +} + +export function __facade_invoke__( + request: IncomingRequest, + env: any, + ctx: ExecutionContext, + dispatch: Dispatcher, + finalMiddleware: Middleware +): Awaitable<Response> { + return __facade_invokeChain__(request, env, ctx, dispatch, [ + ...__facade_middleware__, + finalMiddleware, + ]); +} diff --git a/node_modules/wrangler/templates/middleware/loader-modules.ts b/node_modules/wrangler/templates/middleware/loader-modules.ts new file mode 100644 index 0000000..3da0c44 --- /dev/null +++ b/node_modules/wrangler/templates/middleware/loader-modules.ts @@ -0,0 +1,137 @@ +// // This loads all middlewares exposed on the middleware object +// // and then starts the invocation chain. +// // The big idea is that we can add these to the middleware export dynamically +// // through wrangler, or we can potentially let users directly add them as a sort +// // of "plugin" system. + +import { + Dispatcher, + Middleware, + __facade_invoke__, + __facade_register__, +} from "./common"; + +import worker from "__ENTRY_POINT__"; + +// We need to preserve all of the exports from the worker +export * from "__ENTRY_POINT__"; + +class __Facade_ScheduledController__ implements ScheduledController { + #noRetry: ScheduledController["noRetry"]; + + constructor( + readonly scheduledTime: number, + readonly cron: string, + noRetry: ScheduledController["noRetry"] + ) { + this.#noRetry = noRetry; + } + + noRetry() { + if (!(this instanceof __Facade_ScheduledController__)) { + throw new TypeError("Illegal invocation"); + } + // Need to call native method immediately in case uncaught error thrown + this.#noRetry(); + } +} + +const __facade_modules_fetch__: ExportedHandlerFetchHandler = function ( + request, + env, + ctx +) { + if (worker.fetch === undefined) + throw new Error("Handler does not export a fetch() function."); + return worker.fetch(request, env, ctx); +}; + +function getMaskedEnv(rawEnv: unknown) { + let env = rawEnv as Record<string, unknown>; + if (worker.envWrappers && worker.envWrappers.length > 0) { + for (const wrapFn of worker.envWrappers) { + env = wrapFn(env); + } + } + return env; +} + +/** + * This type is here to cause a type error if a new export handler is added to + * `ExportHandler` without it being included in the `facade` below. + */ +type MissingExportHandlers = Omit< + Required<ExportedHandler>, + "tail" | "trace" | "scheduled" | "queue" | "test" | "email" | "fetch" +>; + +let registeredMiddleware = false; + +const facade: ExportedHandler<unknown> & MissingExportHandlers = { + ...(worker.tail && { + tail: maskHandlerEnv(worker.tail), + }), + ...(worker.trace && { + trace: maskHandlerEnv(worker.trace), + }), + ...(worker.scheduled && { + scheduled: maskHandlerEnv(worker.scheduled), + }), + ...(worker.queue && { + queue: maskHandlerEnv(worker.queue), + }), + ...(worker.test && { + test: maskHandlerEnv(worker.test), + }), + ...(worker.email && { + email: maskHandlerEnv(worker.email), + }), + + fetch(request, rawEnv, ctx) { + const env = getMaskedEnv(rawEnv); + // Get the chain of middleware from the worker object + if (worker.middleware && worker.middleware.length > 0) { + // Make sure we only register middleware once: + // https://github.com/cloudflare/workers-sdk/issues/2386#issuecomment-1614715911 + if (!registeredMiddleware) { + registeredMiddleware = true; + for (const middleware of worker.middleware) { + __facade_register__(middleware); + } + } + + const __facade_modules_dispatch__: Dispatcher = function (type, init) { + if (type === "scheduled" && worker.scheduled !== undefined) { + const controller = new __Facade_ScheduledController__( + Date.now(), + init.cron ?? "", + () => {} + ); + return worker.scheduled(controller, env, ctx); + } + }; + + return __facade_invoke__( + request, + env, + ctx, + __facade_modules_dispatch__, + __facade_modules_fetch__ + ); + } else { + // We didn't have any middleware so we can skip the invocation chain, + // and just call the fetch handler directly + + // We "don't care" if this is undefined as we want to have the same behavior + // as if the worker completely bypassed middleware. + return __facade_modules_fetch__(request, env, ctx); + } + }, +}; + +type HandlerFn<D, R> = (data: D, env: unknown, ctx: ExecutionContext) => R; +function maskHandlerEnv<D, R>(handler: HandlerFn<D, R>): HandlerFn<D, R> { + return (data, env, ctx) => handler(data, getMaskedEnv(env), ctx); +} + +export default facade; diff --git a/node_modules/wrangler/templates/middleware/loader-sw.ts b/node_modules/wrangler/templates/middleware/loader-sw.ts new file mode 100644 index 0000000..9e465f9 --- /dev/null +++ b/node_modules/wrangler/templates/middleware/loader-sw.ts @@ -0,0 +1,228 @@ +import { + Awaitable, + Dispatcher, + IncomingRequest, + Middleware, + __facade_invoke__, + __facade_register__, + __facade_registerInternal__, +} from "./common"; +export { __facade_register__, __facade_registerInternal__ }; + +// Miniflare 2's `EventTarget` follows the spec and doesn't allow exceptions to +// be caught by `dispatchEvent`. Instead it has a custom `ThrowingEventTarget` +// class that rethrows errors from event listeners in `dispatchEvent`. +// We'd like errors to be propagated to the top-level `addEventListener`, so +// we'd like to use `ThrowingEventTarget`. Unfortunately, `ThrowingEventTarget` +// isn't exposed on the global scope, but `WorkerGlobalScope` (which extends +// `ThrowingEventTarget`) is. Therefore, we get at it in this nasty way. +let __FACADE_EVENT_TARGET__: EventTarget; +if ((globalThis as any).MINIFLARE) { + __FACADE_EVENT_TARGET__ = new (Object.getPrototypeOf(WorkerGlobalScope))(); +} else { + __FACADE_EVENT_TARGET__ = new EventTarget(); +} + +function __facade_isSpecialEvent__( + type: string +): type is "fetch" | "scheduled" { + return type === "fetch" || type === "scheduled"; +} +const __facade__originalAddEventListener__ = globalThis.addEventListener; +const __facade__originalRemoveEventListener__ = globalThis.removeEventListener; +const __facade__originalDispatchEvent__ = globalThis.dispatchEvent; + +globalThis.addEventListener = function (type, listener, options) { + if (__facade_isSpecialEvent__(type)) { + __FACADE_EVENT_TARGET__.addEventListener( + type, + listener as EventListenerOrEventListenerObject, + options + ); + } else { + __facade__originalAddEventListener__(type, listener, options); + } +}; +globalThis.removeEventListener = function (type, listener, options) { + if (__facade_isSpecialEvent__(type)) { + __FACADE_EVENT_TARGET__.removeEventListener( + type, + listener as EventListenerOrEventListenerObject, + options + ); + } else { + __facade__originalRemoveEventListener__(type, listener, options); + } +}; +globalThis.dispatchEvent = function (event) { + if (__facade_isSpecialEvent__(event.type)) { + return __FACADE_EVENT_TARGET__.dispatchEvent(event); + } else { + return __facade__originalDispatchEvent__(event); + } +}; + +declare global { + var addMiddleware: typeof __facade_register__; + var addMiddlewareInternal: typeof __facade_registerInternal__; +} +globalThis.addMiddleware = __facade_register__; +globalThis.addMiddlewareInternal = __facade_registerInternal__; + +const __facade_waitUntil__ = Symbol("__facade_waitUntil__"); +const __facade_response__ = Symbol("__facade_response__"); +const __facade_dispatched__ = Symbol("__facade_dispatched__"); + +class __Facade_ExtendableEvent__ extends Event { + [__facade_waitUntil__]: Awaitable<unknown>[] = []; + + waitUntil(promise: Awaitable<any>) { + if (!(this instanceof __Facade_ExtendableEvent__)) { + throw new TypeError("Illegal invocation"); + } + this[__facade_waitUntil__].push(promise); + } +} + +interface FetchEventInit extends EventInit { + request: Request; + passThroughOnException: FetchEvent["passThroughOnException"]; +} + +class __Facade_FetchEvent__ extends __Facade_ExtendableEvent__ { + #request: Request; + #passThroughOnException: FetchEvent["passThroughOnException"]; + [__facade_response__]?: Awaitable<Response>; + [__facade_dispatched__] = false; + + constructor(type: "fetch", init: FetchEventInit) { + super(type); + this.#request = init.request; + this.#passThroughOnException = init.passThroughOnException; + } + + get request() { + return this.#request; + } + + respondWith(response: Awaitable<Response>) { + if (!(this instanceof __Facade_FetchEvent__)) { + throw new TypeError("Illegal invocation"); + } + if (this[__facade_response__] !== undefined) { + throw new DOMException( + "FetchEvent.respondWith() has already been called; it can only be called once.", + "InvalidStateError" + ); + } + if (this[__facade_dispatched__]) { + throw new DOMException( + "Too late to call FetchEvent.respondWith(). It must be called synchronously in the event handler.", + "InvalidStateError" + ); + } + this.stopImmediatePropagation(); + this[__facade_response__] = response; + } + + passThroughOnException() { + if (!(this instanceof __Facade_FetchEvent__)) { + throw new TypeError("Illegal invocation"); + } + // Need to call native method immediately in case uncaught error thrown + this.#passThroughOnException(); + } +} + +interface ScheduledEventInit extends EventInit { + scheduledTime: number; + cron: string; + noRetry: ScheduledEvent["noRetry"]; +} + +class __Facade_ScheduledEvent__ extends __Facade_ExtendableEvent__ { + #scheduledTime: number; + #cron: string; + #noRetry: ScheduledEvent["noRetry"]; + + constructor(type: "scheduled", init: ScheduledEventInit) { + super(type); + this.#scheduledTime = init.scheduledTime; + this.#cron = init.cron; + this.#noRetry = init.noRetry; + } + + get scheduledTime() { + return this.#scheduledTime; + } + + get cron() { + return this.#cron; + } + + noRetry() { + if (!(this instanceof __Facade_ScheduledEvent__)) { + throw new TypeError("Illegal invocation"); + } + // Need to call native method immediately in case uncaught error thrown + this.#noRetry(); + } +} + +__facade__originalAddEventListener__("fetch", (event) => { + const ctx: ExecutionContext = { + waitUntil: event.waitUntil.bind(event), + passThroughOnException: event.passThroughOnException.bind(event), + }; + + const __facade_sw_dispatch__: Dispatcher = function (type, init) { + if (type === "scheduled") { + const facadeEvent = new __Facade_ScheduledEvent__("scheduled", { + scheduledTime: Date.now(), + cron: init.cron ?? "", + noRetry() {}, + }); + + __FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent); + event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__])); + } + }; + + const __facade_sw_fetch__: Middleware = function (request, _env, ctx) { + const facadeEvent = new __Facade_FetchEvent__("fetch", { + request, + passThroughOnException: ctx.passThroughOnException, + }); + + __FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent); + facadeEvent[__facade_dispatched__] = true; + event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__])); + + const response = facadeEvent[__facade_response__]; + if (response === undefined) { + throw new Error("No response!"); // TODO: proper error message + } + return response; + }; + + event.respondWith( + __facade_invoke__( + event.request as IncomingRequest, + globalThis, + ctx, + __facade_sw_dispatch__, + __facade_sw_fetch__ + ) + ); +}); + +__facade__originalAddEventListener__("scheduled", (event) => { + const facadeEvent = new __Facade_ScheduledEvent__("scheduled", { + scheduledTime: event.scheduledTime, + cron: event.cron, + noRetry: event.noRetry.bind(event), + }); + + __FACADE_EVENT_TARGET__.dispatchEvent(facadeEvent); + event.waitUntil(Promise.all(facadeEvent[__facade_waitUntil__])); +}); diff --git a/node_modules/wrangler/templates/middleware/middleware-d1-beta.d.ts b/node_modules/wrangler/templates/middleware/middleware-d1-beta.d.ts new file mode 100644 index 0000000..5ee0e6f --- /dev/null +++ b/node_modules/wrangler/templates/middleware/middleware-d1-beta.d.ts @@ -0,0 +1,3 @@ +declare module "config:middleware/d1-beta" { + export const D1_IMPORTS: string[]; +} diff --git a/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts b/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts new file mode 100644 index 0000000..9377781 --- /dev/null +++ b/node_modules/wrangler/templates/middleware/middleware-miniflare3-json-error.ts @@ -0,0 +1,33 @@ +import type { Middleware } from "./common"; + +interface JsonError { + message?: string; + name?: string; + stack?: string; + cause?: JsonError; +} + +function reduceError(e: any): JsonError { + return { + name: e?.name, + message: e?.message ?? String(e), + stack: e?.stack, + cause: e?.cause === undefined ? undefined : reduceError(e.cause), + }; +} + +// See comment in `bundle.ts` for details on why this is needed +const jsonError: Middleware = async (request, env, _ctx, middlewareCtx) => { + try { + return await middlewareCtx.next(request, env); + } catch (e: any) { + const error = reduceError(e); + return Response.json(error, { + status: 500, + headers: { "MF-Experimental-Error-Stack": "true" }, + }); + } +}; + +export default jsonError; +export const wrap = undefined; diff --git a/node_modules/wrangler/templates/middleware/middleware-multiworker-dev.d.ts b/node_modules/wrangler/templates/middleware/middleware-multiworker-dev.d.ts new file mode 100644 index 0000000..704b273 --- /dev/null +++ b/node_modules/wrangler/templates/middleware/middleware-multiworker-dev.d.ts @@ -0,0 +1,4 @@ +declare module "config:middleware/multiworker-dev" { + import type { WorkerRegistry } from "../../src/dev-registry"; + export const workers: WorkerRegistry; +} diff --git a/node_modules/wrangler/templates/middleware/middleware-multiworker-dev.ts b/node_modules/wrangler/templates/middleware/middleware-multiworker-dev.ts new file mode 100644 index 0000000..fee33d5 --- /dev/null +++ b/node_modules/wrangler/templates/middleware/middleware-multiworker-dev.ts @@ -0,0 +1,59 @@ +// @ts-nocheck +/// <reference path="middleware-multiworker-dev.d.ts"/> + +import { workers } from "config:middleware/multiworker-dev"; +import type { WorkerRegistry } from "../../src/dev-registry"; + +export function wrap(env: Record<string, unknown>) { + const facadeEnv = { ...env }; + // For every Worker definition that's available, + // create a fetcher for it on the facade env. + // for const [name, binding] of env + // if Workers[name] + // const details = Workers[name]; + + for (const [name, details] of Object.entries(workers as WorkerRegistry)) { + if (details) { + facadeEnv[name] = { + async fetch(...reqArgs: Parameters<Fetcher["fetch"]>) { + const reqFromArgs = new Request(...reqArgs); + if (details.headers) { + for (const [key, value] of Object.entries(details.headers)) { + // In remote mode, you need to add a couple of headers + // to make sure it's talking to the 'dev' preview session + // (much like wrangler dev already does via proxy.ts) + reqFromArgs.headers.set(key, value); + } + return (env[name] as Fetcher).fetch(reqFromArgs); + } + + const url = new URL(reqFromArgs.url); + if (details.protocol !== undefined) { + url.protocol = details.protocol; + } + if (details.host !== undefined) { + url.host = details.host; + } + if (details.port !== undefined) { + url.port = details.port.toString(); + } + + const request = new Request(url.toString(), reqFromArgs); + return fetch(request); + }, + }; + } else { + // This means there's no dev binding available. + // Let's use whatever's available, or put a shim with a message. + facadeEnv[name] = facadeEnv[name] || { + async fetch() { + return new Response( + `You should start up wrangler dev --local on the ${name} worker`, + { status: 404 } + ); + }, + }; + } + } + return facadeEnv; +} diff --git a/node_modules/wrangler/templates/middleware/middleware-pretty-error.ts b/node_modules/wrangler/templates/middleware/middleware-pretty-error.ts new file mode 100644 index 0000000..29dc6d0 --- /dev/null +++ b/node_modules/wrangler/templates/middleware/middleware-pretty-error.ts @@ -0,0 +1,40 @@ +import type { Middleware } from "./common"; + +// A middleware has to be a function of type Middleware +const prettyError: Middleware = async (request, env, _ctx, middlewareCtx) => { + try { + const response = await middlewareCtx.next(request, env); + return response; + } catch (e: any) { + const html = ` + <!DOCTYPE html> + <html lang="en"> + <head> + <meta charset="UTF-8"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Error 🚨</title> + <style> + pre { + margin: 16px auto; + max-width: 600px; + background-color: #eeeeee; + border-radius: 4px; + padding: 16px; + } + </style> + </head> + <body> + <pre>${e.stack}</pre> + </body> + </html> + `; + + return new Response(html, { + status: 500, + headers: { "Content-Type": "text/html;charset=utf-8" }, + }); + } +}; + +export default prettyError; diff --git a/node_modules/wrangler/templates/middleware/middleware-scheduled.ts b/node_modules/wrangler/templates/middleware/middleware-scheduled.ts new file mode 100644 index 0000000..3d9da28 --- /dev/null +++ b/node_modules/wrangler/templates/middleware/middleware-scheduled.ts @@ -0,0 +1,15 @@ +import type { Middleware } from "./common"; + +// A middleware has to be a function of type Middleware +const scheduled: Middleware = async (request, env, _ctx, middlewareCtx) => { + const url = new URL(request.url); + if (url.pathname === "/__scheduled") { + const cron = url.searchParams.get("cron") ?? ""; + await middlewareCtx.dispatch("scheduled", { cron }); + + return new Response("Ran scheduled event"); + } + return middlewareCtx.next(request, env); +}; + +export default scheduled; diff --git a/node_modules/wrangler/templates/middleware/middleware-serve-static-assets.d.ts b/node_modules/wrangler/templates/middleware/middleware-serve-static-assets.d.ts new file mode 100644 index 0000000..88ff173 --- /dev/null +++ b/node_modules/wrangler/templates/middleware/middleware-serve-static-assets.d.ts @@ -0,0 +1,6 @@ +declare module "config:middleware/serve-static-assets" { + import type { CacheControl } from "@cloudflare/kv-asset-handler"; + + export const spaMode: boolean; + export const cacheControl: Partial<CacheControl>; +} diff --git a/node_modules/wrangler/templates/middleware/middleware-serve-static-assets.ts b/node_modules/wrangler/templates/middleware/middleware-serve-static-assets.ts new file mode 100644 index 0000000..4f72f40 --- /dev/null +++ b/node_modules/wrangler/templates/middleware/middleware-serve-static-assets.ts @@ -0,0 +1,56 @@ +/// <reference path="middleware-serve-static-assets.d.ts"/> + +import { + getAssetFromKV, + NotFoundError, + MethodNotAllowedError, + serveSinglePageApp, +} from "@cloudflare/kv-asset-handler"; +import type { Options } from "@cloudflare/kv-asset-handler"; +import { spaMode, cacheControl } from "config:middleware/serve-static-assets"; +import type * as kvAssetHandler from "@cloudflare/kv-asset-handler"; +import manifest from "__STATIC_CONTENT_MANIFEST"; +const ASSET_MANIFEST = JSON.parse(manifest); + +import type { Middleware } from "./common"; + +const staticAssets: Middleware = async (request, env, _ctx, middlewareCtx) => { + let options: Partial<Options> = { + ASSET_MANIFEST, + ASSET_NAMESPACE: env.__STATIC_CONTENT, + cacheControl: cacheControl, + mapRequestToAsset: spaMode ? serveSinglePageApp : undefined, + }; + + try { + const page = await (getAssetFromKV as typeof kvAssetHandler.getAssetFromKV)( + { + request, + waitUntil(promise) { + return _ctx.waitUntil(promise); + }, + }, + options + ); + + // allow headers to be altered + const response = new Response(page.body, page); + + response.headers.set("X-XSS-Protection", "1; mode=block"); + response.headers.set("X-Content-Type-Options", "nosniff"); + response.headers.set("X-Frame-Options", "DENY"); + response.headers.set("Referrer-Policy", "unsafe-url"); + response.headers.set("Feature-Policy", "none"); + + return response; + } catch (e) { + if (e instanceof NotFoundError || e instanceof MethodNotAllowedError) { + // if a known error is thrown then serve from actual worker + return await middlewareCtx.next(request, env); + } + // otherwise it's a real error, so throw it + throw e; + } +}; + +export default staticAssets; diff --git a/node_modules/wrangler/templates/new-worker-scheduled.js b/node_modules/wrangler/templates/new-worker-scheduled.js new file mode 100644 index 0000000..818d495 --- /dev/null +++ b/node_modules/wrangler/templates/new-worker-scheduled.js @@ -0,0 +1,17 @@ +/** + * Welcome to Cloudflare Workers! This is your first scheduled worker. + * + * - Run `wrangler dev --local` in your terminal to start a development server + * - Run `curl "http://localhost:8787/cdn-cgi/mf/scheduled"` to trigger the scheduled event + * - Go back to the console to see what your worker has logged + * - Update the Cron trigger in wrangler.toml (see https://developers.cloudflare.com/workers/wrangler/configuration/#triggers) + * - Run `wrangler publish --name my-worker` to publish your worker + * + * Learn more at https://developers.cloudflare.com/workers/runtime-apis/scheduled-event/ + */ + +export default { + async scheduled(controller, env, ctx) { + console.log(`Hello World!`); + }, +}; diff --git a/node_modules/wrangler/templates/new-worker-scheduled.ts b/node_modules/wrangler/templates/new-worker-scheduled.ts new file mode 100644 index 0000000..8ed838f --- /dev/null +++ b/node_modules/wrangler/templates/new-worker-scheduled.ts @@ -0,0 +1,32 @@ +/** + * Welcome to Cloudflare Workers! This is your first scheduled worker. + * + * - Run `wrangler dev --local` in your terminal to start a development server + * - Run `curl "http://localhost:8787/cdn-cgi/mf/scheduled"` to trigger the scheduled event + * - Go back to the console to see what your worker has logged + * - Update the Cron trigger in wrangler.toml (see https://developers.cloudflare.com/workers/wrangler/configuration/#triggers) + * - Run `wrangler deploy --name my-worker` to deploy your worker + * + * Learn more at https://developers.cloudflare.com/workers/runtime-apis/scheduled-event/ + */ + +export interface Env { + // Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/ + // MY_KV_NAMESPACE: KVNamespace; + // + // Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/ + // MY_DURABLE_OBJECT: DurableObjectNamespace; + // + // Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/ + // MY_BUCKET: R2Bucket; +} + +export default { + async scheduled( + controller: ScheduledController, + env: Env, + ctx: ExecutionContext + ): Promise<void> { + console.log(`Hello World!`); + }, +}; diff --git a/node_modules/wrangler/templates/new-worker.js b/node_modules/wrangler/templates/new-worker.js new file mode 100644 index 0000000..adb3b64 --- /dev/null +++ b/node_modules/wrangler/templates/new-worker.js @@ -0,0 +1,15 @@ +/** + * Welcome to Cloudflare Workers! This is your first worker. + * + * - Run `npx wrangler dev src/index.js` in your terminal to start a development server + * - Open a browser tab at http://localhost:8787/ to see your worker in action + * - Run `npx wrangler publish src/index.js --name my-worker` to publish your worker + * + * Learn more at https://developers.cloudflare.com/workers/ + */ + +export default { + async fetch(request, env, ctx) { + return new Response("Hello World!"); + }, +}; diff --git a/node_modules/wrangler/templates/new-worker.ts b/node_modules/wrangler/templates/new-worker.ts new file mode 100644 index 0000000..8fd336f --- /dev/null +++ b/node_modules/wrangler/templates/new-worker.ts @@ -0,0 +1,33 @@ +/** + * Welcome to Cloudflare Workers! This is your first worker. + * + * - Run `wrangler dev src/index.ts` in your terminal to start a development server + * - Open a browser tab at http://localhost:8787/ to see your worker in action + * - Run `wrangler deploy src/index.ts --name my-worker` to deploy your worker + * + * Learn more at https://developers.cloudflare.com/workers/ + */ + +export interface Env { + // Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/ + // MY_KV_NAMESPACE: KVNamespace; + // + // Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/ + // MY_DURABLE_OBJECT: DurableObjectNamespace; + // + // Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/ + // MY_BUCKET: R2Bucket; + // + // Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/ + // MY_SERVICE: Fetcher; +} + +export default { + async fetch( + request: Request, + env: Env, + ctx: ExecutionContext + ): Promise<Response> { + return new Response("Hello World!"); + }, +}; diff --git a/node_modules/wrangler/templates/no-op-worker.js b/node_modules/wrangler/templates/no-op-worker.js new file mode 100644 index 0000000..13aeeaa --- /dev/null +++ b/node_modules/wrangler/templates/no-op-worker.js @@ -0,0 +1,10 @@ +export default { + fetch() { + return new Response("Not found", { + status: 404, + headers: { + "Content-Type": "text/html", + }, + }); + }, +}; diff --git a/node_modules/wrangler/templates/pages-dev-pipeline.ts b/node_modules/wrangler/templates/pages-dev-pipeline.ts new file mode 100644 index 0000000..27fa4e1 --- /dev/null +++ b/node_modules/wrangler/templates/pages-dev-pipeline.ts @@ -0,0 +1,31 @@ +// @ts-ignore entry point will get replaced +import worker from "__ENTRY_POINT__"; +// @ts-ignore entry point will get replaced +export * from "__ENTRY_POINT__"; +import { isRoutingRuleMatch } from "./pages-dev-util"; + +// @ts-ignore routes are injected +const routes = __ROUTES__; + +export default <ExportedHandler<{ ASSETS: Fetcher }>>{ + fetch(request, env, context) { + const { pathname } = new URL(request.url); + + for (const exclude of routes.exclude) { + if (isRoutingRuleMatch(pathname, exclude)) { + return env.ASSETS.fetch(request); + } + } + + for (const include of routes.include) { + if (isRoutingRuleMatch(pathname, include)) { + if (worker.fetch === undefined) { + throw new TypeError("Entry point missing `fetch` handler"); + } + return worker.fetch(request, env, context); + } + } + + return env.ASSETS.fetch(request); + }, +}; diff --git a/node_modules/wrangler/templates/pages-dev-util.ts b/node_modules/wrangler/templates/pages-dev-util.ts new file mode 100644 index 0000000..371e30f --- /dev/null +++ b/node_modules/wrangler/templates/pages-dev-util.ts @@ -0,0 +1,55 @@ +/** + * @param pathname A pathname string, such as `/foo` or `/foo/bar` + * @param routingRule The routing rule, such as `/foo/*` + * @returns True if pathname matches the routing rule + * + * / -> / + * /* -> /* + * /foo -> /foo + * /foo* -> /foo, /foo-bar, /foo/* + * /foo/* -> /foo, /foo/bar + */ +export function isRoutingRuleMatch( + pathname: string, + routingRule: string +): boolean { + // sanity checks + if (!pathname) { + throw new Error("Pathname is undefined."); + } + if (!routingRule) { + throw new Error("Routing rule is undefined."); + } + + const ruleRegExp = transformRoutingRuleToRegExp(routingRule); + return pathname.match(ruleRegExp) !== null; +} + +function transformRoutingRuleToRegExp(rule: string): RegExp { + let transformedRule; + + if (rule === "/" || rule === "/*") { + transformedRule = rule; + } else if (rule.endsWith("/*")) { + // make `/*` an optional group so we can match both /foo/* and /foo + // /foo/* => /foo(/*)? + transformedRule = `${rule.substring(0, rule.length - 2)}(/*)?`; + } else if (rule.endsWith("/")) { + // make `/` an optional group so we can match both /foo/ and /foo + // /foo/ => /foo(/)? + transformedRule = `${rule.substring(0, rule.length - 1)}(/)?`; + } else if (rule.endsWith("*")) { + transformedRule = rule; + } else { + transformedRule = `${rule}(/)?`; + } + + // /foo* => /foo.* => ^/foo.*$ + // /*.* => /*\.* => /.*\..* => ^/.*\..*$ + transformedRule = `^${transformedRule + .replaceAll(/\./g, "\\.") + .replaceAll(/\*/g, ".*")}$`; + + // ^/foo.*$ => /^\/foo.*$/ + return new RegExp(transformedRule); +} diff --git a/node_modules/wrangler/templates/pages-shim.ts b/node_modules/wrangler/templates/pages-shim.ts new file mode 100644 index 0000000..da09e76 --- /dev/null +++ b/node_modules/wrangler/templates/pages-shim.ts @@ -0,0 +1,9 @@ +// This Worker is used as a default when no Pages Functions are present. +// It proxies the request directly on to the asset server binding. + +export default <ExportedHandler<{ ASSETS: Fetcher }>>{ + async fetch(request, env, context) { + const response = await env.ASSETS.fetch(request.url, request); + return new Response(response.body, response); + }, +}; diff --git a/node_modules/wrangler/templates/pages-template-plugin.ts b/node_modules/wrangler/templates/pages-template-plugin.ts new file mode 100644 index 0000000..3b99ea2 --- /dev/null +++ b/node_modules/wrangler/templates/pages-template-plugin.ts @@ -0,0 +1,190 @@ +import { match } from "path-to-regexp"; + +//note: this explicitly does not include the * character, as pages requires this +const escapeRegex = /[.+?^${}()|[\]\\]/g; + +type HTTPMethod = + | "HEAD" + | "OPTIONS" + | "GET" + | "POST" + | "PUT" + | "PATCH" + | "DELETE"; + +/* TODO: Grab these from @cloudflare/workers-types instead */ +type Params<P extends string = string> = Record<P, string | string[]>; + +type EventContext<Env, P extends string, Data> = { + request: Request; + functionPath: string; + waitUntil: (promise: Promise<unknown>) => void; + passThroughOnException: () => void; + next: (input?: Request | string, init?: RequestInit) => Promise<Response>; + env: Env & { ASSETS: { fetch: typeof fetch } }; + params: Params<P>; + data: Data; +}; + +type EventPluginContext<Env, P extends string, Data, PluginArgs> = { + request: Request; + functionPath: string; + waitUntil: (promise: Promise<unknown>) => void; + passThroughOnException: () => void; + next: (input?: Request | string, init?: RequestInit) => Promise<Response>; + env: Env & { ASSETS: { fetch: typeof fetch } }; + params: Params<P>; + data: Data; + pluginArgs: PluginArgs; +}; + +declare type PagesFunction< + Env = unknown, + P extends string = string, + Data extends Record<string, unknown> = Record<string, unknown> +> = (context: EventContext<Env, P, Data>) => Response | Promise<Response>; + +declare type PagesPluginFunction< + Env = unknown, + P extends string = string, + Data extends Record<string, unknown> = Record<string, unknown>, + PluginArgs = unknown +> = ( + context: EventPluginContext<Env, P, Data, PluginArgs> +) => Response | Promise<Response>; +/* end @cloudflare/workers-types */ + +type RouteHandler = { + routePath: string; + mountPath: string; + method?: HTTPMethod; + modules: PagesFunction[]; + middlewares: PagesFunction[]; +}; + +// inject `routes` via ESBuild +declare const routes: RouteHandler[]; + +function* executeRequest(request: Request, relativePathname: string) { + // First, iterate through the routes (backwards) and execute "middlewares" on partial route matches + for (const route of [...routes].reverse()) { + if (route.method && route.method !== request.method) { + continue; + } + + // replaces with "\\$&", this prepends a backslash to the matched string, e.g. "[" becomes "\[" + const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), { + end: false, + }); + const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), { + end: false, + }); + const matchResult = routeMatcher(relativePathname); + const mountMatchResult = mountMatcher(relativePathname); + if (matchResult && mountMatchResult) { + for (const handler of route.middlewares.flat()) { + yield { + handler, + params: matchResult.params as Params, + path: mountMatchResult.path, + }; + } + } + } + + // Then look for the first exact route match and execute its "modules" + for (const route of routes) { + if (route.method && route.method !== request.method) { + continue; + } + + const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), { + end: true, + }); + const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), { + end: false, + }); + const matchResult = routeMatcher(relativePathname); + const mountMatchResult = mountMatcher(relativePathname); + if (matchResult && mountMatchResult && route.modules.length) { + for (const handler of route.modules.flat()) { + yield { + handler, + params: matchResult.params as Params, + path: matchResult.path, + }; + } + break; + } + } +} + +export default function (pluginArgs: unknown) { + const onRequest: PagesPluginFunction = async (workerContext) => { + let { request } = workerContext; + const { env, next } = workerContext; + let { data } = workerContext; + + const url = new URL(request.url); + // TODO: Replace this with something actually legible. + const relativePathname = `/${ + url.pathname.replace(workerContext.functionPath, "") || "" + }`.replace(/^\/\//, "/"); + + const handlerIterator = executeRequest(request, relativePathname); + const pluginNext = async (input?: RequestInfo, init?: RequestInit) => { + if (input !== undefined) { + let url = input; + if (typeof input === "string") { + url = new URL(input, request.url).toString(); + } + request = new Request(url, init); + } + + const result = handlerIterator.next(); + // Note we can't use `!result.done` because this doesn't narrow to the correct type + if (result.done === false) { + const { handler, params, path } = result.value; + const context = { + request: new Request(request.clone()), + functionPath: workerContext.functionPath + path, + next: pluginNext, + params, + get data() { + return data; + }, + set data(value) { + if (typeof value !== "object" || value === null) { + throw new Error("context.data must be an object"); + } + // user has overriden context.data, so we need to merge it with the existing data + data = value; + }, + pluginArgs, + env, + waitUntil: workerContext.waitUntil.bind(workerContext), + passThroughOnException: + workerContext.passThroughOnException.bind(workerContext), + }; + + const response = await handler(context); + + return cloneResponse(response); + } else { + return next(request); + } + }; + + return pluginNext(); + }; + + return onRequest; +} + +// This makes a Response mutable +const cloneResponse = (response: Response) => + // https://fetch.spec.whatwg.org/#null-body-status + new Response( + [101, 204, 205, 304].includes(response.status) ? null : response.body, + response + ); diff --git a/node_modules/wrangler/templates/pages-template-worker.ts b/node_modules/wrangler/templates/pages-template-worker.ts new file mode 100644 index 0000000..67e6cee --- /dev/null +++ b/node_modules/wrangler/templates/pages-template-worker.ts @@ -0,0 +1,198 @@ +import { match } from "path-to-regexp"; + +//note: this explicitly does not include the * character, as pages requires this +const escapeRegex = /[.+?^${}()|[\]\\]/g; + +type HTTPMethod = + | "HEAD" + | "OPTIONS" + | "GET" + | "POST" + | "PUT" + | "PATCH" + | "DELETE"; + +/* TODO: Grab these from @cloudflare/workers-types instead */ +type Params<P extends string = string> = Record<P, string | string[]>; + +type EventContext<Env, P extends string, Data> = { + request: Request; + functionPath: string; + waitUntil: (promise: Promise<unknown>) => void; + passThroughOnException: () => void; + next: (input?: Request | string, init?: RequestInit) => Promise<Response>; + env: Env & { ASSETS: { fetch: typeof fetch } }; + params: Params<P>; + data: Data; +}; + +declare type PagesFunction< + Env = unknown, + P extends string = string, + Data extends Record<string, unknown> = Record<string, unknown> +> = (context: EventContext<Env, P, Data>) => Response | Promise<Response>; +/* end @cloudflare/workers-types */ + +type RouteHandler = { + routePath: string; + mountPath: string; + method?: HTTPMethod; + modules: PagesFunction[]; + middlewares: PagesFunction[]; +}; + +// inject `routes` via ESBuild +declare const routes: RouteHandler[]; +// define `__FALLBACK_SERVICE__` via ESBuild +declare const __FALLBACK_SERVICE__: string; + +// expect an ASSETS fetcher binding pointing to the asset-server stage +type FetchEnv = { + [name: string]: { fetch: typeof fetch }; + ASSETS: { fetch: typeof fetch }; +}; + +type WorkerContext = { + waitUntil: (promise: Promise<unknown>) => void; + passThroughOnException: () => void; +}; + +function* executeRequest(request: Request) { + const requestPath = new URL(request.url).pathname; + + // First, iterate through the routes (backwards) and execute "middlewares" on partial route matches + for (const route of [...routes].reverse()) { + if (route.method && route.method !== request.method) { + continue; + } + + // replaces with "\\$&", this prepends a backslash to the matched string, e.g. "[" becomes "\[" + const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), { + end: false, + }); + const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), { + end: false, + }); + const matchResult = routeMatcher(requestPath); + const mountMatchResult = mountMatcher(requestPath); + if (matchResult && mountMatchResult) { + for (const handler of route.middlewares.flat()) { + yield { + handler, + params: matchResult.params as Params, + path: mountMatchResult.path, + }; + } + } + } + + // Then look for the first exact route match and execute its "modules" + for (const route of routes) { + if (route.method && route.method !== request.method) { + continue; + } + const routeMatcher = match(route.routePath.replace(escapeRegex, "\\$&"), { + end: true, + }); + const mountMatcher = match(route.mountPath.replace(escapeRegex, "\\$&"), { + end: false, + }); + const matchResult = routeMatcher(requestPath); + const mountMatchResult = mountMatcher(requestPath); + if (matchResult && mountMatchResult && route.modules.length) { + for (const handler of route.modules.flat()) { + yield { + handler, + params: matchResult.params as Params, + path: matchResult.path, + }; + } + break; + } + } +} + +export default { + async fetch( + originalRequest: Request, + env: FetchEnv, + workerContext: WorkerContext + ) { + let request = originalRequest; + const handlerIterator = executeRequest(request); + let data = {}; // arbitrary data the user can set between functions + let isFailOpen = false; + + const next = async (input?: RequestInfo, init?: RequestInit) => { + if (input !== undefined) { + let url = input; + if (typeof input === "string") { + url = new URL(input, request.url).toString(); + } + request = new Request(url, init); + } + + const result = handlerIterator.next(); + // Note we can't use `!result.done` because this doesn't narrow to the correct type + if (result.done === false) { + const { handler, params, path } = result.value; + const context = { + request: new Request(request.clone()), + functionPath: path, + next, + params, + get data() { + return data; + }, + set data(value) { + if (typeof value !== "object" || value === null) { + throw new Error("context.data must be an object"); + } + // user has overriden context.data, so we need to merge it with the existing data + data = value; + }, + env, + waitUntil: workerContext.waitUntil.bind(workerContext), + passThroughOnException: () => { + isFailOpen = true; + }, + }; + + const response = await handler(context); + + if (!(response instanceof Response)) { + throw new Error("Your Pages function should return a Response"); + } + + return cloneResponse(response); + } else if (__FALLBACK_SERVICE__) { + // There are no more handlers so finish with the fallback service (`env.ASSETS.fetch` in Pages' case) + const response = await env[__FALLBACK_SERVICE__].fetch(request); + return cloneResponse(response); + } else { + // There was not fallback service so actually make the request to the origin. + const response = await fetch(request); + return cloneResponse(response); + } + }; + + try { + return await next(); + } catch (error) { + if (isFailOpen) { + const response = await env[__FALLBACK_SERVICE__].fetch(request); + return cloneResponse(response); + } + + throw error; + } + }, +}; + +// This makes a Response mutable +const cloneResponse = (response: Response) => + // https://fetch.spec.whatwg.org/#null-body-status + new Response( + [101, 204, 205, 304].includes(response.status) ? null : response.body, + response + ); diff --git a/node_modules/wrangler/templates/tsconfig-sanity.ts b/node_modules/wrangler/templates/tsconfig-sanity.ts new file mode 100644 index 0000000..b6de68f --- /dev/null +++ b/node_modules/wrangler/templates/tsconfig-sanity.ts @@ -0,0 +1,11 @@ +// @ts-nocheck `@types/node` should NOT be included +Buffer.from("test"); + +// @ts-expect-error `@types/jest` should NOT be included +test("test"); + +// `@cloudflare/workers-types` should be included +const _handler: ExportedHandler = {}; +new HTMLRewriter(); + +export {}; diff --git a/node_modules/wrangler/templates/tsconfig.init.json b/node_modules/wrangler/templates/tsconfig.init.json new file mode 100644 index 0000000..cbea026 --- /dev/null +++ b/node_modules/wrangler/templates/tsconfig.init.json @@ -0,0 +1,105 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + "lib": [ + "es2021" + ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, + "jsx": "react" /* Specify what JSX code is generated. */, + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + + /* Modules */ + "module": "es2022" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + "types": [ + "@cloudflare/workers-types" + ] /* Specify type package names to be included without being referenced in a source file. */, + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + "resolveJsonModule": true /* Enable importing .json files */, + // "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */, + "checkJs": false /* Enable error reporting in type-checked JavaScript files. */, + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + "noEmit": true /* Disable emitting files from a compilation. */, + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + "isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */, + "allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */, + // "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/node_modules/wrangler/templates/tsconfig.json b/node_modules/wrangler/templates/tsconfig.json new file mode 100644 index 0000000..358f0c3 --- /dev/null +++ b/node_modules/wrangler/templates/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@cloudflare/workers-tsconfig/tsconfig.json", + "compilerOptions": { + "types": ["@cloudflare/workers-types"] + }, + "include": ["**/*.ts"], + "exclude": ["__tests__", "./init-tests/**"] +} |
