wrangler 0.0.0-e22f9d7 → 0.0.0-e273e09

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 (154) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +27 -14
  3. package/bin/wrangler.js +48 -4
  4. package/miniflare-dist/index.mjs +4886 -0
  5. package/package.json +60 -31
  6. package/pages/functions/buildPlugin.ts +105 -0
  7. package/pages/functions/buildWorker.ts +93 -4
  8. package/pages/functions/filepath-routing.test.ts +223 -28
  9. package/pages/functions/filepath-routing.ts +100 -132
  10. package/pages/functions/routes.ts +16 -18
  11. package/pages/functions/template-plugin.ts +147 -0
  12. package/pages/functions/template-worker.ts +37 -38
  13. package/src/__tests__/config-cache.test.ts +36 -0
  14. package/src/__tests__/configuration.test.ts +3487 -0
  15. package/src/__tests__/dev.test.tsx +977 -61
  16. package/src/__tests__/guess-worker-format.test.ts +85 -0
  17. package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
  18. package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
  19. package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
  20. package/src/__tests__/helpers/mock-account-id.ts +48 -0
  21. package/src/__tests__/helpers/mock-bin.ts +36 -0
  22. package/src/__tests__/{mock-cfetch.ts → helpers/mock-cfetch.ts} +56 -16
  23. package/src/__tests__/helpers/mock-console.ts +87 -0
  24. package/src/__tests__/helpers/mock-dialogs.ts +130 -0
  25. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  26. package/src/__tests__/helpers/mock-istty.ts +27 -0
  27. package/src/__tests__/helpers/mock-kv.ts +33 -0
  28. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  29. package/src/__tests__/helpers/mock-stdin.ts +103 -0
  30. package/src/__tests__/helpers/mock-web-socket.ts +29 -0
  31. package/src/__tests__/helpers/run-in-tmp.ts +39 -0
  32. package/src/__tests__/helpers/run-wrangler.ts +16 -0
  33. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  34. package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
  35. package/src/__tests__/https-options.test.ts +130 -0
  36. package/src/__tests__/index.test.ts +179 -209
  37. package/src/__tests__/init.test.ts +1637 -0
  38. package/src/__tests__/jest.setup.ts +61 -6
  39. package/src/__tests__/kv.test.ts +842 -472
  40. package/src/__tests__/logger.test.ts +115 -0
  41. package/src/__tests__/package-manager.test.ts +353 -0
  42. package/src/__tests__/pages.test.ts +754 -0
  43. package/src/__tests__/parse.test.ts +330 -0
  44. package/src/__tests__/publish.test.ts +5755 -0
  45. package/src/__tests__/r2.test.ts +233 -0
  46. package/src/__tests__/route.test.ts +45 -0
  47. package/src/__tests__/secret.test.ts +481 -0
  48. package/src/__tests__/tail.test.ts +690 -0
  49. package/src/__tests__/user.test.ts +131 -0
  50. package/src/__tests__/whoami.test.tsx +139 -0
  51. package/src/abort.d.ts +3 -0
  52. package/src/bundle.ts +197 -0
  53. package/src/cfetch/index.ts +40 -21
  54. package/src/cfetch/internal.ts +102 -29
  55. package/src/cli.ts +6 -2
  56. package/src/config/README.md +107 -0
  57. package/src/config/config.ts +234 -0
  58. package/src/config/diagnostics.ts +80 -0
  59. package/src/config/environment.ts +420 -0
  60. package/src/config/index.ts +225 -0
  61. package/src/config/validation-helpers.ts +584 -0
  62. package/src/config/validation.ts +1548 -0
  63. package/src/config-cache.ts +45 -0
  64. package/src/create-worker-preview.ts +185 -0
  65. package/src/create-worker-upload-form.ts +254 -0
  66. package/src/dev/dev-vars.ts +36 -0
  67. package/src/dev/dev.tsx +401 -0
  68. package/src/dev/local.tsx +326 -0
  69. package/src/dev/remote.tsx +237 -0
  70. package/src/dev/use-esbuild.ts +118 -0
  71. package/src/dialogs.tsx +66 -8
  72. package/src/durable.ts +102 -0
  73. package/src/entry.ts +322 -0
  74. package/src/environment-variables.ts +35 -0
  75. package/src/errors.ts +11 -0
  76. package/src/https-options.ts +123 -0
  77. package/src/index.tsx +1848 -976
  78. package/src/inspect.ts +156 -33
  79. package/src/intl-polyfill.d.ts +139 -0
  80. package/src/{kv.tsx → kv.ts} +199 -67
  81. package/src/logger.ts +66 -0
  82. package/src/miniflare-cli/README.md +30 -0
  83. package/src/miniflare-cli/enum-keys.ts +17 -0
  84. package/src/miniflare-cli/index.ts +41 -0
  85. package/src/miniflare-cli/request-context.ts +40 -0
  86. package/src/module-collection.ts +229 -35
  87. package/src/open-in-browser.ts +19 -0
  88. package/src/package-manager.ts +174 -0
  89. package/src/pages.tsx +1162 -100
  90. package/src/parse.ts +203 -0
  91. package/src/paths.ts +26 -0
  92. package/src/proxy.ts +227 -51
  93. package/src/publish.ts +633 -276
  94. package/src/r2.ts +50 -0
  95. package/src/selfsigned.d.ts +29 -0
  96. package/src/sites.tsx +270 -62
  97. package/src/tail/filters.ts +279 -0
  98. package/src/tail/index.ts +299 -0
  99. package/src/tail/printing.ts +67 -0
  100. package/src/update-check.ts +19 -0
  101. package/src/user.tsx +382 -195
  102. package/src/whoami.tsx +71 -0
  103. package/src/{api/worker.ts → worker.ts} +66 -40
  104. package/src/zones.ts +73 -0
  105. package/templates/gitignore +171 -0
  106. package/templates/new-worker-scheduled.js +17 -0
  107. package/templates/new-worker-scheduled.ts +32 -0
  108. package/templates/new-worker.js +15 -0
  109. package/templates/new-worker.ts +30 -0
  110. package/{static-asset-facade.js → templates/static-asset-facade.js} +1 -5
  111. package/templates/tsconfig.json +105 -0
  112. package/wrangler-dist/cli.js +106332 -103741
  113. package/src/__tests__/mock-dialogs.ts +0 -65
  114. package/src/__tests__/run-in-tmp.ts +0 -19
  115. package/src/__tests__/run-wrangler.ts +0 -32
  116. package/src/api/form_data.ts +0 -131
  117. package/src/api/preview.ts +0 -128
  118. package/src/config.ts +0 -487
  119. package/src/dev.tsx +0 -784
  120. package/src/tail.tsx +0 -73
  121. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  122. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  123. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  124. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  125. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  126. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  127. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  128. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  129. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  130. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  131. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  132. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  133. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  134. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  135. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  136. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  137. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  138. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  139. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  140. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  141. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  142. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  143. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  144. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  145. package/vendor/wrangler-mime/LICENSE +0 -21
  146. package/vendor/wrangler-mime/Mime.js +0 -97
  147. package/vendor/wrangler-mime/README.md +0 -187
  148. package/vendor/wrangler-mime/cli.js +0 -46
  149. package/vendor/wrangler-mime/index.js +0 -4
  150. package/vendor/wrangler-mime/lite.js +0 -4
  151. package/vendor/wrangler-mime/package.json +0 -52
  152. package/vendor/wrangler-mime/types/other.js +0 -1
  153. package/vendor/wrangler-mime/types/standard.js +0 -1
  154. package/wrangler-dist/cli.js.map +0 -7
