wrangler 0.0.0-fb43821 → 0.0.0-fb53fda

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