wrangler 0.0.0-e2a26a0 → 0.0.0-e2b7352
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/README.md +3 -3
- package/bin/wrangler.js +10 -1
- package/miniflare-dist/index.mjs +79 -0
- package/miniflare-dist/index.mjs.map +7 -0
- package/package.json +24 -12
- package/pages/functions/filepath-routing.test.ts +212 -36
- package/pages/functions/filepath-routing.ts +69 -119
- package/pages/functions/routes.ts +8 -17
- package/pages/functions/template-worker.ts +3 -9
- package/src/__tests__/configuration.test.ts +2820 -0
- package/src/__tests__/dev.test.tsx +535 -65
- package/src/__tests__/guess-worker-format.test.ts +68 -0
- package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
- package/src/__tests__/helpers/mock-account-id.ts +26 -8
- package/src/__tests__/helpers/mock-bin.ts +4 -1
- package/src/__tests__/helpers/mock-cfetch.ts +5 -5
- package/src/__tests__/helpers/mock-console.ts +21 -7
- package/src/__tests__/helpers/mock-kv.ts +9 -16
- package/src/__tests__/helpers/mock-stdin.ts +103 -0
- package/src/__tests__/helpers/mock-web-socket.ts +29 -0
- package/src/__tests__/helpers/run-in-tmp.ts +3 -0
- package/src/__tests__/helpers/run-wrangler.ts +5 -3
- package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
- package/src/__tests__/https-options.test.ts +127 -0
- package/src/__tests__/index.test.ts +467 -27
- package/src/__tests__/jest.setup.ts +37 -5
- package/src/__tests__/kv.test.ts +442 -201
- package/src/__tests__/logout.test.ts +15 -13
- package/src/__tests__/package-manager.test.ts +2 -2
- package/src/__tests__/pages.test.ts +82 -0
- package/src/__tests__/parse.test.ts +322 -0
- package/src/__tests__/publish.test.ts +2717 -430
- package/src/__tests__/r2.test.ts +198 -0
- package/src/__tests__/route.test.ts +45 -0
- package/src/__tests__/secret.test.ts +301 -86
- package/src/__tests__/sentry.test.ts +146 -0
- package/src/__tests__/tail.test.ts +638 -0
- package/src/__tests__/whoami.test.tsx +21 -7
- package/src/bundle.ts +155 -0
- package/src/cfetch/index.ts +8 -6
- package/src/cfetch/internal.ts +60 -22
- package/src/cli.ts +10 -0
- package/src/config/README.md +107 -0
- package/src/config/config.ts +218 -0
- package/src/config/diagnostics.ts +80 -0
- package/src/config/environment.ts +371 -0
- package/src/config/index.ts +62 -0
- package/src/config/validation-helpers.ts +554 -0
- package/src/config/validation.ts +1232 -0
- package/src/{api/preview.ts → create-worker-preview.ts} +59 -21
- package/src/create-worker-upload-form.ts +217 -0
- package/src/dev/dev.tsx +392 -0
- package/src/dev/local.tsx +232 -0
- package/src/dev/remote.tsx +203 -0
- package/src/dev/use-esbuild.ts +104 -0
- package/src/entry.ts +240 -0
- package/src/environment-variables.ts +33 -0
- package/src/https-options.ts +122 -0
- package/src/index.tsx +1068 -780
- package/src/inspect.ts +25 -4
- package/src/intl-polyfill.d.ts +139 -0
- package/src/{kv.tsx → kv.ts} +95 -60
- package/src/miniflare-cli/README.md +30 -0
- package/src/miniflare-cli/enum-keys.ts +17 -0
- package/src/miniflare-cli/index.ts +37 -0
- package/src/module-collection.ts +226 -31
- package/src/open-in-browser.ts +25 -0
- package/src/package-manager.ts +21 -15
- package/src/pages.tsx +35 -20
- package/src/parse.ts +183 -0
- package/src/proxy.ts +115 -45
- package/src/publish.ts +268 -328
- package/src/r2.ts +50 -0
- package/src/reporting.ts +131 -0
- package/src/selfsigned.d.ts +29 -0
- package/src/sites.tsx +83 -43
- package/src/tail/filters.ts +279 -0
- package/src/tail/index.ts +299 -0
- package/src/tail/printing.ts +62 -0
- package/src/user.tsx +234 -126
- package/src/{api/worker.ts → worker.ts} +44 -36
- package/templates/new-worker.js +1 -1
- package/templates/new-worker.ts +1 -1
- package/templates/tsconfig.json +105 -0
- package/wrangler-dist/cli.js +131202 -114243
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/helpers/mock-user.ts +0 -27
- package/src/api/form_data.ts +0 -134
- package/src/config.ts +0 -641
- package/src/dev.tsx +0 -834
- package/src/tail.tsx +0 -73
package/src/parse.ts
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import TOML from "@iarna/toml";
|
|
4
|
+
import { formatMessagesSync } from "esbuild";
|
|
5
|
+
|
|
6
|
+
export type Message = {
|
|
7
|
+
text: string;
|
|
8
|
+
location?: Location;
|
|
9
|
+
notes?: Message[];
|
|
10
|
+
kind?: "warning" | "error";
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type Location = File & {
|
|
14
|
+
line: number;
|
|
15
|
+
column: number;
|
|
16
|
+
length?: number;
|
|
17
|
+
lineText?: string;
|
|
18
|
+
suggestion?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type File = {
|
|
22
|
+
file?: string;
|
|
23
|
+
fileText?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Formats a `Message` using esbuild's pretty-printing algorithm.
|
|
28
|
+
*/
|
|
29
|
+
export function formatMessage(
|
|
30
|
+
{ text, notes, location, kind = "error" }: Message,
|
|
31
|
+
color = true
|
|
32
|
+
): string {
|
|
33
|
+
const input = { text, notes, location };
|
|
34
|
+
delete input.location?.fileText;
|
|
35
|
+
for (const note of notes ?? []) {
|
|
36
|
+
delete note.location?.fileText;
|
|
37
|
+
}
|
|
38
|
+
const lines = formatMessagesSync([input], {
|
|
39
|
+
color,
|
|
40
|
+
kind: kind,
|
|
41
|
+
terminalWidth: process.stderr.columns,
|
|
42
|
+
});
|
|
43
|
+
return lines.join("\n");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* An error that's thrown when something fails to parse.
|
|
48
|
+
*/
|
|
49
|
+
export class ParseError extends Error implements Message {
|
|
50
|
+
readonly text: string;
|
|
51
|
+
readonly notes: Message[];
|
|
52
|
+
readonly location?: Location;
|
|
53
|
+
readonly kind: "warning" | "error";
|
|
54
|
+
|
|
55
|
+
constructor({ text, notes, location, kind }: Message) {
|
|
56
|
+
super(text);
|
|
57
|
+
this.name = this.constructor.name;
|
|
58
|
+
this.text = text;
|
|
59
|
+
this.notes = notes ?? [];
|
|
60
|
+
this.location = location;
|
|
61
|
+
this.kind = kind ?? "error";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
66
|
+
|
|
67
|
+
const TOML_ERROR_NAME = "TomlError";
|
|
68
|
+
const TOML_ERROR_SUFFIX = " at row ";
|
|
69
|
+
|
|
70
|
+
type TomlError = Error & {
|
|
71
|
+
line: number;
|
|
72
|
+
col: number;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A wrapper around `TOML.parse` that throws a `ParseError`.
|
|
77
|
+
*/
|
|
78
|
+
export function parseTOML(input: string, file?: string): object {
|
|
79
|
+
try {
|
|
80
|
+
return TOML.parse(input);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
const { name, message, line, col } = err as TomlError;
|
|
83
|
+
if (name !== TOML_ERROR_NAME) {
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
const text = message.substring(0, message.lastIndexOf(TOML_ERROR_SUFFIX));
|
|
87
|
+
const lineText = input.split("\n")[line];
|
|
88
|
+
const location = {
|
|
89
|
+
lineText,
|
|
90
|
+
line: line + 1,
|
|
91
|
+
column: col - 1,
|
|
92
|
+
file,
|
|
93
|
+
fileText: input,
|
|
94
|
+
};
|
|
95
|
+
throw new ParseError({ text, location });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const JSON_ERROR_SUFFIX = " in JSON at position ";
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A wrapper around `JSON.parse` that throws a `ParseError`.
|
|
103
|
+
*/
|
|
104
|
+
export function parseJSON(input: string, file?: string): any {
|
|
105
|
+
try {
|
|
106
|
+
return JSON.parse(input);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
const { message } = err as Error;
|
|
109
|
+
const index = message.lastIndexOf(JSON_ERROR_SUFFIX);
|
|
110
|
+
if (index < 0) {
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
const text = message.substring(0, index);
|
|
114
|
+
const position = parseInt(
|
|
115
|
+
message.substring(index + JSON_ERROR_SUFFIX.length)
|
|
116
|
+
);
|
|
117
|
+
const location = indexLocation({ file, fileText: input }, position);
|
|
118
|
+
throw new ParseError({ text, location });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Reads a file and parses it based on its type.
|
|
124
|
+
*/
|
|
125
|
+
export function readFileSync(file: string): string {
|
|
126
|
+
try {
|
|
127
|
+
return fs.readFileSync(file, { encoding: "utf-8" });
|
|
128
|
+
} catch (err) {
|
|
129
|
+
const { message } = err as Error;
|
|
130
|
+
throw new ParseError({
|
|
131
|
+
text: `Could not read file: ${file}`,
|
|
132
|
+
notes: [
|
|
133
|
+
{
|
|
134
|
+
text: message.replace(file, resolve(file)),
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Calculates the line and column location from an index.
|
|
143
|
+
*/
|
|
144
|
+
export function indexLocation(file: File, index: number): Location {
|
|
145
|
+
let lineText,
|
|
146
|
+
line = 0,
|
|
147
|
+
column = 0,
|
|
148
|
+
cursor = 0;
|
|
149
|
+
const { fileText = "" } = file;
|
|
150
|
+
for (const row of fileText.split("\n")) {
|
|
151
|
+
line++;
|
|
152
|
+
cursor += row.length + 1;
|
|
153
|
+
if (cursor >= index) {
|
|
154
|
+
lineText = row;
|
|
155
|
+
column = row.length - (cursor - index);
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return { lineText, line, column, ...file };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Guesses the line and column location of a search query.
|
|
164
|
+
*/
|
|
165
|
+
export function searchLocation(file: File, query: unknown): Location {
|
|
166
|
+
let lineText,
|
|
167
|
+
length,
|
|
168
|
+
line = 0,
|
|
169
|
+
column = 0;
|
|
170
|
+
const queryText = String(query);
|
|
171
|
+
const { fileText = "" } = file;
|
|
172
|
+
for (const content of fileText.split("\n")) {
|
|
173
|
+
line++;
|
|
174
|
+
const index = content.indexOf(queryText);
|
|
175
|
+
if (index >= 0) {
|
|
176
|
+
lineText = content;
|
|
177
|
+
column = index;
|
|
178
|
+
length = queryText.length;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { lineText, line, column, length, ...file };
|
|
183
|
+
}
|
package/src/proxy.ts
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
|
-
import { createServer } from "node:http";
|
|
1
|
+
import { createServer as createHttpServer } from "node:http";
|
|
2
2
|
import { connect } from "node:http2";
|
|
3
|
+
import { createServer as createHttpsServer } from "node:https";
|
|
3
4
|
import WebSocket from "faye-websocket";
|
|
4
5
|
import { useEffect, useRef, useState } from "react";
|
|
5
6
|
import serveStatic from "serve-static";
|
|
6
|
-
import
|
|
7
|
+
import { getHttpsOptions } from "./https-options";
|
|
8
|
+
import { reportError } from "./reporting";
|
|
9
|
+
import type { CfPreviewToken } from "./create-worker-preview";
|
|
7
10
|
import type {
|
|
8
11
|
IncomingHttpHeaders,
|
|
9
12
|
RequestListener,
|
|
10
13
|
IncomingMessage,
|
|
11
14
|
ServerResponse,
|
|
12
|
-
Server,
|
|
15
|
+
Server as HttpServer,
|
|
13
16
|
} from "node:http";
|
|
14
17
|
import type { ClientHttp2Session, ServerHttp2Stream } from "node:http2";
|
|
18
|
+
import type { Server as HttpsServer } from "node:https";
|
|
19
|
+
import type ws from "ws";
|
|
20
|
+
|
|
21
|
+
interface IWebsocket extends ws {
|
|
22
|
+
// Pipe implements .on("message", ...)
|
|
23
|
+
pipe<T>(fn: T): IWebsocket;
|
|
24
|
+
}
|
|
15
25
|
|
|
16
26
|
/**
|
|
17
27
|
* `usePreviewServer` is a React hook that creates a local development
|
|
@@ -41,13 +51,17 @@ function addCfPreviewTokenHeader(
|
|
|
41
51
|
function rewriteRemoteHostToLocalHostInHeaders(
|
|
42
52
|
headers: IncomingHttpHeaders,
|
|
43
53
|
remoteHost: string,
|
|
44
|
-
localPort: number
|
|
54
|
+
localPort: number,
|
|
55
|
+
localProtocol: "https" | "http"
|
|
45
56
|
) {
|
|
46
57
|
for (const [name, value] of Object.entries(headers)) {
|
|
47
58
|
// Rewrite the remote host to the local host.
|
|
48
59
|
if (typeof value === "string" && value.includes(remoteHost)) {
|
|
49
60
|
headers[name] = value
|
|
50
|
-
.replaceAll(
|
|
61
|
+
.replaceAll(
|
|
62
|
+
`https://${remoteHost}`,
|
|
63
|
+
`${localProtocol}://localhost:${localPort}`
|
|
64
|
+
)
|
|
51
65
|
.replaceAll(remoteHost, `localhost:${localPort}`);
|
|
52
66
|
}
|
|
53
67
|
}
|
|
@@ -56,15 +70,33 @@ function rewriteRemoteHostToLocalHostInHeaders(
|
|
|
56
70
|
export function usePreviewServer({
|
|
57
71
|
previewToken,
|
|
58
72
|
publicRoot,
|
|
59
|
-
|
|
73
|
+
localProtocol,
|
|
74
|
+
localPort: port,
|
|
75
|
+
ip,
|
|
60
76
|
}: {
|
|
61
77
|
previewToken: CfPreviewToken | undefined;
|
|
62
78
|
publicRoot: undefined | string;
|
|
63
|
-
|
|
79
|
+
localProtocol: "https" | "http";
|
|
80
|
+
localPort: number;
|
|
81
|
+
ip: string;
|
|
64
82
|
}) {
|
|
65
83
|
/** Creates an HTTP/1 proxy that sends requests over HTTP/2. */
|
|
66
|
-
const proxyServer =
|
|
67
|
-
|
|
84
|
+
const [proxyServer, setProxyServer] = useState<HttpServer | HttpsServer>();
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Create the instance of the local proxy server that will pass on
|
|
88
|
+
* requests to the preview worker.
|
|
89
|
+
*/
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (proxyServer === undefined) {
|
|
92
|
+
createProxyServer(localProtocol)
|
|
93
|
+
.then((proxy) => setProxyServer(proxy))
|
|
94
|
+
.catch(async (err) => {
|
|
95
|
+
console.error("Failed to create proxy server.", err);
|
|
96
|
+
await reportError(err);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}, [proxyServer, localProtocol]);
|
|
68
100
|
|
|
69
101
|
/**
|
|
70
102
|
* When we're not connected / getting a fresh token on changes,
|
|
@@ -81,7 +113,7 @@ export function usePreviewServer({
|
|
|
81
113
|
>([]);
|
|
82
114
|
|
|
83
115
|
/**
|
|
84
|
-
* The session doesn't last forever, and will
|
|
116
|
+
* The session doesn't last forever, and will eventually drop
|
|
85
117
|
* (usually within 5-15 minutes). When that happens, we simply
|
|
86
118
|
* restart the effect, effectively restarting the server. We use
|
|
87
119
|
* a state sigil as an effect dependency to do so.
|
|
@@ -92,6 +124,10 @@ export function usePreviewServer({
|
|
|
92
124
|
}
|
|
93
125
|
|
|
94
126
|
useEffect(() => {
|
|
127
|
+
if (proxyServer === undefined) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
95
131
|
// If we don't have a token, that means either we're just starting up,
|
|
96
132
|
// or we're refreshing the token.
|
|
97
133
|
if (!previewToken) {
|
|
@@ -103,8 +139,8 @@ export function usePreviewServer({
|
|
|
103
139
|
// store the stream in a buffer so we can replay it later
|
|
104
140
|
streamBufferRef.current.push({ stream, headers });
|
|
105
141
|
};
|
|
106
|
-
|
|
107
|
-
cleanupListeners.push(() =>
|
|
142
|
+
proxyServer.on("stream", bufferStream);
|
|
143
|
+
cleanupListeners.push(() => proxyServer.off("stream", bufferStream));
|
|
108
144
|
|
|
109
145
|
const bufferRequestResponse = (
|
|
110
146
|
request: IncomingMessage,
|
|
@@ -114,8 +150,10 @@ export function usePreviewServer({
|
|
|
114
150
|
requestResponseBufferRef.current.push({ request, response });
|
|
115
151
|
};
|
|
116
152
|
|
|
117
|
-
|
|
118
|
-
cleanupListeners.push(() =>
|
|
153
|
+
proxyServer.on("request", bufferRequestResponse);
|
|
154
|
+
cleanupListeners.push(() =>
|
|
155
|
+
proxyServer.off("request", bufferRequestResponse)
|
|
156
|
+
);
|
|
119
157
|
return () => {
|
|
120
158
|
cleanupListeners.forEach((cleanup) => cleanup());
|
|
121
159
|
};
|
|
@@ -136,9 +174,14 @@ export function usePreviewServer({
|
|
|
136
174
|
cleanupListeners.push(() => remote.off("close", retryServerSetup));
|
|
137
175
|
|
|
138
176
|
/** HTTP/2 -> HTTP/2 */
|
|
139
|
-
const handleStream = createStreamHandler(
|
|
140
|
-
|
|
141
|
-
|
|
177
|
+
const handleStream = createStreamHandler(
|
|
178
|
+
previewToken,
|
|
179
|
+
remote,
|
|
180
|
+
port,
|
|
181
|
+
localProtocol
|
|
182
|
+
);
|
|
183
|
+
proxyServer.on("stream", handleStream);
|
|
184
|
+
cleanupListeners.push(() => proxyServer.off("stream", handleStream));
|
|
142
185
|
|
|
143
186
|
// flush and replay buffered streams
|
|
144
187
|
streamBufferRef.current.forEach((buffer) =>
|
|
@@ -171,7 +214,8 @@ export function usePreviewServer({
|
|
|
171
214
|
rewriteRemoteHostToLocalHostInHeaders(
|
|
172
215
|
responseHeaders,
|
|
173
216
|
previewToken.host,
|
|
174
|
-
port
|
|
217
|
+
port,
|
|
218
|
+
localProtocol
|
|
175
219
|
);
|
|
176
220
|
for (const name of Object.keys(responseHeaders)) {
|
|
177
221
|
if (name.startsWith(":")) {
|
|
@@ -189,8 +233,10 @@ export function usePreviewServer({
|
|
|
189
233
|
? createHandleAssetsRequest(assetPath, handleRequest)
|
|
190
234
|
: handleRequest;
|
|
191
235
|
|
|
192
|
-
|
|
193
|
-
cleanupListeners.push(() =>
|
|
236
|
+
proxyServer.on("request", actualHandleRequest);
|
|
237
|
+
cleanupListeners.push(() =>
|
|
238
|
+
proxyServer.off("request", actualHandleRequest)
|
|
239
|
+
);
|
|
194
240
|
|
|
195
241
|
// flush and replay buffered requests
|
|
196
242
|
requestResponseBufferRef.current.forEach(({ request, response }) =>
|
|
@@ -207,22 +253,22 @@ export function usePreviewServer({
|
|
|
207
253
|
const { headers, url } = message;
|
|
208
254
|
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
209
255
|
headers["host"] = previewToken.host;
|
|
210
|
-
const localWebsocket = new WebSocket(message, socket, body);
|
|
256
|
+
const localWebsocket = new WebSocket(message, socket, body) as IWebsocket;
|
|
211
257
|
// TODO(soon): Custom WebSocket protocol is not working?
|
|
212
258
|
const remoteWebsocketClient = new WebSocket.Client(
|
|
213
259
|
`wss://${previewToken.host}${url}`,
|
|
214
260
|
[],
|
|
215
261
|
{ headers }
|
|
216
|
-
);
|
|
262
|
+
) as IWebsocket;
|
|
217
263
|
localWebsocket.pipe(remoteWebsocketClient).pipe(localWebsocket);
|
|
218
264
|
// We close down websockets whenever we refresh the token.
|
|
219
265
|
cleanupListeners.push(() => {
|
|
220
|
-
localWebsocket.
|
|
221
|
-
remoteWebsocketClient.
|
|
266
|
+
localWebsocket.close();
|
|
267
|
+
remoteWebsocketClient.close();
|
|
222
268
|
});
|
|
223
269
|
};
|
|
224
|
-
|
|
225
|
-
cleanupListeners.push(() =>
|
|
270
|
+
proxyServer.on("upgrade", handleUpgrade);
|
|
271
|
+
cleanupListeners.push(() => proxyServer.off("upgrade", handleUpgrade));
|
|
226
272
|
|
|
227
273
|
return () => {
|
|
228
274
|
cleanupListeners.forEach((cleanup) => cleanup());
|
|
@@ -231,7 +277,8 @@ export function usePreviewServer({
|
|
|
231
277
|
previewToken,
|
|
232
278
|
publicRoot,
|
|
233
279
|
port,
|
|
234
|
-
|
|
280
|
+
localProtocol,
|
|
281
|
+
proxyServer,
|
|
235
282
|
// We use a state value as a sigil to trigger reconnecting the server.
|
|
236
283
|
// It's not used inside the effect, so react-hooks/exhaustive-deps
|
|
237
284
|
// doesn't complain if it's not included in the dependency array.
|
|
@@ -242,19 +289,23 @@ export function usePreviewServer({
|
|
|
242
289
|
// Start/stop the server whenever the
|
|
243
290
|
// containing component is mounted/unmounted.
|
|
244
291
|
useEffect(() => {
|
|
292
|
+
if (proxyServer === undefined) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
245
296
|
waitForPortToBeAvailable(port, { retryPeriod: 200, timeout: 2000 })
|
|
246
297
|
.then(() => {
|
|
247
|
-
|
|
248
|
-
console.log(`⬣ Listening at
|
|
298
|
+
proxyServer.listen(port, ip);
|
|
299
|
+
console.log(`⬣ Listening at ${localProtocol}://${ip}:${port}`);
|
|
249
300
|
})
|
|
250
301
|
.catch((err) => {
|
|
251
302
|
console.error(`⬣ Failed to start server: ${err}`);
|
|
252
303
|
});
|
|
253
304
|
|
|
254
305
|
return () => {
|
|
255
|
-
|
|
306
|
+
proxyServer.close();
|
|
256
307
|
};
|
|
257
|
-
}, [port,
|
|
308
|
+
}, [port, ip, proxyServer, localProtocol]);
|
|
258
309
|
}
|
|
259
310
|
|
|
260
311
|
function createHandleAssetsRequest(
|
|
@@ -282,8 +333,15 @@ const HTTP1_HEADERS = new Set([
|
|
|
282
333
|
"http2-settings",
|
|
283
334
|
]);
|
|
284
335
|
|
|
285
|
-
function createProxyServer(
|
|
286
|
-
|
|
336
|
+
async function createProxyServer(
|
|
337
|
+
localProtocol: "https" | "http"
|
|
338
|
+
): Promise<HttpServer | HttpsServer> {
|
|
339
|
+
const server: HttpServer | HttpsServer =
|
|
340
|
+
localProtocol === "https"
|
|
341
|
+
? createHttpsServer(await getHttpsOptions())
|
|
342
|
+
: createHttpServer();
|
|
343
|
+
|
|
344
|
+
return server
|
|
287
345
|
.on("request", function (req, res) {
|
|
288
346
|
// log all requests
|
|
289
347
|
console.log(
|
|
@@ -312,7 +370,8 @@ function createProxyServer() {
|
|
|
312
370
|
function createStreamHandler(
|
|
313
371
|
previewToken: CfPreviewToken,
|
|
314
372
|
remote: ClientHttp2Session,
|
|
315
|
-
|
|
373
|
+
localPort: number,
|
|
374
|
+
localProtocol: "https" | "http"
|
|
316
375
|
) {
|
|
317
376
|
return function handleStream(
|
|
318
377
|
stream: ServerHttp2Stream,
|
|
@@ -325,7 +384,8 @@ function createStreamHandler(
|
|
|
325
384
|
rewriteRemoteHostToLocalHostInHeaders(
|
|
326
385
|
responseHeaders,
|
|
327
386
|
previewToken.host,
|
|
328
|
-
|
|
387
|
+
localPort,
|
|
388
|
+
localProtocol
|
|
329
389
|
);
|
|
330
390
|
stream.respond(responseHeaders);
|
|
331
391
|
request.pipe(stream, { end: true });
|
|
@@ -342,29 +402,39 @@ export async function waitForPortToBeAvailable(
|
|
|
342
402
|
): Promise<void> {
|
|
343
403
|
return new Promise((resolve, reject) => {
|
|
344
404
|
const timeout = setTimeout(() => {
|
|
345
|
-
|
|
405
|
+
doReject(new Error(`Timed out waiting for port ${port}`));
|
|
346
406
|
}, options.timeout);
|
|
347
407
|
|
|
408
|
+
const interval = setInterval(checkPort, options.retryPeriod);
|
|
409
|
+
checkPort();
|
|
410
|
+
|
|
411
|
+
function doResolve() {
|
|
412
|
+
clearTimeout(timeout);
|
|
413
|
+
clearInterval(interval);
|
|
414
|
+
resolve();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function doReject(err: unknown) {
|
|
418
|
+
clearInterval(interval);
|
|
419
|
+
clearTimeout(timeout);
|
|
420
|
+
reject(err);
|
|
421
|
+
}
|
|
422
|
+
|
|
348
423
|
function checkPort() {
|
|
349
424
|
// Testing whether a port is 'available' involves simply
|
|
350
425
|
// trying to make a server listen on that port, and retrying
|
|
351
426
|
// until it succeeds.
|
|
352
|
-
const server =
|
|
427
|
+
const server = createHttpServer();
|
|
353
428
|
server.on("error", (err) => {
|
|
354
429
|
// @ts-expect-error non standard property on Error
|
|
355
|
-
if (err.code
|
|
356
|
-
|
|
357
|
-
} else {
|
|
358
|
-
reject(err);
|
|
430
|
+
if (err.code !== "EADDRINUSE") {
|
|
431
|
+
doReject(err);
|
|
359
432
|
}
|
|
360
433
|
});
|
|
361
434
|
server.listen(port, () => {
|
|
362
435
|
server.close();
|
|
363
|
-
|
|
364
|
-
resolve();
|
|
436
|
+
doResolve();
|
|
365
437
|
});
|
|
366
438
|
}
|
|
367
|
-
|
|
368
|
-
checkPort();
|
|
369
439
|
});
|
|
370
440
|
}
|