wrangler 0.0.0-e9a1820 → 0.0.0-e9ab55a

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/src/pages.tsx CHANGED
@@ -1,15 +1,19 @@
1
1
  /* eslint-disable no-shadow */
2
2
 
3
- import assert from "assert";
4
3
  import type { BuilderCallback } from "yargs";
5
4
  import { join } from "path";
6
5
  import { tmpdir } from "os";
7
- import { existsSync, lstatSync, readFileSync } from "fs";
6
+ import { existsSync, lstatSync, readFileSync, writeFileSync } from "fs";
8
7
  import { execSync, spawn } from "child_process";
9
8
  import { URL } from "url";
10
9
  import { getType } from "mime";
11
10
  import open from "open";
12
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";
13
17
 
14
18
  // Defer importing miniflare until we really need it. This takes ~0.5s
15
19
  // and also modifies some `stream/web` and `undici` prototypes, so we
@@ -17,16 +21,28 @@ import { watch } from "chokidar";
17
21
  import type { Headers, Request, fetch } from "@miniflare/core";
18
22
  import type { MiniflareOptions } from "miniflare";
19
23
 
20
- 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
+ };
31
+
32
+ process.on("SIGINT", () => EXIT());
33
+ process.on("SIGTERM", () => EXIT());
21
34
 
22
- const isWindows = () => process.platform === "win32";
35
+ function isWindows() {
36
+ return process.platform === "win32";
37
+ }
23
38
 
24
39
  const SECONDS_TO_WAIT_FOR_PROXY = 5;
25
40
 
26
- const sleep = async (ms: number) =>
41
+ async function sleep(ms: number) {
27
42
  await new Promise((resolve) => setTimeout(resolve, ms));
43
+ }
28
44
 
29
- const getPids = (pid: number) => {
45
+ function getPids(pid: number) {
30
46
  const pids: number[] = [pid];
31
47
  let command: string, regExp: RegExp;
32
48
 
@@ -51,9 +67,9 @@ const getPids = (pid: number) => {
51
67
  } catch {}
52
68
 
53
69
  return pids;
54
- };
70
+ }
55
71
 
56
- const getPort = (pid: number) => {
72
+ function getPort(pid: number) {
57
73
  let command: string, regExp: RegExp;
58
74
 
59
75
  if (isWindows()) {
@@ -78,24 +94,19 @@ const getPort = (pid: number) => {
78
94
  `Error scanning for ports of process with PID ${pid}: ${thrown}`
79
95
  );
80
96
  }
81
- };
97
+ }
82
98
 
83
- const spawnProxyProcess = async ({
99
+ async function spawnProxyProcess({
84
100
  port,
85
101
  command,
86
102
  }: {
87
103
  port?: number;
88
104
  command: (string | number)[];
89
- }) => {
90
- const exit: Exit = (message) => {
91
- if (message) console.error(message);
92
- if (proxy) proxy.kill();
93
- return undefined;
94
- };
95
-
105
+ }): Promise<void | number> {
96
106
  if (command.length === 0)
97
- return exit(
98
- "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
99
110
  );
100
111
 
101
112
  console.log(`Running ${command.join(" ")}...`);
@@ -110,6 +121,9 @@ const spawnProxyProcess = async ({
110
121
  },
111
122
  }
112
123
  );
124
+ EXIT_CALLBACKS.push(() => {
125
+ proxy.kill();
126
+ });
113
127
 
114
128
  proxy.stdout.on("data", (data) => {
115
129
  console.log(`[proxy]: ${data}`);
@@ -138,34 +152,35 @@ const spawnProxyProcess = async ({
138
152
  .filter((port) => port !== undefined)[0];
139
153
 
140
154
  if (port === undefined) {
141
- return exit(
142
- "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
143
158
  );
144
159
  } else {
145
160
  console.log(`Automatically determined the proxy port to be ${port}.`);
146
161
  }
147
162
  }
148
163
 
149
- return { port, exit };
150
- };
164
+ return port;
165
+ }
151
166
 
152
- const escapeRegex = (str: string) => {
167
+ function escapeRegex(str: string) {
153
168
  return str.replace(/[-/\\^$*+?.()|[]{}]/g, "\\$&");
154
- };
169
+ }
155
170
 
156
- export type Replacements = Record<string, string>;
171
+ type Replacements = Record<string, string>;
157
172
 
158
- export const replacer = (str: string, replacements: Replacements) => {
173
+ function replacer(str: string, replacements: Replacements) {
159
174
  for (const [replacement, value] of Object.entries(replacements)) {
160
175
  str = str.replace(`:${replacement}`, value);
161
176
  }
162
177
  return str;
163
- };
178
+ }
164
179
 
165
- export const generateRulesMatcher = <T,>(
180
+ function generateRulesMatcher<T>(
166
181
  rules?: Record<string, T>,
167
182
  replacer: (match: T, replacements: Replacements) => T = (match) => match
168
- ) => {
183
+ ) {
169
184
  // TODO: How can you test cross-host rules?
170
185
  if (!rules) return () => [];
171
186
 
@@ -212,9 +227,9 @@ export const generateRulesMatcher = <T,>(
212
227
  })
213
228
  .filter((value) => value !== undefined) as T[];
214
229
  };
215
- };
230
+ }
216
231
 
