wrangler 0.0.0-fb43821 → 0.0.0-fe5dcfd

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 (41) hide show
  1. package/package.json +25 -10
  2. package/pages/functions/buildWorker.ts +62 -0
  3. package/pages/functions/filepath-routing.test.ts +39 -0
  4. package/pages/functions/filepath-routing.ts +228 -0
  5. package/pages/functions/identifiers.ts +78 -0
  6. package/pages/functions/routes.ts +158 -0
  7. package/pages/functions/template-worker.ts +136 -0
  8. package/src/__tests__/dev.test.tsx +67 -0
  9. package/src/__tests__/index.test.ts +42 -175
  10. package/src/__tests__/jest.setup.ts +22 -0
  11. package/src/__tests__/kv.test.ts +1199 -0
  12. package/src/__tests__/mock-cfetch.ts +173 -0
  13. package/src/__tests__/mock-dialogs.ts +65 -0
  14. package/src/__tests__/publish.test.ts +737 -0
  15. package/src/__tests__/run-in-tmp.ts +19 -0
  16. package/src/__tests__/run-wrangler.ts +32 -0
  17. package/src/__tests__/whoami.test.tsx +130 -0
  18. package/src/api/form_data.ts +66 -76
  19. package/src/api/preview.ts +4 -4
  20. package/src/api/worker.ts +38 -44
  21. package/src/cfetch/index.ts +110 -0
  22. package/src/cfetch/internal.ts +71 -0
  23. package/src/cli.ts +6 -7
  24. package/src/config.ts +634 -110
  25. package/src/dev.tsx +275 -272
  26. package/src/dialogs.tsx +2 -2
  27. package/src/index.tsx +504 -403
  28. package/src/inspect.ts +543 -0
  29. package/src/kv.tsx +109 -44
  30. package/src/pages.tsx +444 -222
  31. package/src/proxy.ts +302 -0
  32. package/src/publish.ts +123 -87
  33. package/src/sites.tsx +169 -41
  34. package/src/tail.tsx +5 -3
  35. package/src/user.tsx +64 -74
  36. package/src/whoami.tsx +69 -0
  37. package/wrangler-dist/cli.js +42792 -42619
  38. package/wrangler-dist/cli.js.map +3 -3
  39. package/src/__tests__/mock-cfetch.js +0 -42
  40. package/src/api/inspect.ts +0 -430
  41. package/src/cfetch.ts +0 -72
package/src/pages.tsx CHANGED
@@ -1,24 +1,48 @@
1
+ /* eslint-disable no-shadow */
2
+
1
3
  import type { BuilderCallback } from "yargs";
2
4
  import { join } from "path";
3
5
  import { tmpdir } from "os";
4
- import { existsSync, lstatSync, readFileSync } from "fs";
6
+ import { existsSync, lstatSync, readFileSync, writeFileSync } from "fs";
5
7
  import { execSync, spawn } from "child_process";
6
- import { Headers, Request, Response } from "undici";
7
- import type { MiniflareOptions } from "miniflare";
8
- import type { RequestInfo, RequestInit } from "undici";
8
+ import { URL } from "url";
9
+ import { getType } from "mime";
9
10
  import open from "open";
10
11
  import { watch } from "chokidar";
12
+ import type { BuildResult } from "esbuild";
13
+ import { buildWorker } from "../pages/functions/buildWorker";
14
+ import type { Config } from "../pages/functions/routes";
15
+ import { writeRoutesModule } from "../pages/functions/routes";
16
+ import { generateConfigFromFileTree } from "../pages/functions/filepath-routing";
17
+
18
+ // Defer importing miniflare until we really need it. This takes ~0.5s
19
+ // and also modifies some `stream/web` and `undici` prototypes, so we
20
+ // don't want to do this if pages commands aren't being called.
21
+ import type { Headers, Request, fetch } from "@miniflare/core";
22
+ import type { MiniflareOptions } from "miniflare";
11
23
 
12
- type Exit = (message?: string) => undefined;
24
+ const EXIT_CALLBACKS: (() => void)[] = [];
25
+ const EXIT = (message?: string, code?: number) => {
26
+ if (message) console.log(message);
27
+ if (code) process.exitCode = code;
28
+ EXIT_CALLBACKS.forEach((callback) => callback());
29
+ process.exit(code);
30
+ };
13
31
 
