wrangler 0.0.0-e1c615f → 0.0.0-e6733a3
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 +3 -2
- package/pages/functions/buildWorker.ts +5 -0
- package/pages/functions/template-worker.ts +9 -2
- package/src/api/worker.ts +6 -6
- package/src/config.ts +475 -119
- package/src/dev.tsx +61 -164
- package/src/index.tsx +48 -3
- package/src/inspect.ts +524 -0
- package/src/pages.tsx +95 -32
- package/src/proxy.ts +267 -76
- package/src/publish.ts +13 -5
- package/src/user.tsx +12 -9
- package/wrangler-dist/cli.js +6817 -6631
- package/wrangler-dist/cli.js.map +3 -3
- package/src/api/inspect.ts +0 -430
package/src/pages.tsx
CHANGED
|
@@ -4,7 +4,7 @@ import assert from "assert";
|
|
|
4
4
|
import type { BuilderCallback } from "yargs";
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
import { tmpdir } from "os";
|
|
7
|
-
import { existsSync, lstatSync, readFileSync } from "fs";
|
|
7
|
+
import { existsSync, lstatSync, readFileSync, writeFileSync } from "fs";
|
|
8
8
|
import { execSync, spawn } from "child_process";
|
|
9
9
|
import { URL } from "url";
|
|
10
10
|
import { getType } from "mime";
|
|
@@ -642,16 +642,20 @@ const RUNNING_BUILDERS: BuildResult[] = [];
|
|
|
642
642
|
|
|
643
643
|
async function buildFunctions({
|
|
644
644
|
scriptPath,
|
|
645
|
+
outputConfigPath,
|
|
645
646
|
functionsDirectory,
|
|
646
647
|
minify = false,
|
|
647
648
|
sourcemap = false,
|
|
649
|
+
fallbackService = "ASSETS",
|
|
648
650
|
watch = false,
|
|
649
651
|
onEnd,
|
|
650
652
|
}: {
|
|
651
653
|
scriptPath: string;
|
|
654
|
+
outputConfigPath?: string;
|
|
652
655
|
functionsDirectory: string;
|
|
653
656
|
minify?: boolean;
|
|
654
657
|
sourcemap?: boolean;
|
|
658
|
+
fallbackService?: string;
|
|
655
659
|
watch?: boolean;
|
|
656
660
|
onEnd?: () => void;
|
|
657
661
|
}) {
|
|
@@ -666,6 +670,13 @@ async function buildFunctions({
|
|
|
666
670
|
baseURL: "/",
|
|
667
671
|
});
|
|
668
672
|
|
|
673
|
+
if (outputConfigPath) {
|
|
674
|
+
writeFileSync(
|
|
675
|
+
outputConfigPath,
|
|
676
|
+
JSON.stringify({ ...config, baseURL: "/" }, null, 2)
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
|
|
669
680
|
await writeRoutesModule({
|
|
670
681
|
config,
|
|
671
682
|
srcDir: functionsDirectory,
|
|
@@ -678,6 +689,7 @@ async function buildFunctions({
|
|
|
678
689
|
outfile: scriptPath,
|
|
679
690
|
minify,
|
|
680
691
|
sourcemap,
|
|
692
|
+
fallbackService,
|
|
681
693
|
watch,
|
|
682
694
|
onEnd,
|
|
683
695
|
})
|
|
@@ -738,6 +750,11 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
738
750
|
description: "Durable Object to bind (NAME=CLASS)",
|
|
739
751
|
alias: "o",
|
|
740
752
|
},
|
|
753
|
+
"live-reload": {
|
|
754
|
+
type: "boolean",
|
|
755
|
+
default: false,
|
|
756
|
+
description: "Auto reload HTML pages when change is detected",
|
|
757
|
+
},
|
|
741
758
|
// TODO: Miniflare user options
|
|
742
759
|
});
|
|
743
760
|
},
|
|
@@ -750,6 +767,7 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
750
767
|
binding: bindings = [],
|
|
751
768
|
kv: kvs = [],
|
|
752
769
|
do: durableObjects = [],
|
|
770
|
+
"live-reload": liveReload,
|
|
753
771
|
"--": remaining = [],
|
|
754
772
|
}) => {
|
|
755
773
|
if (!local) {
|
|
@@ -774,7 +792,10 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
774
792
|
|
|
775
793
|
let miniflareArgs: MiniflareOptions = {};
|
|
776
794
|
|
|
777
|
-
let
|
|
795
|
+
let scriptReadyResolve;
|
|
796
|
+
const scriptReadyPromise = new Promise(
|
|
797
|
+
(resolve) => (scriptReadyResolve = resolve)
|
|
798
|
+
);
|
|
778
799
|
|
|
779
800
|
if (usingFunctions) {
|
|
780
801
|
const scriptPath = join(tmpdir(), "./functionsWorker.js");
|
|
@@ -786,22 +807,19 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
786
807
|
functionsDirectory,
|
|
787
808
|
sourcemap: true,
|
|
788
809
|
watch: true,
|
|
789
|
-
onEnd: () =>
|
|
790
|
-
scriptReady = true;
|
|
791
|
-
},
|
|
810
|
+
onEnd: () => scriptReadyResolve(),
|
|
792
811
|
});
|
|
793
812
|
|
|
794
813
|
watch([functionsDirectory], {
|
|
795
814
|
persistent: true,
|
|
815
|
+
ignoreInitial: true,
|
|
796
816
|
}).on("all", async () => {
|
|
797
817
|
await buildFunctions({
|
|
798
818
|
scriptPath,
|
|
799
819
|
functionsDirectory,
|
|
800
820
|
sourcemap: true,
|
|
801
821
|
watch: true,
|
|
802
|
-
onEnd: () =>
|
|
803
|
-
scriptReady = true;
|
|
804
|
-
},
|
|
822
|
+
onEnd: () => scriptReadyResolve(),
|
|
805
823
|
});
|
|
806
824
|
});
|
|
807
825
|
|
|
@@ -838,6 +856,12 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
838
856
|
const { Miniflare, Log, LogLevel } = await import("miniflare");
|
|
839
857
|
const { Response, fetch } = await import("@miniflare/core");
|
|
840
858
|
|
|
859
|
+
// Wait for esbuild to finish building before starting Miniflare.
|
|
860
|
+
// This must be before the call to `new Miniflare`, as that will
|
|
861
|
+
// asynchronously start loading the script. `await startServer()`
|
|
862
|
+
// internally just waits for that promise to resolve.
|
|
863
|
+
await scriptReadyPromise;
|
|
864
|
+
|
|
841
865
|
// Should only be called if no proxyPort, using `assert.fail()` here
|
|
842
866
|
// means the type of `assetsFetch` is still `typeof fetch`
|
|
843
867
|
const assetsFetch = proxyPort
|
|
@@ -903,13 +927,11 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
903
927
|
kvPersist: true,
|
|
904
928
|
durableObjectsPersist: true,
|
|
905
929
|
cachePersist: true,
|
|
930
|
+
liveReload,
|
|
906
931
|
|
|
907
932
|
...miniflareArgs,
|
|
908
933
|
});
|
|
909
934
|
|
|
910
|
-
// Wait for esbuild to finish building the script
|
|
911
|
-
while (!scriptReady) {}
|
|
912
|
-
|
|
913
935
|
try {
|
|
914
936
|
// `startServer` might throw if user code contains errors
|
|
915
937
|
const server = await miniflare.startServer();
|
|
@@ -921,6 +943,15 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
921
943
|
} catch {}
|
|
922
944
|
}
|
|
923
945
|
|
|
946
|
+
if (directory !== undefined && liveReload) {
|
|
947
|
+
watch([directory], {
|
|
948
|
+
persistent: true,
|
|
949
|
+
ignoreInitial: true,
|
|
950
|
+
}).on("all", async () => {
|
|
951
|
+
await miniflare.reload();
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
|
|
924
955
|
EXIT_CALLBACKS.push(() => {
|
|
925
956
|
server.close();
|
|
926
957
|
miniflare.dispose().catch((err) => miniflare.log.error(err));
|
|
@@ -933,34 +964,66 @@ export const pages: BuilderCallback<unknown, unknown> = (yargs) => {
|
|
|
933
964
|
)
|
|
934
965
|
.command("functions", "Cloudflare Pages Functions", (yargs) =>
|
|
935
966
|
yargs.command(
|
|
936
|
-
"build",
|
|
967
|
+
"build [directory]",
|
|
937
968
|
"Compile a folder of Cloudflare Pages Functions into a single Worker",
|
|
938
969
|
(yargs) =>
|
|
939
|
-
yargs
|
|
940
|
-
"
|
|
970
|
+
yargs
|
|
971
|
+
.positional("directory", {
|
|
941
972
|
type: "string",
|
|
942
|
-
default: "
|
|
943
|
-
description: "The
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
973
|
+
default: "functions",
|
|
974
|
+
description: "The directory of Pages Functions",
|
|
975
|
+
})
|
|
976
|
+
.options({
|
|
977
|
+
"script-path": {
|
|
978
|
+
type: "string",
|
|
979
|
+
default: "_worker.js",
|
|
980
|
+
description: "The location of the output Worker script",
|
|
981
|
+
},
|
|
982
|
+
"output-config-path": {
|
|
983
|
+
type: "string",
|
|
984
|
+
description: "The location for the output config file",
|
|
985
|
+
},
|
|
986
|
+
minify: {
|
|
987
|
+
type: "boolean",
|
|
988
|
+
default: false,
|
|
989
|
+
description: "Minify the output Worker script",
|
|
990
|
+
},
|
|
991
|
+
sourcemap: {
|
|
992
|
+
type: "boolean",
|
|
993
|
+
default: false,
|
|
994
|
+
description:
|
|
995
|
+
"Generate a sourcemap for the output Worker script",
|
|
996
|
+
},
|
|
997
|
+
"fallback-service": {
|
|
998
|
+
type: "string",
|
|
999
|
+
default: "ASSETS",
|
|
1000
|
+
description:
|
|
1001
|
+
"The service to fallback to at the end of the `next` chain. Setting to '' will fallback to the global `fetch`.",
|
|
1002
|
+
},
|
|
1003
|
+
watch: {
|
|
1004
|
+
type: "boolean",
|
|
1005
|
+
default: false,
|
|
1006
|
+
description:
|
|
1007
|
+
"Watch for changes to the functions and automatically rebuild the Worker script",
|
|
1008
|
+
},
|
|
1009
|
+
}),
|
|
1010
|
+
async ({
|
|
1011
|
+
directory,
|
|
1012
|
+
"script-path": scriptPath,
|
|
1013
|
+
"output-config-path": outputConfigPath,
|
|
1014
|
+
minify,
|
|
1015
|
+
sourcemap,
|
|
1016
|
+
fallbackService,
|
|
1017
|
+
watch,
|
|
1018
|
+
}) => {
|
|
959
1019
|
await buildFunctions({
|
|
960
1020
|
scriptPath,
|
|
961
|
-
|
|
1021
|
+
outputConfigPath,
|
|
1022
|
+
functionsDirectory: directory,
|
|
962
1023
|
minify,
|
|
963
1024
|
sourcemap,
|
|
1025
|
+
fallbackService,
|
|
1026
|
+
watch,
|
|
964
1027
|
});
|
|
965
1028
|
}
|
|
966
1029
|
)
|
package/src/proxy.ts
CHANGED
|
@@ -1,97 +1,288 @@
|
|
|
1
1
|
import { connect } from "node:http2";
|
|
2
|
+
import type { ServerHttp2Stream } from "node:http2";
|
|
2
3
|
import { createServer } from "node:http";
|
|
3
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
IncomingHttpHeaders,
|
|
6
|
+
RequestListener,
|
|
7
|
+
IncomingMessage,
|
|
8
|
+
ServerResponse,
|
|
9
|
+
Server,
|
|
10
|
+
} from "node:http";
|
|
4
11
|
import WebSocket from "faye-websocket";
|
|
5
12
|
import serveStatic from "serve-static";
|
|
13
|
+
import type { CfPreviewToken } from "./api/preview";
|
|
14
|
+
import { useEffect, useRef } from "react";
|
|
6
15
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
/**
|
|
17
|
+
* `usePreviewServer` is a React hook that creates a local development
|
|
18
|
+
* server that can be used to develop a Worker.
|
|
19
|
+
*
|
|
20
|
+
* When we run `wrangler dev`, we start by uploading the compiled worker
|
|
21
|
+
* to the preview service, which responds with a preview token.
|
|
22
|
+
* (see `useWorker`/`createWorker` for details.)
|
|
23
|
+
* We can then use that token to connect to the preview server for a
|
|
24
|
+
* great local development experience. Further, as we change the worker,
|
|
25
|
+
* we can update the preview token transparently without having to restart
|
|
26
|
+
* the development server.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/** Rewrite request headers to add the preview token. */
|
|
30
|
+
function addCfPreviewTokenHeader(
|
|
31
|
+
headers: IncomingHttpHeaders,
|
|
32
|
+
previewTokenValue: string
|
|
33
|
+
) {
|
|
34
|
+
headers["cf-workers-preview-token"] = previewTokenValue;
|
|
12
35
|
}
|
|
13
36
|
|
|
14
37
|
/**
|
|
15
|
-
*
|
|
38
|
+
* Rewrite references in request headers
|
|
39
|
+
* from the preview host to the local host.
|
|
16
40
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
stream.respond(responseHeaders);
|
|
29
|
-
request.pipe(stream, { end: true });
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
// HTTP/1 -> HTTP/2
|
|
33
|
-
const handleRequest: RequestListener = (message, response) => {
|
|
34
|
-
const { httpVersionMajor, headers, method, url } = message;
|
|
35
|
-
if (httpVersionMajor >= 2) {
|
|
36
|
-
return; // Already handled by the "stream" event.
|
|
41
|
+
function rewriteRemoteHostToLocalHostInHeaders(
|
|
42
|
+
headers: IncomingHttpHeaders,
|
|
43
|
+
remoteHost: string,
|
|
44
|
+
localPort: number
|
|
45
|
+
) {
|
|
46
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
47
|
+
// Rewrite the remote host to the local host.
|
|
48
|
+
if (typeof value === "string" && value.includes(remoteHost)) {
|
|
49
|
+
headers[name] = value
|
|
50
|
+
.replaceAll(`https://${remoteHost}`, `http://localhost:${localPort}`)
|
|
51
|
+
.replaceAll(remoteHost, `localhost:${localPort}`);
|
|
37
52
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function usePreviewServer({
|
|
57
|
+
previewToken,
|
|
58
|
+
publicRoot,
|
|
59
|
+
port,
|
|
60
|
+
}: {
|
|
61
|
+
previewToken: CfPreviewToken | undefined;
|
|
62
|
+
publicRoot: undefined | string;
|
|
63
|
+
port: number;
|
|
64
|
+
}) {
|
|
65
|
+
/** Creates an HTTP/1 proxy that sends requests over HTTP/2. */
|
|
66
|
+
const proxyServer = useRef<Server>();
|
|
67
|
+
if (!proxyServer.current) {
|
|
68
|
+
proxyServer.current = createServer()
|
|
69
|
+
.on("request", function (req, res) {
|
|
70
|
+
// log all requests
|
|
71
|
+
console.log(
|
|
72
|
+
new Date().toLocaleTimeString(),
|
|
73
|
+
req.method,
|
|
74
|
+
req.url,
|
|
75
|
+
res.statusCode
|
|
76
|
+
);
|
|
77
|
+
})
|
|
78
|
+
.on("upgrade", (req) => {
|
|
79
|
+
// log all websocket connections
|
|
80
|
+
console.log(
|
|
81
|
+
new Date().toLocaleTimeString(),
|
|
82
|
+
req.method,
|
|
83
|
+
req.url,
|
|
84
|
+
101,
|
|
85
|
+
"(WebSocket)"
|
|
86
|
+
);
|
|
87
|
+
})
|
|
88
|
+
.on("error", (err) => {
|
|
89
|
+
// log all connection errors
|
|
90
|
+
console.error(new Date().toLocaleTimeString(), err);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* When we're not connected / getting a fresh token on changes,
|
|
96
|
+
* we'd like to buffer streams/requests until we're connected.
|
|
97
|
+
* Once connected, we can flush the buffered streams/requests.
|
|
98
|
+
* streamBufferRef is used to buffer http/2 streams, while
|
|
99
|
+
* requestResponseBufferRef is used to buffer http/1 requests.
|
|
100
|
+
*/
|
|
101
|
+
const streamBufferRef = useRef<
|
|
102
|
+
{ stream: ServerHttp2Stream; headers: IncomingHttpHeaders }[]
|
|
103
|
+
>([]);
|
|
104
|
+
const requestResponseBufferRef = useRef<
|
|
105
|
+
{ request: IncomingMessage; response: ServerResponse }[]
|
|
106
|
+
>([]);
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
const proxy = proxyServer.current;
|
|
110
|
+
|
|
111
|
+
// If we don't have a token, that means either we're just starting up,
|
|
112
|
+
// or we're refreshing the token.
|
|
113
|
+
if (!previewToken) {
|
|
114
|
+
const cleanupListeners: (() => void)[] = [];
|
|
115
|
+
const bufferStream = (
|
|
116
|
+
stream: ServerHttp2Stream,
|
|
117
|
+
headers: IncomingHttpHeaders
|
|
118
|
+
) => {
|
|
119
|
+
// store the stream in a buffer so we can replay it later
|
|
120
|
+
streamBufferRef.current.push({ stream, headers });
|
|
121
|
+
};
|
|
122
|
+
proxy.on("stream", bufferStream);
|
|
123
|
+
cleanupListeners.push(() => proxy.off("stream", bufferStream));
|
|
124
|
+
|
|
125
|
+
const bufferRequestResponse = (
|
|
126
|
+
request: IncomingMessage,
|
|
127
|
+
response: ServerResponse
|
|
128
|
+
) => {
|
|
129
|
+
// store the request and response in a buffer so we can replay it later
|
|
130
|
+
requestResponseBufferRef.current.push({ request, response });
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
proxy.on("request", bufferRequestResponse);
|
|
134
|
+
cleanupListeners.push(() => proxy.off("request", bufferRequestResponse));
|
|
135
|
+
return () => {
|
|
136
|
+
cleanupListeners.forEach((cleanup) => cleanup());
|
|
137
|
+
};
|
|
47
138
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
139
|
+
|
|
140
|
+
// We have a token. Let's proxy requests to the preview end point.
|
|
141
|
+
const cleanupListeners: (() => void)[] = [];
|
|
142
|
+
|
|
143
|
+
const assetPath = typeof publicRoot === "string" ? publicRoot : null;
|
|
144
|
+
|
|
145
|
+
// create a ClientHttp2Session
|
|
146
|
+
const remote = connect(`https://${previewToken.host}`);
|
|
147
|
+
cleanupListeners.push(() => remote.destroy());
|
|
148
|
+
|
|
149
|
+
/** HTTP/2 -> HTTP/2 */
|
|
150
|
+
function handleStream(
|
|
151
|
+
stream: ServerHttp2Stream,
|
|
152
|
+
headers: IncomingHttpHeaders
|
|
153
|
+
) {
|
|
154
|
+
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
155
|
+
headers[":authority"] = previewToken.host;
|
|
156
|
+
const request = stream.pipe(remote.request(headers));
|
|
157
|
+
request.on("response", (responseHeaders: IncomingHttpHeaders) => {
|
|
158
|
+
rewriteRemoteHostToLocalHostInHeaders(
|
|
159
|
+
responseHeaders,
|
|
160
|
+
previewToken.host,
|
|
161
|
+
port
|
|
162
|
+
);
|
|
163
|
+
stream.respond(responseHeaders);
|
|
164
|
+
request.pipe(stream, { end: true });
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
proxy.on("stream", handleStream);
|
|
168
|
+
cleanupListeners.push(() => proxy.off("stream", handleStream));
|
|
169
|
+
|
|
170
|
+
// flush and replay buffered streams
|
|
171
|
+
streamBufferRef.current.forEach((buffer) =>
|
|
172
|
+
handleStream(buffer.stream, buffer.headers)
|
|
173
|
+
);
|
|
174
|
+
streamBufferRef.current = [];
|
|
175
|
+
|
|
176
|
+
/** HTTP/1 -> HTTP/2 */
|
|
177
|
+
const handleRequest: RequestListener = (
|
|
178
|
+
message: IncomingMessage,
|
|
179
|
+
response: ServerResponse
|
|
180
|
+
) => {
|
|
181
|
+
const { httpVersionMajor, headers, method, url } = message;
|
|
182
|
+
if (httpVersionMajor >= 2) {
|
|
183
|
+
return; // Already handled by the "stream" event.
|
|
184
|
+
}
|
|
185
|
+
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
186
|
+
headers[":method"] = method;
|
|
187
|
+
headers[":path"] = url;
|
|
188
|
+
headers[":authority"] = previewToken.host;
|
|
189
|
+
headers[":scheme"] = "https";
|
|
190
|
+
for (const name of Object.keys(headers)) {
|
|
191
|
+
if (HTTP1_HEADERS.has(name.toLowerCase())) {
|
|
192
|
+
delete headers[name];
|
|
55
193
|
}
|
|
56
194
|
}
|
|
57
|
-
|
|
58
|
-
request.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
195
|
+
const request = message.pipe(remote.request(headers));
|
|
196
|
+
request.on("response", (responseHeaders) => {
|
|
197
|
+
const status = responseHeaders[":status"];
|
|
198
|
+
rewriteRemoteHostToLocalHostInHeaders(
|
|
199
|
+
responseHeaders,
|
|
200
|
+
previewToken.host,
|
|
201
|
+
port
|
|
202
|
+
);
|
|
203
|
+
for (const name of Object.keys(responseHeaders)) {
|
|
204
|
+
if (name.startsWith(":")) {
|
|
205
|
+
delete responseHeaders[name];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
response.writeHead(status, responseHeaders);
|
|
209
|
+
request.pipe(response, { end: true });
|
|
70
210
|
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
{ headers }
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// If an asset path is defined, check the file system
|
|
214
|
+
// for a file first and serve if it exists.
|
|
215
|
+
const actualHandleRequest = assetPath
|
|
216
|
+
? createHandleAssetsRequest(assetPath, handleRequest)
|
|
217
|
+
: handleRequest;
|
|
218
|
+
|
|
219
|
+
proxy.on("request", actualHandleRequest);
|
|
220
|
+
cleanupListeners.push(() => proxy.off("request", actualHandleRequest));
|
|
221
|
+
|
|
222
|
+
// flush and replay buffered requests
|
|
223
|
+
requestResponseBufferRef.current.forEach(({ request, response }) =>
|
|
224
|
+
actualHandleRequest(request, response)
|
|
86
225
|
);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
226
|
+
requestResponseBufferRef.current = [];
|
|
227
|
+
|
|
228
|
+
/** HTTP/1 -> WebSocket (over HTTP/1) */
|
|
229
|
+
const handleUpgrade = (
|
|
230
|
+
message: IncomingMessage,
|
|
231
|
+
socket: WebSocket,
|
|
232
|
+
body: Buffer
|
|
233
|
+
) => {
|
|
234
|
+
const { headers, url } = message;
|
|
235
|
+
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
236
|
+
headers["host"] = previewToken.host;
|
|
237
|
+
const localWebsocket = new WebSocket(message, socket, body);
|
|
238
|
+
// TODO(soon): Custom WebSocket protocol is not working?
|
|
239
|
+
const remoteWebsocketClient = new WebSocket.Client(
|
|
240
|
+
`wss://${previewToken.host}${url}`,
|
|
241
|
+
[],
|
|
242
|
+
{ headers }
|
|
243
|
+
);
|
|
244
|
+
localWebsocket.pipe(remoteWebsocketClient).pipe(localWebsocket);
|
|
245
|
+
// We close down websockets whenever we refresh the token.
|
|
246
|
+
cleanupListeners.push(() => {
|
|
247
|
+
localWebsocket.destroy();
|
|
248
|
+
remoteWebsocketClient.destroy();
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
proxy.on("upgrade", handleUpgrade);
|
|
252
|
+
cleanupListeners.push(() => proxy.off("upgrade", handleUpgrade));
|
|
253
|
+
|
|
254
|
+
return () => {
|
|
255
|
+
cleanupListeners.forEach((d) => d());
|
|
256
|
+
};
|
|
257
|
+
}, [previewToken, publicRoot, port]);
|
|
258
|
+
|
|
259
|
+
// Start/stop the server whenever the
|
|
260
|
+
// containing component is mounted/unmounted.
|
|
261
|
+
useEffect(() => {
|
|
262
|
+
proxyServer.current.listen(port);
|
|
263
|
+
console.log(`⬣ Listening at http://localhost:${port}`);
|
|
264
|
+
|
|
265
|
+
return () => {
|
|
266
|
+
proxyServer.current.close();
|
|
267
|
+
};
|
|
268
|
+
}, [port]);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function createHandleAssetsRequest(
|
|
272
|
+
assetPath: string,
|
|
273
|
+
handleRequest: RequestListener
|
|
274
|
+
) {
|
|
275
|
+
const handleAsset = serveStatic(assetPath, {
|
|
276
|
+
cacheControl: false,
|
|
91
277
|
});
|
|
92
|
-
return
|
|
278
|
+
return (request: IncomingMessage, response: ServerResponse) => {
|
|
279
|
+
handleAsset(request, response, () => {
|
|
280
|
+
handleRequest(request, response);
|
|
281
|
+
});
|
|
282
|
+
};
|
|
93
283
|
}
|
|
94
284
|
|
|
285
|
+
/** A Set of headers we want to remove from HTTP/1 requests. */
|
|
95
286
|
const HTTP1_HEADERS = new Set([
|
|
96
287
|
"host",
|
|
97
288
|
"connection",
|
package/src/publish.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { Config } from "./config";
|
|
|
11
11
|
import makeModuleCollector from "./module-collection";
|
|
12
12
|
import { syncAssets } from "./sites";
|
|
13
13
|
|
|
14
|
-
type CfScriptFormat =
|
|
14
|
+
type CfScriptFormat = undefined | "modules" | "service-worker";
|
|
15
15
|
|
|
16
16
|
type Props = {
|
|
17
17
|
config: Config;
|
|
@@ -19,13 +19,15 @@ type Props = {
|
|
|
19
19
|
script?: string;
|
|
20
20
|
name?: string;
|
|
21
21
|
env?: string;
|
|
22
|
+
compatibilityDate?: string;
|
|
23
|
+
compatibilityFlags?: string[];
|
|
22
24
|
public?: string;
|
|
23
25
|
site?: string;
|
|
24
26
|
triggers?: (string | number)[];
|
|
25
27
|
routes?: (string | number)[];
|
|
26
28
|
legacyEnv?: boolean;
|
|
27
|
-
jsxFactory:
|
|
28
|
-
jsxFragment:
|
|
29
|
+
jsxFactory: undefined | string;
|
|
30
|
+
jsxFragment: undefined | string;
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
function sleep(ms: number) {
|
|
@@ -48,6 +50,13 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
48
50
|
__path__,
|
|
49
51
|
} = config;
|
|
50
52
|
|
|
53
|
+
const envRootObj = props.env ? config.env[props.env] || {} : config;
|
|
54
|
+
|
|
55
|
+
assert(
|
|
56
|
+
envRootObj.compatibility_date || props["compatibility-date"],
|
|
57
|
+
"A compatibility_date is required when publishing. Add one to your wrangler.toml file, or pass it in your terminal as --compatibility_date. See https://developers.cloudflare.com/workers/platform/compatibility-dates for more information."
|
|
58
|
+
);
|
|
59
|
+
|
|
51
60
|
const triggers = props.triggers || config.triggers?.crons;
|
|
52
61
|
const routes = props.routes || config.routes;
|
|
53
62
|
|
|
@@ -125,7 +134,7 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
125
134
|
entryPoint ===
|
|
126
135
|
(props.public
|
|
127
136
|
? path.join(path.dirname(file), "static-asset-facade.js")
|
|
128
|
-
:
|
|
137
|
+
: Object.keys(result.metafile.inputs)[0])
|
|
129
138
|
);
|
|
130
139
|
|
|
131
140
|
const { format } = props;
|
|
@@ -200,7 +209,6 @@ export default async function publish(props: Props): Promise<void> {
|
|
|
200
209
|
)
|
|
201
210
|
: { manifest: undefined, namespace: undefined };
|
|
202
211
|
|
|
203
|
-
const envRootObj = props.env ? config.env[props.env] || {} : config;
|
|
204
212
|
const bindings: CfWorkerInit["bindings"] = {
|
|
205
213
|
kv_namespaces: envRootObj.kv_namespaces?.concat(
|
|
206
214
|
assets.namespace
|