wrangler 2.0.8 → 2.0.12

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 (67) hide show
  1. package/kv-asset-handler.js +1 -0
  2. package/package.json +3 -1
  3. package/src/__tests__/configuration.test.ts +255 -142
  4. package/src/__tests__/dev.test.tsx +88 -58
  5. package/src/__tests__/index.test.ts +2 -1
  6. package/src/__tests__/init.test.ts +3 -0
  7. package/src/__tests__/kv.test.ts +23 -2
  8. package/src/__tests__/pages.test.ts +98 -1
  9. package/src/__tests__/publish.test.ts +514 -162
  10. package/src/__tests__/whoami.test.tsx +34 -0
  11. package/src/bundle.ts +9 -5
  12. package/src/cfetch/internal.ts +6 -9
  13. package/src/config/config.ts +1 -1
  14. package/src/config/environment.ts +1 -1
  15. package/src/config/validation-helpers.ts +10 -1
  16. package/src/config/validation.ts +22 -13
  17. package/src/create-worker-preview.ts +15 -15
  18. package/src/dev/dev.tsx +32 -56
  19. package/src/dev/local.tsx +10 -7
  20. package/src/dev/remote.tsx +30 -17
  21. package/src/dev/use-esbuild.ts +1 -4
  22. package/src/index.tsx +239 -244
  23. package/src/kv.ts +1 -1
  24. package/src/pages.tsx +295 -229
  25. package/src/parse.ts +21 -1
  26. package/src/proxy.ts +19 -6
  27. package/src/publish.ts +154 -16
  28. package/src/sites.tsx +49 -18
  29. package/src/user.tsx +12 -1
  30. package/src/whoami.tsx +3 -2
  31. package/src/worker.ts +2 -1
  32. package/src/zones.ts +73 -0
  33. package/templates/static-asset-facade.js +1 -5
  34. package/wrangler-dist/cli.js +73693 -73458
  35. package/vendor/@cloudflare/kv-asset-handler/CHANGELOG.md +0 -332
  36. package/vendor/@cloudflare/kv-asset-handler/LICENSE_APACHE +0 -176
  37. package/vendor/@cloudflare/kv-asset-handler/LICENSE_MIT +0 -25
  38. package/vendor/@cloudflare/kv-asset-handler/README.md +0 -245
  39. package/vendor/@cloudflare/kv-asset-handler/dist/index.d.ts +0 -32
  40. package/vendor/@cloudflare/kv-asset-handler/dist/index.js +0 -354
  41. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.d.ts +0 -13
  42. package/vendor/@cloudflare/kv-asset-handler/dist/mocks.js +0 -148
  43. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.d.ts +0 -1
  44. package/vendor/@cloudflare/kv-asset-handler/dist/test/getAssetFromKV.js +0 -436
  45. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.d.ts +0 -1
  46. package/vendor/@cloudflare/kv-asset-handler/dist/test/mapRequestToAsset.js +0 -40
  47. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.d.ts +0 -1
  48. package/vendor/@cloudflare/kv-asset-handler/dist/test/serveSinglePageApp.js +0 -42
  49. package/vendor/@cloudflare/kv-asset-handler/dist/types.d.ts +0 -26
  50. package/vendor/@cloudflare/kv-asset-handler/dist/types.js +0 -31
  51. package/vendor/@cloudflare/kv-asset-handler/package.json +0 -52
  52. package/vendor/@cloudflare/kv-asset-handler/src/index.ts +0 -296
  53. package/vendor/@cloudflare/kv-asset-handler/src/mocks.ts +0 -136
  54. package/vendor/@cloudflare/kv-asset-handler/src/test/getAssetFromKV.ts +0 -464
  55. package/vendor/@cloudflare/kv-asset-handler/src/test/mapRequestToAsset.ts +0 -33
  56. package/vendor/@cloudflare/kv-asset-handler/src/test/serveSinglePageApp.ts +0 -42
  57. package/vendor/@cloudflare/kv-asset-handler/src/types.ts +0 -39
  58. package/vendor/wrangler-mime/CHANGELOG.md +0 -289
  59. package/vendor/wrangler-mime/LICENSE +0 -21
  60. package/vendor/wrangler-mime/Mime.js +0 -97
  61. package/vendor/wrangler-mime/README.md +0 -187
  62. package/vendor/wrangler-mime/cli.js +0 -46
  63. package/vendor/wrangler-mime/index.js +0 -4
  64. package/vendor/wrangler-mime/lite.js +0 -4
  65. package/vendor/wrangler-mime/package.json +0 -52
  66. package/vendor/wrangler-mime/types/other.js +0 -1
  67. package/vendor/wrangler-mime/types/standard.js +0 -1
