wrangler 0.0.0-e3cab74 → 0.0.0-e3ce82b

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 (139) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +27 -14
  3. package/bin/wrangler.js +49 -5
  4. package/miniflare-dist/index.mjs +4886 -0
  5. package/package.json +43 -28
  6. package/pages/functions/buildPlugin.ts +105 -0
  7. package/pages/functions/buildWorker.ts +93 -4
  8. package/pages/functions/filepath-routing.test.ts +136 -25
  9. package/pages/functions/filepath-routing.ts +14 -3
  10. package/pages/functions/routes.ts +6 -1
  11. package/pages/functions/template-plugin.ts +147 -0
  12. package/pages/functions/template-worker.ts +21 -8
  13. package/src/__tests__/config-cache.test.ts +36 -0
  14. package/src/__tests__/configuration.test.ts +1713 -411
  15. package/src/__tests__/dev.test.tsx +954 -84
  16. package/src/__tests__/guess-worker-format.test.ts +37 -18
  17. package/src/__tests__/helpers/mock-account-id.ts +22 -4
  18. package/src/__tests__/helpers/mock-cfetch.ts +8 -4
  19. package/src/__tests__/helpers/mock-console.ts +35 -10
  20. package/src/__tests__/helpers/mock-dialogs.ts +67 -2
  21. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  22. package/src/__tests__/helpers/mock-istty.ts +27 -0
  23. package/src/__tests__/helpers/mock-kv.ts +9 -16
  24. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  25. package/src/__tests__/helpers/mock-stdin.ts +103 -0
  26. package/src/__tests__/helpers/run-in-tmp.ts +10 -0
  27. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  28. package/src/__tests__/https-options.test.ts +130 -0
  29. package/src/__tests__/index.test.ts +138 -747
  30. package/src/__tests__/init.test.ts +1637 -0
  31. package/src/__tests__/jest.setup.ts +28 -8
  32. package/src/__tests__/kv.test.ts +598 -258
  33. package/src/__tests__/logger.test.ts +115 -0
  34. package/src/__tests__/package-manager.test.ts +154 -7
  35. package/src/__tests__/pages.test.ts +754 -0
  36. package/src/__tests__/parse.test.ts +8 -0
  37. package/src/__tests__/publish.test.ts +4374 -1025
  38. package/src/__tests__/r2.test.ts +69 -34
  39. package/src/__tests__/route.test.ts +45 -0
  40. package/src/__tests__/secret.test.ts +228 -73
  41. package/src/__tests__/tail.test.ts +146 -20
  42. package/src/__tests__/user.test.ts +131 -0
  43. package/src/__tests__/whoami.test.tsx +60 -7
  44. package/src/abort.d.ts +3 -0
  45. package/src/bundle.ts +75 -11
  46. package/src/cfetch/index.ts +21 -4
  47. package/src/cfetch/internal.ts +29 -20
  48. package/src/cli.ts +5 -11
  49. package/src/config/README.md +7 -7
  50. package/src/config/config.ts +69 -100
  51. package/src/config/environment.ts +119 -4
  52. package/src/config/index.ts +177 -4
  53. package/src/config/validation-helpers.ts +237 -23
  54. package/src/config/validation.ts +722 -191
  55. package/src/config-cache.ts +45 -0
  56. package/src/{api/preview.ts → create-worker-preview.ts} +65 -27
  57. package/src/{api/form_data.ts → create-worker-upload-form.ts} +55 -18
  58. package/src/dev/dev-vars.ts +36 -0
  59. package/src/dev/dev.tsx +401 -0
  60. package/src/dev/local.tsx +322 -0
  61. package/src/dev/remote.tsx +237 -0
  62. package/src/dev/use-esbuild.ts +118 -0
  63. package/src/dialogs.tsx +64 -6
  64. package/src/durable.ts +102 -0
  65. package/src/entry.ts +279 -14
  66. package/src/environment-variables.ts +3 -1
  67. package/src/errors.ts +11 -0
  68. package/src/https-options.ts +123 -0
  69. package/src/index.tsx +1386 -867
  70. package/src/inspect.ts +64 -11
  71. package/src/intl-polyfill.d.ts +139 -0
  72. package/src/{kv.tsx → kv.ts} +195 -62
  73. package/src/logger.ts +66 -0
  74. package/src/miniflare-cli/README.md +30 -0
  75. package/src/miniflare-cli/enum-keys.ts +17 -0
  76. package/src/miniflare-cli/index.ts +41 -0
  77. package/src/miniflare-cli/request-context.ts +40 -0
  78. package/src/module-collection.ts +94 -6
  79. package/src/open-in-browser.ts +9 -3
  80. package/src/package-manager.ts +59 -11
  81. package/src/pages.tsx +1123 -75
  82. package/src/parse.ts +26 -6
  83. package/src/proxy.ts +159 -57
  84. package/src/publish.ts +581 -228
  85. package/src/selfsigned.d.ts +29 -0
  86. package/src/sites.tsx +130 -45
  87. package/src/tail/index.ts +2 -2
  88. package/src/tail/printing.ts +13 -8
  89. package/src/update-check.ts +19 -0
  90. package/src/user.tsx +275 -110
  91. package/src/whoami.tsx +5 -3
  92. package/src/{api/worker.ts → worker.ts} +24 -29
  93. package/src/zones.ts +73 -0
  94. package/templates/gitignore +171 -0
  95. package/templates/new-worker-scheduled.js +17 -0
  96. package/templates/new-worker-scheduled.ts +32 -0
  97. package/templates/new-worker.ts +16 -1
  98. package/templates/static-asset-facade.js +1 -5
  99. package/wrangler-dist/cli.js +104803 -118375
  100. package/src/__tests__/helpers/mock-user.ts +0 -27
  101. package/src/__tests__/logout.test.ts +0 -50
  102. package/src/__tests__/sentry.test.ts +0 -146
  103. package/src/dev.tsx +0 -961
  104. package/src/guess-worker-format.ts +0 -68
  105. package/src/reporting.ts +0 -115
  106. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  107. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  108. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  109. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  110. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  111. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  112. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  113. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  114. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  115. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  116. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  117. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  118. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  119. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  120. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  121. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  122. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  123. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  124. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  125. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  126. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  127. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  128. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  129. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  130. package/vendor/wrangler-mime/LICENSE +0 -21
  131. package/vendor/wrangler-mime/Mime.js +0 -97
  132. package/vendor/wrangler-mime/README.md +0 -187
  133. package/vendor/wrangler-mime/cli.js +0 -46
  134. package/vendor/wrangler-mime/index.js +0 -4
  135. package/vendor/wrangler-mime/lite.js +0 -4
  136. package/vendor/wrangler-mime/package.json +0 -52
  137. package/vendor/wrangler-mime/types/other.js +0 -1
  138. package/vendor/wrangler-mime/types/standard.js +0 -1
  139. package/wrangler-dist/cli.js.map +0 -7
