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.
- package/package.json +25 -10
- package/pages/functions/buildWorker.ts +62 -0
- package/pages/functions/filepath-routing.test.ts +39 -0
- package/pages/functions/filepath-routing.ts +228 -0
- package/pages/functions/identifiers.ts +78 -0
- package/pages/functions/routes.ts +158 -0
- package/pages/functions/template-worker.ts +136 -0
- package/src/__tests__/dev.test.tsx +67 -0
- package/src/__tests__/index.test.ts +42 -175
- package/src/__tests__/jest.setup.ts +22 -0
- package/src/__tests__/kv.test.ts +1199 -0
- package/src/__tests__/mock-cfetch.ts +173 -0
- package/src/__tests__/mock-dialogs.ts +65 -0
- package/src/__tests__/publish.test.ts +737 -0
- package/src/__tests__/run-in-tmp.ts +19 -0
- package/src/__tests__/run-wrangler.ts +32 -0
- package/src/__tests__/whoami.test.tsx +130 -0
- package/src/api/form_data.ts +66 -76
- package/src/api/preview.ts +4 -4
- package/src/api/worker.ts +38 -44
- package/src/cfetch/index.ts +110 -0
- package/src/cfetch/internal.ts +71 -0
- package/src/cli.ts +6 -7
- package/src/config.ts +634 -110
- package/src/dev.tsx +275 -272
- package/src/dialogs.tsx +2 -2
- package/src/index.tsx +504 -403
- package/src/inspect.ts +543 -0
- package/src/kv.tsx +109 -44
- package/src/pages.tsx +444 -222
- package/src/proxy.ts +302 -0
- package/src/publish.ts +123 -87
- package/src/sites.tsx +169 -41
- package/src/tail.tsx +5 -3
- package/src/user.tsx +64 -74
- package/src/whoami.tsx +69 -0
- package/wrangler-dist/cli.js +42792 -42619
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/mock-cfetch.js +0 -42
- package/src/api/inspect.ts +0 -430
- 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 {
|
|
7
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
41
|
+
async function sleep(ms: number) {
|
|
19
42
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
43
|
+
}
|
|
20
44
|
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
142
|
-
}
|
|
164
|
+
return port;
|
|
165
|
+
}
|
|
143
166
|
|
|
144
|
-
|
|
167
|
+
function escapeRegex(str: string) {
|
|
145
168
|
return str.replace(/[-/\\^$*+?.()|[]{}]/g, "\\$&");
|
|
146
|
-
}
|
|
169
|
+
}
|
|
147
170
|
|
|
148
|
-
|
|
171
|
+
type Replacements = Record<string, string>;
|
|
149
172
|
|
|
150
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
368
|
-
|
|
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
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
666
|
-
|
|
669
|
+
const config: Config = await generateConfigFromFileTree({
|
|
670
|
+
baseDir: functionsDirectory,
|
|
671
|
+
baseURL: "/",
|
|
672
|
+
});
|
|
667
673
|
|
|
668
|
-
|
|
674
|
+
if (outputConfigPath) {
|
|
675
|
+
writeFileSync(
|
|
676
|
+
outputConfigPath,
|
|
677
|
+
JSON.stringify({ ...config, baseURL: "/" }, null, 2)
|
|
678
|
+
);
|
|
679
|
+
}
|
|
669
680
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
681
|
+
await writeRoutesModule({
|
|
682
|
+
config,
|
|
683
|
+
srcDir: functionsDirectory,
|
|
684
|
+
outfile: routesModule,
|
|
685
|
+
});
|
|
675
686
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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
|
-
|
|
684
|
-
|
|
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
|
-
|
|
687
|
-
|
|
688
|
-
}
|
|
779
|
+
const functionsDirectory = "./functions";
|
|
780
|
+
const usingFunctions = existsSync(functionsDirectory);
|
|
689
781
|
|
|
690
|
-
|
|
782
|
+
const command = remaining as (string | number)[];
|
|
691
783
|
|
|
692
|
-
|
|
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 (
|
|
706
|
-
|
|
707
|
-
|
|
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
|
-
|
|
717
|
-
|
|
796
|
+
let scriptReadyResolve;
|
|
797
|
+
const scriptReadyPromise = new Promise(
|
|
798
|
+
(resolve) => (scriptReadyResolve = resolve)
|
|
799
|
+
);
|
|
718
800
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
console.log(
|
|
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
|
-
|
|
727
|
-
|
|
728
|
-
|
|
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
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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
|
-
|
|
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
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
),
|
|
873
|
+
const miniflare = new Miniflare({
|
|
874
|
+
port,
|
|
875
|
+
watch: true,
|
|
876
|
+
modules: true,
|
|
742
877
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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
|
-
|
|
751
|
-
|
|
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 = `
|
|
760
|
-
|
|
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
|
-
|
|
791
|
-
|
|
792
|
-
|
|
930
|
+
kvPersist: true,
|
|
931
|
+
durableObjectsPersist: true,
|
|
932
|
+
cachePersist: true,
|
|
933
|
+
liveReload,
|
|
793
934
|
|
|
794
|
-
|
|
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
|
-
|
|
801
|
-
|
|
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
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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
|
};
|