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.
Files changed (65) hide show
  1. package/bin/wrangler.js +1 -1
  2. package/miniflare-dist/index.mjs +1141 -369
  3. package/package.json +6 -4
  4. package/src/__tests__/api-dev.test.ts +19 -0
  5. package/src/__tests__/configuration.test.ts +27 -27
  6. package/src/__tests__/dev.test.tsx +8 -6
  7. package/src/__tests__/helpers/hello-world-worker.js +5 -0
  8. package/src/__tests__/helpers/mock-cfetch.ts +4 -4
  9. package/src/__tests__/helpers/mock-console.ts +11 -2
  10. package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -0
  11. package/src/__tests__/helpers/mock-known-routes.ts +7 -0
  12. package/src/__tests__/index.test.ts +37 -37
  13. package/src/__tests__/init.test.ts +356 -5
  14. package/src/__tests__/jest.setup.ts +13 -0
  15. package/src/__tests__/middleware.test.ts +768 -0
  16. package/src/__tests__/pages.test.ts +829 -104
  17. package/src/__tests__/paths.test.ts +17 -0
  18. package/src/__tests__/publish.test.ts +512 -445
  19. package/src/__tests__/tail.test.ts +79 -72
  20. package/src/__tests__/test-old-node-version.js +3 -3
  21. package/src/__tests__/worker-namespace.test.ts +37 -35
  22. package/src/api/dev.ts +93 -28
  23. package/src/bundle.ts +239 -12
  24. package/src/cfetch/internal.ts +64 -3
  25. package/src/cli.ts +1 -1
  26. package/src/config/environment.ts +1 -1
  27. package/src/config/index.ts +4 -4
  28. package/src/config/validation.ts +3 -3
  29. package/src/create-worker-upload-form.ts +29 -26
  30. package/src/dev/dev.tsx +3 -1
  31. package/src/dev/local.tsx +319 -171
  32. package/src/dev/remote.tsx +16 -4
  33. package/src/dev/start-server.ts +416 -0
  34. package/src/dev/use-esbuild.ts +4 -0
  35. package/src/dev.tsx +340 -166
  36. package/src/dialogs.tsx +12 -0
  37. package/src/{worker-namespace.ts → dispatch-namespace.ts} +18 -18
  38. package/src/entry.ts +2 -1
  39. package/src/index.tsx +59 -12
  40. package/src/init.ts +291 -16
  41. package/src/metrics/send-event.ts +6 -5
  42. package/src/miniflare-cli/assets.ts +130 -476
  43. package/src/miniflare-cli/index.ts +39 -33
  44. package/src/pages/constants.ts +3 -0
  45. package/src/pages/dev.tsx +8 -3
  46. package/src/pages/functions/buildPlugin.ts +2 -1
  47. package/src/pages/functions/buildWorker.ts +2 -1
  48. package/src/pages/functions/routes-transformation.test.ts +12 -1
  49. package/src/pages/functions/routes-transformation.ts +7 -1
  50. package/src/pages/hash.tsx +13 -0
  51. package/src/pages/publish.tsx +82 -38
  52. package/src/pages/upload.tsx +3 -18
  53. package/src/paths.ts +20 -1
  54. package/src/publish.ts +49 -8
  55. package/src/tail/filters.ts +1 -5
  56. package/src/tail/index.ts +6 -3
  57. package/src/worker.ts +10 -9
  58. package/src/zones.ts +91 -0
  59. package/templates/middleware/common.ts +62 -0
  60. package/templates/middleware/loader-modules.ts +84 -0
  61. package/templates/middleware/loader-sw.ts +213 -0
  62. package/templates/middleware/middleware-pretty-error.ts +40 -0
  63. package/templates/middleware/middleware-scheduled.ts +14 -0
  64. package/wrangler-dist/cli.d.ts +22 -8
  65. package/wrangler-dist/cli.js +71020 -65212
@@ -11,9 +11,16 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
11
11
  return require.apply(this, arguments);
12
12
  throw new Error('Dynamic require of "' + x + '" is not supported');
13
13
  });