217
- const generateHeadersMatcher = (headersFile: string) => {
232
+ function generateHeadersMatcher(headersFile: string) {
218
233
  if (existsSync(headersFile)) {
219
234
  const contents = readFileSync(headersFile).toString();
220
235
 
@@ -281,9 +296,9 @@ const generateHeadersMatcher = (headersFile: string) => {
281
296
  } else {
282
297
  return () => undefined;
283
298
  }
284
- };
299
+ }
285
300
 
286
- const generateRedirectsMatcher = (redirectsFile: string) => {
301
+ function generateRedirectsMatcher(redirectsFile: string) {
287
302
  if (existsSync(redirectsFile)) {
288
303
  const contents = readFileSync(redirectsFile).toString();
289
304
 
@@ -330,26 +345,26 @@ const generateRedirectsMatcher = (redirectsFile: string) => {
330
345
  } else {
331
346
  return () => undefined;
332
347
  }
333
- };
348
+ }
334
349
 
335
- const extractPathname = (
350
+ function extractPathname(
336
351
  path = "/",
337
352
  includeSearch: boolean,
338
353
  includeHash: boolean
339
- ) => {
354
+ ) {
340
355
  if (!path.startsWith("/")) path = `/${path}`;
341
356
  const url = new URL(`//${path}`, "relative://");
342
357
  return `${url.pathname}${includeSearch ? url.search : ""}${
343
358
  includeHash ? url.hash : ""
344
359
  }`;
345
- };
360
+ }
346
361
 
347
- const validateURL = (
362
+ function validateURL(
348
363
  token: string,
349
364
  onlyRelative = false,
350
365
  includeSearch = false,
351
366
  includeHash = false
352
- ) => {
367
+ ) {
353
368
  const host = /^https:\/\/+(?<host>[^/]+)\/?(?<path>.*)/.exec(token);
354
369
  if (host && host.groups && host.groups.host) {
355
370
  if (onlyRelative) return;
@@ -370,14 +385,13 @@ const validateURL = (
370
385
  }
371
386
  }
372
387
  return "";
373
- };
388
+ }
374
389
 
375
- const hasFileExtension = (pathname: string) =>
376
- /\/.+\.[a-z0-9]+$/i.test(pathname);
390
+ function hasFileExtension(pathname: string) {
391
+ return /\/.+\.[a-z0-9]+$/i.test(pathname);
392
+ }
377
393
 
378
- const generateAssetsFetch = async (
379
- directory: string
380
- ): Promise<typeof fetch> => {
394
+ async function generateAssetsFetch(directory: string): Promise<typeof fetch> {
381
395
  // Defer importing miniflare until we really need it
382
396
  const { Headers, Request, Response } = await import("@miniflare/core");
383
397
 
@@ -623,229 +637,404 @@ const generateAssetsFetch = async (
623
637
  status: deconstructedResponse.status,
624
638
  });
625
639
  };
626
- };
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
+ );
666
+
667
+ const routesModule = join(tmpdir(), "./functionsRoutes.mjs");
668
+
669
+ const config: Config = await generateConfigFromFileTree({
670
+ baseDir: functionsDirectory,
671
+ baseURL: "/",
672
+ });
673
+
674
+ if (outputConfigPath) {
675
+ writeFileSync(
676
+ outputConfigPath,
677
+ JSON.stringify({ ...config, baseURL: "/" }, null, 2)
678
+ );
679
+ }
680
+
681
+ await writeRoutesModule({
682
+ config,
683
+ srcDir: functionsDirectory,
684
+ outfile: routesModule,
685
+ });
686
+
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
+ }
627
699
 
628
700
  export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
