wrangler 0.0.0-e2a26a0 → 0.0.0-e322475
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/Cloudflare_CA.pem +18 -0
- package/README.md +27 -14
- package/bin/wrangler.js +49 -5
- package/kv-asset-handler.js +1 -0
- package/miniflare-dist/index.mjs +4886 -0
- package/package.json +54 -27
- package/pages/functions/buildPlugin.ts +105 -0
- package/pages/functions/buildWorker.ts +93 -4
- package/pages/functions/filepath-routing.test.ts +223 -36
- package/pages/functions/filepath-routing.ts +78 -120
- package/pages/functions/routes.ts +13 -17
- package/pages/functions/template-plugin.ts +147 -0
- package/pages/functions/template-worker.ts +24 -17
- package/src/__tests__/config-cache.test.ts +36 -0
- package/src/__tests__/configuration.test.ts +3548 -0
- package/src/__tests__/dev.test.tsx +1049 -65
- package/src/__tests__/guess-worker-format.test.ts +85 -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 +12 -8
- package/src/__tests__/helpers/mock-console.ts +49 -8
- package/src/__tests__/helpers/mock-dialogs.ts +99 -11
- package/src/__tests__/helpers/mock-http-server.ts +46 -0
- package/src/__tests__/helpers/mock-istty.ts +27 -0
- package/src/__tests__/helpers/mock-kv.ts +9 -16
- package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
- package/src/__tests__/helpers/mock-process.ts +24 -0
- 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 +10 -0
- package/src/__tests__/helpers/run-wrangler.ts +5 -3
- package/src/__tests__/helpers/write-worker-source.ts +31 -0
- package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
- package/src/__tests__/https-options.test.ts +130 -0
- package/src/__tests__/index.test.ts +150 -435
- package/src/__tests__/init.test.ts +1992 -0
- package/src/__tests__/jest.setup.ts +75 -5
- package/src/__tests__/kv.test.ts +634 -253
- package/src/__tests__/logger.test.ts +141 -0
- package/src/__tests__/package-manager.test.ts +156 -9
- package/src/__tests__/pages.test.ts +754 -0
- package/src/__tests__/parse.test.ts +330 -0
- package/src/__tests__/publish.test.ts +5895 -961
- package/src/__tests__/r2.test.ts +233 -0
- package/src/__tests__/route.test.ts +45 -0
- package/src/__tests__/secret.test.ts +362 -91
- package/src/__tests__/tail.test.ts +690 -0
- package/src/__tests__/user.test.ts +132 -0
- package/src/__tests__/whoami.test.tsx +60 -7
- package/src/abort.d.ts +3 -0
- package/src/bundle-reporter.tsx +29 -0
- package/src/bundle.ts +204 -0
- package/src/cfetch/index.ts +27 -8
- package/src/cfetch/internal.ts +75 -34
- package/src/cli.ts +6 -2
- package/src/config/README.md +107 -0
- package/src/config/config.ts +243 -0
- package/src/config/diagnostics.ts +80 -0
- package/src/config/environment.ts +420 -0
- package/src/config/index.ts +225 -0
- package/src/config/validation-helpers.ts +584 -0
- package/src/config/validation.ts +1580 -0
- package/src/config-cache.ts +45 -0
- package/src/create-worker-preview.ts +185 -0
- package/src/create-worker-upload-form.ts +254 -0
- package/src/dev/dev-vars.ts +36 -0
- package/src/dev/dev.tsx +399 -0
- package/src/dev/local.tsx +321 -0
- package/src/dev/remote.tsx +249 -0
- package/src/dev/use-esbuild.ts +112 -0
- package/src/dialogs.tsx +64 -6
- package/src/durable.ts +102 -0
- package/src/entry.ts +328 -0
- package/src/environment-variables.ts +35 -0
- package/src/errors.ts +11 -0
- package/src/generate-auth-url.ts +33 -0
- package/src/generate-random-state.ts +16 -0
- package/src/git-client.ts +42 -0
- package/src/https-options.ts +123 -0
- package/src/index.tsx +1804 -1009
- package/src/inspect.ts +80 -14
- package/src/intl-polyfill.d.ts +139 -0
- package/src/{kv.tsx → kv.ts} +193 -69
- package/src/logger.ts +73 -0
- package/src/miniflare-cli/README.md +30 -0
- package/src/miniflare-cli/enum-keys.ts +17 -0
- package/src/miniflare-cli/index.ts +41 -0
- package/src/miniflare-cli/request-context.ts +40 -0
- package/src/module-collection.ts +226 -31
- package/src/open-in-browser.ts +17 -0
- package/src/package-manager.ts +79 -25
- package/src/pages.tsx +1131 -84
- package/src/parse.ts +222 -0
- package/src/proxy.ts +176 -70
- package/src/publish.ts +637 -308
- package/src/r2.ts +50 -0
- package/src/selfsigned.d.ts +29 -0
- package/src/sites.tsx +185 -57
- package/src/tail/filters.ts +279 -0
- package/src/tail/index.ts +299 -0
- package/src/tail/printing.ts +67 -0
- package/src/update-check.ts +19 -0
- package/src/user.tsx +349 -166
- package/src/whoami.tsx +5 -3
- package/src/{api/worker.ts → worker.ts} +65 -39
- package/src/zones.ts +73 -0
- package/templates/gitignore +171 -0
- package/templates/new-worker-scheduled.js +17 -0
- package/templates/new-worker-scheduled.ts +32 -0
- package/templates/new-worker.js +1 -1
- package/templates/new-worker.ts +17 -2
- package/templates/no-op-worker.js +10 -0
- package/templates/static-asset-facade.js +3 -7
- package/templates/tsconfig.json +105 -0
- package/wrangler-dist/cli.js +77463 -75999
- package/src/__tests__/helpers/mock-user.ts +0 -27
- package/src/__tests__/logout.test.ts +0 -50
- package/src/api/form_data.ts +0 -134
- package/src/api/preview.ts +0 -128
- package/src/config.ts +0 -641
- package/src/dev.tsx +0 -834
- package/src/tail.tsx +0 -73
- package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
- package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
- package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
- package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
- package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
- package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
- package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
- package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
- package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
- package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
- package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
- package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
- package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
- package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
- package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
- package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
- package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
- package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
- package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
- package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
- package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
- package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
- package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
- package/vendor/wrangler-mime/CHANGELOG.md +0 -289
- package/vendor/wrangler-mime/LICENSE +0 -21
- package/vendor/wrangler-mime/Mime.js +0 -97
- package/vendor/wrangler-mime/README.md +0 -187
- package/vendor/wrangler-mime/cli.js +0 -46
- package/vendor/wrangler-mime/index.js +0 -4
- package/vendor/wrangler-mime/lite.js +0 -4
- package/vendor/wrangler-mime/package.json +0 -52
- package/vendor/wrangler-mime/types/other.js +0 -1
- package/vendor/wrangler-mime/types/standard.js +0 -1
- package/wrangler-dist/cli.js.map +0 -7
package/src/parse.ts
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
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
|
+
import { logger } from "./logger";
|
|
6
|
+
|
|
7
|
+
export type Message = {
|
|
8
|
+
text: string;
|
|
9
|
+
location?: Location;
|
|
10
|
+
notes?: Message[];
|
|
11
|
+
kind?: "warning" | "error";
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type Location = File & {
|
|
15
|
+
line: number;
|
|
16
|
+
column: number;
|
|
17
|
+
length?: number;
|
|
18
|
+
lineText?: string;
|
|
19
|
+
suggestion?: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type File = {
|
|
23
|
+
file?: string;
|
|
24
|
+
fileText?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Formats a `Message` using esbuild's pretty-printing algorithm.
|
|
29
|
+
*/
|
|
30
|
+
export function formatMessage(
|
|
31
|
+
{ text, notes, location, kind = "error" }: Message,
|
|
32
|
+
color = true
|
|
33
|
+
): string {
|
|
34
|
+
const input = { text, notes, location };
|
|
35
|
+
delete input.location?.fileText;
|
|
36
|
+
for (const note of notes ?? []) {
|
|
37
|
+
delete note.location?.fileText;
|
|
38
|
+
}
|
|
39
|
+
const lines = formatMessagesSync([input], {
|
|
40
|
+
color,
|
|
41
|
+
kind: kind,
|
|
42
|
+
terminalWidth: logger.columns,
|
|
43
|
+
});
|
|
44
|
+
return lines.join("\n");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* An error that's thrown when something fails to parse.
|
|
49
|
+
*/
|
|
50
|
+
export class ParseError extends Error implements Message {
|
|
51
|
+
readonly text: string;
|
|
52
|
+
readonly notes: Message[];
|
|
53
|
+
readonly location?: Location;
|
|
54
|
+
readonly kind: "warning" | "error";
|
|
55
|
+
|
|
56
|
+
constructor({ text, notes, location, kind }: Message) {
|
|
57
|
+
super(text);
|
|
58
|
+
this.name = this.constructor.name;
|
|
59
|
+
this.text = text;
|
|
60
|
+
this.notes = notes ?? [];
|
|
61
|
+
this.location = location;
|
|
62
|
+
this.kind = kind ?? "error";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const TOML_ERROR_NAME = "TomlError";
|
|
67
|
+
const TOML_ERROR_SUFFIX = " at row ";
|
|
68
|
+
|
|
69
|
+
type TomlError = Error & {
|
|
70
|
+
line: number;
|
|
71
|
+
col: number;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* A wrapper around `TOML.parse` that throws a `ParseError`.
|
|
76
|
+
*/
|
|
77
|
+
export function parseTOML(input: string, file?: string): TOML.JsonMap | never {
|
|
78
|
+
try {
|
|
79
|
+
// Normalize CRLF to LF to avoid hitting https://github.com/iarna/iarna-toml/issues/33.
|
|
80
|
+
const normalizedInput = input.replace(/\r\n/g, "\n");
|
|
81
|
+
return TOML.parse(normalizedInput);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
const { name, message, line, col } = err as TomlError;
|
|
84
|
+
if (name !== TOML_ERROR_NAME) {
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
const text = message.substring(0, message.lastIndexOf(TOML_ERROR_SUFFIX));
|
|
88
|
+
const lineText = input.split("\n")[line];
|
|
89
|
+
const location = {
|
|
90
|
+
lineText,
|
|
91
|
+
line: line + 1,
|
|
92
|
+
column: col - 1,
|
|
93
|
+
file,
|
|
94
|
+
fileText: input,
|
|
95
|
+
};
|
|
96
|
+
throw new ParseError({ text, location });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const JSON_ERROR_SUFFIX = " in JSON at position ";
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A minimal type describing a package.json file.
|
|
104
|
+
*/
|
|
105
|
+
export type PackageJSON = {
|
|
106
|
+
devDependencies?: Record<string, unknown>;
|
|
107
|
+
dependencies?: Record<string, unknown>;
|
|
108
|
+
scripts?: Record<string, unknown>;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* A typed version of `parseJSON()`.
|
|
113
|
+
*/
|
|
114
|
+
export function parsePackageJSON<T extends PackageJSON = PackageJSON>(
|
|
115
|
+
input: string,
|
|
116
|
+
file?: string
|
|
117
|
+
): T {
|
|
118
|
+
return parseJSON<T>(input, file);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* A wrapper around `JSON.parse` that throws a `ParseError`.
|
|
123
|
+
*/
|
|
124
|
+
export function parseJSON<T>(input: string, file?: string): T {
|
|
125
|
+
try {
|
|
126
|
+
return JSON.parse(input);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
const { message } = err as Error;
|
|
129
|
+
const index = message.lastIndexOf(JSON_ERROR_SUFFIX);
|
|
130
|
+
if (index < 0) {
|
|
131
|
+
throw err;
|
|
132
|
+
}
|
|
133
|
+
const text = message.substring(0, index);
|
|
134
|
+
const position = parseInt(
|
|
135
|
+
message.substring(index + JSON_ERROR_SUFFIX.length)
|
|
136
|
+
);
|
|
137
|
+
const location = indexLocation({ file, fileText: input }, position);
|
|
138
|
+
throw new ParseError({ text, location });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Reads a file into a node Buffer.
|
|
144
|
+
*/
|
|
145
|
+
export function readFileSyncToBuffer(file: string): Buffer {
|
|
146
|
+
try {
|
|
147
|
+
return fs.readFileSync(file);
|
|
148
|
+
} catch (err) {
|
|
149
|
+
const { message } = err as Error;
|
|
150
|
+
throw new ParseError({
|
|
151
|
+
text: `Could not read file: ${file}`,
|
|
152
|
+
notes: [
|
|
153
|
+
{
|
|
154
|
+
text: message.replace(file, resolve(file)),
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Reads a file and parses it based on its type.
|
|
163
|
+
*/
|
|
164
|
+
export function readFileSync(file: string): string {
|
|
165
|
+
try {
|
|
166
|
+
return fs.readFileSync(file, { encoding: "utf-8" });
|
|
167
|
+
} catch (err) {
|
|
168
|
+
const { message } = err as Error;
|
|
169
|
+
throw new ParseError({
|
|
170
|
+
text: `Could not read file: ${file}`,
|
|
171
|
+
notes: [
|
|
172
|
+
{
|
|
173
|
+
text: message.replace(file, resolve(file)),
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Calculates the line and column location from an index.
|
|
182
|
+
*/
|
|
183
|
+
export function indexLocation(file: File, index: number): Location {
|
|
184
|
+
let lineText,
|
|
185
|
+
line = 0,
|
|
186
|
+
column = 0,
|
|
187
|
+
cursor = 0;
|
|
188
|
+
const { fileText = "" } = file;
|
|
189
|
+
for (const row of fileText.split("\n")) {
|
|
190
|
+
line++;
|
|
191
|
+
cursor += row.length + 1;
|
|
192
|
+
if (cursor >= index) {
|
|
193
|
+
lineText = row;
|
|
194
|
+
column = row.length - (cursor - index);
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return { lineText, line, column, ...file };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Guesses the line and column location of a search query.
|
|
203
|
+
*/
|
|
204
|
+
export function searchLocation(file: File, query: unknown): Location {
|
|
205
|
+
let lineText,
|
|
206
|
+
length,
|
|
207
|
+
line = 0,
|
|
208
|
+
column = 0;
|
|
209
|
+
const queryText = String(query);
|
|
210
|
+
const { fileText = "" } = file;
|
|
211
|
+
for (const content of fileText.split("\n")) {
|
|
212
|
+
line++;
|
|
213
|
+
const index = content.indexOf(queryText);
|
|
214
|
+
if (index >= 0) {
|
|
215
|
+
lineText = content;
|
|
216
|
+
column = index;
|
|
217
|
+
length = queryText.length;
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return { lineText, line, column, length, ...file };
|
|
222
|
+
}
|
package/src/proxy.ts
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
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";
|
|
5
|
+
import { createHttpTerminator } from "http-terminator";
|
|
4
6
|
import { useEffect, useRef, useState } from "react";
|
|
5
7
|
import serveStatic from "serve-static";
|
|
6
|
-
import
|
|
8
|
+
import { getHttpsOptions } from "./https-options";
|
|
9
|
+
import { logger } from "./logger";
|
|
10
|
+
import type { CfPreviewToken } from "./create-worker-preview";
|
|
11
|
+
import type { HttpTerminator } from "http-terminator";
|
|
7
12
|
import type {
|
|
8
13
|
IncomingHttpHeaders,
|
|
9
14
|
RequestListener,
|
|
10
15
|
IncomingMessage,
|
|
11
16
|
ServerResponse,
|
|
12
|
-
Server,
|
|
17
|
+
Server as HttpServer,
|
|
13
18
|
} from "node:http";
|
|
14
19
|
import type { ClientHttp2Session, ServerHttp2Stream } from "node:http2";
|
|
20
|
+
import type { Server as HttpsServer } from "node:https";
|
|
21
|
+
import type ws from "ws";
|
|
22
|
+
|
|
23
|
+
interface IWebsocket extends ws {
|
|
24
|
+
// Pipe implements .on("message", ...)
|
|
25
|
+
pipe<T>(fn: T): IWebsocket;
|
|
26
|
+
}
|
|
15
27
|
|
|
16
28
|
/**
|
|
17
29
|
* `usePreviewServer` is a React hook that creates a local development
|
|
@@ -41,30 +53,64 @@ function addCfPreviewTokenHeader(
|
|
|
41
53
|
function rewriteRemoteHostToLocalHostInHeaders(
|
|
42
54
|
headers: IncomingHttpHeaders,
|
|
43
55
|
remoteHost: string,
|
|
44
|
-
localPort: number
|
|
56
|
+
localPort: number,
|
|
57
|
+
localProtocol: "https" | "http"
|
|
45
58
|
) {
|
|
46
59
|
for (const [name, value] of Object.entries(headers)) {
|
|
47
60
|
// Rewrite the remote host to the local host.
|
|
48
61
|
if (typeof value === "string" && value.includes(remoteHost)) {
|
|
49
62
|
headers[name] = value
|
|
50
|
-
.replaceAll(
|
|
63
|
+
.replaceAll(
|
|
64
|
+
`https://${remoteHost}`,
|
|
65
|
+
`${localProtocol}://localhost:${localPort}`
|
|
66
|
+
)
|
|
51
67
|
.replaceAll(remoteHost, `localhost:${localPort}`);
|
|
52
68
|
}
|
|
53
69
|
}
|
|
54
70
|
}
|
|
55
71
|
|
|
72
|
+
type PreviewProxy = {
|
|
73
|
+
server: HttpServer | HttpsServer;
|
|
74
|
+
terminator: HttpTerminator;
|
|
75
|
+
};
|
|
76
|
+
|
|
56
77
|
export function usePreviewServer({
|
|
57
78
|
previewToken,
|
|
58
|
-
|
|
59
|
-
|
|
79
|
+
assetDirectory,
|
|
80
|
+
localProtocol,
|
|
81
|
+
localPort: port,
|
|
82
|
+
ip,
|
|
60
83
|
}: {
|
|
61
84
|
previewToken: CfPreviewToken | undefined;
|
|
62
|
-
|
|
63
|
-
|
|
85
|
+
assetDirectory: string | undefined;
|
|
86
|
+
localProtocol: "https" | "http";
|
|
87
|
+
localPort: number;
|
|
88
|
+
ip: string;
|
|
64
89
|
}) {
|
|
65
90
|
/** Creates an HTTP/1 proxy that sends requests over HTTP/2. */
|
|
66
|
-
const
|
|
67
|
-
|
|
91
|
+
const [proxy, setProxy] = useState<PreviewProxy>();
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Create the instance of the local proxy server that will pass on
|
|
95
|
+
* requests to the preview worker.
|
|
96
|
+
*/
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (proxy === undefined) {
|
|
99
|
+
createProxyServer(localProtocol)
|
|
100
|
+
.then((server) => {
|
|
101
|
+
setProxy({
|
|
102
|
+
server,
|
|
103
|
+
terminator: createHttpTerminator({
|
|
104
|
+
server,
|
|
105
|
+
gracefulTerminationTimeout: 0,
|
|
106
|
+
}),
|
|
107
|
+
});
|
|
108
|
+
})
|
|
109
|
+
.catch(async (err) => {
|
|
110
|
+
logger.error("Failed to create proxy server:", err);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}, [proxy, localProtocol]);
|
|
68
114
|
|
|
69
115
|
/**
|
|
70
116
|
* When we're not connected / getting a fresh token on changes,
|
|
@@ -81,7 +127,7 @@ export function usePreviewServer({
|
|
|
81
127
|
>([]);
|
|
82
128
|
|
|
83
129
|
/**
|
|
84
|
-
* The session doesn't last forever, and will
|
|
130
|
+
* The session doesn't last forever, and will eventually drop
|
|
85
131
|
* (usually within 5-15 minutes). When that happens, we simply
|
|
86
132
|
* restart the effect, effectively restarting the server. We use
|
|
87
133
|
* a state sigil as an effect dependency to do so.
|
|
@@ -92,6 +138,10 @@ export function usePreviewServer({
|
|
|
92
138
|
}
|
|
93
139
|
|
|
94
140
|
useEffect(() => {
|
|
141
|
+
if (proxy === undefined) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
95
145
|
// If we don't have a token, that means either we're just starting up,
|
|
96
146
|
// or we're refreshing the token.
|
|
97
147
|
if (!previewToken) {
|
|
@@ -103,8 +153,8 @@ export function usePreviewServer({
|
|
|
103
153
|
// store the stream in a buffer so we can replay it later
|
|
104
154
|
streamBufferRef.current.push({ stream, headers });
|
|
105
155
|
};
|
|
106
|
-
proxy.on("stream", bufferStream);
|
|
107
|
-
cleanupListeners.push(() => proxy.off("stream", bufferStream));
|
|
156
|
+
proxy.server.on("stream", bufferStream);
|
|
157
|
+
cleanupListeners.push(() => proxy.server.off("stream", bufferStream));
|
|
108
158
|
|
|
109
159
|
const bufferRequestResponse = (
|
|
110
160
|
request: IncomingMessage,
|
|
@@ -114,8 +164,10 @@ export function usePreviewServer({
|
|
|
114
164
|
requestResponseBufferRef.current.push({ request, response });
|
|
115
165
|
};
|
|
116
166
|
|
|
117
|
-
proxy.on("request", bufferRequestResponse);
|
|
118
|
-
cleanupListeners.push(() =>
|
|
167
|
+
proxy.server.on("request", bufferRequestResponse);
|
|
168
|
+
cleanupListeners.push(() =>
|
|
169
|
+
proxy.server.off("request", bufferRequestResponse)
|
|
170
|
+
);
|
|
119
171
|
return () => {
|
|
120
172
|
cleanupListeners.forEach((cleanup) => cleanup());
|
|
121
173
|
};
|
|
@@ -124,8 +176,6 @@ export function usePreviewServer({
|
|
|
124
176
|
// We have a token. Let's proxy requests to the preview end point.
|
|
125
177
|
const cleanupListeners: (() => void)[] = [];
|
|
126
178
|
|
|
127
|
-
const assetPath = typeof publicRoot === "string" ? publicRoot : null;
|
|
128
|
-
|
|
129
179
|
// create a ClientHttp2Session
|
|
130
180
|
const remote = connect(`https://${previewToken.host}`);
|
|
131
181
|
cleanupListeners.push(() => remote.destroy());
|
|
@@ -136,9 +186,14 @@ export function usePreviewServer({
|
|
|
136
186
|
cleanupListeners.push(() => remote.off("close", retryServerSetup));
|
|
137
187
|
|
|
138
188
|
/** HTTP/2 -> HTTP/2 */
|
|
139
|
-
const handleStream = createStreamHandler(
|
|
140
|
-
|
|
141
|
-
|
|
189
|
+
const handleStream = createStreamHandler(
|
|
190
|
+
previewToken,
|
|
191
|
+
remote,
|
|
192
|
+
port,
|
|
193
|
+
localProtocol
|
|
194
|
+
);
|
|
195
|
+
proxy.server.on("stream", handleStream);
|
|
196
|
+
cleanupListeners.push(() => proxy.server.off("stream", handleStream));
|
|
142
197
|
|
|
143
198
|
// flush and replay buffered streams
|
|
144
199
|
streamBufferRef.current.forEach((buffer) =>
|
|
@@ -168,10 +223,15 @@ export function usePreviewServer({
|
|
|
168
223
|
const request = message.pipe(remote.request(headers));
|
|
169
224
|
request.on("response", (responseHeaders) => {
|
|
170
225
|
const status = responseHeaders[":status"] ?? 500;
|
|
226
|
+
|
|
227
|
+
// log all requests to terminal
|
|
228
|
+
logger.log(new Date().toLocaleTimeString(), method, url, status);
|
|
229
|
+
|
|
171
230
|
rewriteRemoteHostToLocalHostInHeaders(
|
|
172
231
|
responseHeaders,
|
|
173
232
|
previewToken.host,
|
|
174
|
-
port
|
|
233
|
+
port,
|
|
234
|
+
localProtocol
|
|
175
235
|
);
|
|
176
236
|
for (const name of Object.keys(responseHeaders)) {
|
|
177
237
|
if (name.startsWith(":")) {
|
|
@@ -185,12 +245,14 @@ export function usePreviewServer({
|
|
|
185
245
|
|
|
186
246
|
// If an asset path is defined, check the file system
|
|
187
247
|
// for a file first and serve if it exists.
|
|
188
|
-
const actualHandleRequest =
|
|
189
|
-
? createHandleAssetsRequest(
|
|
248
|
+
const actualHandleRequest = assetDirectory
|
|
249
|
+
? createHandleAssetsRequest(assetDirectory, handleRequest)
|
|
190
250
|
: handleRequest;
|
|
191
251
|
|
|
192
|
-
proxy.on("request", actualHandleRequest);
|
|
193
|
-
cleanupListeners.push(() =>
|
|
252
|
+
proxy.server.on("request", actualHandleRequest);
|
|
253
|
+
cleanupListeners.push(() =>
|
|
254
|
+
proxy.server.off("request", actualHandleRequest)
|
|
255
|
+
);
|
|
194
256
|
|
|
195
257
|
// flush and replay buffered requests
|
|
196
258
|
requestResponseBufferRef.current.forEach(({ request, response }) =>
|
|
@@ -207,30 +269,31 @@ export function usePreviewServer({
|
|
|
207
269
|
const { headers, url } = message;
|
|
208
270
|
addCfPreviewTokenHeader(headers, previewToken.value);
|
|
209
271
|
headers["host"] = previewToken.host;
|
|
210
|
-
const localWebsocket = new WebSocket(message, socket, body);
|
|
272
|
+
const localWebsocket = new WebSocket(message, socket, body) as IWebsocket;
|
|
211
273
|
// TODO(soon): Custom WebSocket protocol is not working?
|
|
212
274
|
const remoteWebsocketClient = new WebSocket.Client(
|
|
213
275
|
`wss://${previewToken.host}${url}`,
|
|
214
276
|
[],
|
|
215
277
|
{ headers }
|
|
216
|
-
);
|
|
278
|
+
) as IWebsocket;
|
|
217
279
|
localWebsocket.pipe(remoteWebsocketClient).pipe(localWebsocket);
|
|
218
280
|
// We close down websockets whenever we refresh the token.
|
|
219
281
|
cleanupListeners.push(() => {
|
|
220
|
-
localWebsocket.
|
|
221
|
-
remoteWebsocketClient.
|
|
282
|
+
localWebsocket.close();
|
|
283
|
+
remoteWebsocketClient.close();
|
|
222
284
|
});
|
|
223
285
|
};
|
|
224
|
-
proxy.on("upgrade", handleUpgrade);
|
|
225
|
-
cleanupListeners.push(() => proxy.off("upgrade", handleUpgrade));
|
|
286
|
+
proxy.server.on("upgrade", handleUpgrade);
|
|
287
|
+
cleanupListeners.push(() => proxy.server.off("upgrade", handleUpgrade));
|
|
226
288
|
|
|
227
289
|
return () => {
|
|
228
290
|
cleanupListeners.forEach((cleanup) => cleanup());
|
|
229
291
|
};
|
|
230
292
|
}, [
|
|
231
293
|
previewToken,
|
|
232
|
-
|
|
294
|
+
assetDirectory,
|
|
233
295
|
port,
|
|
296
|
+
localProtocol,
|
|
234
297
|
proxy,
|
|
235
298
|
// We use a state value as a sigil to trigger reconnecting the server.
|
|
236
299
|
// It's not used inside the effect, so react-hooks/exhaustive-deps
|
|
@@ -242,26 +305,44 @@ export function usePreviewServer({
|
|
|
242
305
|
// Start/stop the server whenever the
|
|
243
306
|
// containing component is mounted/unmounted.
|
|
244
307
|
useEffect(() => {
|
|
245
|
-
|
|
308
|
+
const abortController = new AbortController();
|
|
309
|
+
if (proxy === undefined) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
waitForPortToBeAvailable(port, {
|
|
314
|
+
retryPeriod: 200,
|
|
315
|
+
timeout: 2000,
|
|
316
|
+
abortSignal: abortController.signal,
|
|
317
|
+
})
|
|
246
318
|
.then(() => {
|
|
247
|
-
proxy.
|
|
248
|
-
|
|
319
|
+
proxy.server.on("listening", () => {
|
|
320
|
+
logger.log(`⬣ Listening at ${localProtocol}://${ip}:${port}`);
|
|
321
|
+
});
|
|
322
|
+
proxy.server.listen(port, ip);
|
|
249
323
|
})
|
|
250
324
|
.catch((err) => {
|
|
251
|
-
|
|
325
|
+
if ((err as { code: string }).code !== "ABORT_ERR") {
|
|
326
|
+
logger.error(`Failed to start server: ${err}`);
|
|
327
|
+
}
|
|
252
328
|
});
|
|
253
329
|
|
|
254
330
|
return () => {
|
|
255
|
-
|
|
331
|
+
abortController.abort();
|
|
332
|
+
// Running `proxy.server.close()` does not close open connections, preventing the process from exiting.
|
|
333
|
+
// So we use this `terminator` to close all the connections and force the server to shutdown.
|
|
334
|
+
proxy.terminator
|
|
335
|
+
.terminate()
|
|
336
|
+
.catch(() => logger.error("Failed to terminate the proxy server."));
|
|
256
337
|
};
|
|
257
|
-
}, [port, proxy]);
|
|
338
|
+
}, [port, ip, proxy, localProtocol]);
|
|
258
339
|
}
|
|
259
340
|
|
|
260
341
|
function createHandleAssetsRequest(
|
|
261
|
-
|
|
342
|
+
assetDirectory: string,
|
|
262
343
|
handleRequest: RequestListener
|
|
263
344
|
) {
|
|
264
|
-
const handleAsset = serveStatic(
|
|
345
|
+
const handleAsset = serveStatic(assetDirectory, {
|
|
265
346
|
cacheControl: false,
|
|
266
347
|
});
|
|
267
348
|
return (request: IncomingMessage, response: ServerResponse) => {
|
|
@@ -282,20 +363,18 @@ const HTTP1_HEADERS = new Set([
|
|
|
282
363
|
"http2-settings",
|
|
283
364
|
]);
|
|
284
365
|
|
|
285
|
-
function createProxyServer(
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
);
|
|
295
|
-
})
|
|
366
|
+
async function createProxyServer(
|
|
367
|
+
localProtocol: "https" | "http"
|
|
368
|
+
): Promise<HttpServer | HttpsServer> {
|
|
369
|
+
const server: HttpServer | HttpsServer =
|
|
370
|
+
localProtocol === "https"
|
|
371
|
+
? createHttpsServer(await getHttpsOptions())
|
|
372
|
+
: createHttpServer();
|
|
373
|
+
|
|
374
|
+
return server
|
|
296
375
|
.on("upgrade", (req) => {
|
|
297
376
|
// log all websocket connections
|
|
298
|
-
|
|
377
|
+
logger.log(
|
|
299
378
|
new Date().toLocaleTimeString(),
|
|
300
379
|
req.method,
|
|
301
380
|
req.url,
|
|
@@ -305,14 +384,15 @@ function createProxyServer() {
|
|
|
305
384
|
})
|
|
306
385
|
.on("error", (err) => {
|
|
307
386
|
// log all connection errors
|
|
308
|
-
|
|
387
|
+
logger.error(new Date().toLocaleTimeString(), err);
|
|
309
388
|
});
|
|
310
389
|
}
|
|
311
390
|
|
|
312
391
|
function createStreamHandler(
|
|
313
392
|
previewToken: CfPreviewToken,
|
|
314
393
|
remote: ClientHttp2Session,
|
|
315
|
-
|
|
394
|
+
localPort: number,
|
|
395
|
+
localProtocol: "https" | "http"
|
|
316
396
|
) {
|
|
317
397
|
return function handleStream(
|
|
318
398
|
stream: ServerHttp2Stream,
|
|
@@ -325,7 +405,8 @@ function createStreamHandler(
|
|
|
325
405
|
rewriteRemoteHostToLocalHostInHeaders(
|
|
326
406
|
responseHeaders,
|
|
327
407
|
previewToken.host,
|
|
328
|
-
|
|
408
|
+
localPort,
|
|
409
|
+
localProtocol
|
|
329
410
|
);
|
|
330
411
|
stream.respond(responseHeaders);
|
|
331
412
|
request.pipe(stream, { end: true });
|
|
@@ -338,33 +419,58 @@ function createStreamHandler(
|
|
|
338
419
|
*/
|
|
339
420
|
export async function waitForPortToBeAvailable(
|
|
340
421
|
port: number,
|
|
341
|
-
options: { retryPeriod: number; timeout: number }
|
|
422
|
+
options: { retryPeriod: number; timeout: number; abortSignal: AbortSignal }
|
|
342
423
|
): Promise<void> {
|
|
343
424
|
return new Promise((resolve, reject) => {
|
|
425
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
426
|
+
options.abortSignal.addEventListener("abort", () => {
|
|
427
|
+
const abortError = new Error("waitForPortToBeAvailable() aborted");
|
|
428
|
+
(abortError as Error & { code: string }).code = "ABORT_ERR";
|
|
429
|
+
doReject(abortError);
|
|
430
|
+
});
|
|
431
|
+
|
|
344
432
|
const timeout = setTimeout(() => {
|
|
345
|
-
|
|
433
|
+
doReject(new Error(`Timed out waiting for port ${port}`));
|
|
346
434
|
}, options.timeout);
|
|
347
435
|
|
|
436
|
+
const interval = setInterval(checkPort, options.retryPeriod);
|
|
437
|
+
checkPort();
|
|
438
|
+
|
|
439
|
+
function doResolve() {
|
|
440
|
+
clearTimeout(timeout);
|
|
441
|
+
clearInterval(interval);
|
|
442
|
+
resolve();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function doReject(err: unknown) {
|
|
446
|
+
clearInterval(interval);
|
|
447
|
+
clearTimeout(timeout);
|
|
448
|
+
reject(err);
|
|
449
|
+
}
|
|
450
|
+
|
|
348
451
|
function checkPort() {
|
|
349
452
|
// Testing whether a port is 'available' involves simply
|
|
350
453
|
// trying to make a server listen on that port, and retrying
|
|
351
454
|
// until it succeeds.
|
|
352
|
-
const server =
|
|
455
|
+
const server = createHttpServer();
|
|
456
|
+
const terminator = createHttpTerminator({
|
|
457
|
+
server,
|
|
458
|
+
gracefulTerminationTimeout: 0, // default 1000
|
|
459
|
+
});
|
|
460
|
+
|
|
353
461
|
server.on("error", (err) => {
|
|
354
462
|
// @ts-expect-error non standard property on Error
|
|
355
|
-
if (err.code
|
|
356
|
-
|
|
357
|
-
} else {
|
|
358
|
-
reject(err);
|
|
463
|
+
if (err.code !== "EADDRINUSE") {
|
|
464
|
+
doReject(err);
|
|
359
465
|
}
|
|
360
466
|
});
|
|
361
|
-
server.listen(port, () =>
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
467
|
+
server.listen(port, () =>
|
|
468
|
+
terminator
|
|
469
|
+
.terminate()
|
|
470
|
+
.then(doResolve, () =>
|
|
471
|
+
logger.error("Failed to terminate the port checker.")
|
|
472
|
+
)
|
|
473
|
+
);
|
|
366
474
|
}
|
|
367
|
-
|
|
368
|
-
checkPort();
|
|
369
475
|
});
|
|
370
476
|
}
|