14
+ var __esm = (fn, res) => function __init() {
15
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
16
+ };
14
17
  var __commonJS = (cb, mod) => function __require2() {
15
18
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
16
19
  };
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, { get: all[name], enumerable: true });
23
+ };
17
24
  var __copyProps = (to, from, except, desc) => {
18
25
  if (from && typeof from === "object" || typeof from === "function") {
19
26
  for (let key of __getOwnPropNames(from))
@@ -104,6 +111,626 @@ var require_mime = __commonJS({
104
111
  }
105
112
  });
106
113
 
114
+ // ../pages-shared/src/environment-polyfills/index.ts
115
+ var polyfill;
116
+ var init_environment_polyfills = __esm({
117
+ "../pages-shared/src/environment-polyfills/index.ts"() {
118
+ polyfill = (environment) => {
119
+ Object.entries(environment).map(([name, value]) => {
120
+ Object.defineProperty(globalThis, name, {
121
+ value,
122
+ configurable: true,
123
+ enumerable: true,
124
+ writable: true
125
+ });
126
+ });
127
+ };
128
+ }
129
+ });
130
+
131
+ // ../pages-shared/src/environment-polyfills/miniflare.ts
132
+ var miniflare_exports = {};
133
+ import {
134
+ fetch as miniflareFetch,
135
+ Headers as MiniflareHeaders,
136
+ Request as MiniflareRequest,
137
+ Response as MiniflareResponse
138
+ } from "@miniflare/core";
139
+ var init_miniflare = __esm({
140
+ "../pages-shared/src/environment-polyfills/miniflare.ts"() {
141
+ init_environment_polyfills();
142
+ polyfill({
143
+ fetch: miniflareFetch,
144
+ Headers: MiniflareHeaders,
145
+ Request: MiniflareRequest,
146
+ Response: MiniflareResponse
147
+ });
148
+ }
149
+ });
150
+
151
+ // ../pages-shared/src/asset-server/responses.ts
152
+ function mergeHeaders(base, extra) {
153
+ base = new Headers(base ?? {});
154
+ extra = new Headers(extra ?? {});
155
+ return new Headers({
156
+ ...Object.fromEntries(base.entries()),
157
+ ...Object.fromEntries(extra.entries())
158
+ });
159
+ }
160
+ var OkResponse, MovedPermanentlyResponse, FoundResponse, NotModifiedResponse, PermanentRedirectResponse, NotFoundResponse, MethodNotAllowedResponse, NotAcceptableResponse, InternalServerErrorResponse, SeeOtherResponse, TemporaryRedirectResponse;
161
+ var init_responses = __esm({
162
+ "../pages-shared/src/asset-server/responses.ts"() {
163
+ OkResponse = class extends Response {
164
+ constructor(...[body, init]) {
165
+ super(body, {
166
+ ...init,
167
+ status: 200,
168
+ statusText: "OK"
169
+ });
170
+ }
171
+ };
172
+ MovedPermanentlyResponse = class extends Response {
173
+ constructor(location, init) {
174
+ super(`Redirecting to ${location}`, {
175
+ ...init,
176
+ status: 301,
177
+ statusText: "Moved Permanently",
178
+ headers: mergeHeaders(init?.headers, {
179
+ location
180
+ })
181
+ });
182
+ }
183
+ };
184
+ FoundResponse = class extends Response {
185
+ constructor(location, init) {
186
+ super(`Redirecting to ${location}`, {
187
+ ...init,
188
+ status: 302,
189
+ statusText: "Found",
190
+ headers: mergeHeaders(init?.headers, {
191
+ location
192
+ })
193
+ });
194
+ }
195
+ };
196
+ NotModifiedResponse = class extends Response {
197
+ constructor(...[_body, _init]) {
198
+ super(void 0, {
199
+ status: 304,
200
+ statusText: "Not Modified"
201
+ });
202
+ }
203
+ };
204
+ PermanentRedirectResponse = class extends Response {
205
+ constructor(location, init) {
206
+ super(void 0, {
207
+ ...init,
208
+ status: 308,
209
+ statusText: "Permanent Redirect",
210
+ headers: mergeHeaders(init?.headers, {
211
+ location
212
+ })
213
+ });
214
+ }
215
+ };
216
+ NotFoundResponse = class extends Response {
217
+ constructor(...[body, init]) {
218
+ super(body, {
219
+ ...init,
220
+ status: 404,
221
+ statusText: "Not Found"
222
+ });
223
+ }
224
+ };
225
+ MethodNotAllowedResponse = class extends Response {
226
+ constructor(...[body, init]) {
227
+ super(body, {
228
+ ...init,
229
+ status: 405,
230
+ statusText: "Method Not Allowed"
231
+ });
232
+ }
233
+ };
234
+ NotAcceptableResponse = class extends Response {
235
+ constructor(...[body, init]) {
236
+ super(body, {
237
+ ...init,
238
+ status: 406,
239
+ statusText: "Not Acceptable"
240
+ });
241
+ }
242
+ };
243
+ InternalServerErrorResponse = class extends Response {
244
+ constructor(err, init) {
245
+ let body = void 0;
246
+ if (globalThis.DEBUG) {
247
+ body = `${err.message}
248
+
249
+ ${err.stack}`;
250
+ }
251
+ super(body, {
252
+ ...init,
253
+ status: 500,
254
+ statusText: "Internal Server Error"
255
+ });
256
+ }
257
+ };
258
+ SeeOtherResponse = class extends Response {
259
+ constructor(location, init) {
260
+ super(`Redirecting to ${location}`, {
261
+ ...init,
262
+ status: 303,
263
+ statusText: "See Other",
264
+ headers: mergeHeaders(init?.headers, { location })
265
+ });
266
+ }
267
+ };
268
+ TemporaryRedirectResponse = class extends Response {
269
+ constructor(location, init) {
270
+ super(`Redirecting to ${location}`, {
271
+ ...init,
272
+ status: 307,
273
+ statusText: "Temporary Redirect",
274
+ headers: mergeHeaders(init?.headers, { location })
275
+ });
276
+ }
277
+ };
278
+ }
279
+ });
280
+
281
+ // ../pages-shared/src/asset-server/rulesEngine.ts
282
+ var ESCAPE_REGEX_CHARACTERS, escapeRegex, HOST_PLACEHOLDER_REGEX, PLACEHOLDER_REGEX2, replacer, generateRulesMatcher;
283
+ var init_rulesEngine = __esm({
284
+ "../pages-shared/src/asset-server/rulesEngine.ts"() {
285
+ ESCAPE_REGEX_CHARACTERS = /[-/\\^$*+?.()|[\]{}]/g;
286
+ escapeRegex = (str) => {
287
+ return str.replace(ESCAPE_REGEX_CHARACTERS, "\\$&");
288
+ };
289
+ HOST_PLACEHOLDER_REGEX = /(?<=^https:\\\/\\\/[^/]*?):([^\\]+)(?=\\)/g;
290
+ PLACEHOLDER_REGEX2 = /:(\w+)/g;
291
+ replacer = (str, replacements) => {
292
+ for (const [replacement, value] of Object.entries(replacements)) {
293
+ str = str.replaceAll(`:${replacement}`, value);
294
+ }
295
+ return str;
296
+ };
297
+ generateRulesMatcher = (rules, replacerFn = (match) => match) => {
298
+ if (!rules)
299
+ return () => [];
300
+ const compiledRules = Object.entries(rules).map(([rule, match]) => {
301
+ const crossHost = rule.startsWith("https://");
302
+ rule = rule.split("*").map(escapeRegex).join("(?<splat>.*)");
303
+ const host_matches = rule.matchAll(HOST_PLACEHOLDER_REGEX);
304
+ for (const host_match of host_matches) {
305
+ rule = rule.split(host_match[0]).join(`(?<${host_match[1]}>[^/.]+)`);
306
+ }
307
+ const path_matches = rule.matchAll(PLACEHOLDER_REGEX2);
308
+ for (const path_match of path_matches) {
309
+ rule = rule.split(path_match[0]).join(`(?<${path_match[1]}>[^/]+)`);
310
+ }
311
+ rule = "^" + rule + "$";
312
+ try {
313
+ const regExp = new RegExp(rule);
314
+ return [{ crossHost, regExp }, match];
315
+ } catch {
316
+ }
317
+ }).filter((value) => value !== void 0);
318
+ return ({ request }) => {
319
+ const { pathname, host } = new URL(request.url);
320
+ return compiledRules.map(([{ crossHost, regExp }, match]) => {
321
+ const test = crossHost ? `https://${host}${pathname}` : pathname;
322
+ const result = regExp.exec(test);
323
+ if (result) {
324
+ return replacerFn(match, result.groups || {});
325
+ }
326
+ }).filter((value) => value !== void 0);
327
+ };
328
+ };
329
+ }
330
+ });
331
+
332
+ // ../pages-shared/src/asset-server/handler.ts
333
+ var handler_exports = {};
334
+ __export(handler_exports, {
335
+ ANALYTICS_VERSION: () => ANALYTICS_VERSION2,
336
+ ASSET_PRESERVATION_CACHE: () => ASSET_PRESERVATION_CACHE,
337
+ CACHE_CONTROL_BROWSER: () => CACHE_CONTROL_BROWSER,
338
+ HEADERS_VERSION: () => HEADERS_VERSION2,
339
+ HEADERS_VERSION_V1: () => HEADERS_VERSION_V1,
340
+ REDIRECTS_VERSION: () => REDIRECTS_VERSION2,
341
+ generateHandler: () => generateHandler,
342
+ normaliseHeaders: () => normaliseHeaders,
343
+ parseQualityWeightedList: () => parseQualityWeightedList
344
+ });
345
+ function normaliseHeaders(headers) {
346
+ if (headers.version === HEADERS_VERSION2) {
347
+ return headers.rules;
348
+ } else if (headers.version === HEADERS_VERSION_V1) {
349
+ return Object.keys(headers.rules).reduce(
350
+ (acc, key) => {
351
+ acc[key] = {
352
+ set: headers.rules[key]
353
+ };
354
+ return acc;
355
+ },
356
+ {}
357
+ );
358
+ } else {
359
+ return {};
360
+ }
361
+ }
362
+ async function generateHandler({
363
+ request,
364
+ metadata,
365
+ xServerEnvHeader,
366
+ logError,
367
+ findAssetEntryForPath,
368
+ getAssetKey,
369
+ negotiateContent,
370
+ fetchAsset,
371
+ generateNotFoundResponse = async (notFoundRequest, notFoundFindAssetEntryForPath, notFoundServeAsset) => {
372
+ let assetEntry;
373
+ if (assetEntry = await notFoundFindAssetEntryForPath("/index.html")) {
374
+ return notFoundServeAsset(assetEntry, { preserve: false });
375
+ }
376
+ return new NotFoundResponse();
377
+ },
378
+ attachAdditionalHeaders = () => {
379
+ },
380
+ caches,
381
+ waitUntil
382
+ }) {
383
+ const url = new URL(request.url);
384
+ const { protocol, host, search } = url;
385
+ let { pathname } = url;
386
+ const earlyHintsCache = metadata.deploymentId ? await caches?.open(`eh:${metadata.deploymentId}`) : void 0;
387
+ const headerRules = metadata.headers ? normaliseHeaders(metadata.headers) : {};
388
+ const staticRules = metadata.redirects?.version === REDIRECTS_VERSION2 ? metadata.redirects.staticRules || {} : {};
389
+ const staticRedirectsMatcher = () => {
390
+ const withHostMatch = staticRules[`https://${host}${pathname}`];
391
+ const withoutHostMatch = staticRules[pathname];
392
+ if (withHostMatch && withoutHostMatch) {
393
+ if (withHostMatch.lineNumber < withoutHostMatch.lineNumber) {
394
+ return withHostMatch;
395
+ } else {
396
+ return withoutHostMatch;
397
+ }
398
+ }
399
+ return withHostMatch || withoutHostMatch;
400
+ };
401
+ const generateRedirectsMatcher = () => generateRulesMatcher(
402
+ metadata.redirects?.version === REDIRECTS_VERSION2 ? metadata.redirects.rules : {},
403
+ ({ status, to }, replacements) => ({
404
+ status,
405
+ to: replacer(to, replacements)
406
+ })
407
+ );
408
+ let assetEntry;
409
+ async function generateResponse() {
410
+ const match = staticRedirectsMatcher() || generateRedirectsMatcher()({ request })[0];
411
+ if (match) {
412
+ const { status, to } = match;
413
+ const destination = new URL(to, request.url);
414
+ const location = destination.origin === new URL(request.url).origin ? `${destination.pathname}${destination.search || search}${destination.hash}` : `${destination.href}${destination.search ? "" : search}${destination.hash}`;
415
+ switch (status) {
416
+ case 301:
417
+ return new MovedPermanentlyResponse(location);
418
+ case 303:
419
+ return new SeeOtherResponse(location);
420
+ case 307:
421
+ return new TemporaryRedirectResponse(location);
422
+ case 308:
423
+ return new PermanentRedirectResponse(location);
424
+ case 302:
425
+ default:
426
+ return new FoundResponse(location);
427
+ }
428
+ }
429
+ if (!request.method.match(/^(get|head)$/i)) {
430
+ return new MethodNotAllowedResponse();
431
+ }
432
+ try {
433
+ pathname = globalThis.decodeURIComponent(pathname);
434
+ } catch (err) {
435
+ }
436
+ if (pathname.endsWith("/")) {
437
+ if (assetEntry = await findAssetEntryForPath(`${pathname}index.html`)) {
438
+ return serveAsset(assetEntry);
439
+ } else if (pathname.endsWith("/index/")) {
440
+ return new PermanentRedirectResponse(
441
+ `/${pathname.slice(1, -"index/".length)}${search}`
442
+ );
443
+ } else if (assetEntry = await findAssetEntryForPath(
444
+ `${pathname.replace(/\/$/, ".html")}`
445
+ )) {
446
+ return new PermanentRedirectResponse(
447
+ `/${pathname.slice(1, -1)}${search}`
448
+ );
449
+ } else {
450
+ return notFound();
451
+ }
452
+ }
453
+ if (assetEntry = await findAssetEntryForPath(pathname)) {
454
+ if (pathname.endsWith(".html")) {
455
+ const extensionlessPath = pathname.slice(0, -".html".length);
456
+ if (extensionlessPath.endsWith("/index")) {
457
+ return new PermanentRedirectResponse(
458
+ `${extensionlessPath.replace(/\/index$/, "/")}${search}`
459
+ );
460
+ } else if (await findAssetEntryForPath(extensionlessPath) || extensionlessPath === "/") {
461
+ return serveAsset(assetEntry);
462
+ } else {
463
+ return new PermanentRedirectResponse(`${extensionlessPath}${search}`);
464
+ }
465
+ } else {
466
+ return serveAsset(assetEntry);
467
+ }
468
+ } else if (pathname.endsWith("/index")) {
469
+ return new PermanentRedirectResponse(
470
+ `/${pathname.slice(1, -"index".length)}${search}`
471
+ );
472
+ } else if (assetEntry = await findAssetEntryForPath(`${pathname}.html`)) {
473
+ return serveAsset(assetEntry);
474
+ } else if (hasFileExtension(pathname)) {
475
+ return notFound();
476
+ }
477
+ if (assetEntry = await findAssetEntryForPath(`${pathname}/index.html`)) {
478
+ return new PermanentRedirectResponse(`${pathname}/${search}`);
479
+ } else {
480
+ return notFound();
481
+ }
482
+ }
483
+ async function attachHeaders(response) {
484
+ const existingHeaders = new Headers(response.headers);
485
+ const extraHeaders = new Headers({
486
+ "access-control-allow-origin": "*",
487
+ "referrer-policy": "strict-origin-when-cross-origin",
488
+ ...existingHeaders.has("content-type") ? { "x-content-type-options": "nosniff" } : {}
489
+ });
490
+ const headers = new Headers({
491
+ ...Object.fromEntries(existingHeaders.entries()),
492
+ ...Object.fromEntries(extraHeaders.entries())
493
+ });
494
+ const headersMatcher = generateRulesMatcher(
495
+ headerRules,
496
+ ({ set = {}, unset = [] }, replacements) => {
497
+ const replacedSet = {};
498
+ Object.keys(set).forEach((key) => {
499
+ replacedSet[key] = replacer(set[key], replacements);
500
+ });
501
+ return {
502
+ set: replacedSet,
503
+ unset
504
+ };
505
+ }
506
+ );
507
+ const matches = headersMatcher({ request });
508
+ const setMap = /* @__PURE__ */ new Set();
509
+ matches.forEach(({ set = {}, unset = [] }) => {
510
+ Object.keys(set).forEach((key) => {
511
+ if (setMap.has(key.toLowerCase())) {
512
+ headers.append(key, set[key]);
513
+ } else {
514
+ headers.set(key, set[key]);
515
+ setMap.add(key.toLowerCase());
516
+ }
517
+ });
518
+ unset.forEach((key) => {
519
+ headers.delete(key);
520
+ });
521
+ });
522
+ if (earlyHintsCache) {
523
+ const preEarlyHintsHeaders = new Headers(headers);
524
+ const earlyHintsCacheKey = `${protocol}//${host}${pathname}`;
525
+ const earlyHintsResponse = await earlyHintsCache.match(
526
+ earlyHintsCacheKey
527
+ );
528
+ if (earlyHintsResponse) {
529
+ const earlyHintsLinkHeader = earlyHintsResponse.headers.get("Link");
530
+ if (earlyHintsLinkHeader) {
531
+ headers.set("Link", earlyHintsLinkHeader);
532
+ }
533
+ }
534
+ const clonedResponse = response.clone();
535
+ if (waitUntil) {
536
+ waitUntil(
537
+ (async () => {
538
+ try {
539
+ const links = [];
540
+ const transformedResponse = new HTMLRewriter().on("link[rel=preconnect],link[rel=preload]", {
541
+ element(element) {
542
+ const href = element.getAttribute("href") || void 0;
543
+ const rel = element.getAttribute("rel") || void 0;
544
+ const as = element.getAttribute("as") || void 0;
545
+ if (href && !href.startsWith("data:") && rel) {
546
+ links.push({ href, rel, as });
547
+ }
548
+ }
549
+ }).transform(clonedResponse);
550
+ await transformedResponse.text();
551
+ links.forEach(({ href, rel, as }) => {
552
+ let link = `<${href}>; rel="${rel}"`;
553
+ if (as) {
554
+ link += `; as=${as}`;
555
+ }
556
+ preEarlyHintsHeaders.append("Link", link);
557
+ });
558
+ const linkHeader = preEarlyHintsHeaders.get("Link");
559
+ if (linkHeader) {
560
+ await earlyHintsCache.put(
561
+ earlyHintsCacheKey,
562
+ new Response(null, { headers: { Link: linkHeader } })
563
+ );
564
+ }
565
+ } catch (err) {
566
+ }
567
+ })()
568
+ );
569
+ }
570
+ }
571
+ return new Response(
572
+ [101, 204, 205, 304].includes(response.status) ? null : response.body,
573
+ {
574
+ headers,
575
+ status: response.status,
576
+ statusText: response.statusText
577
+ }
578
+ );
579
+ }
580
+ return await attachHeaders(await generateResponse());
581
+ async function serveAsset(servingAssetEntry, options = { preserve: true }) {
582
+ let content;
583
+ try {
584
+ content = negotiateContent(request, servingAssetEntry);
585
+ } catch (err) {
586
+ return new NotAcceptableResponse();
587
+ }
588
+ const assetKey = getAssetKey(servingAssetEntry, content);
589
+ const etag = `"${assetKey}"`;
590
+ const weakEtag = `W/${etag}`;
591
+ const ifNoneMatch = request.headers.get("if-none-match");
592
+ if (ifNoneMatch === weakEtag || ifNoneMatch === etag) {
593
+ return new NotModifiedResponse();
594
+ }
595
+ try {
596
+ const asset = await fetchAsset(assetKey);
597
+ const headers = {
598
+ etag,
599
+ "content-type": asset.contentType
600
+ };
601
+ let encodeBody = "automatic";
602
+ if (xServerEnvHeader) {
603
+ headers["x-server-env"] = xServerEnvHeader;
604
+ }
605
+ if (content.encoding) {
606
+ encodeBody = "manual";
607
+ headers["cache-control"] = "no-transform";
608
+ headers["content-encoding"] = content.encoding;
609
+ }
610
+ const response = new OkResponse(
611
+ request.method === "HEAD" ? null : asset.body,
612
+ {
613
+ headers,
614
+ encodeBody
615
+ }
616
+ );
617
+ if (isCacheable(request)) {
618
+ response.headers.append("cache-control", CACHE_CONTROL_BROWSER);
619
+ }
620
+ attachAdditionalHeaders(response, content, servingAssetEntry, asset);
621
+ if (isPreview(new URL(request.url))) {
622
+ response.headers.set("x-robots-tag", "noindex");
623
+ }
624
+ if (options.preserve) {
625
+ const preservedResponse = new Response(
626
+ [101, 204, 205, 304].includes(response.status) ? null : response.clone().body,
627
+ response
628
+ );
629
+ preservedResponse.headers.set(
630
+ "cache-control",
631
+ CACHE_CONTROL_PRESERVATION
632
+ );
633
+ preservedResponse.headers.set("x-robots-tag", "noindex");
634
+ if (waitUntil && caches) {
635
+ waitUntil(
636
+ caches.open(ASSET_PRESERVATION_CACHE).then(
637
+ (assetPreservationCache) => assetPreservationCache.put(request.url, preservedResponse)
638
+ ).catch((err) => {
639
+ logError(err);
640
+ })
641
+ );
642
+ }
643
+ }
644
+ if (asset.contentType.startsWith("text/html") && metadata.analytics?.version === ANALYTICS_VERSION2) {
645
+ return new HTMLRewriter().on("body", {
646
+ element(e) {
647
+ e.append(
648
+ `<!-- Cloudflare Pages Analytics --><script defer src='https://static.cloudflareinsights.com/beacon.min.js' data-cf-beacon='{"token": "${metadata.analytics?.token}"}'><\/script><!-- Cloudflare Pages Analytics -->`,
649
+ { html: true }
650
+ );
651
+ }
652
+ }).transform(response);
653
+ }
654
+ return response;
655
+ } catch (err) {
656
+ logError(err);
657
+ return new InternalServerErrorResponse(err);
658
+ }
659
+ }
660
+ async function notFound() {
661
+ if (caches) {
662
+ const assetPreservationCache = await caches.open(
663
+ ASSET_PRESERVATION_CACHE
664
+ );
665
+ const preservedResponse = await assetPreservationCache.match(request.url);
666
+ if (preservedResponse) {
667
+ return preservedResponse;
668
+ }
669
+ }
670
+ let cwd = pathname;
671
+ while (cwd) {
672
+ cwd = cwd.slice(0, cwd.lastIndexOf("/"));
673
+ if (assetEntry = await findAssetEntryForPath(`${cwd}/404.html`)) {
674
+ let content;
675
+ try {
676
+ content = negotiateContent(request, assetEntry);
677
+ } catch (err) {
678
+ return new NotAcceptableResponse();
679
+ }
680
+ const assetKey = getAssetKey(assetEntry, content);
681
+ try {
682
+ const { body, contentType } = await fetchAsset(assetKey);
683
+ const response = new NotFoundResponse(body);
684
+ response.headers.set("content-type", contentType);
685
+ return response;
686
+ } catch (err) {
687
+ logError(err);
688
+ return new InternalServerErrorResponse(err);
689
+ }
690
+ }
691
+ }
692
+ return await generateNotFoundResponse(
693
+ request,
694
+ findAssetEntryForPath,
695
+ serveAsset
696
+ );
697
+ }
698
+ }
699
+ function parseQualityWeightedList(list = "") {
700
+ const items = {};
701
+ list.replace(/\s/g, "").split(",").forEach((el) => {
702
+ const [item, weight] = el.split(";q=");
703
+ items[item] = weight ? parseFloat(weight) : 1;
704
+ });
705
+ return items;
706
+ }
707
+ function isCacheable(request) {
708
+ return !request.headers.has("authorization") && !request.headers.has("range");
709
+ }
710
+ function hasFileExtension(path) {
711
+ return /\/.+\.[a-z0-9]+$/i.test(path);
712
+ }
713
+ function isPreview(url) {
714
+ if (url.hostname.endsWith(".pages.dev")) {
715
+ return url.hostname.split(".").length > 3 ? true : false;
716
+ }
717
+ return false;
718
+ }
719
+ var ASSET_PRESERVATION_CACHE, CACHE_CONTROL_PRESERVATION, CACHE_CONTROL_BROWSER, REDIRECTS_VERSION2, HEADERS_VERSION2, HEADERS_VERSION_V1, ANALYTICS_VERSION2;
720
+ var init_handler = __esm({
721
+ "../pages-shared/src/asset-server/handler.ts"() {
722
+ init_responses();
723
+ init_rulesEngine();
724
+ ASSET_PRESERVATION_CACHE = "assetPreservationCache";
725
+ CACHE_CONTROL_PRESERVATION = "public, s-maxage=604800";
726
+ CACHE_CONTROL_BROWSER = "public, max-age=0, must-revalidate";
727
+ REDIRECTS_VERSION2 = 1;
728
+ HEADERS_VERSION2 = 2;
729
+ HEADERS_VERSION_V1 = 1;
730
+ ANALYTICS_VERSION2 = 1;
731
+ }
732
+ });
733
+
107
734
  // src/miniflare-cli/index.ts
108
735
  import { fetch } from "@miniflare/core";
109
736
  import {
@@ -114,8 +741,8 @@ import {
114
741
  Log,
115
742
  LogLevel,
116
743
  Miniflare,
117
- Response as MiniflareResponse,
118
- Request as MiniflareRequest
744
+ Response as MiniflareResponse3,
745
+ Request as MiniflareRequest3
119
746
  } from "miniflare";
120
747
 
121
748
  // ../../node_modules/yargs/lib/platform-shims/esm.mjs
@@ -4934,412 +5561,552 @@ var FatalError = class extends Error {
4934
5561
  };
4935
5562
 
4936
5563
  // src/miniflare-cli/assets.ts
4937
- var import_mime = __toESM(require_mime());
4938
- import { existsSync, lstatSync, readFileSync as readFileSync4 } from "node:fs";
5564
+ import { existsSync, lstatSync, readFileSync as readFileSync5 } from "node:fs";
4939
5565
  import { join } from "node:path";
4940
- import { fetch as miniflareFetch } from "@miniflare/core";
4941
- import { watch } from "chokidar";
4942
- import { Response } from "miniflare";
4943
- async function generateASSETSBinding(options) {
4944
- const assetsFetch = options.directory !== void 0 ? await generateAssetsFetch(options.directory, options.log) : invalidAssetsFetch;
4945
- return async function(request) {
4946
- if (options.proxyPort) {
4947
- try {
4948
- const url = new URL(request.url);
4949
- url.host = `localhost:${options.proxyPort}`;
4950
- return await miniflareFetch(url, request);
4951
- } catch (thrown) {
4952
- options.log.error(new Error(`Could not proxy request: ${thrown}`));
4953
- return new Response(`[wrangler] Could not proxy request: ${thrown}`, {
4954
- status: 502
4955
- });
4956
- }
4957
- } else {
4958
- try {
4959
- return await assetsFetch(request);
4960
- } catch (thrown) {
4961
- options.log.error(new Error(`Could not serve static asset: ${thrown}`));
4962
- return new Response(
4963
- `[wrangler] Could not serve static asset: ${thrown}`,
4964
- { status: 502 }
5566
+
5567
+ // ../pages-shared/src/metadata-generator/constants.ts
5568
+ var REDIRECTS_VERSION = 1;
5569
+ var HEADERS_VERSION = 2;
5570
+ var ANALYTICS_VERSION = 1;
5571
+ var PERMITTED_STATUS_CODES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
5572
+ var HEADER_SEPARATOR = ":";
5573
+ var MAX_LINE_LENGTH = 2e3;
5574
+ var MAX_HEADER_RULES = 100;
5575
+ var MAX_DYNAMIC_REDIRECT_RULES = 100;
5576
+ var MAX_STATIC_REDIRECT_RULES = 2e3;
5577
+ var UNSET_OPERATOR = "! ";
5578
+ var SPLAT_REGEX = /\*/g;
5579
+ var PLACEHOLDER_REGEX = /:\w+/g;
5580
+
5581
+ // ../pages-shared/src/metadata-generator/createMetadataObject.ts
5582
+ function createMetadataObject({
5583
+ redirects,
5584
+ headers,
5585
+ webAnalyticsToken,
5586
+ deploymentId,
5587
+ logger = (_message) => {
5588
+ }
5589
+ }) {
5590
+ return {
5591
+ ...constructRedirects({ redirects, logger }),
5592
+ ...constructHeaders({ headers, logger }),
5593
+ ...constructWebAnalytics({ webAnalyticsToken, logger }),
5594
+ deploymentId
5595
+ };
5596
+ }
5597
+ function constructRedirects({
5598
+ redirects,
5599
+ logger
5600
+ }) {
5601
+ if (!redirects)
5602
+ return {};
5603
+ const num_valid = redirects.rules.length;
5604
+ const num_invalid = redirects.invalid.length;
5605
+ logger(
5606
+ `Parsed ${num_valid} valid redirect rule${num_valid === 1 ? "" : "s"}.`
5607
+ );
5608
+ if (num_invalid > 0) {
5609
+ logger(`Found invalid redirect lines:`);
5610
+ for (const { line, lineNumber, message } of redirects.invalid) {
5611
+ if (line)
5612
+ logger(` - ${lineNumber ? `#${lineNumber}: ` : ""}${line}`);
5613
+ logger(` ${message}`);
5614
+ }
5615
+ }
5616
+ if (num_valid === 0) {
5617
+ return {};
5618
+ }
5619
+ const staticRedirects = {};
5620
+ const dynamicRedirects = {};
5621
+ let canCreateStaticRule = true;
5622
+ for (const rule of redirects.rules) {
5623
+ if (!rule.from.match(SPLAT_REGEX) && !rule.from.match(PLACEHOLDER_REGEX)) {
5624
+ if (canCreateStaticRule) {
5625
+ staticRedirects[rule.from] = { status: rule.status, to: rule.to };
5626
+ continue;
5627
+ } else {
5628
+ logger(
5629
+ `Info: the redirect rule ${rule.from} \u2192 ${rule.status} ${rule.to} could be made more performant by bringing it above any lines with splats or placeholders.`
4965
5630
  );
4966
5631
  }
4967
5632
  }
5633
+ dynamicRedirects[rule.from] = { status: rule.status, to: rule.to };
5634
+ canCreateStaticRule = false;
5635
+ }
5636
+ return {
5637
+ redirects: {
5638
+ version: REDIRECTS_VERSION,
5639
+ staticRules: staticRedirects,
5640
+ rules: dynamicRedirects
5641
+ }
4968
5642
  };
4969
5643
  }
4970
- function escapeRegex(str) {
4971
- return str.replace(/[-/\\^$*+?.()|[]{}]/g, "\\$&");
4972
- }
4973
- function replacer(str, replacements) {
4974
- for (const [replacement, value] of Object.entries(replacements)) {
4975
- str = str.replace(`:${replacement}`, value);
5644
+ function constructHeaders({
5645
+ headers,
5646
+ logger
5647
+ }) {
5648
+ if (!headers)
5649
+ return {};
5650
+ const num_valid = headers.rules.length;
5651
+ const num_invalid = headers.invalid.length;
5652
+ logger(`Parsed ${num_valid} valid header rule${num_valid === 1 ? "" : "s"}.`);
5653
+ if (num_invalid > 0) {
5654
+ logger(`Found invalid header lines:`);
5655
+ for (const { line, lineNumber, message } of headers.invalid) {
5656
+ if (line)
5657
+ logger(` - ${lineNumber ? `#${lineNumber}: ` : ""} ${line}`);
5658
+ logger(` ${message}`);
5659
+ }
4976
5660
  }
4977
- return str;
4978
- }
4979
- function generateRulesMatcher(rules, replacerFn = (match) => match) {
4980
- if (!rules)
4981
- return () => [];
4982
- const compiledRules = Object.entries(rules).map(([rule, match]) => {
4983
- const crossHost = rule.startsWith("https://");
4984
- rule = rule.split("*").map(escapeRegex).join("(?<splat>.*)");
4985
- const host_matches = rule.matchAll(
4986
- /(?<=^https:\\\/\\\/[^/]*?):([^\\]+)(?=\\)/g
4987
- );
4988
- for (const hostMatch of host_matches) {
4989
- rule = rule.split(hostMatch[0]).join(`(?<${hostMatch[1]}>[^/.]+)`);
5661
+ if (num_valid === 0) {
5662
+ return {};
5663
+ }
5664
+ const rules = {};
5665
+ for (const rule of headers.rules) {
5666
+ rules[rule.path] = {};
5667
+ if (Object.keys(rule.headers).length) {
5668
+ rules[rule.path].set = rule.headers;
4990
5669
  }
4991
- const path_matches = rule.matchAll(/:(\w+)/g);
4992
- for (const pathMatch of path_matches) {
4993
- rule = rule.split(pathMatch[0]).join(`(?<${pathMatch[1]}>[^/]+)`);
5670
+ if (rule.unsetHeaders.length) {
5671
+ rules[rule.path].unset = rule.unsetHeaders;
4994
5672
  }
4995
- rule = "^" + rule + "$";
4996
- try {
4997
- const regExp = new RegExp(rule);
4998
- return [{ crossHost, regExp }, match];
4999
- } catch {
5000
- }
5001
- }).filter((value) => value !== void 0);
5002
- return ({ request }) => {
5003
- const { pathname, host } = new URL(request.url);
5004
- return compiledRules.map(([{ crossHost, regExp }, match]) => {
5005
- const test = crossHost ? `https://${host}${pathname}` : pathname;
5006
- const result = regExp.exec(test);
5007
- if (result) {
5008
- return replacerFn(match, result.groups || {});
5009
- }
5010
- }).filter((value) => value !== void 0);
5011
- };
5012
- }
5013
- function generateHeadersMatcher(headersFile) {
5014
- if (existsSync(headersFile)) {
5015
- const contents = readFileSync4(headersFile).toString();
5016
- const lines = contents.split("\n").map((line) => line.trim()).filter((line) => !line.startsWith("#") && line !== "");
5017
- const rules = {};
5018
- let rule = void 0;
5019
- for (const line of lines) {
5020
- if (/^([^\s]+:\/\/|^\/)/.test(line)) {
5021
- if (rule && Object.keys(rule.headers).length > 0) {
5022
- rules[rule.path] = rule.headers;
5023
- }
5024
- const path = validateURL(line);
5025
- if (path) {
5026
- rule = {
5027
- path,
5028
- headers: {}
5029
- };
5030
- continue;
5031
- }
5032
- }
5033
- if (!line.includes(":"))
5034
- continue;
5035
- const [rawName, ...rawValue] = line.split(":");
5036
- const name = rawName.trim().toLowerCase();
5037
- const value = rawValue.join(":").trim();
5038
- if (name === "")
5039
- continue;
5040
- if (!rule)
5041
- continue;
5042
- const existingValues = rule.headers[name];
5043
- rule.headers[name] = existingValues ? `${existingValues}, ${value}` : value;
5044
- }
5045
- if (rule && Object.keys(rule.headers).length > 0) {
5046
- rules[rule.path] = rule.headers;
5047
- }
5048
- const rulesMatcher = generateRulesMatcher(
5049
- rules,
5050
- (match, replacements) => Object.fromEntries(
5051
- Object.entries(match).map(([name, value]) => [
5052
- name,
5053
- replacer(value, replacements)
5054
- ])
5055
- )
5056
- );
5057
- return (request) => {
5058
- const matches = rulesMatcher({
5059
- request
5060
- });
5061
- if (matches)
5062
- return matches;
5063
- };
5064
- } else {
5065
- return () => void 0;
5066
5673
  }
5674
+ return {
5675
+ headers: {
5676
+ version: HEADERS_VERSION,
5677
+ rules
5678
+ }
5679
+ };
5067
5680
  }
5068
- function generateRedirectsMatcher(redirectsFile) {
5069
- if (existsSync(redirectsFile)) {
5070
- const contents = readFileSync4(redirectsFile).toString();
5071
- const lines = contents.split("\n").map((line) => line.trim()).filter((line) => !line.startsWith("#") && line !== "");
5072
- const rules = Object.fromEntries(
5073
- lines.map((line) => line.split(" ")).filter((tokens) => tokens.length === 2 || tokens.length === 3).map((tokens) => {
5074
- const from = validateURL(tokens[0], true, false, false);
5075
- const to = validateURL(tokens[1], false, true, true);
5076
- let status = parseInt(tokens[2]) || 302;
5077
- status = [301, 302, 303, 307, 308].includes(status) ? status : void 0;
5078
- return from && to && status ? [from, { to, status }] : void 0;
5079
- }).filter((rule) => rule !== void 0)
5080
- );
5081
- const rulesMatcher = generateRulesMatcher(
5082
- rules,
5083
- ({ status, to }, replacements) => ({
5084
- status,
5085
- to: replacer(to, replacements)
5086
- })
5087
- );
5088
- return (request) => {
5089
- const match = rulesMatcher({
5090
- request
5091
- })[0];
5092
- if (match)
5093
- return match;
5094
- };
5095
- } else {
5096
- return () => void 0;
5097
- }
5681
+ function constructWebAnalytics({
5682
+ webAnalyticsToken
5683
+ }) {
5684
+ if (!webAnalyticsToken)
5685
+ return {};
5686
+ return {
5687
+ analytics: {
5688
+ version: ANALYTICS_VERSION,
5689
+ token: webAnalyticsToken
5690
+ }
5691
+ };
5098
5692
  }
5099
- function extractPathname(path = "/", includeSearch, includeHash) {
5693
+
5694
+ // ../pages-shared/src/metadata-generator/validateURL.ts
5695
+ var extractPathname = (path = "/", includeSearch, includeHash) => {
5100
5696
  if (!path.startsWith("/"))
5101
5697
  path = `/${path}`;
5102
5698
  const url = new URL(`//${path}`, "relative://");
5103
5699
  return `${url.pathname}${includeSearch ? url.search : ""}${includeHash ? url.hash : ""}`;
5104
- }
5105
- function validateURL(token, onlyRelative = false, includeSearch = false, includeHash = false) {
5106
- const host = /^https:\/\/+(?<host>[^/]+)\/?(?<path>.*)/.exec(token);
5700
+ };
5701
+ var URL_REGEX = /^https:\/\/+(?<host>[^/]+)\/?(?<path>.*)/;
5702
+ var PATH_REGEX = /^\//;
5703
+ var validateUrl = (token, onlyRelative = false, includeSearch = false, includeHash = false) => {
5704
+ const host = URL_REGEX.exec(token);
5107
5705
  if (host && host.groups && host.groups.host) {
5108
5706
  if (onlyRelative)
5109
- return;
5110
- return `https://${host.groups.host}${extractPathname(
5111
- host.groups.path,
5112
- includeSearch,
5113
- includeHash
5114
- )}`;
5707
+ return [
5708
+ void 0,
5709
+ `Only relative URLs are allowed. Skipping absolute URL ${token}.`
5710
+ ];
5711
+ return [
5712
+ `https://${host.groups.host}${extractPathname(
5713
+ host.groups.path,
5714
+ includeSearch,
5715
+ includeHash
5716
+ )}`,
5717
+ void 0
5718
+ ];
5115
5719
  } else {
5116
5720
  if (!token.startsWith("/") && onlyRelative)
5117
5721
  token = `/${token}`;
5118
- const path = /^\//.exec(token);
5722
+ const path = PATH_REGEX.exec(token);
5119
5723
  if (path) {
5120
5724
  try {
5121
- return extractPathname(token, includeSearch, includeHash);
5725
+ return [extractPathname(token, includeSearch, includeHash), void 0];
5122
5726
  } catch {
5727
+ return [void 0, `Error parsing URL segment ${token}. Skipping.`];
5123
5728
  }
5124
5729
  }
5125
5730
  }
5126
- return "";
5127
- }
5128
- function hasFileExtension(pathname) {
5129
- return /\/.+\.[a-z0-9]+$/i.test(pathname);
5130
- }
5131
- async function generateAssetsFetch(directory, log) {
5132
- const { Headers, Request } = await import("@miniflare/core");
5133
- const headersFile = join(directory, "_headers");
5134
- const redirectsFile = join(directory, "_redirects");
5135
- const workerFile = join(directory, "_worker.js");
5136
- const ignoredFiles = [headersFile, redirectsFile, workerFile];
5137
- const assetExists = (path) => {
5138
- path = join(directory, path);
5139
- return existsSync(path) && lstatSync(path).isFile() && !ignoredFiles.includes(path);
5140
- };
5141
- const getAsset = (path) => {
5142
- if (assetExists(path)) {
5143
- return join(directory, path);
5731
+ return [
5732
+ void 0,
5733
+ onlyRelative ? "URLs should begin with a forward-slash." : 'URLs should either be relative (e.g. begin with a forward-slash), or use HTTPS (e.g. begin with "https://").'
5734
+ ];
5735
+ };
5736
+
5737
+ // ../pages-shared/src/metadata-generator/parseHeaders.ts
5738
+ var LINE_IS_PROBABLY_A_PATH = new RegExp(/^([^\s]+:\/\/|^\/)/);
5739
+ function parseHeaders(input) {
5740
+ const lines = input.split("\n");
5741
+ const rules = [];
5742
+ const invalid = [];
5743
+ let rule = void 0;
5744
+ for (let i = 0; i < lines.length; i++) {
5745
+ const line = lines[i].trim();
5746
+ if (line.length === 0 || line.startsWith("#"))
5747
+ continue;
5748
+ if (line.length > MAX_LINE_LENGTH) {
5749
+ invalid.push({
5750
+ message: `Ignoring line ${i + 1} as it exceeds the maximum allowed length of ${MAX_LINE_LENGTH}.`
5751
+ });
5752
+ continue;
5144
5753
  }
5145
- };
5146
- let redirectsMatcher = generateRedirectsMatcher(redirectsFile);
5147
- let headersMatcher = generateHeadersMatcher(headersFile);
5148
- watch([headersFile, redirectsFile], {
5149
- persistent: true
5150
- }).on("change", (path) => {
5151
- switch (path) {
5152
- case headersFile: {
5153
- log.log("_headers modified. Re-evaluating...");
5154
- headersMatcher = generateHeadersMatcher(headersFile);
5754
+ if (LINE_IS_PROBABLY_A_PATH.test(line)) {
5755
+ if (rules.length >= MAX_HEADER_RULES) {
5756
+ invalid.push({
5757
+ message: `Maximum number of rules supported is ${MAX_HEADER_RULES}. Skipping remaining ${lines.length - i} lines of file.`
5758
+ });
5155
5759
  break;
5156
5760
  }
5157
- case redirectsFile: {
5158
- log.log("_redirects modified. Re-evaluating...");
5159
- redirectsMatcher = generateRedirectsMatcher(redirectsFile);
5160
- break;
5761
+ if (rule) {
5762
+ if (isValidRule(rule)) {
5763
+ rules.push({
5764
+ path: rule.path,
5765
+ headers: rule.headers,
5766
+ unsetHeaders: rule.unsetHeaders
5767
+ });
5768
+ } else {
5769
+ invalid.push({
5770
+ line: rule.line,
5771
+ lineNumber: i + 1,
5772
+ message: "No headers specified"
5773
+ });
5774
+ }
5775
+ }
5776
+ const [path, pathError] = validateUrl(line);
5777
+ if (pathError) {
5778
+ invalid.push({
5779
+ line,
5780
+ lineNumber: i + 1,
5781
+ message: pathError
5782
+ });
5783
+ rule = void 0;
5784
+ continue;
5161
5785
  }
5786
+ rule = {
5787
+ path,
5788
+ line,
5789
+ headers: {},
5790
+ unsetHeaders: []
5791
+ };
5792
+ continue;
5162
5793
  }
5163
- });
5164
- const serveAsset = (file) => {
5165
- return readFileSync4(file);
5794
+ if (!line.includes(HEADER_SEPARATOR)) {
5795
+ if (!rule) {
5796
+ invalid.push({
5797
+ line,
5798
+ lineNumber: i + 1,
5799
+ message: "Expected a path beginning with at least one forward-slash"
5800
+ });
5801
+ } else {
5802
+ if (line.trim().startsWith(UNSET_OPERATOR)) {
5803
+ rule.unsetHeaders.push(line.trim().replace(UNSET_OPERATOR, ""));
5804
+ } else {
5805
+ invalid.push({
5806
+ line,
5807
+ lineNumber: i + 1,
5808
+ message: "Expected a colon-separated header pair (e.g. name: value)"
5809
+ });
5810
+ }
5811
+ }
5812
+ continue;
5813
+ }
5814
+ const [rawName, ...rawValue] = line.split(HEADER_SEPARATOR);
5815
+ const name = rawName.trim().toLowerCase();
5816
+ if (name.includes(" ")) {
5817
+ invalid.push({
5818
+ line,
5819
+ lineNumber: i + 1,
5820
+ message: "Header name cannot include spaces"
5821
+ });
5822
+ continue;
5823
+ }
5824
+ const value = rawValue.join(HEADER_SEPARATOR).trim();
5825
+ if (name === "") {
5826
+ invalid.push({
5827
+ line,
5828
+ lineNumber: i + 1,
5829
+ message: "No header name specified"
5830
+ });
5831
+ continue;
5832
+ }
5833
+ if (value === "") {
5834
+ invalid.push({
5835
+ line,
5836
+ lineNumber: i + 1,
5837
+ message: "No header value specified"
5838
+ });
5839
+ continue;
5840
+ }
5841
+ if (!rule) {
5842
+ invalid.push({
5843
+ line,
5844
+ lineNumber: i + 1,
5845
+ message: `Path should come before header (${name}: ${value})`
5846
+ });
5847
+ continue;
5848
+ }
5849
+ const existingValues = rule.headers[name];
5850
+ rule.headers[name] = existingValues ? `${existingValues}, ${value}` : value;
5851
+ }
5852
+ if (rule) {
5853
+ if (isValidRule(rule)) {
5854
+ rules.push({
5855
+ path: rule.path,
5856
+ headers: rule.headers,
5857
+ unsetHeaders: rule.unsetHeaders
5858
+ });
5859
+ } else {
5860
+ invalid.push({ line: rule.line, message: "No headers specified" });
5861
+ }
5862
+ }
5863
+ return {
5864
+ rules,
5865
+ invalid
5166
5866
  };
5167
- const generateResponse = (request) => {
5168
- const url = new URL(request.url);
5169
- let assetName = url.pathname;
5170
- try {
5171
- assetName = decodeURIComponent(url.pathname);
5172
- } catch {
5867
+ }
5868
+ function isValidRule(rule) {
5869
+ return Object.keys(rule.headers).length > 0 || rule.unsetHeaders.length > 0;
5870
+ }
5871
+
5872
+ // ../pages-shared/src/metadata-generator/parseRedirects.ts
5873
+ function parseRedirects(input) {
5874
+ const lines = input.split("\n");
5875
+ const rules = [];
5876
+ const seen_paths = /* @__PURE__ */ new Set();
5877
+ const invalid = [];
5878
+ let staticRules = 0;
5879
+ let dynamicRules = 0;
5880
+ let canCreateStaticRule = true;
5881
+ for (let i = 0; i < lines.length; i++) {
5882
+ const line = lines[i].trim();
5883
+ if (line.length === 0 || line.startsWith("#"))
5884
+ continue;
5885
+ if (line.length > MAX_LINE_LENGTH) {
5886
+ invalid.push({
5887
+ message: `Ignoring line ${i + 1} as it exceeds the maximum allowed length of ${MAX_LINE_LENGTH}.`
5888
+ });
5889
+ continue;
5173
5890
  }
5174
- const deconstructedResponse = {
5175
- status: 200,
5176
- headers: new Headers(),
5177
- body: void 0
5178
- };
5179
- const match = redirectsMatcher(request);
5180
- if (match) {
5181
- const { status, to } = match;
5182
- let location = to;
5183
- let search;
5184
- if (to.startsWith("/")) {
5185
- search = new URL(location, "http://fakehost").search;
5186
- } else {
5187
- search = new URL(location).search;
5891
+ const tokens = line.split(/\s+/);
5892
+ if (tokens.length < 2 || tokens.length > 3) {
5893
+ invalid.push({
5894
+ line,
5895
+ lineNumber: i + 1,
5896
+ message: `Expected exactly 2 or 3 whitespace-separated tokens. Got ${tokens.length}.`
5897
+ });
5898
+ continue;
5899
+ }
5900
+ const [str_from, str_to, str_status = "302"] = tokens;
5901
+ const fromResult = validateUrl(str_from, true, false, false);
5902
+ if (fromResult[0] === void 0) {
5903
+ invalid.push({
5904
+ line,
5905
+ lineNumber: i + 1,
5906
+ message: fromResult[1]
5907
+ });
5908
+ continue;
5909
+ }
5910
+ const from = fromResult[0];
5911
+ if (canCreateStaticRule && !from.match(SPLAT_REGEX) && !from.match(PLACEHOLDER_REGEX)) {
5912
+ staticRules += 1;
5913
+ if (staticRules > MAX_STATIC_REDIRECT_RULES) {
5914
+ invalid.push({
5915
+ message: `Maximum number of static rules supported is ${MAX_STATIC_REDIRECT_RULES}. Skipping line.`
5916
+ });
5917
+ continue;
5188
5918
  }
5189
- location = `${location}${search ? "" : url.search}`;
5190
- if (status && [301, 302, 303, 307, 308].includes(status)) {
5191
- deconstructedResponse.status = status;
5192
- } else {
5193
- deconstructedResponse.status = 302;
5194
- }
5195
- deconstructedResponse.headers.set("Location", location);
5196
- return deconstructedResponse;
5197
- }
5198
- if (!request.method?.match(/^(get|head)$/i)) {
5199
- deconstructedResponse.status = 405;
5200
- return deconstructedResponse;
5201
- }
5202
- const notFound = () => {
5203
- let cwd = assetName;
5204
- while (cwd) {
5205
- cwd = cwd.slice(0, cwd.lastIndexOf("/"));
5206
- if (asset = getAsset(`${cwd}/404.html`)) {
5207
- deconstructedResponse.status = 404;
5208
- deconstructedResponse.body = serveAsset(asset);
5209
- deconstructedResponse.headers.set(
5210
- "Content-Type",
5211
- (0, import_mime.getType)(asset) || "application/octet-stream"
5212
- );
5213
- return deconstructedResponse;
5214
- }
5919
+ } else {
5920
+ dynamicRules += 1;
5921
+ canCreateStaticRule = false;
5922
+ if (dynamicRules > MAX_DYNAMIC_REDIRECT_RULES) {
5923
+ invalid.push({
5924
+ message: `Maximum number of dynamic rules supported is ${MAX_DYNAMIC_REDIRECT_RULES}. Skipping remaining ${lines.length - i} lines of file.`
5925
+ });
5926
+ break;
5215
5927
  }
5216
- if (asset = getAsset(`/index.html`)) {
5217
- deconstructedResponse.body = serveAsset(asset);
5218
- deconstructedResponse.headers.set(
5219
- "Content-Type",
5220
- (0, import_mime.getType)(asset) || "application/octet-stream"
5928
+ }
5929
+ const toResult = validateUrl(str_to, false, true, true);
5930
+ if (toResult[0] === void 0) {
5931
+ invalid.push({
5932
+ line,
5933
+ lineNumber: i + 1,
5934
+ message: toResult[1]
5935
+ });
5936
+ continue;
5937
+ }
5938
+ const to = toResult[0];
5939
+ const status = Number(str_status);
5940
+ if (isNaN(status) || !PERMITTED_STATUS_CODES.has(status)) {
5941
+ invalid.push({
5942
+ line,
5943
+ lineNumber: i + 1,
5944
+ message: `Valid status codes are 301, 302 (default), 303, 307, or 308. Got ${str_status}.`
5945
+ });
5946
+ continue;
5947
+ }
5948
+ if (seen_paths.has(from)) {
5949
+ invalid.push({
5950
+ line,
5951
+ lineNumber: i + 1,
5952
+ message: `Ignoring duplicate rule for path ${from}.`
5953
+ });
5954
+ continue;
5955
+ }
5956
+ seen_paths.add(from);
5957
+ rules.push({ from, to, status, lineNumber: i + 1 });
5958
+ }
5959
+ return {
5960
+ rules,
5961
+ invalid
5962
+ };
5963
+ }
5964
+
5965
+ // src/miniflare-cli/assets.ts
5966
+ var import_mime = __toESM(require_mime());
5967
+ import { fetch as miniflareFetch2 } from "@miniflare/core";
5968
+ import {
5969
+ Response as MiniflareResponse2,
5970
+ Request as MiniflareRequest2
5971
+ } from "@miniflare/core";
5972
+ import { watch } from "chokidar";
5973
+
5974
+ // src/pages/hash.tsx
5975
+ import { readFileSync as readFileSync4 } from "node:fs";
5976
+ import { extname as extname2 } from "node:path";
5977
+ import { hash as blake3hash } from "blake3-wasm";
5978
+ var hashFile = (filepath) => {
5979
+ const contents = readFileSync4(filepath);
5980
+ const base64Contents = contents.toString("base64");
5981
+ const extension = extname2(filepath).substring(1);
5982
+ return blake3hash(base64Contents + extension).toString("hex").slice(0, 32);
5983
+ };
5984
+
5985
+ // src/miniflare-cli/assets.ts
5986
+ async function generateASSETSBinding(options) {
5987
+ const assetsFetch = options.directory !== void 0 ? await generateAssetsFetch(options.directory, options.log) : invalidAssetsFetch;
5988
+ return async function(miniflareRequest) {
5989
+ if (options.proxyPort) {
5990
+ try {
5991
+ const url = new URL(miniflareRequest.url);
5992
+ url.host = `localhost:${options.proxyPort}`;
5993
+ return await miniflareFetch2(url, miniflareRequest);
5994
+ } catch (thrown) {
5995
+ options.log.error(new Error(`Could not proxy request: ${thrown}`));
5996
+ return new MiniflareResponse2(
5997
+ `[wrangler] Could not proxy request: ${thrown}`,
5998
+ {
5999
+ status: 502
6000
+ }
5221
6001
  );
5222
- return deconstructedResponse;
5223
6002
  }
5224
- deconstructedResponse.status = 404;
5225
- return deconstructedResponse;
5226
- };
5227
- let asset;
5228
- if (assetName.endsWith("/")) {
5229
- if (asset = getAsset(`${assetName}/index.html`)) {
5230
- deconstructedResponse.body = serveAsset(asset);
5231
- deconstructedResponse.headers.set(
5232
- "Content-Type",
5233
- (0, import_mime.getType)(asset) || "application/octet-stream"
5234
- );
5235
- return deconstructedResponse;
5236
- } else if (asset = getAsset(`${assetName.replace(/\/$/, ".html")}`)) {
5237
- deconstructedResponse.status = 301;
5238
- deconstructedResponse.headers.set(
5239
- "Location",
5240
- `${assetName.slice(0, -1)}${url.search}`
6003
+ } else {
6004
+ try {
6005
+ return await assetsFetch(miniflareRequest);
6006
+ } catch (thrown) {
6007
+ options.log.error(new Error(`Could not serve static asset: ${thrown}`));
6008
+ return new MiniflareResponse2(
6009
+ `[wrangler] Could not serve static asset: ${thrown}`,
6010
+ { status: 502 }
5241
6011
  );
5242
- return deconstructedResponse;
5243
6012
  }
5244
6013
  }
5245
- if (assetName.endsWith("/index")) {
5246
- deconstructedResponse.status = 301;
5247
- deconstructedResponse.headers.set(
5248
- "Location",
5249
- `${assetName.slice(0, -"index".length)}${url.search}`
5250
- );
5251
- return deconstructedResponse;
5252
- }
5253
- if (asset = getAsset(assetName)) {
5254
- if (assetName.endsWith(".html")) {
5255
- const extensionlessPath = assetName.slice(0, -".html".length);
5256
- if (getAsset(extensionlessPath) || extensionlessPath === "/") {
5257
- deconstructedResponse.body = serveAsset(asset);
5258
- deconstructedResponse.headers.set(
5259
- "Content-Type",
5260
- (0, import_mime.getType)(asset) || "application/octet-stream"
5261
- );
5262
- return deconstructedResponse;
6014
+ };
6015
+ }
6016
+ async function generateAssetsFetch(directory, log) {
6017
+ await Promise.resolve().then(() => (init_miniflare(), miniflare_exports));
6018
+ const { generateHandler: generateHandler2, parseQualityWeightedList: parseQualityWeightedList2 } = await Promise.resolve().then(() => (init_handler(), handler_exports));
6019
+ const headersFile = join(directory, "_headers");
6020
+ const redirectsFile = join(directory, "_redirects");
6021
+ const workerFile = join(directory, "_worker.js");
6022
+ const ignoredFiles = [headersFile, redirectsFile, workerFile];
6023
+ let redirects;
6024
+ if (existsSync(redirectsFile)) {
6025
+ const contents = readFileSync5(redirectsFile, "utf-8");
6026
+ redirects = parseRedirects(contents);
6027
+ }
6028
+ let headers;
6029
+ if (existsSync(headersFile)) {
6030
+ const contents = readFileSync5(headersFile, "utf-8");
6031
+ headers = parseHeaders(contents);
6032
+ }
6033
+ let metadata = createMetadataObject({
6034
+ redirects,
6035
+ headers,
6036
+ logger: log.warn
6037
+ });
6038
+ watch([headersFile, redirectsFile], { persistent: true }).on(
6039
+ "change",
6040
+ (path) => {
6041
+ switch (path) {
6042
+ case headersFile: {
6043
+ log.log("_headers modified. Re-evaluating...");
6044
+ const contents = readFileSync5(headersFile).toString();
6045
+ headers = parseHeaders(contents);
6046
+ break;
6047
+ }
6048
+ case redirectsFile: {
6049
+ log.log("_redirects modified. Re-evaluating...");
6050
+ const contents = readFileSync5(redirectsFile).toString();
6051
+ redirects = parseRedirects(contents);
6052
+ break;
6053
+ }
6054
+ }
6055
+ metadata = createMetadataObject({
6056
+ redirects,
6057
+ headers,
6058
+ logger: log.warn
6059
+ });
6060
+ }
6061
+ );
6062
+ const generateResponse = async (request) => {
6063
+ const assetKeyEntryMap = /* @__PURE__ */ new Map();
6064
+ return await generateHandler2({
6065
+ request,
6066
+ metadata,
6067
+ xServerEnvHeader: "dev",
6068
+ logError: console.error,
6069
+ findAssetEntryForPath: async (path) => {
6070
+ const filepath = join(directory, path);
6071
+ if (existsSync(filepath) && lstatSync(filepath).isFile() && !ignoredFiles.includes(filepath)) {
6072
+ const hash = hashFile(filepath);
6073
+ assetKeyEntryMap.set(hash, filepath);
6074
+ return hash;
6075
+ }
6076
+ return null;
6077
+ },
6078
+ getAssetKey: (assetEntry) => {
6079
+ return assetEntry;
6080
+ },
6081
+ negotiateContent: (contentRequest) => {
6082
+ let rawAcceptEncoding;
6083
+ if (contentRequest.cf && "clientAcceptEncoding" in contentRequest.cf && contentRequest.cf.clientAcceptEncoding) {
6084
+ rawAcceptEncoding = contentRequest.cf.clientAcceptEncoding;
5263
6085
  } else {
5264
- deconstructedResponse.status = 301;
5265
- deconstructedResponse.headers.set(
5266
- "Location",
5267
- `${extensionlessPath}${url.search}`
6086
+ rawAcceptEncoding = contentRequest.headers.get("Accept-Encoding") || void 0;
6087
+ }
6088
+ const acceptEncoding = parseQualityWeightedList2(rawAcceptEncoding);
6089
+ if (acceptEncoding["identity"] === 0 || acceptEncoding["*"] === 0 && acceptEncoding["identity"] === void 0) {
6090
+ throw new Error("No acceptable encodings available");
6091
+ }
6092
+ return { encoding: null };
6093
+ },
6094
+ fetchAsset: async (assetKey) => {
6095
+ const filepath = assetKeyEntryMap.get(assetKey);
6096
+ if (!filepath) {
6097
+ throw new Error(
6098
+ "Could not fetch asset. Please file an issue on GitHub (https://github.com/cloudflare/wrangler2/issues/new/choose) with reproduction steps."
5268
6099
  );
5269
- return deconstructedResponse;
5270
6100
  }
5271
- } else {
5272
- deconstructedResponse.body = serveAsset(asset);
5273
- deconstructedResponse.headers.set(
5274
- "Content-Type",
5275
- (0, import_mime.getType)(asset) || "application/octet-stream"
5276
- );
5277
- return deconstructedResponse;
5278
- }
5279
- } else if (hasFileExtension(assetName)) {
5280
- if (asset = getAsset(assetName + ".html")) {
5281
- deconstructedResponse.body = serveAsset(asset);
5282
- deconstructedResponse.headers.set(
5283
- "Content-Type",
5284
- (0, import_mime.getType)(asset) || "application/octet-stream"
5285
- );
5286
- return deconstructedResponse;
6101
+ const body = readFileSync5(filepath);
6102
+ const contentType = (0, import_mime.getType)(filepath) || "application/octet-stream";
6103
+ return { body, contentType };
5287
6104
  }
5288
- notFound();
5289
- return deconstructedResponse;
5290
- }
5291
- if (asset = getAsset(`${assetName}.html`)) {
5292
- deconstructedResponse.body = serveAsset(asset);
5293
- deconstructedResponse.headers.set(
5294
- "Content-Type",
5295
- (0, import_mime.getType)(asset) || "application/octet-stream"
5296
- );
5297
- return deconstructedResponse;
5298
- }
5299
- if (asset = getAsset(`${assetName}/index.html`)) {
5300
- deconstructedResponse.status = 301;
5301
- deconstructedResponse.headers.set(
5302
- "Location",
5303
- `${assetName}/${url.search}`
5304
- );
5305
- return deconstructedResponse;
5306
- } else {
5307
- notFound();
5308
- return deconstructedResponse;
5309
- }
5310
- };
5311
- const attachHeaders = (request, deconstructedResponse) => {
5312
- const headers = deconstructedResponse.headers;
5313
- const newHeaders = new Headers({});
5314
- const matches = headersMatcher(request) || [];
5315
- matches.forEach((match) => {
5316
- Object.entries(match).forEach(([name, value]) => {
5317
- newHeaders.append(name, `${value}`);
5318
- });
5319
- });
5320
- const combinedHeaders = {
5321
- ...Object.fromEntries(headers.entries()),
5322
- ...Object.fromEntries(newHeaders.entries())
5323
- };
5324
- deconstructedResponse.headers = new Headers({});
5325
- Object.entries(combinedHeaders).forEach(([name, value]) => {
5326
- if (value)
5327
- deconstructedResponse.headers.set(name, value);
5328
6105
  });
5329
6106
  };
5330
6107
  return async (input, init) => {
5331
- const request = new Request(input, init);
5332
- const deconstructedResponse = generateResponse(request);
5333
- attachHeaders(request, deconstructedResponse);
5334
- const headers = new Headers();
5335
- [...deconstructedResponse.headers.entries()].forEach(([name, value]) => {
5336
- if (value)
5337
- headers.set(name, value);
5338
- });
5339
- return new Response(deconstructedResponse.body, {
5340
- headers,
5341
- status: deconstructedResponse.status
5342
- });
6108
+ const request = new MiniflareRequest2(input, init);
6109
+ return await generateResponse(request);
5343
6110
  };
5344
6111
  }
5345
6112
  var invalidAssetsFetch = () => {
@@ -5403,7 +6170,7 @@ async function main() {
5403
6170
  }
5404
6171
  config.bindings = {
5405
6172
  ...config.bindings,
5406
- ...Object.fromEntries(
6173
+ ...config.externalDurableObjects && Object.fromEntries(
5407
6174
  Object.entries(
5408
6175
  config.externalDurableObjects
5409
6176
  ).map(([binding, { name, host, port }]) => {
@@ -5417,17 +6184,20 @@ async function main() {
5417
6184
  namespace.get = (id) => {
5418
6185
  const stub = new DurableObjectStub(factory, id);
5419
6186
  stub.fetch = (...reqArgs) => {
5420
- const requestFromArgs = new MiniflareRequest(...reqArgs);
6187
+ const requestFromArgs = new MiniflareRequest3(...reqArgs);
5421
6188
  const url = new URL(requestFromArgs.url);
5422
6189
  url.host = host;
5423
6190
  if (port !== void 0)
5424
6191
  url.port = port.toString();
5425
- const request = new MiniflareRequest(
6192
+ const request = new MiniflareRequest3(
5426
6193
  url.toString(),
5427
6194
  requestFromArgs
5428
6195
  );
5429
6196
  request.headers.set("x-miniflare-durable-object-name", name);
5430
- request.headers.set("x-miniflare-durable-object-id", id.toString());
6197
+ request.headers.set(
6198
+ "x-miniflare-durable-object-id",
6199
+ id.toString()
6200
+ );
5431
6201
  return fetch(request);
5432
6202
  };
5433
6203
  return stub;
@@ -5460,7 +6230,8 @@ async function main() {
5460
6230
  };
5461
6231
  }
5462
6232
  mf = new Miniflare(config);
5463
- await mf.startServer();
6233
+ const mfServer = await mf.startServer();
6234
+ const mfPort = mfServer.address().port;
5464
6235
  await mf.startScheduler();
5465
6236
  const internalDurableObjectClassNames = Object.values(
5466
6237
  config.durableObjects
@@ -5477,7 +6248,7 @@ async function main() {
5477
6248
  }`,
5478
6249
  serviceBindings: {
5479
6250
  DO: async (request) => {
5480
- request = new MiniflareRequest(request);
6251
+ request = new MiniflareRequest3(request);
5481
6252
  const name = request.headers.get("x-miniflare-durable-object-name");
5482
6253
  const idString = request.headers.get(
5483
6254
  "x-miniflare-durable-object-id"
@@ -5485,7 +6256,7 @@ async function main() {
5485
6256
  request.headers.delete("x-miniflare-durable-object-name");
5486
6257
  request.headers.delete("x-miniflare-durable-object-id");
5487
6258
  if (!name || !idString) {
5488
- return new MiniflareResponse(
6259
+ return new MiniflareResponse3(
5489
6260
  "[durable-object-proxy-err] Missing `x-miniflare-durable-object-name` or `x-miniflare-durable-object-id` headers.",
5490
6261
  { status: 400 }
5491
6262
  );
@@ -5493,14 +6264,14 @@ async function main() {
5493
6264
  const namespace = await mf?.getDurableObjectNamespace(name);
5494
6265
  const id = namespace?.idFromString(idString);
5495
6266
  if (!id) {
5496
- return new MiniflareResponse(
6267
+ return new MiniflareResponse3(
5497
6268
  "[durable-object-proxy-err] Could not generate an ID. Possibly due to a mismatched DO name and ID?",
5498
6269
  { status: 500 }
5499
6270
  );
5500
6271
  }
5501
6272
  const stub = namespace?.get(id);
5502
6273
  if (!stub) {
5503
- return new MiniflareResponse(
6274
+ return new MiniflareResponse3(
5504
6275
  "[durable-object-proxy-err] Could not generate a stub. Possibly due to a mismatched DO name and ID?",
5505
6276
  { status: 500 }
5506
6277
  );
@@ -5515,6 +6286,7 @@ async function main() {
5515
6286
  }
5516
6287
  process.send && process.send(
5517
6288
  JSON.stringify({
6289
+ mfPort,
5518
6290
  ready: true,
5519
6291
  durableObjectsPort: durableObjectsMfPort
5520
6292
  })