629
- return yargs.command(
630
- "dev [directory] [-- command]",
631
- "🧑‍💻 Develop your full-stack Pages application locally",
632
- (yargs) => {
633
- return yargs
634
- .positional("directory", {
635
- type: "string",
636
- demandOption: undefined,
637
- description: "The directory of static assets to serve",
638
- })
639
- .positional("command", {
640
- type: "string",
641
- demandOption: undefined,
642
- description: "The proxy command to run",
643
- })
644
- .options({
645
- local: {
646
- type: "boolean",
647
- default: true,
648
- description: "Run on my machine",
649
- },
650
- port: {
651
- type: "number",
652
- default: 8788,
653
- description: "The port to listen on (serve from)",
654
- },
655
- proxy: {
656
- type: "number",
657
- description:
658
- "The port to proxy (where the static assets are served)",
659
- },
660
- "script-path": {
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", {
661
708
  type: "string",
662
- default: "_worker.js",
663
- description:
664
- "The location of the single Worker script if not using functions",
665
- },
666
- binding: {
667
- type: "array",
668
- description: "Bind variable/secret (KEY=VALUE)",
669
- alias: "b",
670
- },
671
- kv: {
672
- type: "array",
673
- description: "KV namespace to bind",
674
- alias: "k",
675
- },
676
- do: {
677
- type: "array",
678
- description: "Durable Object to bind (NAME=CLASS)",
679
- alias: "o",
680
- },
681
- // TODO: Miniflare user options
682
- });
683
- },
684
- async ({
685
- local,
686
- directory,
687
- port,
688
- proxy: requestedProxyPort,
689
- "script-path": singleWorkerScriptPath,
690
- binding: bindings = [],
691
- kv: kvs = [],
692
- do: durableObjects = [],
693
- "--": remaining = [],
694
- }) => {
695
- if (!local) {
696
- console.error("Only local mode is supported at the moment.");
697
- return;
698
- }
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
+ }
699
778
 
700
- const functionsDirectory = "./functions";
701
- const usingFunctions = existsSync(functionsDirectory);
779
+ const functionsDirectory = "./functions";
780
+ const usingFunctions = existsSync(functionsDirectory);
702
781
 
703
- const command = remaining as (string | number)[];
782
+ const command = remaining as (string | number)[];
704
783
 
705
- let proxyPort: number | undefined;
706
- let exit: Exit = (message) => {
707
- console.error(message);
708
- return undefined;
709
- };
784
+ let proxyPort: number | void;
710
785
 
711
- if (directory === undefined) {
712
- const proxy = await spawnProxyProcess({
713
- port: requestedProxyPort,
714
- command,
715
- });
716
- if (proxy === undefined) return undefined;
786
+ if (directory === undefined) {
787
+ proxyPort = await spawnProxyProcess({
788
+ port: requestedProxyPort,
789
+ command,
790
+ });
791
+ if (proxyPort === undefined) return undefined;
792
+ }
717
793
 
718
- exit = proxy.exit;
719
- proxyPort = proxy.port;
794
+ let miniflareArgs: MiniflareOptions = {};
720
795
 
721
- process.on("SIGINT", () => exit());
722
- process.on("SIGTERM", () => exit());
723
- }
796
+ let scriptReadyResolve;
797
+ const scriptReadyPromise = new Promise(
798
+ (resolve) => (scriptReadyResolve = resolve)
799
+ );
724
800
 
725
- let miniflareArgs: MiniflareOptions = {};
801
+ if (usingFunctions) {
802
+ const scriptPath = join(tmpdir(), "./functionsWorker.js");
726
803
 
727
- if (usingFunctions) {
728
- const scriptPath = join(tmpdir(), "./functionsWorker.js");
729
- miniflareArgs = {
730
- scriptPath,
731
- buildWatchPaths: [functionsDirectory],
732
- buildCommand: `npx @cloudflare/pages-functions-compiler build ${functionsDirectory} --outfile ${scriptPath}`,
733
- };
734
- } else {
735
- const scriptPath =
736
- directory !== undefined
737
- ? join(directory, singleWorkerScriptPath)
738
- : singleWorkerScriptPath;
804
+ console.log(`Compiling worker to "${scriptPath}"...`);
739
805
 
740
- if (!existsSync(scriptPath)) {
741
- return exit(
742
- `No Worker script found at ${scriptPath}. Please either create a functions directory or create a single Worker at ${scriptPath}.`
743
- );
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
+ }
744
854
  }
745
855
 
746
- miniflareArgs = {
747
- scriptPath,
748
- };
749
- }
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");
750
859
 
751
- // Defer importing miniflare until we really need it
752
- const { Miniflare, Log, LogLevel } = await import("miniflare");
753
- const { Response, fetch } = await import("@miniflare/core");
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;
754
865
 
755
- // Should only be called if no proxyPort, using `assert.fail()` here
756
- // means the type of `assetsFetch` is still `typeof fetch`
757
- const assetsFetch = proxyPort
758
- ? () => assert.fail()
759
- : await generateAssetsFetch(directory);
760
- const miniflare = new Miniflare({
761
- port,
762
- watch: true,
763
- modules: true,
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;
764
872
 
765
- log: new Log(LogLevel.ERROR, { prefix: "pages" }),
766
- logUnhandledRejections: true,
767
- sourceMap: true,
873
+ const miniflare = new Miniflare({
874
+ port,
875
+ watch: true,
876
+ modules: true,
768
877
 
769
- kvNamespaces: kvs.map((kv) => kv.toString()),
878
+ log: new Log(LogLevel.ERROR, { prefix: "pages" }),
879
+ logUnhandledRejections: true,
880
+ sourceMap: true,
770
881
 
771
- durableObjects: Object.fromEntries(
772
- durableObjects.map((durableObject) =>
773
- durableObject.toString().split("=")
774
- )
775
- ),
882
+ kvNamespaces: kvs.map((kv) => kv.toString()),
776
883
 
777
- // User bindings
778
- bindings: {
779
- ...Object.fromEntries(
780
- bindings.map((binding) => binding.toString().split("="))
884
+ durableObjects: Object.fromEntries(
885
+ durableObjects.map((durableObject) =>
886
+ durableObject.toString().split("=")
887
+ )
781
888
  ),
782
- },
783
-
784
- // env.ASSETS.fetch
785
- serviceBindings: {
786
- async ASSETS(request) {
787
- if (proxyPort) {
788
- try {
789
- const url = new URL(request.url);
790
- url.host = `127.0.0.1:${proxyPort}`;
791
- return await fetch(url, request);
792
- } catch (thrown) {
793
- console.error(`Could not proxy request: ${thrown}`);
794
-
795
- // TODO: Pretty error page
796
- return new Response(
797
- `[wrangler] Could not proxy request: ${thrown}`,
798
- { status: 502 }
799
- );
800
- }
801
- } else {
802
- try {
803
- return await assetsFetch(request);
804
- } catch (thrown) {
805
- console.error(`Could not serve static asset: ${thrown}`);
806
-
807
- // TODO: Pretty error page
808
- return new Response(
809
- `[wrangler] Could not serve static asset: ${thrown}`,
810
- { status: 502 }
811
- );
812
- }
813
- }
814
- },
815
- },
816
889
 
817
- kvPersist: true,
818
- durableObjectsPersist: true,
819
- cachePersist: true,
820
-
821
- ...miniflareArgs,
822
- });
890
+ // User bindings
891
+ bindings: {
892
+ ...Object.fromEntries(
893
+ bindings.map((binding) => binding.toString().split("="))
894
+ ),
895
+ },
823
896
 
824
- try {
825
- // `startServer` might throw if user code contains errors
826
- const server = await miniflare.startServer();
827
- console.log(`Serving at http://127.0.0.1:${port}/`);
897
+ // env.ASSETS.fetch
898
+ serviceBindings: {
899
+ async ASSETS(request) {
900
+ if (proxyPort) {
901
+ try {
902
+ const url = new URL(request.url);
903
+ url.host = `localhost:${proxyPort}`;
904
+ return await fetch(url, request);
905
+ } catch (thrown) {
906
+ console.error(`Could not proxy request: ${thrown}`);
907
+
908
+ // TODO: Pretty error page
909
+ return new Response(
910
+ `[wrangler] Could not proxy request: ${thrown}`,
911
+ { status: 502 }
912
+ );
913
+ }
914
+ } else {
915
+ try {
916
+ return await assetsFetch(request);
917
+ } catch (thrown) {
918
+ console.error(`Could not serve static asset: ${thrown}`);
919
+
920
+ // TODO: Pretty error page
921
+ return new Response(
922
+ `[wrangler] Could not serve static asset: ${thrown}`,
923
+ { status: 502 }
924
+ );
925
+ }
926
+ }
927
+ },
928
+ },
828
929
 
829
- if (process.env.BROWSER !== "none") {
830
- await open(`http://127.0.0.1:${port}/`);
831
- }
930
+ kvPersist: true,
931
+ durableObjectsPersist: true,
932
+ cachePersist: true,
933
+ liveReload,
832
934
 
833
- process.on("SIGINT", () => {
834
- server.close();
835
- miniflare.dispose().catch((err) => {
836
- miniflare.log.error(err);
837
- });
935
+ ...miniflareArgs,
838
936
  });
839
- process.on("SIGTERM", () => {
840
- server.close();
841
- miniflare.dispose().catch((err) => {
842
- miniflare.log.error(err);
937
+
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));
843
961
  });
844
- });
845
- } catch (e) {
846
- miniflare.log.error(e);
847
- process.exitCode = 1;
962
+ } catch (e) {
963
+ miniflare.log.error(e);
964
+ EXIT("Could not start Miniflare.", 1);
965
+ }
848
966
  }
849
- }
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
+ };
1035
+
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."
850
1039
  );
851
1040
  };