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.
Files changed (91) hide show
  1. package/README.md +3 -3
  2. package/bin/wrangler.js +10 -1
  3. package/miniflare-dist/index.mjs +79 -0
  4. package/miniflare-dist/index.mjs.map +7 -0
  5. package/package.json +24 -12
  6. package/pages/functions/filepath-routing.test.ts +212 -36
  7. package/pages/functions/filepath-routing.ts +69 -119
  8. package/pages/functions/routes.ts +8 -17
  9. package/pages/functions/template-worker.ts +3 -9
  10. package/src/__tests__/configuration.test.ts +2820 -0
  11. package/src/__tests__/dev.test.tsx +535 -65
  12. package/src/__tests__/guess-worker-format.test.ts +68 -0
  13. package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
  14. package/src/__tests__/helpers/mock-account-id.ts +26 -8
  15. package/src/__tests__/helpers/mock-bin.ts +4 -1
  16. package/src/__tests__/helpers/mock-cfetch.ts +5 -5
  17. package/src/__tests__/helpers/mock-console.ts +21 -7
  18. package/src/__tests__/helpers/mock-kv.ts +9 -16
  19. package/src/__tests__/helpers/mock-stdin.ts +103 -0
  20. package/src/__tests__/helpers/mock-web-socket.ts +29 -0
  21. package/src/__tests__/helpers/run-in-tmp.ts +3 -0
  22. package/src/__tests__/helpers/run-wrangler.ts +5 -3
  23. package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
  24. package/src/__tests__/https-options.test.ts +127 -0
  25. package/src/__tests__/index.test.ts +467 -27
  26. package/src/__tests__/jest.setup.ts +37 -5
  27. package/src/__tests__/kv.test.ts +442 -201
  28. package/src/__tests__/logout.test.ts +15 -13
  29. package/src/__tests__/package-manager.test.ts +2 -2
  30. package/src/__tests__/pages.test.ts +82 -0
  31. package/src/__tests__/parse.test.ts +322 -0
  32. package/src/__tests__/publish.test.ts +2717 -430
  33. package/src/__tests__/r2.test.ts +198 -0
  34. package/src/__tests__/route.test.ts +45 -0
  35. package/src/__tests__/secret.test.ts +301 -86
  36. package/src/__tests__/sentry.test.ts +146 -0
  37. package/src/__tests__/tail.test.ts +638 -0
  38. package/src/__tests__/whoami.test.tsx +21 -7
  39. package/src/bundle.ts +155 -0
  40. package/src/cfetch/index.ts +8 -6
  41. package/src/cfetch/internal.ts +60 -22
  42. package/src/cli.ts +10 -0
  43. package/src/config/README.md +107 -0
  44. package/src/config/config.ts +218 -0
  45. package/src/config/diagnostics.ts +80 -0
  46. package/src/config/environment.ts +371 -0
  47. package/src/config/index.ts +62 -0
  48. package/src/config/validation-helpers.ts +554 -0
  49. package/src/config/validation.ts +1232 -0
  50. package/src/{api/preview.ts → create-worker-preview.ts} +59 -21
  51. package/src/create-worker-upload-form.ts +217 -0
  52. package/src/dev/dev.tsx +392 -0
  53. package/src/dev/local.tsx +232 -0
  54. package/src/dev/remote.tsx +203 -0
  55. package/src/dev/use-esbuild.ts +104 -0
  56. package/src/entry.ts +240 -0
  57. package/src/environment-variables.ts +33 -0
  58. package/src/https-options.ts +122 -0
  59. package/src/index.tsx +1068 -780
  60. package/src/inspect.ts +25 -4
  61. package/src/intl-polyfill.d.ts +139 -0
  62. package/src/{kv.tsx → kv.ts} +95 -60
  63. package/src/miniflare-cli/README.md +30 -0
  64. package/src/miniflare-cli/enum-keys.ts +17 -0
  65. package/src/miniflare-cli/index.ts +37 -0
  66. package/src/module-collection.ts +226 -31
  67. package/src/open-in-browser.ts +25 -0
  68. package/src/package-manager.ts +21 -15
  69. package/src/pages.tsx +35 -20
  70. package/src/parse.ts +183 -0
  71. package/src/proxy.ts +115 -45
  72. package/src/publish.ts +268 -328
  73. package/src/r2.ts +50 -0
  74. package/src/reporting.ts +131 -0
  75. package/src/selfsigned.d.ts +29 -0
  76. package/src/sites.tsx +83 -43
  77. package/src/tail/filters.ts +279 -0
  78. package/src/tail/index.ts +299 -0
  79. package/src/tail/printing.ts +62 -0
  80. package/src/user.tsx +234 -126
  81. package/src/{api/worker.ts → worker.ts} +44 -36
  82. package/templates/new-worker.js +1 -1
  83. package/templates/new-worker.ts +1 -1
  84. package/templates/tsconfig.json +105 -0
  85. package/wrangler-dist/cli.js +131202 -114243
  86. package/wrangler-dist/cli.js.map +3 -3
  87. package/src/__tests__/helpers/mock-user.ts +0 -27
  88. package/src/api/form_data.ts +0 -134
  89. package/src/config.ts +0 -641
  90. package/src/dev.tsx +0 -834
  91. 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 type { CfPreviewToken } from "./api/preview";
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(`https://${remoteHost}`, `http://localhost:${localPort}`)
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
- port,
73
+ localProtocol,
74
+ localPort: port,
75
+ ip,
60
76
  }: {
61
77
  previewToken: CfPreviewToken | undefined;
62
78
  publicRoot: undefined | string;
63
- port: number;
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 = useRef<Server>();
67
- const proxy = (proxyServer.current ??= createProxyServer());
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 evetually drop
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
- proxy.on("stream", bufferStream);
107
- cleanupListeners.push(() => proxy.off("stream", bufferStream));
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
- proxy.on("request", bufferRequestResponse);
118
- cleanupListeners.push(() => proxy.off("request", bufferRequestResponse));
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(previewToken, remote, port);
140
- proxy.on("stream", handleStream);
141
- cleanupListeners.push(() => proxy.off("stream", handleStream));
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
- proxy.on("request", actualHandleRequest);
193
- cleanupListeners.push(() => proxy.off("request", actualHandleRequest));
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.destroy();
221
- remoteWebsocketClient.destroy();
266
+ localWebsocket.close();
267
+ remoteWebsocketClient.close();
222
268
  });
223
269
  };
224
- proxy.on("upgrade", handleUpgrade);
225
- cleanupListeners.push(() => proxy.off("upgrade", handleUpgrade));
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
- proxy,
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
- proxy.listen(port);
248
- console.log(`⬣ Listening at http://localhost:${port}`);
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
- proxy.close();
306
+ proxyServer.close();
256
307
  };
257
- }, [port, proxy]);
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
- return createServer()
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
- port: number
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
- port
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
- reject(new Error(`Timed out waiting for port ${port}`));
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 = createServer();
427
+ const server = createHttpServer();
353
428
  server.on("error", (err) => {
354
429
  // @ts-expect-error non standard property on Error
355
- if (err.code === "EADDRINUSE") {
356
- setTimeout(checkPort, options.retryPeriod);
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
- clearTimeout(timeout);
364
- resolve();
436
+ doResolve();
365
437
  });
366
438
  }
367
-
368
- checkPort();
369
439
  });
370
440
  }