wrangler 2.0.27 → 2.1.0
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.
- package/bin/wrangler.js +1 -1
- package/miniflare-dist/index.mjs +1141 -369
- package/package.json +6 -4
- package/src/__tests__/api-dev.test.ts +19 -0
- package/src/__tests__/configuration.test.ts +27 -27
- package/src/__tests__/dev.test.tsx +8 -6
- package/src/__tests__/helpers/hello-world-worker.js +5 -0
- package/src/__tests__/helpers/mock-cfetch.ts +4 -4
- package/src/__tests__/helpers/mock-console.ts +11 -2
- package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -0
- package/src/__tests__/helpers/mock-known-routes.ts +7 -0
- package/src/__tests__/index.test.ts +37 -37
- package/src/__tests__/init.test.ts +356 -5
- package/src/__tests__/jest.setup.ts +13 -0
- package/src/__tests__/middleware.test.ts +768 -0
- package/src/__tests__/pages.test.ts +829 -104
- package/src/__tests__/paths.test.ts +17 -0
- package/src/__tests__/publish.test.ts +512 -445
- package/src/__tests__/tail.test.ts +79 -72
- package/src/__tests__/test-old-node-version.js +3 -3
- package/src/__tests__/worker-namespace.test.ts +37 -35
- package/src/api/dev.ts +93 -28
- package/src/bundle.ts +239 -12
- package/src/cfetch/internal.ts +64 -3
- package/src/cli.ts +1 -1
- package/src/config/environment.ts +1 -1
- package/src/config/index.ts +4 -4
- package/src/config/validation.ts +3 -3
- package/src/create-worker-upload-form.ts +29 -26
- package/src/dev/dev.tsx +3 -1
- package/src/dev/local.tsx +319 -171
- package/src/dev/remote.tsx +16 -4
- package/src/dev/start-server.ts +416 -0
- package/src/dev/use-esbuild.ts +4 -0
- package/src/dev.tsx +340 -166
- package/src/dialogs.tsx +12 -0
- package/src/{worker-namespace.ts → dispatch-namespace.ts} +18 -18
- package/src/entry.ts +2 -1
- package/src/index.tsx +59 -12
- package/src/init.ts +291 -16
- package/src/metrics/send-event.ts +6 -5
- package/src/miniflare-cli/assets.ts +130 -476
- package/src/miniflare-cli/index.ts +39 -33
- package/src/pages/constants.ts +3 -0
- package/src/pages/dev.tsx +8 -3
- package/src/pages/functions/buildPlugin.ts +2 -1
- package/src/pages/functions/buildWorker.ts +2 -1
- package/src/pages/functions/routes-transformation.test.ts +12 -1
- package/src/pages/functions/routes-transformation.ts +7 -1
- package/src/pages/hash.tsx +13 -0
- package/src/pages/publish.tsx +82 -38
- package/src/pages/upload.tsx +3 -18
- package/src/paths.ts +20 -1
- package/src/publish.ts +49 -8
- package/src/tail/filters.ts +1 -5
- package/src/tail/index.ts +6 -3
- package/src/worker.ts +10 -9
- package/src/zones.ts +91 -0
- package/templates/middleware/common.ts +62 -0
- package/templates/middleware/loader-modules.ts +84 -0
- package/templates/middleware/loader-sw.ts +213 -0
- package/templates/middleware/middleware-pretty-error.ts +40 -0
- package/templates/middleware/middleware-scheduled.ts +14 -0
- package/wrangler-dist/cli.d.ts +22 -8
- package/wrangler-dist/cli.js +71020 -65212
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import { existsSync, lstatSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { createMetadataObject } from "@cloudflare/pages-shared/src/metadata-generator/createMetadataObject";
|
|
4
|
+
import { parseHeaders } from "@cloudflare/pages-shared/src/metadata-generator/parseHeaders";
|
|
5
|
+
import { parseRedirects } from "@cloudflare/pages-shared/src/metadata-generator/parseRedirects";
|
|
3
6
|
import { fetch as miniflareFetch } from "@miniflare/core";
|
|
7
|
+
import {
|
|
8
|
+
Response as MiniflareResponse,
|
|
9
|
+
Request as MiniflareRequest,
|
|
10
|
+
} from "@miniflare/core";
|
|
4
11
|
import { watch } from "chokidar";
|
|
5
12
|
import { getType } from "mime";
|
|
6
|
-
import {
|
|
7
|
-
import type {
|
|
8
|
-
import type { Log } from "miniflare";
|
|
13
|
+
import { hashFile } from "../pages/hash";
|
|
14
|
+
import type { Metadata } from "@cloudflare/pages-shared/src/asset-server/metadata";
|
|
9
15
|
import type {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
fetch,
|
|
17
|
+
Request,
|
|
18
|
+
} from "@cloudflare/pages-shared/src/environment-polyfills/types";
|
|
19
|
+
import type {
|
|
20
|
+
ParsedRedirects,
|
|
21
|
+
ParsedHeaders,
|
|
22
|
+
} from "@cloudflare/pages-shared/src/metadata-generator/types";
|
|
23
|
+
import type { RequestInfo, RequestInit, FetcherFetch } from "@miniflare/core";
|
|
24
|
+
import type { Log } from "miniflare";
|
|
14
25
|
|
|
15
26
|
export interface Options {
|
|
16
27
|
log: Log;
|
|
@@ -24,269 +35,49 @@ export default async function generateASSETSBinding(options: Options) {
|
|
|
24
35
|
? await generateAssetsFetch(options.directory, options.log)
|
|
25
36
|
: invalidAssetsFetch;
|
|
26
37
|
|
|
27
|
-
return async function (
|
|
38
|
+
return async function (miniflareRequest: MiniflareRequest) {
|
|
28
39
|
if (options.proxyPort) {
|
|
29
40
|
try {
|
|
30
|
-
const url = new URL(
|
|
41
|
+
const url = new URL(miniflareRequest.url);
|
|
31
42
|
url.host = `localhost:${options.proxyPort}`;
|
|
32
|
-
return await miniflareFetch(url,
|
|
43
|
+
return await miniflareFetch(url, miniflareRequest);
|
|
33
44
|
} catch (thrown) {
|
|
34
45
|
options.log.error(new Error(`Could not proxy request: ${thrown}`));
|
|
35
46
|
|
|
36
47
|
// TODO: Pretty error page
|
|
37
|
-
return new
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
return new MiniflareResponse(
|
|
49
|
+
`[wrangler] Could not proxy request: ${thrown}`,
|
|
50
|
+
{
|
|
51
|
+
status: 502,
|
|
52
|
+
}
|
|
53
|
+
);
|
|
40
54
|
}
|
|
41
55
|
} else {
|
|
42
56
|
try {
|
|
43
|
-
return await assetsFetch(
|
|
57
|
+
return await assetsFetch(miniflareRequest);
|
|
44
58
|
} catch (thrown) {
|
|
45
59
|
options.log.error(new Error(`Could not serve static asset: ${thrown}`));
|
|
46
60
|
|
|
47
61
|
// TODO: Pretty error page
|
|
48
|
-
return new
|
|
62
|
+
return new MiniflareResponse(
|
|
49
63
|
`[wrangler] Could not serve static asset: ${thrown}`,
|
|
50
64
|
{ status: 502 }
|
|
51
65
|
);
|
|
52
66
|
}
|
|
53
67
|
}
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function escapeRegex(str: string) {
|
|
58
|
-
return str.replace(/[-/\\^$*+?.()|[]{}]/g, "\\$&");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
type Replacements = Record<string, string>;
|
|
62
|
-
|
|
63
|
-
function replacer(str: string, replacements: Replacements) {
|
|
64
|
-
for (const [replacement, value] of Object.entries(replacements)) {
|
|
65
|
-
str = str.replace(`:${replacement}`, value);
|
|
66
|
-
}
|
|
67
|
-
return str;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function generateRulesMatcher<T>(
|
|
71
|
-
rules?: Record<string, T>,
|
|
72
|
-
replacerFn: (match: T, replacements: Replacements) => T = (match) => match
|
|
73
|
-
) {
|
|
74
|
-
// TODO: How can you test cross-host rules?
|
|
75
|
-
if (!rules) return () => [];
|
|
76
|
-
|
|
77
|
-
const compiledRules = Object.entries(rules)
|
|
78
|
-
.map(([rule, match]) => {
|
|
79
|
-
const crossHost = rule.startsWith("https://");
|
|
80
|
-
|
|
81
|
-
rule = rule.split("*").map(escapeRegex).join("(?<splat>.*)");
|
|
82
|
-
|
|
83
|
-
const host_matches = rule.matchAll(
|
|
84
|
-
/(?<=^https:\\\/\\\/[^/]*?):([^\\]+)(?=\\)/g
|
|
85
|
-
);
|
|
86
|
-
for (const hostMatch of host_matches) {
|
|
87
|
-
rule = rule.split(hostMatch[0]).join(`(?<${hostMatch[1]}>[^/.]+)`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const path_matches = rule.matchAll(/:(\w+)/g);
|
|
91
|
-
for (const pathMatch of path_matches) {
|
|
92
|
-
rule = rule.split(pathMatch[0]).join(`(?<${pathMatch[1]}>[^/]+)`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
rule = "^" + rule + "$";
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
const regExp = new RegExp(rule);
|
|
99
|
-
return [{ crossHost, regExp }, match];
|
|
100
|
-
} catch {}
|
|
101
|
-
})
|
|
102
|
-
.filter((value) => value !== undefined) as [
|
|
103
|
-
{ crossHost: boolean; regExp: RegExp },
|
|
104
|
-
T
|
|
105
|
-
][];
|
|
106
|
-
|
|
107
|
-
return ({ request }: { request: MiniflareRequest }) => {
|
|
108
|
-
const { pathname, host } = new URL(request.url);
|
|
109
|
-
|
|
110
|
-
return compiledRules
|
|
111
|
-
.map(([{ crossHost, regExp }, match]) => {
|
|
112
|
-
const test = crossHost ? `https://${host}${pathname}` : pathname;
|
|
113
|
-
const result = regExp.exec(test);
|
|
114
|
-
if (result) {
|
|
115
|
-
return replacerFn(match, result.groups || {});
|
|
116
|
-
}
|
|
117
|
-
})
|
|
118
|
-
.filter((value) => value !== undefined) as T[];
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function generateHeadersMatcher(headersFile: string) {
|
|
123
|
-
if (existsSync(headersFile)) {
|
|
124
|
-
const contents = readFileSync(headersFile).toString();
|
|
125
|
-
|
|
126
|
-
// TODO: Log errors
|
|
127
|
-
const lines = contents
|
|
128
|
-
.split("\n")
|
|
129
|
-
.map((line) => line.trim())
|
|
130
|
-
.filter((line) => !line.startsWith("#") && line !== "");
|
|
131
|
-
|
|
132
|
-
const rules: Record<string, Record<string, string>> = {};
|
|
133
|
-
let rule: { path: string; headers: Record<string, string> } | undefined =
|
|
134
|
-
undefined;
|
|
135
|
-
|
|
136
|
-
for (const line of lines) {
|
|
137
|
-
if (/^([^\s]+:\/\/|^\/)/.test(line)) {
|
|
138
|
-
if (rule && Object.keys(rule.headers).length > 0) {
|
|
139
|
-
rules[rule.path] = rule.headers;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const path = validateURL(line);
|
|
143
|
-
if (path) {
|
|
144
|
-
rule = {
|
|
145
|
-
path,
|
|
146
|
-
headers: {},
|
|
147
|
-
};
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (!line.includes(":")) continue;
|
|
153
|
-
|
|
154
|
-
const [rawName, ...rawValue] = line.split(":");
|
|
155
|
-
const name = rawName.trim().toLowerCase();
|
|
156
|
-
const value = rawValue.join(":").trim();
|
|
157
|
-
|
|
158
|
-
if (name === "") continue;
|
|
159
|
-
if (!rule) continue;
|
|
160
|
-
|
|
161
|
-
const existingValues = rule.headers[name];
|
|
162
|
-
rule.headers[name] = existingValues
|
|
163
|
-
? `${existingValues}, ${value}`
|
|
164
|
-
: value;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (rule && Object.keys(rule.headers).length > 0) {
|
|
168
|
-
rules[rule.path] = rule.headers;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const rulesMatcher = generateRulesMatcher(rules, (match, replacements) =>
|
|
172
|
-
Object.fromEntries(
|
|
173
|
-
Object.entries(match).map(([name, value]) => [
|
|
174
|
-
name,
|
|
175
|
-
replacer(value, replacements),
|
|
176
|
-
])
|
|
177
|
-
)
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
return (request: MiniflareRequest) => {
|
|
181
|
-
const matches = rulesMatcher({
|
|
182
|
-
request,
|
|
183
|
-
});
|
|
184
|
-
if (matches) return matches;
|
|
185
|
-
};
|
|
186
|
-
} else {
|
|
187
|
-
return () => undefined;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function generateRedirectsMatcher(redirectsFile: string) {
|
|
192
|
-
if (existsSync(redirectsFile)) {
|
|
193
|
-
const contents = readFileSync(redirectsFile).toString();
|
|
194
|
-
|
|
195
|
-
// TODO: Log errors
|
|
196
|
-
const lines = contents
|
|
197
|
-
.split("\n")
|
|
198
|
-
.map((line) => line.trim())
|
|
199
|
-
.filter((line) => !line.startsWith("#") && line !== "");
|
|
200
|
-
|
|
201
|
-
const rules = Object.fromEntries(
|
|
202
|
-
lines
|
|
203
|
-
.map((line) => line.split(" "))
|
|
204
|
-
.filter((tokens) => tokens.length === 2 || tokens.length === 3)
|
|
205
|
-
.map((tokens) => {
|
|
206
|
-
const from = validateURL(tokens[0], true, false, false);
|
|
207
|
-
const to = validateURL(tokens[1], false, true, true);
|
|
208
|
-
let status: number | undefined = parseInt(tokens[2]) || 302;
|
|
209
|
-
status = [301, 302, 303, 307, 308].includes(status)
|
|
210
|
-
? status
|
|
211
|
-
: undefined;
|
|
212
|
-
|
|
213
|
-
return from && to && status ? [from, { to, status }] : undefined;
|
|
214
|
-
})
|
|
215
|
-
.filter((rule) => rule !== undefined) as [
|
|
216
|
-
string,
|
|
217
|
-
{ to: string; status?: number }
|
|
218
|
-
][]
|
|
219
|
-
);
|
|
220
|
-
|
|
221
|
-
const rulesMatcher = generateRulesMatcher(
|
|
222
|
-
rules,
|
|
223
|
-
({ status, to }, replacements) => ({
|
|
224
|
-
status,
|
|
225
|
-
to: replacer(to, replacements),
|
|
226
|
-
})
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
return (request: MiniflareRequest) => {
|
|
230
|
-
const match = rulesMatcher({
|
|
231
|
-
request,
|
|
232
|
-
})[0];
|
|
233
|
-
if (match) return match;
|
|
234
|
-
};
|
|
235
|
-
} else {
|
|
236
|
-
return () => undefined;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function extractPathname(
|
|
241
|
-
path = "/",
|
|
242
|
-
includeSearch: boolean,
|
|
243
|
-
includeHash: boolean
|
|
244
|
-
) {
|
|
245
|
-
if (!path.startsWith("/")) path = `/${path}`;
|
|
246
|
-
const url = new URL(`//${path}`, "relative://");
|
|
247
|
-
return `${url.pathname}${includeSearch ? url.search : ""}${
|
|
248
|
-
includeHash ? url.hash : ""
|
|
249
|
-
}`;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function validateURL(
|
|
253
|
-
token: string,
|
|
254
|
-
onlyRelative = false,
|
|
255
|
-
includeSearch = false,
|
|
256
|
-
includeHash = false
|
|
257
|
-
) {
|
|
258
|
-
const host = /^https:\/\/+(?<host>[^/]+)\/?(?<path>.*)/.exec(token);
|
|
259
|
-
if (host && host.groups && host.groups.host) {
|
|
260
|
-
if (onlyRelative) return;
|
|
261
|
-
|
|
262
|
-
return `https://${host.groups.host}${extractPathname(
|
|
263
|
-
host.groups.path,
|
|
264
|
-
includeSearch,
|
|
265
|
-
includeHash
|
|
266
|
-
)}`;
|
|
267
|
-
} else {
|
|
268
|
-
if (!token.startsWith("/") && onlyRelative) token = `/${token}`;
|
|
269
|
-
|
|
270
|
-
const path = /^\//.exec(token);
|
|
271
|
-
if (path) {
|
|
272
|
-
try {
|
|
273
|
-
return extractPathname(token, includeSearch, includeHash);
|
|
274
|
-
} catch {}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
return "";
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function hasFileExtension(pathname: string) {
|
|
281
|
-
return /\/.+\.[a-z0-9]+$/i.test(pathname);
|
|
68
|
+
} as FetcherFetch;
|
|
282
69
|
}
|
|
283
70
|
|
|
284
71
|
async function generateAssetsFetch(
|
|
285
72
|
directory: string,
|
|
286
73
|
log: Log
|
|
287
|
-
): Promise<typeof
|
|
74
|
+
): Promise<typeof fetch> {
|
|
288
75
|
// Defer importing miniflare until we really need it
|
|
289
|
-
|
|
76
|
+
await import("@cloudflare/pages-shared/src/environment-polyfills/miniflare");
|
|
77
|
+
|
|
78
|
+
const { generateHandler, parseQualityWeightedList } = await import(
|
|
79
|
+
"@cloudflare/pages-shared/src/asset-server/handler"
|
|
80
|
+
);
|
|
290
81
|
|
|
291
82
|
const headersFile = join(directory, "_headers");
|
|
292
83
|
const redirectsFile = join(directory, "_redirects");
|
|
@@ -294,260 +85,123 @@ async function generateAssetsFetch(
|
|
|
294
85
|
|
|
295
86
|
const ignoredFiles = [headersFile, redirectsFile, workerFile];
|
|
296
87
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
!ignoredFiles.includes(path)
|
|
303
|
-
);
|
|
304
|
-
};
|
|
305
|
-
|
|
306
|
-
const getAsset = (path: string) => {
|
|
307
|
-
if (assetExists(path)) {
|
|
308
|
-
return join(directory, path);
|
|
309
|
-
}
|
|
310
|
-
};
|
|
88
|
+
let redirects: ParsedRedirects | undefined;
|
|
89
|
+
if (existsSync(redirectsFile)) {
|
|
90
|
+
const contents = readFileSync(redirectsFile, "utf-8");
|
|
91
|
+
redirects = parseRedirects(contents);
|
|
92
|
+
}
|
|
311
93
|
|
|
312
|
-
let
|
|
313
|
-
|
|
94
|
+
let headers: ParsedHeaders | undefined;
|
|
95
|
+
if (existsSync(headersFile)) {
|
|
96
|
+
const contents = readFileSync(headersFile, "utf-8");
|
|
97
|
+
headers = parseHeaders(contents);
|
|
98
|
+
}
|
|
314
99
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
case headersFile: {
|
|
320
|
-
log.log("_headers modified. Re-evaluating...");
|
|
321
|
-
headersMatcher = generateHeadersMatcher(headersFile);
|
|
322
|
-
break;
|
|
323
|
-
}
|
|
324
|
-
case redirectsFile: {
|
|
325
|
-
log.log("_redirects modified. Re-evaluating...");
|
|
326
|
-
redirectsMatcher = generateRedirectsMatcher(redirectsFile);
|
|
327
|
-
break;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
100
|
+
let metadata = createMetadataObject({
|
|
101
|
+
redirects,
|
|
102
|
+
headers,
|
|
103
|
+
logger: log.warn,
|
|
330
104
|
});
|
|
331
105
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
body?: Buffer;
|
|
348
|
-
} = {
|
|
349
|
-
status: 200,
|
|
350
|
-
headers: new Headers(),
|
|
351
|
-
body: undefined,
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
const match = redirectsMatcher(request);
|
|
355
|
-
if (match) {
|
|
356
|
-
const { status, to } = match;
|
|
357
|
-
|
|
358
|
-
let location = to;
|
|
359
|
-
let search;
|
|
360
|
-
|
|
361
|
-
if (to.startsWith("/")) {
|
|
362
|
-
search = new URL(location, "http://fakehost").search;
|
|
363
|
-
} else {
|
|
364
|
-
search = new URL(location).search;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
location = `${location}${search ? "" : url.search}`;
|
|
368
|
-
|
|
369
|
-
if (status && [301, 302, 303, 307, 308].includes(status)) {
|
|
370
|
-
deconstructedResponse.status = status;
|
|
371
|
-
} else {
|
|
372
|
-
deconstructedResponse.status = 302;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
deconstructedResponse.headers.set("Location", location);
|
|
376
|
-
return deconstructedResponse;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
if (!request.method?.match(/^(get|head)$/i)) {
|
|
380
|
-
deconstructedResponse.status = 405;
|
|
381
|
-
return deconstructedResponse;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const notFound = () => {
|
|
385
|
-
let cwd = assetName;
|
|
386
|
-
while (cwd) {
|
|
387
|
-
cwd = cwd.slice(0, cwd.lastIndexOf("/"));
|
|
388
|
-
|
|
389
|
-
if ((asset = getAsset(`${cwd}/404.html`))) {
|
|
390
|
-
deconstructedResponse.status = 404;
|
|
391
|
-
deconstructedResponse.body = serveAsset(asset);
|
|
392
|
-
deconstructedResponse.headers.set(
|
|
393
|
-
"Content-Type",
|
|
394
|
-
getType(asset) || "application/octet-stream"
|
|
395
|
-
);
|
|
396
|
-
return deconstructedResponse;
|
|
106
|
+
watch([headersFile, redirectsFile], { persistent: true }).on(
|
|
107
|
+
"change",
|
|
108
|
+
(path) => {
|
|
109
|
+
switch (path) {
|
|
110
|
+
case headersFile: {
|
|
111
|
+
log.log("_headers modified. Re-evaluating...");
|
|
112
|
+
const contents = readFileSync(headersFile).toString();
|
|
113
|
+
headers = parseHeaders(contents);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case redirectsFile: {
|
|
117
|
+
log.log("_redirects modified. Re-evaluating...");
|
|
118
|
+
const contents = readFileSync(redirectsFile).toString();
|
|
119
|
+
redirects = parseRedirects(contents);
|
|
120
|
+
break;
|
|
397
121
|
}
|
|
398
122
|
}
|
|
399
123
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
);
|
|
406
|
-
return deconstructedResponse;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
deconstructedResponse.status = 404;
|
|
410
|
-
return deconstructedResponse;
|
|
411
|
-
};
|
|
412
|
-
|
|
413
|
-
let asset;
|
|
414
|
-
|
|
415
|
-
if (assetName.endsWith("/")) {
|
|
416
|
-
if ((asset = getAsset(`${assetName}/index.html`))) {
|
|
417
|
-
deconstructedResponse.body = serveAsset(asset);
|
|
418
|
-
deconstructedResponse.headers.set(
|
|
419
|
-
"Content-Type",
|
|
420
|
-
getType(asset) || "application/octet-stream"
|
|
421
|
-
);
|
|
422
|
-
return deconstructedResponse;
|
|
423
|
-
} else if ((asset = getAsset(`${assetName.replace(/\/$/, ".html")}`))) {
|
|
424
|
-
deconstructedResponse.status = 301;
|
|
425
|
-
deconstructedResponse.headers.set(
|
|
426
|
-
"Location",
|
|
427
|
-
`${assetName.slice(0, -1)}${url.search}`
|
|
428
|
-
);
|
|
429
|
-
return deconstructedResponse;
|
|
430
|
-
}
|
|
124
|
+
metadata = createMetadataObject({
|
|
125
|
+
redirects,
|
|
126
|
+
headers,
|
|
127
|
+
logger: log.warn,
|
|
128
|
+
});
|
|
431
129
|
}
|
|
130
|
+
);
|
|
432
131
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
132
|
+
const generateResponse = async (request: Request) => {
|
|
133
|
+
const assetKeyEntryMap = new Map<string, string>();
|
|
134
|
+
|
|
135
|
+
return await generateHandler<string>({
|
|
136
|
+
request,
|
|
137
|
+
metadata: metadata as Metadata,
|
|
138
|
+
xServerEnvHeader: "dev",
|
|
139
|
+
logError: console.error,
|
|
140
|
+
findAssetEntryForPath: async (path) => {
|
|
141
|
+
const filepath = join(directory, path);
|
|
142
|
+
|
|
143
|
+
if (
|
|
144
|
+
existsSync(filepath) &&
|
|
145
|
+
lstatSync(filepath).isFile() &&
|
|
146
|
+
!ignoredFiles.includes(filepath)
|
|
147
|
+
) {
|
|
148
|
+
const hash = hashFile(filepath);
|
|
149
|
+
assetKeyEntryMap.set(hash, filepath);
|
|
150
|
+
return hash;
|
|
151
|
+
}
|
|
441
152
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
153
|
+
return null;
|
|
154
|
+
},
|
|
155
|
+
getAssetKey: (assetEntry) => {
|
|
156
|
+
return assetEntry;
|
|
157
|
+
},
|
|
158
|
+
negotiateContent: (contentRequest) => {
|
|
159
|
+
let rawAcceptEncoding: string | undefined;
|
|
160
|
+
if (
|
|
161
|
+
contentRequest.cf &&
|
|
162
|
+
"clientAcceptEncoding" in contentRequest.cf &&
|
|
163
|
+
contentRequest.cf.clientAcceptEncoding
|
|
164
|
+
) {
|
|
165
|
+
rawAcceptEncoding = contentRequest.cf.clientAcceptEncoding as string;
|
|
452
166
|
} else {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
"Location",
|
|
456
|
-
`${extensionlessPath}${url.search}`
|
|
457
|
-
);
|
|
458
|
-
return deconstructedResponse;
|
|
167
|
+
rawAcceptEncoding =
|
|
168
|
+
contentRequest.headers.get("Accept-Encoding") || undefined;
|
|
459
169
|
}
|
|
460
|
-
} else {
|
|
461
|
-
deconstructedResponse.body = serveAsset(asset);
|
|
462
|
-
deconstructedResponse.headers.set(
|
|
463
|
-
"Content-Type",
|
|
464
|
-
getType(asset) || "application/octet-stream"
|
|
465
|
-
);
|
|
466
|
-
return deconstructedResponse;
|
|
467
|
-
}
|
|
468
|
-
} else if (hasFileExtension(assetName)) {
|
|
469
|
-
if ((asset = getAsset(assetName + ".html"))) {
|
|
470
|
-
deconstructedResponse.body = serveAsset(asset);
|
|
471
|
-
deconstructedResponse.headers.set(
|
|
472
|
-
"Content-Type",
|
|
473
|
-
getType(asset) || "application/octet-stream"
|
|
474
|
-
);
|
|
475
|
-
return deconstructedResponse;
|
|
476
|
-
}
|
|
477
|
-
notFound();
|
|
478
|
-
return deconstructedResponse;
|
|
479
|
-
}
|
|
480
170
|
|
|
481
|
-
|
|
482
|
-
deconstructedResponse.body = serveAsset(asset);
|
|
483
|
-
deconstructedResponse.headers.set(
|
|
484
|
-
"Content-Type",
|
|
485
|
-
getType(asset) || "application/octet-stream"
|
|
486
|
-
);
|
|
487
|
-
return deconstructedResponse;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
if ((asset = getAsset(`${assetName}/index.html`))) {
|
|
491
|
-
deconstructedResponse.status = 301;
|
|
492
|
-
deconstructedResponse.headers.set(
|
|
493
|
-
"Location",
|
|
494
|
-
`${assetName}/${url.search}`
|
|
495
|
-
);
|
|
496
|
-
return deconstructedResponse;
|
|
497
|
-
} else {
|
|
498
|
-
notFound();
|
|
499
|
-
return deconstructedResponse;
|
|
500
|
-
}
|
|
501
|
-
};
|
|
171
|
+
const acceptEncoding = parseQualityWeightedList(rawAcceptEncoding);
|
|
502
172
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
) => {
|
|
511
|
-
const headers = deconstructedResponse.headers;
|
|
512
|
-
const newHeaders = new Headers({});
|
|
513
|
-
const matches = headersMatcher(request) || [];
|
|
514
|
-
|
|
515
|
-
matches.forEach((match) => {
|
|
516
|
-
Object.entries(match).forEach(([name, value]) => {
|
|
517
|
-
newHeaders.append(name, `${value}`);
|
|
518
|
-
});
|
|
519
|
-
});
|
|
173
|
+
if (
|
|
174
|
+
acceptEncoding["identity"] === 0 ||
|
|
175
|
+
(acceptEncoding["*"] === 0 &&
|
|
176
|
+
acceptEncoding["identity"] === undefined)
|
|
177
|
+
) {
|
|
178
|
+
throw new Error("No acceptable encodings available");
|
|
179
|
+
}
|
|
520
180
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
181
|
+
return { encoding: null };
|
|
182
|
+
},
|
|
183
|
+
fetchAsset: async (assetKey) => {
|
|
184
|
+
const filepath = assetKeyEntryMap.get(assetKey);
|
|
185
|
+
if (!filepath) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
"Could not fetch asset. Please file an issue on GitHub (https://github.com/cloudflare/wrangler2/issues/new/choose) with reproduction steps."
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
const body = readFileSync(filepath) as unknown as ReadableStream;
|
|
525
191
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
192
|
+
const contentType = getType(filepath) || "application/octet-stream";
|
|
193
|
+
return { body, contentType };
|
|
194
|
+
},
|
|
529
195
|
});
|
|
530
196
|
};
|
|
531
197
|
|
|
532
198
|
return async (input: RequestInfo, init?: RequestInit) => {
|
|
533
|
-
const request = new
|
|
534
|
-
|
|
535
|
-
attachHeaders(request, deconstructedResponse);
|
|
536
|
-
|
|
537
|
-
const headers = new Headers();
|
|
538
|
-
|
|
539
|
-
[...deconstructedResponse.headers.entries()].forEach(([name, value]) => {
|
|
540
|
-
if (value) headers.set(name, value);
|
|
541
|
-
});
|
|
542
|
-
|
|
543
|
-
return new Response(deconstructedResponse.body, {
|
|
544
|
-
headers,
|
|
545
|
-
status: deconstructedResponse.status,
|
|
546
|
-
});
|
|
199
|
+
const request = new MiniflareRequest(input, init);
|
|
200
|
+
return await generateResponse(request as unknown as Request);
|
|
547
201
|
};
|
|
548
202
|
}
|
|
549
203
|
|
|
550
|
-
const invalidAssetsFetch: typeof
|
|
204
|
+
const invalidAssetsFetch: typeof fetch = () => {
|
|
551
205
|
throw new Error(
|
|
552
206
|
"Trying to fetch assets directly when there is no `directory` option specified."
|
|
553
207
|
);
|