package/src/pages.tsx CHANGED
@@ -821,6 +821,299 @@ interface CreateDeploymentArgs {
821
821
  commitDirty?: boolean;
822
822
  }
823
823
 
824
+ const upload = async ({
825
+ directory,
826
+ accountId,
827
+ projectName,
828
+ }: {
829
+ directory: string;
830
+ accountId: string;
831
+ projectName: string;
832
+ }) => {
833
+ type FileContainer = {
834
+ content: string;
835
+ contentType: string;
836
+ sizeInBytes: number;
837
+ hash: string;
838
+ };
839
+
840
+ const IGNORE_LIST = [
841
+ "_worker.js",
842
+ "_redirects",
843
+ "_headers",
844
+ ".DS_Store",
845
+ "node_modules",
846
+ ".git",
847
+ ];
848
+
849
+ const walk = async (
850
+ dir: string,
851
+ fileMap: Map<string, FileContainer> = new Map(),
852
+ depth = 0
853
+ ) => {
854
+ const files = await readdir(dir);
855
+
856
+ await Promise.all(
857
+ files.map(async (file) => {
858
+ const filepath = join(dir, file);
859
+ const filestat = await stat(filepath);
860
+
861
+ if (IGNORE_LIST.includes(file)) {
862
+ return;
863
+ }
864
+
865
+ if (filestat.isSymbolicLink()) {
866
+ return;
867
+ }
868
+
869
+ if (filestat.isDirectory()) {
870
+ fileMap = await walk(filepath, fileMap, depth + 1);
871
+ } else {
872
+ let name;
873
+ if (depth) {
874
+ name = filepath.split(sep).slice(1).join("/");
875
+ } else {
876
+ name = file;
877
+ }
878
+
879
+ // TODO: Move this to later so we don't hold as much in memory
880
+ const fileContent = await readFile(filepath);
881
+
882
+ const base64Content = fileContent.toString("base64");
883
+ const extension = extname(basename(name)).substring(1);
884
+
885
+ if (filestat.size > 25 * 1024 * 1024) {
886
+ throw new Error(
887
+ `Error: Pages only supports files up to ${prettyBytes(
888
+ 25 * 1024 * 1024
889
+ )} in size\n${name} is ${prettyBytes(filestat.size)} in size`
890
+ );
891
+ }
892
+
893
+ fileMap.set(name, {
894
+ content: base64Content,
895
+ contentType: getType(name) || "application/octet-stream",
896
+ sizeInBytes: filestat.size,
897
+ hash: hash(base64Content + extension)
898
+ .toString("hex")
899
+ .slice(0, 32),
900
+ });
901
+ }
902
+ })
903
+ );
904
+
905
+ return fileMap;
906
+ };
907
+
908
+ const fileMap = await walk(directory);
909
+
910
+ if (fileMap.size > 20000) {
911
+ throw new FatalError(
912
+ `Error: Pages only supports up to 20,000 files in a deployment. Ensure you have specified your build output directory correctly.`,
913
+ 1
914
+ );
915
+ }
916
+
917
+ const files = [...fileMap.values()];
918
+
919
+ async function fetchJwt(): Promise<string> {
920
+ return (
921
+ await fetchResult<{ jwt: string }>(
922
+ `/accounts/${accountId}/pages/projects/${projectName}/upload-token`
923
+ )
924
+ ).jwt;
925
+ }
926
+
927
+ let jwt = await fetchJwt();
928
+
929
+ const start = Date.now();
930
+
931
+ const missingHashes = await fetchResult<string[]>(
932
+ `/pages/assets/check-missing`,
933
+ {
934
+ method: "POST",
935
+ headers: {
936
+ "Content-Type": "application/json",
937
+ Authorization: `Bearer ${jwt}`,
938
+ },
939
+ body: JSON.stringify({
940
+ hashes: files.map(({ hash }) => hash),
941
+ }),
942
+ }
943
+ );
944
+
945
+ const sortedFiles = files
946
+ .filter((file) => missingHashes.includes(file.hash))
947
+ .sort((a, b) => b.sizeInBytes - a.sizeInBytes);
948
+
949
+ // Start with a few buckets so small projects still get
950
+ // the benefit of multiple upload streams
951
+ const buckets: {
952
+ files: FileContainer[];
953
+ remainingSize: number;
954
+ }[] = new Array(BULK_UPLOAD_CONCURRENCY).fill(null).map(() => ({
955
+ files: [],
956
+ remainingSize: MAX_BUCKET_SIZE,
957
+ }));
958
+
959
+ let bucketOffset = 0;
960
+ for (const file of sortedFiles) {
961
+ let inserted = false;
962
+
963
+ for (let i = 0; i < buckets.length; i++) {
964
+ // Start at a different bucket for each new file
965
+ const bucket = buckets[(i + bucketOffset) % buckets.length];
966
+ if (
967
+ bucket.remainingSize >= file.sizeInBytes &&
968
+ bucket.files.length < MAX_BUCKET_FILE_COUNT
969
+ ) {
970
+ bucket.files.push(file);
971
+ bucket.remainingSize -= file.sizeInBytes;
972
+ inserted = true;
973
+ break;
974
+ }
975
+ }
976
+
977
+ if (!inserted) {
978
+ buckets.push({
979
+ files: [file],
980
+ remainingSize: MAX_BUCKET_SIZE - file.sizeInBytes,
981
+ });
982
+ }
983
+ bucketOffset++;
984
+ }
985
+
986
+ let counter = fileMap.size - sortedFiles.length;
987
+ const { rerender, unmount } = render(
988
+ <Progress done={counter} total={fileMap.size} />
989
+ );
990
+
991
+ const queue = new PQueue({ concurrency: BULK_UPLOAD_CONCURRENCY });
992
+
993
+ for (const bucket of buckets) {
994
+ // Don't upload empty buckets (can happen for tiny projects)
995
+ if (bucket.files.length === 0) continue;
996
+
997
+ const payload: UploadPayloadFile[] = bucket.files.map((file) => ({
998
+ key: file.hash,
999
+ value: file.content,
1000
+ metadata: {
1001
+ contentType: file.contentType,
1002
+ },
1003
+ base64: true,
1004
+ }));
1005
+
1006
+ let attempts = 0;
1007
+ const doUpload = async (): Promise<void> => {
1008
+ try {
1009
+ return await fetchResult(`/pages/assets/upload`, {
1010
+ method: "POST",
1011
+ headers: {
1012
+ "Content-Type": "application/json",
1013
+ Authorization: `Bearer ${jwt}`,
1014
+ },
1015
+ body: JSON.stringify(payload),
1016
+ });
1017
+ } catch (e) {
1018
+ if (attempts < MAX_UPLOAD_ATTEMPTS) {
1019
+ // Linear backoff, 0 second first time, then 1 second etc.
1020
+ await new Promise((resolve) =>
1021
+ setTimeout(resolve, attempts++ * 1000)
1022
+ );
1023
+
1024
+ if ((e as { code: number }).code === 8000013) {
1025
+ // Looks like the JWT expired, fetch another one
1026
+ jwt = await fetchJwt();
1027
+ }
1028
+ return doUpload();
1029
+ } else {
1030
+ throw e;
1031
+ }
1032
+ }
1033
+ };
1034
+
1035
+ queue.add(() =>
1036
+ doUpload().then(
1037
+ () => {
1038
+ counter += bucket.files.length;
1039
+ rerender(<Progress done={counter} total={fileMap.size} />);
1040
+ },
1041
+ (error) => {
1042
+ return Promise.reject(
1043
+ new FatalError(
1044
+ "Failed to upload files. Please try again.",
1045
+ error.code || 1
1046
+ )
1047
+ );
1048
+ }
1049
+ )
1050
+ );
1051
+ }
1052
+
1053
+ await queue.onIdle();
1054
+
1055
+ unmount();
1056
+
1057
+ const uploadMs = Date.now() - start;
1058
+
1059
+ const skipped = fileMap.size - missingHashes.length;
1060
+ const skippedMessage = skipped > 0 ? `(${skipped} already uploaded) ` : "";
1061
+
1062
+ logger.log(
1063
+ `✨ Success! Uploaded ${
1064
+ sortedFiles.length
1065
+ } files ${skippedMessage}${formatTime(uploadMs)}\n`
1066
+ );
1067
+
1068
+ const doUpsertHashes = async (): Promise<void> => {
1069
+ try {
1070
+ return await fetchResult(`/pages/assets/upsert-hashes`, {
1071
+ method: "POST",
1072
+ headers: {
1073
+ "Content-Type": "application/json",
1074
+ Authorization: `Bearer ${jwt}`,
1075
+ },
1076
+ body: JSON.stringify({
1077
+ hashes: files.map(({ hash }) => hash),
1078
+ }),
1079
+ });
1080
+ } catch (e) {
1081
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1082
+
1083
+ if ((e as { code: number }).code === 8000013) {
1084
+ // Looks like the JWT expired, fetch another one
1085
+ jwt = await fetchJwt();
1086
+ }
1087
+
1088
+ return await fetchResult(`/pages/assets/upsert-hashes`, {
1089
+ method: "POST",
1090
+ headers: {
1091
+ "Content-Type": "application/json",
1092
+ Authorization: `Bearer ${jwt}`,
1093
+ },
1094
+ body: JSON.stringify({
1095
+ hashes: files.map(({ hash }) => hash),
1096
+ }),
1097
+ });
1098
+ }
1099
+ };
1100
+
1101
+ try {
1102
+ await doUpsertHashes();
1103
+ } catch {
1104
+ logger.warn(
1105
+ "Failed to update file hashes. Every upload appeared to succeed for this deployment, but you might need to re-upload for future deployments. This shouldn't have any impact other than slowing the upload speed of your next deployment."
1106
+ );
1107
+ }
1108
+
1109
+ return Object.fromEntries(
1110
+ [...fileMap.entries()].map(([fileName, file]) => [
1111
+ `/${fileName}`,
1112
+ file.hash,
1113
+ ])
1114
+ );
1115
+ };
1116
+
824
1117
  const createDeployment: CommandModule<
