com.jimuwd.xian.registry-proxy 1.0.31 → 1.0.33

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 (3) hide show
  1. package/dist/index.js +93 -64
  2. package/package.json +1 -1
  3. package/src/index.ts +103 -61
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import fetch from 'node-fetch';
7
7
  import { homedir } from 'os';
8
8
  import { join, resolve } from 'path';
9
9
  import { URL } from 'url';
10
+ import { Readable } from "node:stream";
10
11
  const { readFile, writeFile } = fsPromises;
11
12
  class ConcurrencyLimiter {
12
13
  maxConcurrency;
@@ -69,7 +70,7 @@ function resolvePath(path) {
69
70
  function removeRegistryPrefix(tarballUrl, registries) {
70
71
  const normalizedTarball = normalizeUrl(tarballUrl);
71
72
  const normalizedRegistries = registries
72
- .map(r => normalizeUrl(r.url))
73
+ .map(r => normalizeUrl(r.normalizedRegistryUrl))
73
74
  .sort((a, b) => b.length - a.length);
74
75
  for (const normalizedRegistry of normalizedRegistries) {
75
76
  if (normalizedTarball.startsWith(normalizedRegistry)) {
@@ -130,7 +131,7 @@ async function loadProxyInfo(proxyConfigPath = './.registry-proxy.yml', localYar
130
131
  }
131
132
  }
132
133
  }
133
- registryMap.set(normalizedProxiedRegUrl, { url: normalizedProxiedRegUrl, token });
134
+ registryMap.set(normalizedProxiedRegUrl, { normalizedRegistryUrl: normalizedProxiedRegUrl, token });
134
135
  }
135
136
  const registries = Array.from(registryMap.values());
136
137
  const https = proxyConfig.https;
@@ -139,9 +140,9 @@ async function loadProxyInfo(proxyConfigPath = './.registry-proxy.yml', localYar
139
140
  }
140
141
  export async function startProxyServer(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath, port = 0) {
141
142
  const proxyInfo = await loadProxyInfo(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath);
142
- const registries = proxyInfo.registries;
143
+ const registryInfos = proxyInfo.registries;
143
144
  const basePathPrefixedWithSlash = removeEndingSlashAndForceStartingSlash(proxyInfo.basePath);
144
- console.log('Active registries:', registries.map(r => r.url));
145
+ console.log('Active registries:', registryInfos.map(r => r.normalizedRegistryUrl));
145
146
  console.log('Proxy base path:', basePathPrefixedWithSlash);
146
147
  console.log('HTTPS:', !!proxyInfo.https);
147
148
  let proxyPort;
@@ -160,78 +161,105 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
160
161
  }
161
162
  const relativePathPrefixedWithSlash = basePathPrefixedWithSlash === '/' ? fullUrl.pathname : fullUrl.pathname.slice(basePathPrefixedWithSlash.length);
162
163
  console.log(`Proxying: ${relativePathPrefixedWithSlash}`);
163
- const fetchPromises = registries.map(async ({ url, token }) => {
164
+ // 修改为按顺序尝试注册表,找到第一个成功响应即返回
165
+ for (const { normalizedRegistryUrl, token } of registryInfos) {
164
166
  await limiter.acquire();
167
+ let response = null;
165
168
  try {
166
- const cleanRelativePath = relativePathPrefixedWithSlash.replace(/^\/+|\/+$/g, '');
167
- const targetUrl = `${url}/${cleanRelativePath}${fullUrl.search || ''}`;
169
+ const targetUrl = `${normalizedRegistryUrl}${relativePathPrefixedWithSlash}${fullUrl.search || ''}`;
168
170
  console.log(`Fetching from: ${targetUrl}`);
169
171
  const headers = token ? { Authorization: `Bearer ${token}` } : undefined;
170
- const response = await fetch(targetUrl, { headers, });
172
+ response = await fetch(targetUrl, { headers });
171
173
  console.log(`Response from ${targetUrl}: ${response.status} ${response.statusText}`);
172
- return response.ok ? response : null;
173
- }
174
- catch (e) {
175
- console.error(`Failed to fetch from ${url}:`, e);
176
- return null;
177
- }
178
- finally {
179
- limiter.release();
180
- }
181
- });
182
- const responses = await Promise.all(fetchPromises);
183
- const successResponse = responses.find((r) => r !== null);
184
- if (!successResponse) {
185
- console.error(`All registries failed for ${relativePathPrefixedWithSlash}`);
186
- res.writeHead(404).end('Not Found - All upstream registries failed');
187
- return;
188
- }
189
- const contentType = successResponse.headers.get('Content-Type') || 'application/octet-stream';
190
- if (contentType.includes('application/json')) {
191
- try {
192
- const data = await successResponse.json();
193
- if (data.versions) {
194
- const requestHeadersHostFromYarnClient = req.headers.host || 'localhost:' + proxyPort;
195
- console.log("Request headers.host from yarn client is", requestHeadersHostFromYarnClient);
196
- const proxyBaseUrlNoSuffixedWithSlash = `${proxyInfo.https ? 'https' : 'http'}://${requestHeadersHostFromYarnClient}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`;
197
- console.log("proxyBaseUrlNoSuffixedWithSlash", proxyBaseUrlNoSuffixedWithSlash);
198
- for (const version in data.versions) {
199
- const dist = data.versions[version]?.dist;
200
- if (dist?.tarball) {
201
- const originalUrl = new URL(dist.tarball);
202
- const originalSearchParamsStr = originalUrl.search || '';
203
- const tarballPathPrefixedWithSlash = removeRegistryPrefix(dist.tarball, registries);
204
- dist.tarball = `${proxyBaseUrlNoSuffixedWithSlash}${tarballPathPrefixedWithSlash}${originalSearchParamsStr}`;
205
- if (!tarballPathPrefixedWithSlash.startsWith("/"))
206
- console.error("bad tarballPath, must be PrefixedWithSlash", tarballPathPrefixedWithSlash);
174
+ if (response.ok) {
175
+ const contentType = response.headers.get('Content-Type') || 'application/octet-stream';
176
+ if (contentType.includes('application/json')) {
177
+ // application/json 元数据
178
+ try {
179
+ const data = await response.json();
180
+ if (data.versions) {
181
+ const requestHeadersHostFromYarnClient = req.headers.host || 'localhost:' + proxyPort;
182
+ console.log("Request headers.host from yarn client is", requestHeadersHostFromYarnClient);
183
+ const proxyBaseUrlNoSuffixedWithSlash = `${proxyInfo.https ? 'https' : 'http'}://${requestHeadersHostFromYarnClient}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`;
184
+ console.log("proxyBaseUrlNoSuffixedWithSlash", proxyBaseUrlNoSuffixedWithSlash);
185
+ for (const version in data.versions) {
186
+ const dist = data.versions[version]?.dist;
187
+ if (dist?.tarball) {
188
+ const originalUrl = new URL(dist.tarball);
189
+ const originalSearchParamsStr = originalUrl.search || '';
190
+ const tarballPathPrefixedWithSlash = removeRegistryPrefix(dist.tarball, registryInfos);
191
+ dist.tarball = `${proxyBaseUrlNoSuffixedWithSlash}${tarballPathPrefixedWithSlash}${originalSearchParamsStr}`;
192
+ if (!tarballPathPrefixedWithSlash.startsWith("/"))
193
+ console.error("bad tarballPath, must be PrefixedWithSlash", tarballPathPrefixedWithSlash);
194
+ }
195
+ }
196
+ }
197
+ res.writeHead(200, { 'Content-Type': 'application/json' });
198
+ res.end(JSON.stringify(data));
199
+ return;
200
+ }
201
+ catch (e) {
202
+ console.error('Failed to parse JSON response:', e);
203
+ // 继续尝试下一个注册表
204
+ }
205
+ }
206
+ else {
207
+ if (!response.body) {
208
+ console.error(`Empty response body from ${response.url}, status: ${response.status}`);
209
+ continue;
210
+ }
211
+ // 设置正确的响应头
212
+ const contentLength = response.headers.get('Content-Length');
213
+ const safeHeaders = {
214
+ 'content-type': contentType,
215
+ 'connection': 'keep-alive'
216
+ };
217
+ if (contentLength && !isNaN(Number(contentLength))) {
218
+ safeHeaders['content-length'] = contentLength;
219
+ }
220
+ // 复制其他可能有用的头信息
221
+ const headersToCopy = ['cache-control', 'etag', 'last-modified'];
222
+ headersToCopy.forEach(header => {
223
+ const value = response?.headers.get(header);
224
+ if (value) {
225
+ safeHeaders[header] = value;
226
+ }
227
+ });
228
+ res.writeHead(response.status, safeHeaders);
229
+ // 使用 pipeline 处理流
230
+ const { pipeline } = await import('stream');
231
+ const { promisify } = await import('util');
232
+ const pipelineAsync = promisify(pipeline);
233
+ try {
234
+ // 将 ReadableStream 转换为 Node.js Readable
235
+ const nodeStream = Readable.fromWeb(response.body);
236
+ // 双向终止保护
237
+ req.on('close', () => nodeStream.destroy());
238
+ res.on('close', () => nodeStream.destroy());
239
+ await pipelineAsync(nodeStream, res);
240
+ return; // 成功完成管道传输后返回
241
+ }
242
+ catch (pipeError) {
243
+ console.error(`Stream pipeline error for ${relativePathPrefixedWithSlash}:`, pipeError);
244
+ if (!res.headersSent) {
245
+ res.writeHead(502).end('Stream Error');
246
+ }
247
+ continue;
207
248
  }
208
249
  }
209
250
  }
210
- res.writeHead(200, { 'Content-Type': 'application/json' });
211
- res.end(JSON.stringify(data));
212
251
  }
213
252
  catch (e) {
214
- console.error('Failed to parse JSON response:', e);
215
- res.writeHead(502).end('Invalid Upstream Response');
253
+ console.error(`Failed to fetch from ${normalizedRegistryUrl}:`, e);
216
254
  }
217
- }
218
- else {
219
- if (!successResponse.body) {
220
- console.error(`Empty response body from ${successResponse.url}, status: ${successResponse.status}`);
221
- res.writeHead(502).end('Empty Response Body');
222
- return;
255
+ finally {
256
+ // 不需要手动销毁 ReadableStream
257
+ limiter.release();
223
258
  }
224
- const contentLength = successResponse.headers.get('Content-Length');
225
- const safeHeaders = {};
226
- safeHeaders["content-type"] = contentType;
227
- if (contentLength && !isNaN(Number(contentLength)))
228
- safeHeaders["content-length"] = contentLength;
229
- res.writeHead(successResponse.status, safeHeaders);
230
- successResponse.body.pipe(res).on('error', (err) => {
231
- console.error(`Stream error for ${relativePathPrefixedWithSlash}:`, err);
232
- res.writeHead(502).end('Stream Error');
233
- });
234
259
  }
260
+ // 所有注册表都尝试失败
261
+ console.error(`All registries failed for ${relativePathPrefixedWithSlash}`);
262
+ res.writeHead(404).end('Not Found - All upstream registries failed');
235
263
  };
236
264
  let server;
237
265
  if (proxyInfo.https) {
@@ -255,7 +283,7 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
255
283
  else {
256
284
  server = createServer(requestHandler);
257
285
  }
258
- return new Promise((resolve, reject) => {
286
+ const promisedServer = new Promise((resolve, reject) => {
259
287
  server.on('error', (err) => {
260
288
  if (err.code === 'EADDRINUSE') {
261
289
  console.error(`Port ${port} is in use, please specify a different port or free it.`);
@@ -273,6 +301,7 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
273
301
  resolve(server);
274
302
  });
275
303
  });
304
+ return promisedServer;
276
305
  }
277
306
  if (import.meta.url === `file://${process.argv[1]}`) {
278
307
  const [, , configPath, localYarnPath, globalYarnPath, port] = process.argv;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.jimuwd.xian.registry-proxy",
3
- "version": "1.0.31",
3
+ "version": "1.0.33",
4
4
  "type": "module",
5
5
  "description": "A lightweight npm registry proxy with fallback support",
6
6
  "main": "dist/index.js",
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ import fetch, {Response} from 'node-fetch';
8
8
  import {homedir} from 'os';
9
9
  import {join, resolve} from 'path';
10
10
  import {URL} from 'url';
11
+ import {Readable} from "node:stream";
11
12
 
12
13
  const {readFile, writeFile} = fsPromises;
13
14
 
@@ -31,7 +32,7 @@ interface YarnConfig {
31
32
  }
32
33
 
33
34
  interface RegistryInfo {
34
- url: string;
35
+ normalizedRegistryUrl: string;
35
36
  token?: string;
36
37
  }
37
38
 
@@ -111,7 +112,7 @@ function resolvePath(path: string): string {
111
112
  function removeRegistryPrefix(tarballUrl: string, registries: RegistryInfo[]): string {
112
113
  const normalizedTarball = normalizeUrl(tarballUrl);
113
114
  const normalizedRegistries = registries
114
- .map(r => normalizeUrl(r.url))
115
+ .map(r => normalizeUrl(r.normalizedRegistryUrl))
115
116
  .sort((a, b) => b.length - a.length);
116
117
  for (const normalizedRegistry of normalizedRegistries) {
117
118
  if (normalizedTarball.startsWith(normalizedRegistry)) {
@@ -177,7 +178,7 @@ async function loadProxyInfo(
177
178
  }
178
179
  }
179
180
  }
180
- registryMap.set(normalizedProxiedRegUrl, {url: normalizedProxiedRegUrl, token});
181
+ registryMap.set(normalizedProxiedRegUrl, {normalizedRegistryUrl: normalizedProxiedRegUrl, token});
181
182
  }
182
183
  const registries = Array.from(registryMap.values());
183
184
  const https = proxyConfig.https;
@@ -192,10 +193,10 @@ export async function startProxyServer(
192
193
  port: number = 0
193
194
  ): Promise<HttpServer | HttpsServer> {
194
195
  const proxyInfo = await loadProxyInfo(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath);
195
- const registries = proxyInfo.registries;
196
+ const registryInfos = proxyInfo.registries;
196
197
  const basePathPrefixedWithSlash: string = removeEndingSlashAndForceStartingSlash(proxyInfo.basePath);
197
198
 
198
- console.log('Active registries:', registries.map(r => r.url));
199
+ console.log('Active registries:', registryInfos.map(r => r.normalizedRegistryUrl));
199
200
  console.log('Proxy base path:', basePathPrefixedWithSlash);
200
201
  console.log('HTTPS:', !!proxyInfo.https);
201
202
 
@@ -219,74 +220,113 @@ export async function startProxyServer(
219
220
  const relativePathPrefixedWithSlash = basePathPrefixedWithSlash === '/' ? fullUrl.pathname : fullUrl.pathname.slice(basePathPrefixedWithSlash.length);
220
221
  console.log(`Proxying: ${relativePathPrefixedWithSlash}`);
221
222
 
222
- const fetchPromises = registries.map(async ({url, token}) => {
223
+ // 修改为按顺序尝试注册表,找到第一个成功响应即返回
224
+ for (const {normalizedRegistryUrl, token} of registryInfos) {
223
225
  await limiter.acquire();
226
+ let response: Response | null = null;
224
227
  try {
225
- const cleanRelativePath = relativePathPrefixedWithSlash.replace(/^\/+|\/+$/g, '');
226
- const targetUrl = `${url}/${cleanRelativePath}${fullUrl.search || ''}`;
228
+ const targetUrl = `${normalizedRegistryUrl}${relativePathPrefixedWithSlash}${fullUrl.search || ''}`;
227
229
  console.log(`Fetching from: ${targetUrl}`);
228
230
  const headers = token ? {Authorization: `Bearer ${token}`} : undefined;
229
- const response = await fetch(targetUrl, {headers,});
231
+ response = await fetch(targetUrl, {headers});
230
232
  console.log(`Response from ${targetUrl}: ${response.status} ${response.statusText}`);
231
- return response.ok ? response : null;
232
- } catch (e) {
233
- console.error(`Failed to fetch from ${url}:`, e);
234
- return null;
235
- } finally {
236
- limiter.release();
237
- }
238
- });
239
233
 
240
- const responses = await Promise.all(fetchPromises);
241
- const successResponse = responses.find((r): r is Response => r !== null);
242
- if (!successResponse) {
243
- console.error(`All registries failed for ${relativePathPrefixedWithSlash}`);
244
- res.writeHead(404).end('Not Found - All upstream registries failed');
245
- return;
246
- }
234
+ if (response.ok) {
235
+ const contentType = response.headers.get('Content-Type') || 'application/octet-stream';
236
+
237
+ if (contentType.includes('application/json')) {
238
+ // application/json 元数据
239
+ try {
240
+ const data = await response.json() as PackageData;
241
+ if (data.versions) {
242
+ const requestHeadersHostFromYarnClient = req.headers.host || 'localhost:' + proxyPort;
243
+ console.log("Request headers.host from yarn client is", requestHeadersHostFromYarnClient);
244
+ const proxyBaseUrlNoSuffixedWithSlash = `${proxyInfo.https ? 'https' : 'http'}://${requestHeadersHostFromYarnClient}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`;
245
+ console.log("proxyBaseUrlNoSuffixedWithSlash", proxyBaseUrlNoSuffixedWithSlash);
246
+ for (const version in data.versions) {
247
+ const dist = data.versions[version]?.dist;
248
+ if (dist?.tarball) {
249
+ const originalUrl = new URL(dist.tarball);
250
+ const originalSearchParamsStr = originalUrl.search || '';
251
+ const tarballPathPrefixedWithSlash = removeRegistryPrefix(dist.tarball, registryInfos);
252
+ dist.tarball = `${proxyBaseUrlNoSuffixedWithSlash}${tarballPathPrefixedWithSlash}${originalSearchParamsStr}`;
253
+ if (!tarballPathPrefixedWithSlash.startsWith("/")) console.error("bad tarballPath, must be PrefixedWithSlash", tarballPathPrefixedWithSlash);
254
+ }
255
+ }
256
+ }
257
+ res.writeHead(200, {'Content-Type': 'application/json'});
258
+ res.end(JSON.stringify(data));
259
+ return;
260
+ } catch (e) {
261
+ console.error('Failed to parse JSON response:', e);
262
+ // 继续尝试下一个注册表
263
+ }
264
+ } else {
265
+ if (!response.body) {
266
+ console.error(`Empty response body from ${response.url}, status: ${response.status}`);
267
+ continue;
268
+ }
247
269
 
248
- const contentType = successResponse.headers.get('Content-Type') || 'application/octet-stream';
249
- if (contentType.includes('application/json')) {
250
- try {
251
- const data = await successResponse.json() as PackageData;
252
- if (data.versions) {
253
- const requestHeadersHostFromYarnClient = req.headers.host || 'localhost:' + proxyPort;
254
- console.log("Request headers.host from yarn client is", requestHeadersHostFromYarnClient);
255
- const proxyBaseUrlNoSuffixedWithSlash = `${proxyInfo.https ? 'https' : 'http'}://${requestHeadersHostFromYarnClient}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`;
256
- console.log("proxyBaseUrlNoSuffixedWithSlash", proxyBaseUrlNoSuffixedWithSlash);
257
- for (const version in data.versions) {
258
- const dist = data.versions[version]?.dist;
259
- if (dist?.tarball) {
260
- const originalUrl = new URL(dist.tarball);
261
- const originalSearchParamsStr = originalUrl.search || '';
262
- const tarballPathPrefixedWithSlash = removeRegistryPrefix(dist.tarball, registries);
263
- dist.tarball = `${proxyBaseUrlNoSuffixedWithSlash}${tarballPathPrefixedWithSlash}${originalSearchParamsStr}`;
264
- if (!tarballPathPrefixedWithSlash.startsWith("/")) console.error("bad tarballPath, must be PrefixedWithSlash", tarballPathPrefixedWithSlash);
270
+ // 设置正确的响应头
271
+ const contentLength = response.headers.get('Content-Length');
272
+ const safeHeaders: OutgoingHttpHeaders = {
273
+ 'content-type': contentType,
274
+ 'connection': 'keep-alive'
275
+ };
276
+
277
+ if (contentLength && !isNaN(Number(contentLength))) {
278
+ safeHeaders['content-length'] = contentLength;
279
+ }
280
+
281
+ // 复制其他可能有用的头信息
282
+ const headersToCopy = ['cache-control', 'etag', 'last-modified'];
283
+ headersToCopy.forEach(header => {
284
+ const value = response?.headers.get(header);
285
+ if (value) {
286
+ safeHeaders[header] = value;
287
+ }
288
+ });
289
+
290
+ res.writeHead(response.status, safeHeaders);
291
+
292
+ // 使用 pipeline 处理流
293
+ const {pipeline} = await import('stream');
294
+ const {promisify} = await import('util');
295
+ const pipelineAsync = promisify(pipeline);
296
+
297
+ try {
298
+ // 将 ReadableStream 转换为 Node.js Readable
299
+ const nodeStream = Readable.fromWeb(response.body as any);
300
+
301
+ // 双向终止保护
302
+ req.on('close', () => nodeStream.destroy());
303
+ res.on('close', () => nodeStream.destroy());
304
+
305
+ await pipelineAsync(
306
+ nodeStream,
307
+ res
308
+ );
309
+ return; // 成功完成管道传输后返回
310
+ } catch (pipeError) {
311
+ console.error(`Stream pipeline error for ${relativePathPrefixedWithSlash}:`, pipeError);
312
+ if (!res.headersSent) {
313
+ res.writeHead(502).end('Stream Error');
314
+ }
315
+ continue;
265
316
  }
266
317
  }
267
318
  }
268
- res.writeHead(200, {'Content-Type': 'application/json'});
269
- res.end(JSON.stringify(data));
270
319
  } catch (e) {
271
- console.error('Failed to parse JSON response:', e);
272
- res.writeHead(502).end('Invalid Upstream Response');
273
- }
274
- } else {
275
- if (!successResponse.body) {
276
- console.error(`Empty response body from ${successResponse.url}, status: ${successResponse.status}`);
277
- res.writeHead(502).end('Empty Response Body');
278
- return;
320
+ console.error(`Failed to fetch from ${normalizedRegistryUrl}:`, e);
321
+ } finally {
322
+ // 不需要手动销毁 ReadableStream
323
+ limiter.release();
279
324
  }
280
- const contentLength = successResponse.headers.get('Content-Length');
281
- const safeHeaders: OutgoingHttpHeaders = {};
282
- safeHeaders["content-type"] = contentType;
283
- if (contentLength && !isNaN(Number(contentLength))) safeHeaders["content-length"] = contentLength;
284
- res.writeHead(successResponse.status, safeHeaders);
285
- successResponse.body.pipe(res).on('error', (err: any) => {
286
- console.error(`Stream error for ${relativePathPrefixedWithSlash}:`, err);
287
- res.writeHead(502).end('Stream Error');
288
- });
289
325
  }
326
+
327
+ // 所有注册表都尝试失败
328
+ console.error(`All registries failed for ${relativePathPrefixedWithSlash}`);
329
+ res.writeHead(404).end('Not Found - All upstream registries failed');
290
330
  };
291
331
 
292
332
  let server: HttpServer | HttpsServer;
@@ -310,7 +350,7 @@ export async function startProxyServer(
310
350
  server = createServer(requestHandler);
311
351
  }
312
352
 
313
- return new Promise((resolve, reject) => {
353
+ const promisedServer: Promise<HttpServer | HttpsServer> = new Promise((resolve, reject) => {
314
354
  server.on('error', (err: NodeJS.ErrnoException) => {
315
355
  if (err.code === 'EADDRINUSE') {
316
356
  console.error(`Port ${port} is in use, please specify a different port or free it.`);
@@ -328,6 +368,8 @@ export async function startProxyServer(
328
368
  resolve(server);
329
369
  });
330
370
  });
371
+
372
+ return promisedServer as Promise<HttpServer | HttpsServer>;
331
373
  }
332
374
 
333
375
  if (import.meta.url === `file://${process.argv[1]}`) {