14
- const isWindows = () => process.platform === "win32";
32
+ process.on("SIGINT", () => EXIT());
33
+ process.on("SIGTERM", () => EXIT());
34
+
35
+ function isWindows() {
36
+ return process.platform === "win32";
37
+ }
15
38
 
16
39
  const SECONDS_TO_WAIT_FOR_PROXY = 5;
17
40
 
18
- const sleep = async (ms: number) =>
41
+ async function sleep(ms: number) {
19
42
  await new Promise((resolve) => setTimeout(resolve, ms));
43
+ }
20
44
 
21
- const getPids = (pid: number) => {
45
+ function getPids(pid: number) {
22
46
  const pids: number[] = [pid];
23
47
  let command: string, regExp: RegExp;
24
48
 
@@ -43,9 +67,9 @@ const getPids = (pid: number) => {
43
67
  } catch {}
44
68
 
45
69
  return pids;
46
- };
70
+ }
47
71
 
48
- const getPort = (pid: number) => {
72
+ function getPort(pid: number) {
49
73
  let command: string, regExp: RegExp;
50
74
 
51
75
  if (isWindows()) {
@@ -70,24 +94,19 @@ const getPort = (pid: number) => {
70
94
  `Error scanning for ports of process with PID ${pid}: ${thrown}`
71
95
  );
72
96
  }
73
- };
97
+ }
74
98
 