package/src/parse.ts CHANGED
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import TOML from "@iarna/toml";
4
4
  import { formatMessagesSync } from "esbuild";
5
+ import { logger } from "./logger";
5
6
 
6
7
  export type Message = {
7
8
  text: string;
@@ -38,7 +39,7 @@ export function formatMessage(
38
39
  const lines = formatMessagesSync([input], {
39
40
  color,
40
41
  kind: kind,
41
- terminalWidth: process.stderr.columns,
42
+ terminalWidth: logger.columns,
42
43
  });
43
44
  return lines.join("\n");
44
45
  }
@@ -62,8 +63,6 @@ export class ParseError extends Error implements Message {
62
63
  }
63
64
  }
64
65
 
65
- /* eslint-disable @typescript-eslint/no-explicit-any */
66
-
67
66
  const TOML_ERROR_NAME = "TomlError";
68
67
  const TOML_ERROR_SUFFIX = " at row ";
69
68
 
@@ -75,9 +74,11 @@ type TomlError = Error & {
75
74
  /**
76
75
  * A wrapper around `TOML.parse` that throws a `ParseError`.
77
76
  */
78
- export function parseTOML(input: string, file?: string): object {
77
+ export function parseTOML(input: string, file?: string): TOML.JsonMap | never {
79
78
  try {
80
- return TOML.parse(input);
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);
81
82
  } catch (err) {
82
83
  const { name, message, line, col } = err as TomlError;
83
84
  if (name !== TOML_ERROR_NAME) {
@@ -98,10 +99,29 @@ export function parseTOML(input: string, file?: string): object {
98
99
 
99
100
  const JSON_ERROR_SUFFIX = " in JSON at position ";
100
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
+
101
121
  /**
102
122
  * A wrapper around `JSON.parse` that throws a `ParseError`.
103
123
  */
104
- export function parseJSON(input: string, file?: string): any {
124
+ export function parseJSON<T>(input: string, file?: string): T {
105
125
  try {
106
126
  return JSON.parse(input);
107
127
  } catch (err) {
package/src/proxy.ts CHANGED
@@ -1,17 +1,23 @@
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 type { CfPreviewToken } from "./api/preview";
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";
15
21
  import type ws from "ws";
16
22
 
17
23
  interface IWebsocket extends ws {
@@ -47,30 +53,64 @@ function addCfPreviewTokenHeader(
47
53
  function rewriteRemoteHostToLocalHostInHeaders(
48
54
  headers: IncomingHttpHeaders,
49
55
  remoteHost: string,
50
- localPort: number
56
+ localPort: number,
57
+ localProtocol: "https" | "http"
51
58
  ) {
52
59
  for (const [name, value] of Object.entries(headers)) {
53
60
  // Rewrite the remote host to the local host.
54
61
  if (typeof value === "string" && value.includes(remoteHost)) {
55
62
  headers[name] = value
56
- .replaceAll(`https://${remoteHost}`, `http://localhost:${localPort}`)
63
+ .replaceAll(
64
+ `https://${remoteHost}`,
65
+ `${localProtocol}://localhost:${localPort}`
66
+ )
57
67
  .replaceAll(remoteHost, `localhost:${localPort}`);
58
68
  }
59
69
  }
60
70
  }
61
71
 
72
+ type PreviewProxy = {
73
+ server: HttpServer | HttpsServer;
74
+ terminator: HttpTerminator;
75
+ };
76
+
62
77
  export function usePreviewServer({
63
78
  previewToken,
64
79
  publicRoot,
65
- port,
80
+ localProtocol,
81
+ localPort: port,
82
+ ip,
66
83
  }: {
67
84
  previewToken: CfPreviewToken | undefined;
68
- publicRoot: undefined | string;
69
- port: number;
85
+ publicRoot: string | undefined;
86
+ localProtocol: "https" | "http";
87
+ localPort: number;
88
+ ip: string;
70
89
  }) {
71
90
  /** Creates an HTTP/1 proxy that sends requests over HTTP/2. */
72
- const proxyServer = useRef<Server>();
73
- const proxy = (proxyServer.current ??= createProxyServer());
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]);
74
114
 
75
115
  /**
76
116
  * When we're not connected / getting a fresh token on changes,
@@ -98,6 +138,10 @@ export function usePreviewServer({
98
138
  }
99
139
 
100
140
  useEffect(() => {
141
+ if (proxy === undefined) {
142
+ return;
143
+ }
144
+
101
145
  // If we don't have a token, that means either we're just starting up,
102
146
  // or we're refreshing the token.
103
147
  if (!previewToken) {
@@ -109,8 +153,8 @@ export function usePreviewServer({
109
153
  // store the stream in a buffer so we can replay it later
110
154
  streamBufferRef.current.push({ stream, headers });
111
155
  };
112
- proxy.on("stream", bufferStream);
113
- cleanupListeners.push(() => proxy.off("stream", bufferStream));
156
+ proxy.server.on("stream", bufferStream);
157
+ cleanupListeners.push(() => proxy.server.off("stream", bufferStream));
114
158
 
115
159
  const bufferRequestResponse = (
116
160
  request: IncomingMessage,
@@ -120,8 +164,10 @@ export function usePreviewServer({
120
164
  requestResponseBufferRef.current.push({ request, response });
121
165
  };
122
166
 
123
- proxy.on("request", bufferRequestResponse);
124
- cleanupListeners.push(() => proxy.off("request", bufferRequestResponse));
167
+ proxy.server.on("request", bufferRequestResponse);
168
+ cleanupListeners.push(() =>
169
+ proxy.server.off("request", bufferRequestResponse)
170
+ );
125
171
  return () => {
126
172
  cleanupListeners.forEach((cleanup) => cleanup());
127
173
  };
@@ -142,9 +188,14 @@ export function usePreviewServer({
142
188
  cleanupListeners.push(() => remote.off("close", retryServerSetup));
143
189
 
144
190
  /** HTTP/2 -> HTTP/2 */
145
- const handleStream = createStreamHandler(previewToken, remote, port);
146
- proxy.on("stream", handleStream);
147
- cleanupListeners.push(() => proxy.off("stream", handleStream));
191
+ const handleStream = createStreamHandler(
192
+ previewToken,
193
+ remote,
194
+ port,
195
+ localProtocol
196
+ );
197
+ proxy.server.on("stream", handleStream);
198
+ cleanupListeners.push(() => proxy.server.off("stream", handleStream));
148
199
 
149
200
  // flush and replay buffered streams
150
201
  streamBufferRef.current.forEach((buffer) =>
@@ -174,10 +225,15 @@ export function usePreviewServer({
174
225
  const request = message.pipe(remote.request(headers));
175
226
  request.on("response", (responseHeaders) => {
176
227
  const status = responseHeaders[":status"] ?? 500;
228
+
229
+ // log all requests to terminal
230
+ logger.log(new Date().toLocaleTimeString(), method, url, status);
231
+
177
232
  rewriteRemoteHostToLocalHostInHeaders(
178
233
  responseHeaders,
179
234
  previewToken.host,
180
- port
235
+ port,
236
+ localProtocol
181
237
  );
182
238
  for (const name of Object.keys(responseHeaders)) {
183
239
  if (name.startsWith(":")) {
@@ -195,8 +251,10 @@ export function usePreviewServer({
195
251
  ? createHandleAssetsRequest(assetPath, handleRequest)
196
252
  : handleRequest;
197
253
 
198
- proxy.on("request", actualHandleRequest);
199
- cleanupListeners.push(() => proxy.off("request", actualHandleRequest));
254
+ proxy.server.on("request", actualHandleRequest);
255
+ cleanupListeners.push(() =>
256
+ proxy.server.off("request", actualHandleRequest)
257
+ );
200
258
 
201
259
  // flush and replay buffered requests
202
260
  requestResponseBufferRef.current.forEach(({ request, response }) =>
@@ -227,8 +285,8 @@ export function usePreviewServer({
227
285
  remoteWebsocketClient.close();
228
286
  });
229
287
  };
230
- proxy.on("upgrade", handleUpgrade);
231
- cleanupListeners.push(() => proxy.off("upgrade", handleUpgrade));
288
+ proxy.server.on("upgrade", handleUpgrade);
289
+ cleanupListeners.push(() => proxy.server.off("upgrade", handleUpgrade));
232
290
 
233
291
  return () => {
234
292
  cleanupListeners.forEach((cleanup) => cleanup());
@@ -237,6 +295,7 @@ export function usePreviewServer({
237
295
  previewToken,
238
296
  publicRoot,
239
297
  port,
298
+ localProtocol,
240
299
  proxy,
241
300
  // We use a state value as a sigil to trigger reconnecting the server.
242
301
  // It's not used inside the effect, so react-hooks/exhaustive-deps
@@ -248,19 +307,37 @@ export function usePreviewServer({
248
307
  // Start/stop the server whenever the
249
308
  // containing component is mounted/unmounted.
250
309
  useEffect(() => {
251
- waitForPortToBeAvailable(port, { retryPeriod: 200, timeout: 2000 })
310
+ const abortController = new AbortController();
311
+ if (proxy === undefined) {
312
+ return;
313
+ }
314
+
315
+ waitForPortToBeAvailable(port, {
316
+ retryPeriod: 200,
317
+ timeout: 2000,
318
+ abortSignal: abortController.signal,
319
+ })
252
320
  .then(() => {
253
- proxy.listen(port);
254
- console.log(`⬣ Listening at http://localhost:${port}`);
321
+ proxy.server.on("listening", () => {
322
+ logger.log(`⬣ Listening at ${localProtocol}://${ip}:${port}`);
323
+ });
324
+ proxy.server.listen(port, ip);
255
325
  })
256
326
  .catch((err) => {
257
- console.error(`⬣ Failed to start server: ${err}`);
327
+ if ((err as { code: string }).code !== "ABORT_ERR") {
328
+ logger.error(`Failed to start server: ${err}`);
329
+ }
258
330
  });
259
331
 
260
332
  return () => {
261
- proxy.close();
333
+ abortController.abort();
334
+ // Running `proxy.server.close()` does not close open connections, preventing the process from exiting.
335
+ // So we use this `terminator` to close all the connections and force the server to shutdown.
336
+ proxy.terminator
337
+ .terminate()
338
+ .catch(() => logger.error("Failed to terminate the proxy server."));
262
339
  };
263
- }, [port, proxy]);
340
+ }, [port, ip, proxy, localProtocol]);
264
341
  }
265
342
 
266
343
  function createHandleAssetsRequest(
@@ -288,20 +365,18 @@ const HTTP1_HEADERS = new Set([
288
365
  "http2-settings",
289
366
  ]);
290
367
 
291
- function createProxyServer() {
292
- return createServer()
293
- .on("request", function (req, res) {
294
- // log all requests
295
- console.log(
296
- new Date().toLocaleTimeString(),
297
- req.method,
298
- req.url,
299
- res.statusCode
300
- );
301
- })
368
+ async function createProxyServer(
369
+ localProtocol: "https" | "http"
370
+ ): Promise<HttpServer | HttpsServer> {
371
+ const server: HttpServer | HttpsServer =
372
+ localProtocol === "https"
373
+ ? createHttpsServer(await getHttpsOptions())
374
+ : createHttpServer();
375
+
376
+ return server
302
377
  .on("upgrade", (req) => {
303
378
  // log all websocket connections
304
- console.log(
379
+ logger.log(
305
380
  new Date().toLocaleTimeString(),
306
381
  req.method,
307
382
  req.url,
@@ -311,14 +386,15 @@ function createProxyServer() {
311
386
  })
312
387
  .on("error", (err) => {
313
388
  // log all connection errors
314
- console.error(new Date().toLocaleTimeString(), err);
389
+ logger.error(new Date().toLocaleTimeString(), err);
315
390
  });
316
391
  }
317
392
 
318
393
  function createStreamHandler(
319
394
  previewToken: CfPreviewToken,
320
395
  remote: ClientHttp2Session,
321
- port: number
396
+ localPort: number,
397
+ localProtocol: "https" | "http"
322
398
  ) {
323
399
  return function handleStream(
324
400
  stream: ServerHttp2Stream,
@@ -331,7 +407,8 @@ function createStreamHandler(
331
407
  rewriteRemoteHostToLocalHostInHeaders(
332
408
  responseHeaders,
333
409
  previewToken.host,
334
- port
410
+ localPort,
411
+ localProtocol
335
412
  );
336
413
  stream.respond(responseHeaders);
337
414
  request.pipe(stream, { end: true });
@@ -344,33 +421,58 @@ function createStreamHandler(
344
421
  */
345
422
  export async function waitForPortToBeAvailable(
346
423
  port: number,
347
- options: { retryPeriod: number; timeout: number }
424
+ options: { retryPeriod: number; timeout: number; abortSignal: AbortSignal }
348
425
  ): Promise<void> {
349
426
  return new Promise((resolve, reject) => {
427
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
428
+ options.abortSignal.addEventListener("abort", () => {
429
+ const abortError = new Error("waitForPortToBeAvailable() aborted");
430
+ (abortError as Error & { code: string }).code = "ABORT_ERR";
431
+ doReject(abortError);
432
+ });
433
+
350
434
  const timeout = setTimeout(() => {
351
- reject(new Error(`Timed out waiting for port ${port}`));
435
+ doReject(new Error(`Timed out waiting for port ${port}`));
352
436
  }, options.timeout);
353
437
 
438
+ const interval = setInterval(checkPort, options.retryPeriod);
439
+ checkPort();
440
+
441
+ function doResolve() {
442
+ clearTimeout(timeout);
443
+ clearInterval(interval);
444
+ resolve();
445
+ }
446
+
447
+ function doReject(err: unknown) {
448
+ clearInterval(interval);
449
+ clearTimeout(timeout);
450
+ reject(err);
451
+ }
452
+
354
453
  function checkPort() {
355
454
  // Testing whether a port is 'available' involves simply
356
455
  // trying to make a server listen on that port, and retrying
357
456
  // until it succeeds.
358
- const server = createServer();
457
+ const server = createHttpServer();
458
+ const terminator = createHttpTerminator({
459
+ server,
460
+ gracefulTerminationTimeout: 0, // default 1000
461
+ });
462
+
359
463
  server.on("error", (err) => {
360
464
  // @ts-expect-error non standard property on Error
361
- if (err.code === "EADDRINUSE") {
362
- setTimeout(checkPort, options.retryPeriod);
363
- } else {
364
- reject(err);
465
+ if (err.code !== "EADDRINUSE") {
466
+ doReject(err);
365
467
  }
366
468
  });
367
- server.listen(port, () => {
368
- server.close();
369
- clearTimeout(timeout);
370
- resolve();
371
- });
469
+ server.listen(port, () =>
470
+ terminator
471
+ .terminate()
472
+ .then(doResolve, () =>
473
+ logger.error("Failed to terminate the port checker.")
474
+ )
475
+ );
372
476
  }
373
-
374
- checkPort();
375
477
  });
376
478
  }