825
1118
  CreateDeploymentArgs,
826
1119
  CreateDeploymentArgs
@@ -1053,238 +1346,11 @@ const createDeployment: CommandModule<
1053
1346
  builtFunctions = readFileSync(outfile, "utf-8");
1054
1347
  }
1055
1348
 
1056
- type FileContainer = {
1057
- content: string;
1058
- contentType: string;
1059
- sizeInBytes: number;
1060
- hash: string;
1061
- };
1062
-
1063
- const IGNORE_LIST = [
1064
- "_worker.js",
1065
- "_redirects",
1066
- "_headers",
1067
- ".DS_Store",
1068
- "node_modules",
1069
- ".git",
1070
- ];
1071
-
1072
- const walk = async (
1073
- dir: string,
1074
- fileMap: Map<string, FileContainer> = new Map(),
1075
- depth = 0
1076
- ) => {
1077
- const files = await readdir(dir);
1078
-
1079
- await Promise.all(
1080
- files.map(async (file) => {
1081
- const filepath = join(dir, file);
1082
- const filestat = await stat(filepath);
1083
-
1084
- if (IGNORE_LIST.includes(file)) {
1085
- return;
1086
- }
1087
-
1088
- if (filestat.isSymbolicLink()) {
1089
- return;
1090
- }
1091
-
1092
- if (filestat.isDirectory()) {
1093
- fileMap = await walk(filepath, fileMap, depth + 1);
1094
- } else {
1095
- let name;
1096
- if (depth) {
1097
- name = filepath.split(sep).slice(1).join("/");
1098
- } else {
1099
- name = file;
1100
- }
1101
-
1102
- // TODO: Move this to later so we don't hold as much in memory
1103
- const fileContent = await readFile(filepath);
1104
-
1105
- const base64Content = fileContent.toString("base64");
1106
- const extension = extname(basename(name)).substring(1);
1107
-
1108
- if (filestat.size > 25 * 1024 * 1024) {
1109
- throw new Error(
1110
- `Error: Pages only supports files up to ${prettyBytes(
1111
- 25 * 1024 * 1024
1112
- )} in size\n${name} is ${prettyBytes(filestat.size)} in size`
1113
- );
1114
- }
1115
-
1116
- fileMap.set(name, {
1117
- content: base64Content,
1118
- contentType: getType(name) || "application/octet-stream",
1119
- sizeInBytes: filestat.size,
1120
- hash: hash(base64Content + extension)
1121
- .toString("hex")
1122
- .slice(0, 32),
1123
- });
1124
- }
1125
- })
1126
- );
1127
-
1128
- return fileMap;
1129
- };
1130
-
1131
- const fileMap = await walk(directory);
1132
-
1133
- if (fileMap.size > 20000) {
1134
- throw new FatalError(
1135
- `Error: Pages only supports up to 20,000 files in a deployment. Ensure you have specified your build output directory correctly.`,
1136
- 1
1137
- );
1138
- }
1139
-
1140
- const files = [...fileMap.values()];
1141
-
1142
- const { jwt } = await fetchResult<{ jwt: string }>(
1143
- `/accounts/${accountId}/pages/projects/${projectName}/upload-token`
1144
- );
1145
-
1146
- const start = Date.now();
1147
-
1148
- const missingHashes = await fetchResult<string[]>(
1149
- `/pages/assets/check-missing`,
1150
- {
1151
- method: "POST",
1152
- headers: {
1153
- "Content-Type": "application/json",
1154
- Authorization: `Bearer ${jwt}`,
1155
- },
1156
- body: JSON.stringify({
1157
- hashes: files.map(({ hash }) => hash),
1158
- }),
1159
- }
1160
- );
1161
-
1162
- const sortedFiles = files
1163
- .filter((file) => missingHashes.includes(file.hash))
1164
- .sort((a, b) => b.sizeInBytes - a.sizeInBytes);
1165
-
1166
- // Start with a few buckets so small projects still get
1167
- // the benefit of multiple upload streams
1168
- const buckets: {
1169
- files: FileContainer[];
1170
- remainingSize: number;
1171
- }[] = new Array(BULK_UPLOAD_CONCURRENCY).fill(null).map(() => ({
1172
- files: [],
1173
- remainingSize: MAX_BUCKET_SIZE,
1174
- }));
1175
-
1176
- let bucketOffset = 0;
1177
- for (const file of sortedFiles) {
1178
- let inserted = false;
1179
-
1180
- for (let i = 0; i < buckets.length; i++) {
1181
- // Start at a different bucket for each new file
1182
- const bucket = buckets[(i + bucketOffset) % buckets.length];
1183
- if (
1184
- bucket.remainingSize >= file.sizeInBytes &&
1185
- bucket.files.length < MAX_BUCKET_FILE_COUNT
1186
- ) {
1187
- bucket.files.push(file);
1188
- bucket.remainingSize -= file.sizeInBytes;
1189
- inserted = true;
1190
- break;
1191
- }
1192
- }
1193
-
1194
- if (!inserted) {
1195
- buckets.push({
1196
- files: [file],
1197
- remainingSize: MAX_BUCKET_SIZE - file.sizeInBytes,
1198
- });
1199
- }
1200
- bucketOffset++;
1201
- }
1202
-
1203
- let counter = fileMap.size - sortedFiles.length;
1204
- const { rerender, unmount } = render(
1205
- <Progress done={counter} total={fileMap.size} />
1206
- );
1207
-
1208
- const queue = new PQueue({ concurrency: BULK_UPLOAD_CONCURRENCY });
1209
-
1210
- for (const bucket of buckets) {
1211
- // Don't upload empty buckets (can happen for tiny projects)
1212
- if (bucket.files.length === 0) continue;
1213
-
1214
- const payload: UploadPayloadFile[] = bucket.files.map((file) => ({
1215
- key: file.hash,
1216
- value: file.content,
1217
- metadata: {
1218
- contentType: file.contentType,
1219
- },
1220
- base64: true,
1221
- }));
1222
-
1223
- let attempts = 0;
1224
- const doUpload = async (): Promise<void> => {
1225
- try {
1226
- return await fetchResult(`/pages/assets/upload`, {
1227
- method: "POST",
1228
- headers: {
1229
- "Content-Type": "application/json",
1230
- Authorization: `Bearer ${jwt}`,
1231
- },
1232
- body: JSON.stringify(payload),
1233
- });
1234
- } catch (e) {
1235
- if (attempts < MAX_UPLOAD_ATTEMPTS) {
1236
- // Linear backoff, 0 second first time, then 1 second etc.
1237
- await new Promise((resolve) =>
1238
- setTimeout(resolve, attempts++ * 1000)
1239
- );
1240
- return doUpload();
1241
- } else {
1242
- throw e;
1243
- }
1244
- }
1245
- };
1246
-
1247
- queue.add(() =>
1248
- doUpload().then(
1249
- () => {
1250
- counter += bucket.files.length;
1251
- rerender(<Progress done={counter} total={fileMap.size} />);
1252
- },
1253
- (error) => {
1254
- return Promise.reject(
1255
- new FatalError(
1256
- "Failed to upload files. Please try again.",
1257
- error.code || 1
1258
- )
1259
- );
1260
- }
1261
- )
1262
- );
1263
- }
1264
-
1265
- await queue.onIdle();
1266
-
1267
- unmount();
1268
-
1269
- const uploadMs = Date.now() - start;
1270
-
1271
- logger.log(
1272
- `✨ Success! Uploaded ${fileMap.size} files ${formatTime(uploadMs)}\n`
1273
- );
1349
+ const manifest = await upload({ directory, accountId, projectName });
1274
1350
 