@@ -1,173 +1,134 @@
1
- import path from "path";
2
- import fs from "fs/promises";
3
- import { transform } from "esbuild";
4
- import * as acorn from "acorn";
5
- import * as acornWalk from "acorn-walk";
6
- import type { Config, RouteConfig } from "./routes";
7
- import type { ExportNamedDeclaration, Identifier } from "estree";
8
-
9
- type Arguments = {
10
- baseDir: string;
11
- baseURL: string;
12
- };
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { build } from "esbuild";
4
+ import { toUrlPath } from "../../src/paths";
5
+ import type { UrlPath } from "../../src/paths";
6
+ import type { HTTPMethod, RouteConfig } from "./routes";
13
7
 
14
8
  export async function generateConfigFromFileTree({
15
9
  baseDir,
16
10
  baseURL,
17
- }: Arguments) {
18
- let routeEntries: [string, RouteConfig][] = [];
11
+ }: {
12
+ baseDir: string;
13
+ baseURL: UrlPath;
14
+ }) {
15
+ let routeEntries: RouteConfig[] = [];
19
16
 
20
17
  if (!baseURL.startsWith("/")) {
21
- baseURL = `/${baseURL}`;
18
+ baseURL = `/${baseURL}` as UrlPath;
22
19
  }
23
20
 
24
21
  if (baseURL.endsWith("/")) {
25
- baseURL = baseURL.slice(0, -1);
22
+ baseURL = baseURL.slice(0, -1) as UrlPath;
26
23
  }
27
24
 
28
25
  await forEachFile(baseDir, async (filepath) => {
29
26
  const ext = path.extname(filepath);
30
27
  if (/^\.(mjs|js|ts|tsx|jsx)$/.test(ext)) {
31
- // transform the code to ensure we're working with vanilla JS + ESM
32
- const { code } = await transform(await fs.readFile(filepath, "utf-8"), {
33
- loader: ext === ".ts" ? "ts" : "js",
28
+ const { metafile } = await build({
29
+ metafile: true,
30
+ write: false,
31
+ bundle: false,
32
+ entryPoints: [path.resolve(filepath)],
34
33
  });
35
-
36
- // parse each file into an AST and search for module exports that match "onRequest" and friends
37
- const ast = acorn.parse(code, {
38
- ecmaVersion: "latest",
39
- sourceType: "module",
40
- });
41
- acornWalk.simple(ast, {
42
- ExportNamedDeclaration(_node: unknown) {
43
- // This dynamic cast assumes that the AST generated by acornWalk will generate nodes that
44
- // are compatible with the eslint AST nodes.
45
- const node = _node as ExportNamedDeclaration;
46
-
47
- // this is an array because multiple things can be exported from a single statement
48
- // i.e. `export {foo, bar}` or `export const foo = "f", bar = "b"`
49
- const exportNames: string[] = [];
50
-
51
- if (node.declaration) {
52
- const declaration = node.declaration;
53
-
54
- // `export async function onRequest() {...}`
55
- if (declaration.type === "FunctionDeclaration") {
56
- exportNames.push(declaration.id.name);
57
- }
58
-
59
- // `export const onRequestGet = () => {}, onRequestPost = () => {}`
60
- if (declaration.type === "VariableDeclaration") {
61
- exportNames.push(
62
- ...declaration.declarations.map(
63
- (variableDeclarator) =>
64
- (variableDeclarator.id as unknown as Identifier).name
65
- )
66
- );
67
- }
34
+ const exportNames: string[] = [];
35
+ if (metafile) {
36
+ for (const output in metafile?.outputs) {
37
+ exportNames.push(...metafile.outputs[output].exports);
38
+ }
39
+ }
40
+ for (const exportName of exportNames) {
41
+ const [match, method = ""] = (exportName.match(
42
+ /^onRequest(Get|Post|Put|Patch|Delete|Options|Head)?$/
43
+ ) ?? []) as (string | undefined)[];
44
+
45
+ if (match) {
46
+ const basename = path.basename(filepath).slice(0, -ext.length);
47
+
48
+ const isIndexFile = basename === "index";
49
+ // TODO: deprecate _middleware_ in favor of _middleware
50
+ const isMiddlewareFile =
51
+ basename === "_middleware" || basename === "_middleware_";
52
+
53
+ let routePath = path
54
+ .relative(baseDir, filepath)
55
+ .slice(0, -ext.length);
56
+ let mountPath = path.dirname(routePath);
57
+
58
+ if (isIndexFile || isMiddlewareFile) {
59
+ routePath = path.dirname(routePath);
68
60
  }
69
61
 
70
- // `export {foo, bar}`
71
- if (node.specifiers.length) {
72
- exportNames.push(
73
- ...node.specifiers.map(
74
- (exportSpecifier) =>
75
- (exportSpecifier.exported as unknown as Identifier).name
76
- )
77
- );
62
+ if (routePath === ".") {
63
+ routePath = "";
78
64
  }
79
-
80
- for (const exportName of exportNames) {
81
- const [match, method] =
82
- exportName.match(
83
- /^onRequest(Get|Post|Put|Patch|Delete|Options|Head)?$/
84
- ) ?? [];
85
-
86
- if (match) {
87
- const basename = path.basename(filepath).slice(0, -ext.length);
88
-
89
- const isIndexFile = basename === "index";
90
- // TODO: deprecate _middleware_ in favor of _middleware
91
- const isMiddlewareFile =
92
- basename === "_middleware" || basename === "_middleware_";
93
-
94
- let routePath = path
95
- .relative(baseDir, filepath)
96
- .slice(0, -ext.length);
97
-
98
- if (isIndexFile || isMiddlewareFile) {
99
- routePath = path.dirname(routePath);
100
- }
101
-
102
- if (routePath === ".") {
103
- routePath = "";
104
- }
105
-
106
- routePath = `${baseURL}/${routePath}`;
107
-
108
- routePath = routePath.replace(/\[\[(.+)]]/g, ":$1*"); // transform [[id]] => :id*
109
- routePath = routePath.replace(/\[(.+)]/g, ":$1"); // transform [id] => :id
110
-
111
- if (method) {
112
- routePath = `${method.toUpperCase()} ${routePath}`;
113
- }
114
-
115
- routeEntries.push([
116
- routePath,
117
- {
118
- [isMiddlewareFile ? "middleware" : "module"]: [
119
- `${path.relative(baseDir, filepath)}:${exportName}`,
120
- ],
121
- },
122
- ]);
123
- }
65
+ if (mountPath === ".") {
66
+ mountPath = "";
124
67
  }
125
- },
126
- });
68
+
69
+ routePath = `${baseURL}/${routePath}`;
70
+ mountPath = `${baseURL}/${mountPath}`;
71
+
72
+ routePath = routePath.replace(/\[\[([^\]]+)\]\]/g, ":$1*"); // transform [[id]] => :id*
73
+ routePath = routePath.replaceAll(/\[([^\]]+)\]/g, ":$1"); // transform [id] => :id
74
+ mountPath = mountPath.replace(/\[\[([^\]]+)\]\]/g, ":$1*"); // transform [[id]] => :id*
75
+ mountPath = mountPath.replaceAll(/\[([^\]]+)\]/g, ":$1"); // transform [id] => :id
76
+
77
+ // These are used as module specifiers so UrlPaths are okay to use even on Windows
78
+ const modulePath = toUrlPath(path.relative(baseDir, filepath));
79
+
80
+ const routeEntry: RouteConfig = {
81
+ routePath: toUrlPath(routePath),
82
+ mountPath: toUrlPath(mountPath),
83
+ method: method.toUpperCase() as HTTPMethod,
84
+ [isMiddlewareFile ? "middleware" : "module"]: [
85
+ `${modulePath}:${exportName}`,
86
+ ],
87
+ };
88
+
89
+ routeEntries.push(routeEntry);
90
+ }
91
+ }
127
92
  }
128
93
  });
129
-
130
94
  // Combine together any routes (index routes) which contain both a module and a middleware
131
95
  routeEntries = routeEntries.reduce(
132
- (acc: typeof routeEntries, [routePath, routeHandler]) => {
96
+ (acc: typeof routeEntries, { routePath, ...rest }) => {
133
97
  const existingRouteEntry = acc.find(
134
- (routeEntry) => routeEntry[0] === routePath
98
+ (routeEntry) =>
99
+ routeEntry.routePath === routePath &&
100
+ routeEntry.method === rest.method
135
101
  );
136
102
  if (existingRouteEntry !== undefined) {
137
- existingRouteEntry[1] = {
138
- ...existingRouteEntry[1],
139
- ...routeHandler,
140
- };
103
+ Object.assign(existingRouteEntry, rest);
141
104
  } else {
142
- acc.push([routePath, routeHandler]);
105
+ acc.push({ routePath, ...rest });
143
106
  }
144
107
  return acc;
145
108
  },
146
109
  []
147
110
  );
148
111
 
149
- routeEntries.sort(([pathA], [pathB]) => compareRoutes(pathA, pathB));
112
+ routeEntries.sort((a, b) => compareRoutes(a, b));
150
113
 
151
- return { routes: Object.fromEntries(routeEntries) } as Config;
114
+ return {
115
+ routes: routeEntries,
116
+ };
152
117
  }
153
118
 
154
119
  // Ensure routes are produced in order of precedence so that
155
120
  // more specific routes aren't occluded from matching due to
156
121
  // less specific routes appearing first in the route list.
157
- export function compareRoutes(a: string, b: string) {
158
- function parseRoutePath(routePath: string) {
159
- let [method, segmentedPath] = routePath.split(" ");
160
- if (!segmentedPath) {
161
- segmentedPath = method;
162
- method = null;
163
- }
164
-
165
- const segments = segmentedPath.slice(1).split("/").filter(Boolean);
166
- return [method, segments];
122
+ export function compareRoutes(
123
+ { routePath: routePathA, method: methodA }: RouteConfig,
124
+ { routePath: routePathB, method: methodB }: RouteConfig
125
+ ) {
126
+ function parseRoutePath(routePath: UrlPath): string[] {
127
+ return routePath.slice(1).split("/").filter(Boolean);
167
128
  }
168
129
 
169
- const [methodA, segmentsA] = parseRoutePath(a);
170
- const [methodB, segmentsB] = parseRoutePath(b);
130
+ const segmentsA = parseRoutePath(routePathA);
131
+ const segmentsB = parseRoutePath(routePathB);
171
132
 
172
133
  // sort routes with fewer segments after those with more segments
173
134
  if (segmentsA.length !== segmentsB.length) {
@@ -193,8 +154,8 @@ export function compareRoutes(a: string, b: string) {
193
154
  if (methodA && !methodB) return -1;
194
155
  if (!methodA && methodB) return 1;
195
156
 
196
- // all else equal, just sort them lexicographically
197
- return a.localeCompare(b);
157
+ // all else equal, just sort the paths lexicographically
158
+ return routePathA.localeCompare(routePathB);
198
159
  }
199
160
 
200
161
  async function forEachFile<T>(
@@ -204,7 +165,7 @@ async function forEachFile<T>(
204
165
  const searchPaths = [baseDir];
205
166
  const returnValues: T[] = [];
206
167
 
207
- while (searchPaths.length) {
168
+ while (isNotEmpty(searchPaths)) {
208
169
  const cwd = searchPaths.shift();
209
170
  const dir = await fs.readdir(cwd, { withFileTypes: true });
210
171
  for (const entry of dir) {
@@ -219,3 +180,10 @@ async function forEachFile<T>(
219
180
 
220
181
  return returnValues;
221
182
  }
183
+
184
+ interface NonEmptyArray<T> extends Array<T> {
185
+ shift(): T;
186
+ }
187
+ function isNotEmpty<T>(array: T[]): array is NonEmptyArray<T> {
188
+ return array.length > 0;
189
+ }
@@ -1,6 +1,7 @@
1
- import path from "path";
2
- import fs from "fs/promises";
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
3
  import { isValidIdentifier, normalizeIdentifier } from "./identifiers";
4
+ import type { UrlPath } from "../../src/paths";
4
5
 
5
6
  export const HTTP_METHODS = [
6
7
  "HEAD",
@@ -19,22 +20,22 @@ export function isHTTPMethod(
19
20
  }
20
21
 
21
22
  export type RoutesCollection = Array<{
22
- routePath: string;
23
- methods: HTTPMethod[];
23
+ routePath: UrlPath;
24
+ mountPath: UrlPath;
25
+ method?: HTTPMethod;
24
26
  modules: string[];
25
27
  middlewares: string[];
26
28
  }>;
27
29
 
28
30
  export type Config = {
29
- routes?: RoutesConfig;
31
+ routes?: RouteConfig[];
30
32
  schedules?: unknown;
31
33
  };
32
34
 
33
- export type RoutesConfig = {
34
- [route: string]: RouteConfig;
35
- };
36
-
37
35
  export type RouteConfig = {
36
+ routePath: UrlPath;
37
+ mountPath: UrlPath;
38
+ method?: HTTPMethod;
38
39
  middleware?: string | string[];
39
40
  module?: string | string[];
40
41
  };
@@ -114,16 +115,12 @@ export function parseConfig(config: Config, baseDir: string) {
114
115
  });
115
116
  }
116
117
 
117
- for (const [route, props] of Object.entries(config.routes)) {
118
- let [_methods, routePath] = route.split(" ");
119
- if (!routePath) {
120
- routePath = _methods;
121
- _methods = "";
122
- }
123
-
118
+ for (const { routePath, mountPath, method, ...props } of config.routes ??
119
+ []) {
124
120
  routes.push({
125
121
  routePath,
126
- methods: _methods.split("|").filter(isHTTPMethod),
122
+ mountPath,
123
+ method,
127
124
  middlewares: parseModuleIdentifiers(props.middleware),
128
125
  modules: parseModuleIdentifiers(props.module),
129
126
  });
@@ -148,7 +145,8 @@ export const routes = [
148
145
  .map(
149
146
  (route) => ` {
150
147
  routePath: "${route.routePath}",
151
- methods: ${JSON.stringify(route.methods)},
148
+ mountPath: "${route.mountPath}",
149
+ method: "${route.method}",
152
150
  middlewares: [${route.middlewares.join(", ")}],
153
151
  modules: [${route.modules.join(", ")}],
154
152
  },`
@@ -0,0 +1,147 @@
1
+ import { match } from "path-to-regexp";
2
+ import type { HTTPMethod } from "./routes";
3
+
4
+ /* TODO: Grab these from @cloudflare/workers-types instead */
5
+ type Params<P extends string = string> = Record<P, string | string[]>;
6
+
7
+ type EventContext<Env, P extends string, Data> = {
8
+ request: Request;
9
+ functionPath: string;
10
+ waitUntil: (promise: Promise<unknown>) => void;
11
+ next: (input?: Request | string, init?: RequestInit) => Promise<Response>;
12
+ env: Env & { ASSETS: { fetch: typeof fetch } };
13
+ params: Params<P>;
14
+ data: Data;
15
+ };
16
+
17
+ type EventPluginContext<Env, P extends string, Data, PluginArgs> = {
18
+ request: Request;
19
+ functionPath: string;
20
+ waitUntil: (promise: Promise<unknown>) => void;
21
+ next: (input?: Request | string, init?: RequestInit) => Promise<Response>;
22
+ env: Env & { ASSETS: { fetch: typeof fetch } };
23
+ params: Params<P>;
24
+ data: Data;
25
+ pluginArgs: PluginArgs;
26
+ };
27
+
28
+ declare type PagesFunction<
29
+ Env = unknown,
30
+ P extends string = string,
31
+ Data extends Record<string, unknown> = Record<string, unknown>
32
+ > = (context: EventContext<Env, P, Data>) => Response | Promise<Response>;
33
+
34
+ declare type PagesPluginFunction<
35
+ Env = unknown,
36
+ P extends string = string,
37
+ Data extends Record<string, unknown> = Record<string, unknown>,
38
+ PluginArgs = unknown
39
+ > = (
40
+ context: EventPluginContext<Env, P, Data, PluginArgs>
41
+ ) => Response | Promise<Response>;
42
+ /* end @cloudflare/workers-types */
43
+
44
+ type RouteHandler = {
45
+ routePath: string;
46
+ mountPath: string;
47
+ method?: HTTPMethod;
48
+ modules: PagesFunction[];
49
+ middlewares: PagesFunction[];
50
+ };
51
+
52
+ // inject `routes` via ESBuild
53
+ declare const routes: RouteHandler[];
54
+
55
+ function* executeRequest(request: Request, relativePathname: string) {
56
+ // First, iterate through the routes (backwards) and execute "middlewares" on partial route matches
57
+ for (const route of [...routes].reverse()) {
58
+ if (route.method && route.method !== request.method) {
59
+ continue;
60
+ }
61
+
62
+ const routeMatcher = match(route.routePath, { end: false });
63
+ const mountMatcher = match(route.mountPath, { end: false });
64
+ const matchResult = routeMatcher(relativePathname);
65
+ const mountMatchResult = mountMatcher(relativePathname);
66
+ if (matchResult && mountMatchResult) {
67
+ for (const handler of route.middlewares.flat()) {
68
+ yield {
69
+ handler,
70
+ params: matchResult.params as Params,
71
+ path: mountMatchResult.path,
72
+ };
73
+ }
74
+ }
75
+ }
76
+
77
+ // Then look for the first exact route match and execute its "modules"
78
+ for (const route of routes) {
79
+ if (route.method && route.method !== request.method) {
80
+ continue;
81
+ }
82
+
83
+ const routeMatcher = match(route.routePath, { end: true });
84
+ const mountMatcher = match(route.mountPath, { end: false });
85
+ const matchResult = routeMatcher(relativePathname);
86
+ const mountMatchResult = mountMatcher(relativePathname);
87
+ if (matchResult && mountMatchResult && route.modules.length) {
88
+ for (const handler of route.modules.flat()) {
89
+ yield {
90
+ handler,
91
+ params: matchResult.params as Params,
92
+ path: matchResult.path,
93
+ };
94
+ }
95
+ break;
96
+ }
97
+ }
98
+ }
99
+
100
+ export default function (pluginArgs) {
101
+ const onRequest: PagesPluginFunction = async (workerContext) => {
102
+ let { request } = workerContext;
103
+ const { env, next, data } = workerContext;
104
+
105
+ const url = new URL(request.url);
106
+ const relativePathname = `/${
107
+ url.pathname.split(workerContext.functionPath)[1] || ""
108
+ }`.replace(/^\/\//, "/");
109
+
110
+ const handlerIterator = executeRequest(request, relativePathname);
111
+ const pluginNext = async (input?: RequestInfo, init?: RequestInit) => {
112
+ if (input !== undefined) {
113
+ request = new Request(input, init);
114
+ }
115
+
116
+ const result = handlerIterator.next();
117
+ // Note we can't use `!result.done` because this doesn't narrow to the correct type
118
+ if (result.done === false) {
119
+ const { handler, params, path } = result.value;
120
+ const context = {
121
+ request,
122
+ functionPath: workerContext.functionPath + path,
123
+ next: pluginNext,
124
+ params,
125
+ data,
126
+ pluginArgs,
127
+ env,
128
+ waitUntil: workerContext.waitUntil.bind(workerContext),
129
+ };
130
+
131
+ const response = await handler(context);
132
+
133
+ // https://fetch.spec.whatwg.org/#null-body-status
134
+ return new Response(
135
+ [101, 204, 205, 304].includes(response.status) ? null : response.body,
136
+ { ...response, headers: new Headers(response.headers) }
137
+ );
138
+ } else {
139
+ return next();
140
+ }
141
+ };
142
+
143
+ return pluginNext();
144
+ };
145
+
146
+ return onRequest;
147
+ }
@@ -6,6 +6,7 @@ type Params<P extends string = string> = Record<P, string | string[]>;
6
6
 
7
7
  type EventContext<Env, P extends string, Data> = {
8
8
  request: Request;
9
+ functionPath: string;
9
10
  waitUntil: (promise: Promise<unknown>) => void;
10
11
  next: (input?: Request | string, init?: RequestInit) => Promise<Response>;
11
12
  env: Env & { ASSETS: { fetch: typeof fetch } };
@@ -22,7 +23,8 @@ declare type PagesFunction<
22
23
 
23
24
  type RouteHandler = {
24
25
  routePath: string;
25
- methods: HTTPMethod[];
26
+ mountPath: string;
27
+ method?: HTTPMethod;
26
28
  modules: PagesFunction[];
27
29
  middlewares: PagesFunction[];
28
30
  };
@@ -33,35 +35,34 @@ declare const routes: RouteHandler[];
33
35
  declare const __FALLBACK_SERVICE__: string;
34
36
 
35
37
  // expect an ASSETS fetcher binding pointing to the asset-server stage
36
- type Env = {
37
- [name: string]: unknown;
38
- ASSETS: { fetch(url: string, init: RequestInit): Promise<Response> };
38
+ type FetchEnv = {
39
+ [name: string]: { fetch: typeof fetch };
40
+ ASSETS: { fetch: typeof fetch };
39
41
  };
40
42
 
41
43
  type WorkerContext = {
42
44
  waitUntil: (promise: Promise<unknown>) => void;
43
45
  };
44
46
 
45
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- `env` can be used by __FALLBACK_SERVICE_FETCH__
46
- function* executeRequest(request: Request, env: Env) {
47
+ function* executeRequest(request: Request) {
47
48
  const requestPath = new URL(request.url).pathname;
48
49
 
49
50
  // First, iterate through the routes (backwards) and execute "middlewares" on partial route matches
50
51
  for (const route of [...routes].reverse()) {
51
- if (
52
- route.methods.length &&
53
- !route.methods.includes(request.method as HTTPMethod)
54
- ) {
52
+ if (route.method && route.method !== request.method) {
55
53
  continue;
56
54
  }
57
55
 
58
56
  const routeMatcher = match(route.routePath, { end: false });
57
+ const mountMatcher = match(route.mountPath, { end: false });
59
58
  const matchResult = routeMatcher(requestPath);
60
- if (matchResult) {
59
+ const mountMatchResult = mountMatcher(requestPath);
60
+ if (matchResult && mountMatchResult) {
61
61
  for (const handler of route.middlewares.flat()) {
62
62
  yield {
63
63
  handler,
64
64
  params: matchResult.params as Params,
65
+ path: mountMatchResult.path,
65
66
  };
66
67
  }
67
68
  }
@@ -69,55 +70,47 @@ function* executeRequest(request: Request, env: Env) {
69
70
 
70
71
  // Then look for the first exact route match and execute its "modules"
71
72
  for (const route of routes) {
72
- if (
73
- route.methods.length &&
74
- !route.methods.includes(request.method as HTTPMethod)
75
- ) {
73
+ if (route.method && route.method !== request.method) {
76
74
  continue;
77
75
  }
78
76
 
79
77
  const routeMatcher = match(route.routePath, { end: true });
78
+ const mountMatcher = match(route.mountPath, { end: false });
80
79
  const matchResult = routeMatcher(requestPath);
81
- if (matchResult && route.modules.length) {
80
+ const mountMatchResult = mountMatcher(requestPath);
81
+ if (matchResult && mountMatchResult && route.modules.length) {
82
82
  for (const handler of route.modules.flat()) {
83
83
  yield {
84
84
  handler,
85
85
  params: matchResult.params as Params,
86
+ path: matchResult.path,
86
87
  };
87
88
  }
88
89
  break;
89
90
  }
90
91
  }
91
-
92
- // Finally, yield to the fallback service (`env.ASSETS.fetch` in Pages' case)
93
- return {
94
- handler: () =>
95
- __FALLBACK_SERVICE__
96
- ? // @ts-expect-error expecting __FALLBACK_SERVICE__ to be the name of a service binding, so fetch should be defined
97
- env[__FALLBACK_SERVICE__].fetch(request)
98
- : fetch(request),
99
- params: {} as Params,
100
- };
101
92
  }
102
93
 
103
94
  export default {
104
- async fetch(request: Request, env: Env, workerContext: WorkerContext) {
105
- const handlerIterator = executeRequest(request, env);
95
+ async fetch(request: Request, env: FetchEnv, workerContext: WorkerContext) {
96
+ const handlerIterator = executeRequest(request);
106
97
  const data = {}; // arbitrary data the user can set between functions
107
98
  const next = async (input?: RequestInfo, init?: RequestInit) => {
108
99
  if (input !== undefined) {
109
- request = new Request(input, init);
100
+ let url = input;
101
+ if (typeof input === "string") {
102
+ url = new URL(input, request.url).toString();
103
+ }
104
+ request = new Request(url, init);
110
105
  }
111
106
 
112
- const { value } = handlerIterator.next();
113
- if (value) {
114
- const { handler, params } = value;
115
- const context: EventContext<
116
- unknown,
117
- string,
118
- Record<string, unknown>
119
- > = {
107
+ const result = handlerIterator.next();
108
+ // Note we can't use `!result.done` because this doesn't narrow to the correct type
109
+ if (result.done === false) {
110
+ const { handler, params, path } = result.value;
111
+ const context = {
120
112
  request: new Request(request.clone()),
113
+ functionPath: path,
121
114
  next,
122
115
  params,
123
116
  data,
@@ -130,8 +123,14 @@ export default {
130
123
  // https://fetch.spec.whatwg.org/#null-body-status
131
124
  return new Response(
132
125
  [101, 204, 205, 304].includes(response.status) ? null : response.body,
133
- response
126
+ { ...response, headers: new Headers(response.headers) }
134
127
  );
128
+ } else if (__FALLBACK_SERVICE__) {
129
+ // There are no more handlers so finish with the fallback service (`env.ASSETS.fetch` in Pages' case)
130
+ return env[__FALLBACK_SERVICE__].fetch(request);
131
+ } else {
132
+ // There was not fallback service so actually make the request to the origin.
133
+ return fetch(request);
135
134
  }
136
135
  };
137
136