75
- const spawnProxyProcess = async ({
99
+ async function spawnProxyProcess({
76
100
  port,
77
101
  command,
78
102
  }: {
79
103
  port?: number;
80
104
  command: (string | number)[];
81
- }) => {
82
- const exit: Exit = (message) => {
83
- if (message) console.error(message);
84
- if (proxy) proxy.kill();
85
- return undefined;
86
- };
87
-
105
+ }): Promise<void | number> {
88
106
  if (command.length === 0)
89
- return exit(
90
- "Must specify a directory of static assets to serve or a command to run."
107
+ return EXIT(
108
+ "Must specify a directory of static assets to serve or a command to run.",
109
+ 1
91
110
  );
92
111
 
93
112
  console.log(`Running ${command.join(" ")}...`);
@@ -102,6 +121,9 @@ const spawnProxyProcess = async ({
102
121
  },
103
122
  }
104
123
  );
124
+ EXIT_CALLBACKS.push(() => {
125
+ proxy.kill();
126
+ });
105
127
 
106
128
  proxy.stdout.on("data", (data) => {
107
129
  console.log(`[proxy]: ${data}`);
@@ -130,34 +152,35 @@ const spawnProxyProcess = async ({
130
152
  .filter((port) => port !== undefined)[0];
131
153
 
132
154
  if (port === undefined) {
133
- return exit(
134
- "Could not automatically determine proxy port. Please specify the proxy port with --proxy."
155
+ return EXIT(
156
+ "Could not automatically determine proxy port. Please specify the proxy port with --proxy.",
157
+ 1
135
158
  );
136
159
  } else {
137
160
  console.log(`Automatically determined the proxy port to be ${port}.`);
138
161
  }
139
162
  }
140
163
 
141
- return { port, exit };
142
- };
164
+ return port;
165
+ }
143
166
 
144
- const escapeRegex = (str: string) => {
167
+ function escapeRegex(str: string) {
145
168
  return str.replace(/[-/\\^$*+?.()|[]{}]/g, "\\$&");
146
- };
169
+ }
147
170
 
148
- export type Replacements = Record<string, string>;
171
+ type Replacements = Record<string, string>;
149
172
 
150
- export const replacer = (str: string, replacements: Replacements) => {
173
+ function replacer(str: string, replacements: Replacements) {
151
174
  for (const [replacement, value] of Object.entries(replacements)) {
152
175
  str = str.replace(`:${replacement}`, value);
153
176
  }
154
177
  return str;
155
- };
178
+ }
156
179
 
157
- export const generateRulesMatcher = <T,>(
180
+ function generateRulesMatcher<T>(
158
181
  rules?: Record<string, T>,
159
182
  replacer: (match: T, replacements: Replacements) => T = (match) => match
160
- ) => {
183
+ ) {
161
184
  // TODO: How can you test cross-host rules?
162
185
  if (!rules) return () => [];
163
186
 
@@ -204,9 +227,9 @@ export const generateRulesMatcher = <T,>(
204
227
  })
205
228
  .filter((value) => value !== undefined) as T[];
206
229
  };
207
- };
230
+ }
208
231
 
209
- const generateHeadersMatcher = (headersFile: string) => {
232
+ function generateHeadersMatcher(headersFile: string) {
210
233
  if (existsSync(headersFile)) {
211
234
  const contents = readFileSync(headersFile).toString();
212
235
 
@@ -273,9 +296,9 @@ const generateHeadersMatcher = (headersFile: string) => {
273
296
  } else {
274
297
  return () => undefined;
275
298
  }
276
- };
299
+ }
277
300
 
278
- const generateRedirectsMatcher = (redirectsFile: string) => {
301
+ function generateRedirectsMatcher(redirectsFile: string) {
279
302
  if (existsSync(redirectsFile)) {
280
303
  const contents = readFileSync(redirectsFile).toString();
281
304
 
@@ -322,26 +345,26 @@ const generateRedirectsMatcher = (redirectsFile: string) => {
322
345
  } else {
323
346
  return () => undefined;
324
347
  }
325
- };
348
+ }
326
349
 
327
- const extractPathname = (
350
+ function extractPathname(
328
351
  path = "/",
329
352
  includeSearch: boolean,
330
353
  includeHash: boolean
331
- ) => {
354
+ ) {
332
355
  if (!path.startsWith("/")) path = `/${path}`;
333
356
  const url = new URL(`//${path}`, "relative://");
334
357
  return `${url.pathname}${includeSearch ? url.search : ""}${
335
358
  includeHash ? url.hash : ""
336
359
  }`;
337
- };
360
+ }
338
361
 
339
- const validateURL = (
362
+ function validateURL(
340
363
  token: string,
341
364
  onlyRelative = false,
342
365
  includeSearch = false,
343
366
  includeHash = false
344
- ) => {
367
+ ) {
345
368
  const host = /^https:\/\/+(?<host>[^/]+)\/?(?<path>.*)/.exec(token);
346
369
  if (host && host.groups && host.groups.host) {
347
370
  if (onlyRelative) return;
@@ -362,14 +385,16 @@ const validateURL = (
362
385
  }
363
386
  }
364
387
  return "";
365
- };
388
+ }
389
+
390
+ function hasFileExtension(pathname: string) {
391
+ return /\/.+\.[a-z0-9]+$/i.test(pathname);
392
+ }
366
393
 
367
- const hasFileExtension = (pathname: string) =>
368
- /\/.+\.[a-z0-9]+$/i.test(pathname);
394
+ async function generateAssetsFetch(directory: string): Promise<typeof fetch> {
395
+ // Defer importing miniflare until we really need it
396
+ const { Headers, Request, Response } = await import("@miniflare/core");
369
397
 
370
- const generateAssetsFetch = async (
371
- directory: string
372
- ): Promise<typeof fetch> => {
373
398
  const headersFile = join(directory, "_headers");
374
399
  const redirectsFile = join(directory, "_redirects");
375
400
  const workerFile = join(directory, "_worker.js");
@@ -466,12 +491,20 @@ const generateAssetsFetch = async (
466
491
  if ((asset = getAsset(`${cwd}/404.html`))) {
467
492
  deconstructedResponse.status = 404;
468
493
  deconstructedResponse.body = serveAsset(asset);
494
+ deconstructedResponse.headers.set(
495
+ "Content-Type",
496
+ getType(asset) || "application/octet-stream"
497
+ );
469
498
  return deconstructedResponse;
470
499
  }
471
500
  }
472
501
 
473
502
  if ((asset = getAsset(`/index.html`))) {
474
503
  deconstructedResponse.body = serveAsset(asset);
504
+ deconstructedResponse.headers.set(
505
+ "Content-Type",
506
+ getType(asset) || "application/octet-stream"
507
+ );
475
508
  return deconstructedResponse;
476
509
  }
477
510
 
@@ -484,6 +517,10 @@ const generateAssetsFetch = async (
484
517
  if (url.pathname.endsWith("/")) {
485
518
  if ((asset = getAsset(`${url.pathname}/index.html`))) {
486
519
  deconstructedResponse.body = serveAsset(asset);
520
+ deconstructedResponse.headers.set(
521
+ "Content-Type",
522
+ getType(asset) || "application/octet-stream"
523
+ );
487
524
  return deconstructedResponse;
488
525
  } else if (
489
526
  (asset = getAsset(`${url.pathname.replace(/\/$/, ".html")}`))
@@ -511,6 +548,10 @@ const generateAssetsFetch = async (
511
548
  const extensionlessPath = url.pathname.slice(0, -".html".length);
512
549
  if (getAsset(extensionlessPath) || extensionlessPath === "/") {
513
550
  deconstructedResponse.body = serveAsset(asset);
551
+ deconstructedResponse.headers.set(
552
+ "Content-Type",
553
+ getType(asset) || "application/octet-stream"
554
+ );
514
555
  return deconstructedResponse;
515
556
  } else {
516
557
  deconstructedResponse.status = 301;
@@ -522,6 +563,10 @@ const generateAssetsFetch = async (
522
563
  }
523
564
  } else {
524
565
  deconstructedResponse.body = serveAsset(asset);
566
+ deconstructedResponse.headers.set(
567
+ "Content-Type",
568
+ getType(asset) || "application/octet-stream"
569
+ );
525
570
  return deconstructedResponse;
526
571
  }
527
572
  } else if (hasFileExtension(url.pathname)) {
@@ -531,6 +576,10 @@ const generateAssetsFetch = async (
531
576
 
532
577
  if ((asset = getAsset(`${url.pathname}.html`))) {
533
578
  deconstructedResponse.body = serveAsset(asset);
579
+ deconstructedResponse.headers.set(
580
+ "Content-Type",
581
+ getType(asset) || "application/octet-stream"
582
+ );
534
583
  return deconstructedResponse;
535
584
  }
536
585
 
@@ -572,7 +621,7 @@ const generateAssetsFetch = async (
572
621
  });
573
622
  };
574
623
 
575
- return (async (input, init) => {
624
+ return async (input, init) => {
576
625
  const request = new Request(input, init);
577
626
  const deconstructedResponse = generateResponse(request);
578
627
  attachHeaders(request, deconstructedResponse);
@@ -587,178 +636,272 @@ const generateAssetsFetch = async (
587
636
  headers,
588
637
  status: deconstructedResponse.status,
589
638
  });
590
- }) as any;
591
- };
639
+ };
640
+ }
641
+
642
+ const RUNNING_BUILDERS: BuildResult[] = [];
643
+
644
+ async function buildFunctions({
645
+ scriptPath,
646
+ outputConfigPath,
647
+ functionsDirectory,
648
+ minify = false,
649
+ sourcemap = false,
650
+ fallbackService = "ASSETS",
651
+ watch = false,
652
+ onEnd,
653
+ }: {
654
+ scriptPath: string;
655
+ outputConfigPath?: string;
656
+ functionsDirectory: string;
657
+ minify?: boolean;
658
+ sourcemap?: boolean;
659
+ fallbackService?: string;
660
+ watch?: boolean;
661
+ onEnd?: () => void;
662
+ }) {
663
+ RUNNING_BUILDERS.forEach(
664
+ (runningBuilder) => runningBuilder.stop && runningBuilder.stop()
665
+ );
592
666
 
593
- export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
594
- return yargs.command(
595
- "dev [directory] [-- command]",
596
- "🧑‍💻 Develop your full-stack Pages application locally",
597
- (yargs) => {
598
- return yargs
599
- .positional("directory", {
600
- type: "string",
601
- demandOption: undefined,
602
- description: "The directory of static assets to serve",
603
- })
604
- .positional("command", {
605
- type: "string",
606
- demandOption: undefined,
607
- description: "The proxy command to run",
608
- })
609
- .options({
610
- local: {
611
- type: "boolean",
612
- default: true,
613
- description: "Run on my machine",
614
- },
615
- port: {
616
- type: "number",
617
- default: 8788,
618
- description: "The port to listen on (serve from)",
619
- },
620
- proxy: {
621
- type: "number",
622
- description:
623
- "The port to proxy (where the static assets are served)",
624
- },
625
- "script-path": {
626
- type: "string",
627
- default: "_worker.js",
628
- description:
629
- "The location of the single Worker script if not using functions",
630
- },
631
- binding: {
632
- type: "array",
633
- description: "Bind variable/secret (KEY=VALUE)",
634
- alias: "b",
635
- },
636
- kv: {
637
- type: "array",
638
- description: "KV namespace to bind",
639
- alias: "k",
640
- },
641
- do: {
642
- type: "array",
643
- description: "Durable Object to bind (NAME=CLASS)",
644
- alias: "o",
645
- },
646
- // TODO: Miniflare user options
647
- });
648
- },
649
- async ({
650
- local,
651
- directory,
652
- port,
653
- proxy: requestedProxyPort,
654
- "script-path": singleWorkerScriptPath,
655
- binding: bindings = [],
656
- kv: kvs = [],
657
- do: durableObjects = [],
658
- "--": remaining = [],
659
- }) => {
660
- if (!local) {
661
- console.error("Only local mode is supported at the moment.");
662
- return;
663
- }
667
+ const routesModule = join(tmpdir(), "./functionsRoutes.mjs");
664
668
 
665
- const functionsDirectory = "./functions";
666
- const usingFunctions = existsSync(functionsDirectory);
669
+ const config: Config = await generateConfigFromFileTree({
670
+ baseDir: functionsDirectory,
671
+ baseURL: "/",
672
+ });
667
673
 
668
- const command = remaining as (string | number)[];
674
+ if (outputConfigPath) {
675
+ writeFileSync(
676
+ outputConfigPath,
677
+ JSON.stringify({ ...config, baseURL: "/" }, null, 2)
678
+ );
679
+ }
669
680
 
670
- let proxyPort: number | undefined;
671
- let exit: Exit = (message) => {
672
- console.error(message);
673
- return undefined;
674
- };
681
+ await writeRoutesModule({
682
+ config,
683
+ srcDir: functionsDirectory,
684
+ outfile: routesModule,
685
+ });
675
686
 
676
- if (directory === undefined) {
677
- const proxy = await spawnProxyProcess({
678
- port: requestedProxyPort,
679
- command,
680
- });
681
- if (proxy === undefined) return undefined;
687
+ RUNNING_BUILDERS.push(
688
+ await buildWorker({
689
+ routesModule,
690
+ outfile: scriptPath,
691
+ minify,
692
+ sourcemap,
693
+ fallbackService,
694
+ watch,
695
+ onEnd,
696
+ })
697
+ );
698
+ }
682
699
 
683
- exit = proxy.exit;
684
- proxyPort = proxy.port;
700
+ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
701
+ return yargs
702
+ .command(
703
+ "dev [directory] [-- command]",
704
+ "🧑‍💻 Develop your full-stack Pages application locally",
705
+ (yargs) => {
706
+ return yargs
707
+ .positional("directory", {
708
+ type: "string",
709
+ demandOption: undefined,
710
+ description: "The directory of static assets to serve",
711
+ })
712
+ .positional("command", {
713
+ type: "string",
714
+ demandOption: undefined,
715
+ description: "The proxy command to run",
716
+ })
717
+ .options({
718
+ local: {
719
+ type: "boolean",
720
+ default: true,
721
+ description: "Run on my machine",
722
+ },
723
+ port: {
724
+ type: "number",
725
+ default: 8788,
726
+ description: "The port to listen on (serve from)",
727
+ },
728
+ proxy: {
729
+ type: "number",
730
+ description:
731
+ "The port to proxy (where the static assets are served)",
732
+ },
733
+ "script-path": {
734
+ type: "string",
735
+ default: "_worker.js",
736
+ description:
737
+ "The location of the single Worker script if not using functions",
738
+ },
739
+ binding: {
740
+ type: "array",
741
+ description: "Bind variable/secret (KEY=VALUE)",
742
+ alias: "b",
743
+ },
744
+ kv: {
745
+ type: "array",
746
+ description: "KV namespace to bind",
747
+ alias: "k",
748
+ },
749
+ do: {
750
+ type: "array",
751
+ description: "Durable Object to bind (NAME=CLASS)",
752
+ alias: "o",
753
+ },
754
+ "live-reload": {
755
+ type: "boolean",
756
+ default: false,
757
+ description: "Auto reload HTML pages when change is detected",
758
+ },
759
+ // TODO: Miniflare user options
760
+ });
761
+ },
762
+ async ({
763
+ local,
764
+ directory,
765
+ port,
766
+ proxy: requestedProxyPort,
767
+ "script-path": singleWorkerScriptPath,
768
+ binding: bindings = [],
769
+ kv: kvs = [],
770
+ do: durableObjects = [],
771
+ "live-reload": liveReload,
772
+ "--": remaining = [],
773
+ }) => {
774
+ if (!local) {
775
+ console.error("Only local mode is supported at the moment.");
776
+ return;
777
+ }
685
778
 
686
- process.on("SIGINT", () => exit());
687
- process.on("SIGTERM", () => exit());
688
- }
779
+ const functionsDirectory = "./functions";
780
+ const usingFunctions = existsSync(functionsDirectory);
689
781
 
690
- let miniflareArgs: MiniflareOptions = {};
782
+ const command = remaining as (string | number)[];
691
783
 
692
- if (usingFunctions) {
693
- const scriptPath = join(tmpdir(), "./functionsWorker.js");
694
- miniflareArgs = {
695
- scriptPath,
696
- buildWatchPaths: [functionsDirectory],
697
- buildCommand: `npx @cloudflare/pages-functions-compiler build ${functionsDirectory} --outfile ${scriptPath}`,
698
- };
699
- } else {
700
- const scriptPath =
701
- directory !== undefined
702
- ? join(directory, singleWorkerScriptPath)
703
- : singleWorkerScriptPath;
784
+ let proxyPort: number | void;
704
785
 
705
- if (!existsSync(scriptPath)) {
706
- return exit(
707
- `No Worker script found at ${scriptPath}. Please either create a functions directory or create a single Worker at ${scriptPath}.`
708
- );
786
+ if (directory === undefined) {
787
+ proxyPort = await spawnProxyProcess({
788
+ port: requestedProxyPort,
789
+ command,
790
+ });
791
+ if (proxyPort === undefined) return undefined;
709
792
  }
710
793
 
711
- miniflareArgs = {
712
- scriptPath,
713
- };
714
- }
794
+ let miniflareArgs: MiniflareOptions = {};
715
795
 
716
- const { Miniflare, Log, LogLevel } = await import("miniflare");
717
- const { fetch } = await import("@miniflare/core");
796
+ let scriptReadyResolve;
797
+ const scriptReadyPromise = new Promise(
798
+ (resolve) => (scriptReadyResolve = resolve)
799
+ );
718
800
 
719
- class MiniflareLogger extends Log {
720
- log(message: string) {
721
- message = message.replace("[mf:", "[pages:");
722
- console.log(message);
801
+ if (usingFunctions) {
802
+ const scriptPath = join(tmpdir(), "./functionsWorker.js");
803
+
804
+ console.log(`Compiling worker to "${scriptPath}"...`);
805
+
806
+ await buildFunctions({
807
+ scriptPath,
808
+ functionsDirectory,
809
+ sourcemap: true,
810
+ watch: true,
811
+ onEnd: () => scriptReadyResolve(),
812
+ });
813
+
814
+ watch([functionsDirectory], {
815
+ persistent: true,
816
+ ignoreInitial: true,
817
+ }).on("all", async () => {
818
+ await buildFunctions({
819
+ scriptPath,
820
+ functionsDirectory,
821
+ sourcemap: true,
822
+ watch: true,
823
+ onEnd: () => scriptReadyResolve(),
824
+ });
825
+ });
826
+
827
+ miniflareArgs = {
828
+ scriptPath,
829
+ };
830
+ } else {
831
+ const scriptPath =
832
+ directory !== undefined
833
+ ? join(directory, singleWorkerScriptPath)
834
+ : singleWorkerScriptPath;
835
+
836
+ if (existsSync(scriptPath)) {
837
+ miniflareArgs = {
838
+ scriptPath,
839
+ };
840
+ } else {
841
+ console.log("No functions. Shimming...");
842
+ miniflareArgs = {
843
+ // TODO: The fact that these request/response hacks are necessary is ridiculous.
844
+ // We need to eliminate them from env.ASSETS.fetch (not sure if just local or prod as well)
845
+ script: `
846
+ export default {
847
+ async fetch(request, env, context) {
848
+ const response = await env.ASSETS.fetch(request.url, request)
849
+ return new Response(response.body, response)
850
+ }
851
+ }`,
852
+ };
853
+ }
723
854
  }
724
- }
725
855
 
726
- const miniflare = new Miniflare({
727
- port,
728
- watch: true,
729
- modules: true,
856
+ // Defer importing miniflare until we really need it
857
+ const { Miniflare, Log, LogLevel } = await import("miniflare");
858
+ const { Response, fetch } = await import("@miniflare/core");
730
859
 
731
- log: new MiniflareLogger(LogLevel.ERROR),
732
- logUnhandledRejections: true,
733
- sourceMap: true,
860
+ // Wait for esbuild to finish building before starting Miniflare.
861
+ // This must be before the call to `new Miniflare`, as that will
862
+ // asynchronously start loading the script. `await startServer()`
863
+ // internally just waits for that promise to resolve.
864
+ await scriptReadyPromise;
734
865
 
735
- kvNamespaces: kvs.map((kv) => kv.toString()),
866
+ // `assetsFetch()` will only be called if there is `proxyPort` defined.
867
+ // We only define `proxyPort`, above, when there is no `directory` defined.
868
+ const assetsFetch =
869
+ directory !== undefined
870
+ ? await generateAssetsFetch(directory)
871
+ : invalidAssetsFetch;
736
872
 
737
- durableObjects: Object.fromEntries(
738
- durableObjects.map((durableObject) =>
739
- durableObject.toString().split("=")
740
- )
741
- ),
873
+ const miniflare = new Miniflare({
874
+ port,
875
+ watch: true,
876
+ modules: true,
742
877
 
743
- bindings: {
744
- // User bindings
745
- ...Object.fromEntries(
746
- bindings.map((binding) => binding.toString().split("="))
878
+ log: new Log(LogLevel.ERROR, { prefix: "pages" }),
879
+ logUnhandledRejections: true,
880
+ sourceMap: true,
881
+
882
+ kvNamespaces: kvs.map((kv) => kv.toString()),
883
+
884
+ durableObjects: Object.fromEntries(
885
+ durableObjects.map((durableObject) =>
886
+ durableObject.toString().split("=")
887
+ )
747
888
  ),
748
889
 
890
+ // User bindings
891
+ bindings: {
892
+ ...Object.fromEntries(
893
+ bindings.map((binding) => binding.toString().split("="))
894
+ ),
895
+ },
896
+
749
897
  // env.ASSETS.fetch
750
- ASSETS: {
751
- fetch: async (
752
- input: RequestInfo,
753
- init?: RequestInit | undefined
754
- ) => {
898
+ serviceBindings: {
899
+ async ASSETS(request) {
755
900
  if (proxyPort) {
756
901
  try {
757
- let request = new Request(input, init);
758
902
  const url = new URL(request.url);
759
- url.host = `127.0.0.1:${proxyPort}`;
760
- request = new Request(url.toString(), request);
761
- return await fetch(request.url, request);
903
+ url.host = `localhost:${proxyPort}`;
904
+ return await fetch(url, request);
762
905
  } catch (thrown) {
763
906
  console.error(`Could not proxy request: ${thrown}`);
764
907
 
@@ -770,9 +913,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
770
913
  }
771
914
  } else {
772
915
  try {
773
- return await (
774
- await generateAssetsFetch(directory)
775
- )(input as any, init as any);
916
+ return await assetsFetch(request);
776
917
  } catch (thrown) {
777
918
  console.error(`Could not serve static asset: ${thrown}`);
778
919
 
@@ -785,34 +926,115 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
785
926
  }
786
927
  },
787
928
  },
788
- },
789
929
 
790
- kvPersist: true,
791
- durableObjectsPersist: true,
792
- cachePersist: true,
930
+ kvPersist: true,
931
+ durableObjectsPersist: true,
932
+ cachePersist: true,
933
+ liveReload,
793
934
 
794
- ...miniflareArgs,
795
- });
796
-
797
- const server = await miniflare.startServer();
798
- console.log(`Serving at http://127.0.0.1:${port}/`);
935
+ ...miniflareArgs,
936
+ });
799
937
 
800
- if (process.env.BROWSER !== "none") {
801
- await open(`http://127.0.0.1:${port}/`);
938
+ try {
939
+ // `startServer` might throw if user code contains errors
940
+ const server = await miniflare.startServer();
941
+ console.log(`Serving at http://localhost:${port}/`);
942
+
943
+ if (process.env.BROWSER !== "none") {
944
+ const childProcess = await open(`http://localhost:${port}/`);
945
+ // fail silently if the open command doesn't work (e.g. in GitHub Codespaces)
946
+ childProcess.on("error", (_err) => {});
947
+ }
948
+
949
+ if (directory !== undefined && liveReload) {
950
+ watch([directory], {
951
+ persistent: true,
952
+ ignoreInitial: true,
953
+ }).on("all", async () => {
954
+ await miniflare.reload();
955
+ });
956
+ }
957
+
958
+ EXIT_CALLBACKS.push(() => {
959
+ server.close();
960
+ miniflare.dispose().catch((err) => miniflare.log.error(err));
961
+ });
962
+ } catch (e) {
963
+ miniflare.log.error(e);
964
+ EXIT("Could not start Miniflare.", 1);
965
+ }
802
966
  }
967
+ )
968
+ .command("functions", "Cloudflare Pages Functions", (yargs) =>
969
+ yargs.command(
970
+ "build [directory]",
971
+ "Compile a folder of Cloudflare Pages Functions into a single Worker",
972
+ (yargs) =>
973
+ yargs
974
+ .positional("directory", {
975
+ type: "string",
976
+ default: "functions",
977
+ description: "The directory of Pages Functions",
978
+ })
979
+ .options({
980
+ "script-path": {
981
+ type: "string",
982
+ default: "_worker.js",
983
+ description: "The location of the output Worker script",
984
+ },
985
+ "output-config-path": {
986
+ type: "string",
987
+ description: "The location for the output config file",
988
+ },
989
+ minify: {
990
+ type: "boolean",
991
+ default: false,
992
+ description: "Minify the output Worker script",
993
+ },
994
+ sourcemap: {
995
+ type: "boolean",
996
+ default: false,
997
+ description:
998
+ "Generate a sourcemap for the output Worker script",
999
+ },
1000
+ "fallback-service": {
1001
+ type: "string",
1002
+ default: "ASSETS",
1003
+ description:
1004
+ "The service to fallback to at the end of the `next` chain. Setting to '' will fallback to the global `fetch`.",
1005
+ },
1006
+ watch: {
1007
+ type: "boolean",
1008
+ default: false,
1009
+ description:
1010
+ "Watch for changes to the functions and automatically rebuild the Worker script",
1011
+ },
1012
+ }),
1013
+ async ({
1014
+ directory,
1015
+ "script-path": scriptPath,
1016
+ "output-config-path": outputConfigPath,
1017
+ minify,
1018
+ sourcemap,
1019
+ fallbackService,
1020
+ watch,
1021
+ }) => {
1022
+ await buildFunctions({
1023
+ scriptPath,
1024
+ outputConfigPath,
1025
+ functionsDirectory: directory,
1026
+ minify,
1027
+ sourcemap,
1028
+ fallbackService,
1029
+ watch,
1030
+ });
1031
+ }
1032
+ )
1033
+ );
1034
+ };
803
1035
 
804
- process.on("SIGINT", () => {
805
- server.close();
806
- miniflare.dispose().catch((err) => {
807
- console.error(err);
808
- });
809
- });
810
- process.on("SIGTERM", () => {
811
- server.close();
812
- miniflare.dispose().catch((err) => {
813
- console.error(err);
814
- });
815
- });
816
- }
1036
+ const invalidAssetsFetch: typeof fetch = () => {
1037
+ throw new Error(
1038
+ "Trying to fetch assets directly when there is no `directory` option specified, and not in `local` mode."
817
1039
  );
818
1040
  };