1275
1351
  const formData = new FormData();
1276
1352
 
1277
- formData.append(
1278
- "manifest",
1279
- JSON.stringify(
1280
- Object.fromEntries(
1281
- [...fileMap.entries()].map(([fileName, file]) => [
1282
- `/${fileName}`,
1283
- file.hash,
1284
- ])
1285
- )
1286
- )
1287
- );
1353
+ formData.append("manifest", JSON.stringify(manifest));
1288
1354
 
1289
1355
  if (branch) {
1290
1356
  formData.append("branch", branch);
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
  }
@@ -138,6 +139,25 @@ export function parseJSON<T>(input: string, file?: string): T {
138
139
  }
139
140
  }
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
+
141
161
  /**
142
162
  * Reads a file and parses it based on its type.
143
163
  */
package/src/proxy.ts CHANGED
@@ -100,7 +100,10 @@ export function usePreviewServer({
100
100
  .then((server) => {
101
101
  setProxy({
102
102
  server,
103
- terminator: createHttpTerminator({ server }),
103
+ terminator: createHttpTerminator({
104
+ server,
105
+ gracefulTerminationTimeout: 0,
106
+ }),
104
107
  });
105
108
  })
106
109
  .catch(async (err) => {
@@ -330,7 +333,9 @@ export function usePreviewServer({
330
333
  abortController.abort();
331
334
  // Running `proxy.server.close()` does not close open connections, preventing the process from exiting.
332
335
  // So we use this `terminator` to close all the connections and force the server to shutdown.
333
- proxy.terminator.terminate();
336
+ proxy.terminator
337
+ .terminate()
338
+ .catch(() => logger.error("Failed to terminate the proxy server."));
334
339
  };
335
340
  }, [port, ip, proxy, localProtocol]);
336
341
  }
@@ -450,16 +455,24 @@ export async function waitForPortToBeAvailable(
450
455
  // trying to make a server listen on that port, and retrying
451
456
  // until it succeeds.
452
457
  const server = createHttpServer();
458
+ const terminator = createHttpTerminator({
459
+ server,
460
+ gracefulTerminationTimeout: 0, // default 1000
461
+ });
462
+
453
463
  server.on("error", (err) => {
454
464
  // @ts-expect-error non standard property on Error
455
465
  if (err.code !== "EADDRINUSE") {
456
466
  doReject(err);
457
467
  }
458
468
  });
459
- server.listen(port, () => {
460
- server.close();
461
- doResolve();
462
- });
469
+ server.listen(port, () =>
470
+ terminator
471
+ .terminate()
472
+ .then(doResolve, () =>
473
+ logger.error("Failed to terminate the port checker.")
474
+ )
475
+ );
463
476
  }
464
477
  });
465
478
  }