com.jimuwd.xian.registry-proxy 1.0.30 → 1.0.32

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 +80 -73
  2. package/package.json +1 -1
  3. package/src/index.ts +80 -71
package/dist/index.js CHANGED
@@ -69,7 +69,7 @@ function resolvePath(path) {
69
69
  function removeRegistryPrefix(tarballUrl, registries) {
70
70
  const normalizedTarball = normalizeUrl(tarballUrl);
71
71
  const normalizedRegistries = registries
72
- .map(r => normalizeUrl(r.url))
72
+ .map(r => normalizeUrl(r.normalizedRegistryUrl))
73
73
  .sort((a, b) => b.length - a.length);
74
74
  for (const normalizedRegistry of normalizedRegistries) {
75
75
  if (normalizedTarball.startsWith(normalizedRegistry)) {
@@ -111,21 +111,26 @@ async function loadProxyInfo(proxyConfigPath = './.registry-proxy.yml', localYar
111
111
  readYarnConfig(globalYarnConfigPath)
112
112
  ]);
113
113
  const registryMap = new Map();
114
- for (const [url, regConfig] of Object.entries(proxyConfig.registries)) {
115
- const normalizedUrl = normalizeUrl(url);
116
- let token = regConfig?.npmAuthToken;
114
+ for (const [proxiedRegUrl, proxyRegConfig] of Object.entries(proxyConfig.registries)) {
115
+ const normalizedProxiedRegUrl = normalizeUrl(proxiedRegUrl);
116
+ let token = proxyRegConfig?.npmAuthToken;
117
117
  if (!token) {
118
118
  const yarnConfigs = [localYarnConfig, globalYarnConfig];
119
- for (const config of yarnConfigs) {
120
- const registryConfig = config.npmRegistries?.[normalizedUrl] ||
121
- config.npmRegistries?.[url];
122
- if (registryConfig?.npmAuthToken) {
123
- token = registryConfig.npmAuthToken;
124
- break;
119
+ for (const yarnConfig of yarnConfigs) {
120
+ if (yarnConfig.npmRegistries) {
121
+ const foundEntry = Object.entries(yarnConfig.npmRegistries)
122
+ .find(([registryUrl]) => normalizedProxiedRegUrl === normalizeUrl(registryUrl));
123
+ if (foundEntry) {
124
+ const [, registryConfig] = foundEntry;
125
+ if (registryConfig?.npmAuthToken) {
126
+ token = registryConfig.npmAuthToken;
127
+ break;
128
+ }
129
+ }
125
130
  }
126
131
  }
127
132
  }
128
- registryMap.set(normalizedUrl, { url: normalizedUrl, token });
133
+ registryMap.set(normalizedProxiedRegUrl, { normalizedRegistryUrl: normalizedProxiedRegUrl, token });
129
134
  }
130
135
  const registries = Array.from(registryMap.values());
131
136
  const https = proxyConfig.https;
@@ -134,9 +139,9 @@ async function loadProxyInfo(proxyConfigPath = './.registry-proxy.yml', localYar
134
139
  }
135
140
  export async function startProxyServer(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath, port = 0) {
136
141
  const proxyInfo = await loadProxyInfo(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath);
137
- const registries = proxyInfo.registries;
142
+ const registryInfos = proxyInfo.registries;
138
143
  const basePathPrefixedWithSlash = removeEndingSlashAndForceStartingSlash(proxyInfo.basePath);
139
- console.log('Active registries:', registries.map(r => r.url));
144
+ console.log('Active registries:', registryInfos.map(r => r.normalizedRegistryUrl));
140
145
  console.log('Proxy base path:', basePathPrefixedWithSlash);
141
146
  console.log('HTTPS:', !!proxyInfo.https);
142
147
  let proxyPort;
@@ -155,78 +160,79 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
155
160
  }
156
161
  const relativePathPrefixedWithSlash = basePathPrefixedWithSlash === '/' ? fullUrl.pathname : fullUrl.pathname.slice(basePathPrefixedWithSlash.length);
157
162
  console.log(`Proxying: ${relativePathPrefixedWithSlash}`);
158
- const fetchPromises = registries.map(async ({ url, token }) => {
163
+ // 修改为按顺序尝试注册表,找到第一个成功响应即返回
164
+ for (const { normalizedRegistryUrl, token } of registryInfos) {
159
165
  await limiter.acquire();
160
166
  try {
161
- const cleanRelativePath = relativePathPrefixedWithSlash.replace(/^\/+|\/+$/g, '');
162
- const targetUrl = `${url}/${cleanRelativePath}${fullUrl.search || ''}`;
167
+ const targetUrl = `${normalizedRegistryUrl}${relativePathPrefixedWithSlash}${fullUrl.search || ''}`;
163
168
  console.log(`Fetching from: ${targetUrl}`);
164
169
  const headers = token ? { Authorization: `Bearer ${token}` } : undefined;
165
- const response = await fetch(targetUrl, { headers, });
170
+ const response = await fetch(targetUrl, { headers });
166
171
  console.log(`Response from ${targetUrl}: ${response.status} ${response.statusText}`);
167
- return response.ok ? response : null;
168
- }
169
- catch (e) {
170
- console.error(`Failed to fetch from ${url}:`, e);
171
- return null;
172
- }
173
- finally {
174
- limiter.release();
175
- }
176
- });
177
- const responses = await Promise.all(fetchPromises);
178
- const successResponse = responses.find((r) => r !== null);
179
- if (!successResponse) {
180
- console.error(`All registries failed for ${relativePathPrefixedWithSlash}`);
181
- res.writeHead(404).end('Not Found - All upstream registries failed');
182
- return;
183
- }
184
- const contentType = successResponse.headers.get('Content-Type') || 'application/octet-stream';
185
- if (contentType.includes('application/json')) {
186
- try {
187
- const data = await successResponse.json();
188
- if (data.versions) {
189
- const requestHeadersHostFromYarnClient = req.headers.host || 'localhost:' + proxyPort;
190
- console.log("Request headers.host from yarn client is", requestHeadersHostFromYarnClient);
191
- const proxyBaseUrlNoSuffixedWithSlash = `${proxyInfo.https ? 'https' : 'http'}://${requestHeadersHostFromYarnClient}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`;
192
- console.log("proxyBaseUrlNoSuffixedWithSlash", proxyBaseUrlNoSuffixedWithSlash);
193
- for (const version in data.versions) {
194
- const dist = data.versions[version]?.dist;
195
- if (dist?.tarball) {
196
- const originalUrl = new URL(dist.tarball);
197
- const originalSearchParamsStr = originalUrl.search || '';
198
- const tarballPathPrefixedWithSlash = removeRegistryPrefix(dist.tarball, registries);
199
- dist.tarball = `${proxyBaseUrlNoSuffixedWithSlash}${tarballPathPrefixedWithSlash}${originalSearchParamsStr}`;
200
- if (!tarballPathPrefixedWithSlash.startsWith("/"))
201
- console.error("bad tarballPath, must be PrefixedWithSlash", tarballPathPrefixedWithSlash);
172
+ if (response.ok) {
173
+ const contentType = response.headers.get('Content-Type') || 'application/octet-stream';
174
+ if (contentType.includes('application/json')) {
175
+ // application/json 元数据
176
+ try {
177
+ const data = await response.json();
178
+ if (data.versions) {
179
+ const requestHeadersHostFromYarnClient = req.headers.host || 'localhost:' + proxyPort;
180
+ console.log("Request headers.host from yarn client is", requestHeadersHostFromYarnClient);
181
+ const proxyBaseUrlNoSuffixedWithSlash = `${proxyInfo.https ? 'https' : 'http'}://${requestHeadersHostFromYarnClient}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`;
182
+ console.log("proxyBaseUrlNoSuffixedWithSlash", proxyBaseUrlNoSuffixedWithSlash);
183
+ for (const version in data.versions) {
184
+ const dist = data.versions[version]?.dist;
185
+ if (dist?.tarball) {
186
+ const originalUrl = new URL(dist.tarball);
187
+ const originalSearchParamsStr = originalUrl.search || '';
188
+ const tarballPathPrefixedWithSlash = removeRegistryPrefix(dist.tarball, registryInfos);
189
+ dist.tarball = `${proxyBaseUrlNoSuffixedWithSlash}${tarballPathPrefixedWithSlash}${originalSearchParamsStr}`;
190
+ if (!tarballPathPrefixedWithSlash.startsWith("/"))
191
+ console.error("bad tarballPath, must be PrefixedWithSlash", tarballPathPrefixedWithSlash);
192
+ }
193
+ }
194
+ }
195
+ res.writeHead(200, { 'Content-Type': 'application/json' });
196
+ res.end(JSON.stringify(data));
197
+ return;
198
+ }
199
+ catch (e) {
200
+ console.error('Failed to parse JSON response:', e);
201
+ // 继续尝试下一个注册表
202
202
  }
203
203
  }
204
+ else {
205
+ // 非application/json则是tarball
206
+ if (!response.body) {
207
+ console.error(`Empty response body from ${response.url}, status: ${response.status}`);
208
+ // 继续尝试下一个注册表
209
+ continue;
210
+ }
211
+ const contentLength = response.headers.get('Content-Length');
212
+ const safeHeaders = {};
213
+ safeHeaders["content-type"] = contentType;
214
+ if (contentLength && !isNaN(Number(contentLength)))
215
+ safeHeaders["content-length"] = contentLength;
216
+ res.writeHead(response.status, safeHeaders);
217
+ response.body.pipe(res).on('error', (err) => {
218
+ console.error(`Stream error for ${relativePathPrefixedWithSlash}:`, err);
219
+ res.writeHead(502).end('Stream Error');
220
+ });
221
+ return;
222
+ }
204
223
  }
205
- res.writeHead(200, { 'Content-Type': 'application/json' });
206
- res.end(JSON.stringify(data));
207
224
  }
208
225
  catch (e) {
209
- console.error('Failed to parse JSON response:', e);
210
- res.writeHead(502).end('Invalid Upstream Response');
226
+ console.error(`Failed to fetch from ${normalizedRegistryUrl}:`, e);
227
+ // 继续尝试下一个注册表
211
228
  }
212
- }
213
- else {
214
- if (!successResponse.body) {
215
- console.error(`Empty response body from ${successResponse.url}, status: ${successResponse.status}`);
216
- res.writeHead(502).end('Empty Response Body');
217
- return;
229
+ finally {
230
+ limiter.release();
218
231
  }
219
- const contentLength = successResponse.headers.get('Content-Length');
220
- const safeHeaders = {};
221
- safeHeaders["content-type"] = contentType;
222
- if (contentLength && !isNaN(Number(contentLength)))
223
- safeHeaders["content-length"] = contentLength;
224
- res.writeHead(successResponse.status, safeHeaders);
225
- successResponse.body.pipe(res).on('error', (err) => {
226
- console.error(`Stream error for ${relativePathPrefixedWithSlash}:`, err);
227
- res.writeHead(502).end('Stream Error');
228
- });
229
232
  }
233
+ // 所有注册表都尝试失败
234
+ console.error(`All registries failed for ${relativePathPrefixedWithSlash}`);
235
+ res.writeHead(404).end('Not Found - All upstream registries failed');
230
236
  };
231
237
  let server;
232
238
  if (proxyInfo.https) {
@@ -250,7 +256,7 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
250
256
  else {
251
257
  server = createServer(requestHandler);
252
258
  }
253
- return new Promise((resolve, reject) => {
259
+ const promisedServer = new Promise((resolve, reject) => {
254
260
  server.on('error', (err) => {
255
261
  if (err.code === 'EADDRINUSE') {
256
262
  console.error(`Port ${port} is in use, please specify a different port or free it.`);
@@ -268,6 +274,7 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
268
274
  resolve(server);
269
275
  });
270
276
  });
277
+ return promisedServer;
271
278
  }
272
279
  if (import.meta.url === `file://${process.argv[1]}`) {
273
280
  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.30",
3
+ "version": "1.0.32",
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
@@ -31,7 +31,7 @@ interface YarnConfig {
31
31
  }
32
32
 
33
33
  interface RegistryInfo {
34
- url: string;
34
+ normalizedRegistryUrl: string;
35
35
  token?: string;
36
36
  }
37
37
 
@@ -111,7 +111,7 @@ function resolvePath(path: string): string {
111
111
  function removeRegistryPrefix(tarballUrl: string, registries: RegistryInfo[]): string {
112
112
  const normalizedTarball = normalizeUrl(tarballUrl);
113
113
  const normalizedRegistries = registries
114
- .map(r => normalizeUrl(r.url))
114
+ .map(r => normalizeUrl(r.normalizedRegistryUrl))
115
115
  .sort((a, b) => b.length - a.length);
116
116
  for (const normalizedRegistry of normalizedRegistries) {
117
117
  if (normalizedTarball.startsWith(normalizedRegistry)) {
@@ -158,21 +158,26 @@ async function loadProxyInfo(
158
158
  readYarnConfig(globalYarnConfigPath)
159
159
  ]);
160
160
  const registryMap = new Map<string, RegistryInfo>();
161
- for (const [url, regConfig] of Object.entries(proxyConfig.registries)) {
162
- const normalizedUrl = normalizeUrl(url);
163
- let token = regConfig?.npmAuthToken;
161
+ for (const [proxiedRegUrl, proxyRegConfig] of Object.entries(proxyConfig.registries)) {
162
+ const normalizedProxiedRegUrl = normalizeUrl(proxiedRegUrl);
163
+ let token = proxyRegConfig?.npmAuthToken;
164
164
  if (!token) {
165
165
  const yarnConfigs = [localYarnConfig, globalYarnConfig];
166
- for (const config of yarnConfigs) {
167
- const registryConfig = config.npmRegistries?.[normalizedUrl] ||
168
- config.npmRegistries?.[url];
169
- if (registryConfig?.npmAuthToken) {
170
- token = registryConfig.npmAuthToken;
171
- break;
166
+ for (const yarnConfig of yarnConfigs) {
167
+ if (yarnConfig.npmRegistries) {
168
+ const foundEntry = Object.entries(yarnConfig.npmRegistries)
169
+ .find(([registryUrl]) => normalizedProxiedRegUrl === normalizeUrl(registryUrl))
170
+ if (foundEntry) {
171
+ const [, registryConfig] = foundEntry;
172
+ if (registryConfig?.npmAuthToken) {
173
+ token = registryConfig.npmAuthToken;
174
+ break;
175
+ }
176
+ }
172
177
  }
173
178
  }
174
179
  }
175
- registryMap.set(normalizedUrl, {url: normalizedUrl, token});
180
+ registryMap.set(normalizedProxiedRegUrl, {normalizedRegistryUrl: normalizedProxiedRegUrl, token});
176
181
  }
177
182
  const registries = Array.from(registryMap.values());
178
183
  const https = proxyConfig.https;
@@ -187,10 +192,10 @@ export async function startProxyServer(
187
192
  port: number = 0
188
193
  ): Promise<HttpServer | HttpsServer> {
189
194
  const proxyInfo = await loadProxyInfo(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath);
190
- const registries = proxyInfo.registries;
195
+ const registryInfos = proxyInfo.registries;
191
196
  const basePathPrefixedWithSlash: string = removeEndingSlashAndForceStartingSlash(proxyInfo.basePath);
192
197
 
193
- console.log('Active registries:', registries.map(r => r.url));
198
+ console.log('Active registries:', registryInfos.map(r => r.normalizedRegistryUrl));
194
199
  console.log('Proxy base path:', basePathPrefixedWithSlash);
195
200
  console.log('HTTPS:', !!proxyInfo.https);
196
201
 
@@ -214,74 +219,76 @@ export async function startProxyServer(
214
219
  const relativePathPrefixedWithSlash = basePathPrefixedWithSlash === '/' ? fullUrl.pathname : fullUrl.pathname.slice(basePathPrefixedWithSlash.length);
215
220
  console.log(`Proxying: ${relativePathPrefixedWithSlash}`);
216
221
 
217
- const fetchPromises = registries.map(async ({url, token}) => {
222
+ // 修改为按顺序尝试注册表,找到第一个成功响应即返回
223
+ for (const {normalizedRegistryUrl, token} of registryInfos) {
218
224
  await limiter.acquire();
219
225
  try {
220
- const cleanRelativePath = relativePathPrefixedWithSlash.replace(/^\/+|\/+$/g, '');
221
- const targetUrl = `${url}/${cleanRelativePath}${fullUrl.search || ''}`;
226
+ const targetUrl = `${normalizedRegistryUrl}${relativePathPrefixedWithSlash}${fullUrl.search || ''}`;
222
227
  console.log(`Fetching from: ${targetUrl}`);
223
228
  const headers = token ? {Authorization: `Bearer ${token}`} : undefined;
224
- const response = await fetch(targetUrl, {headers,});
229
+ const response = await fetch(targetUrl, {headers});
225
230
  console.log(`Response from ${targetUrl}: ${response.status} ${response.statusText}`);
226
- return response.ok ? response : null;
227
- } catch (e) {
228
- console.error(`Failed to fetch from ${url}:`, e);
229
- return null;
230
- } finally {
231
- limiter.release();
232
- }
233
- });
234
231
 
235
- const responses = await Promise.all(fetchPromises);
236
- const successResponse = responses.find((r): r is Response => r !== null);
237
- if (!successResponse) {
238
- console.error(`All registries failed for ${relativePathPrefixedWithSlash}`);
239
- res.writeHead(404).end('Not Found - All upstream registries failed');
240
- return;
241
- }
242
-
243
- const contentType = successResponse.headers.get('Content-Type') || 'application/octet-stream';
244
- if (contentType.includes('application/json')) {
245
- try {
246
- const data = await successResponse.json() as PackageData;
247
- if (data.versions) {
248
- const requestHeadersHostFromYarnClient = req.headers.host || 'localhost:' + proxyPort;
249
- console.log("Request headers.host from yarn client is", requestHeadersHostFromYarnClient);
250
- const proxyBaseUrlNoSuffixedWithSlash = `${proxyInfo.https ? 'https' : 'http'}://${requestHeadersHostFromYarnClient}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`;
251
- console.log("proxyBaseUrlNoSuffixedWithSlash", proxyBaseUrlNoSuffixedWithSlash);
252
- for (const version in data.versions) {
253
- const dist = data.versions[version]?.dist;
254
- if (dist?.tarball) {
255
- const originalUrl = new URL(dist.tarball);
256
- const originalSearchParamsStr = originalUrl.search || '';
257
- const tarballPathPrefixedWithSlash = removeRegistryPrefix(dist.tarball, registries);
258
- dist.tarball = `${proxyBaseUrlNoSuffixedWithSlash}${tarballPathPrefixedWithSlash}${originalSearchParamsStr}`;
259
- if (!tarballPathPrefixedWithSlash.startsWith("/")) console.error("bad tarballPath, must be PrefixedWithSlash", tarballPathPrefixedWithSlash);
232
+ if (response.ok) {
233
+ const contentType = response.headers.get('Content-Type') || 'application/octet-stream';
234
+
235
+ if (contentType.includes('application/json')) {
236
+ // application/json 元数据
237
+ try {
238
+ const data = await response.json() as PackageData;
239
+ if (data.versions) {
240
+ const requestHeadersHostFromYarnClient = req.headers.host || 'localhost:' + proxyPort;
241
+ console.log("Request headers.host from yarn client is", requestHeadersHostFromYarnClient);
242
+ const proxyBaseUrlNoSuffixedWithSlash = `${proxyInfo.https ? 'https' : 'http'}://${requestHeadersHostFromYarnClient}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`;
243
+ console.log("proxyBaseUrlNoSuffixedWithSlash", proxyBaseUrlNoSuffixedWithSlash);
244
+ for (const version in data.versions) {
245
+ const dist = data.versions[version]?.dist;
246
+ if (dist?.tarball) {
247
+ const originalUrl = new URL(dist.tarball);
248
+ const originalSearchParamsStr = originalUrl.search || '';
249
+ const tarballPathPrefixedWithSlash = removeRegistryPrefix(dist.tarball, registryInfos);
250
+ dist.tarball = `${proxyBaseUrlNoSuffixedWithSlash}${tarballPathPrefixedWithSlash}${originalSearchParamsStr}`;
251
+ if (!tarballPathPrefixedWithSlash.startsWith("/")) console.error("bad tarballPath, must be PrefixedWithSlash", tarballPathPrefixedWithSlash);
252
+ }
253
+ }
254
+ }
255
+ res.writeHead(200, {'Content-Type': 'application/json'});
256
+ res.end(JSON.stringify(data));
257
+ return;
258
+ } catch (e) {
259
+ console.error('Failed to parse JSON response:', e);
260
+ // 继续尝试下一个注册表
260
261
  }
262
+ } else {
263
+ // 非application/json则是tarball
264
+ if (!response.body) {
265
+ console.error(`Empty response body from ${response.url}, status: ${response.status}`);
266
+ // 继续尝试下一个注册表
267
+ continue;
268
+ }
269
+ const contentLength = response.headers.get('Content-Length');
270
+ const safeHeaders: OutgoingHttpHeaders = {};
271
+ safeHeaders["content-type"] = contentType;
272
+ if (contentLength && !isNaN(Number(contentLength))) safeHeaders["content-length"] = contentLength;
273
+ res.writeHead(response.status, safeHeaders);
274
+ response.body.pipe(res).on('error', (err: any) => {
275
+ console.error(`Stream error for ${relativePathPrefixedWithSlash}:`, err);
276
+ res.writeHead(502).end('Stream Error');
277
+ });
278
+ return;
261
279
  }
262
280
  }
263
- res.writeHead(200, {'Content-Type': 'application/json'});
264
- res.end(JSON.stringify(data));
265
281
  } catch (e) {
266
- console.error('Failed to parse JSON response:', e);
267
- res.writeHead(502).end('Invalid Upstream Response');
268
- }
269
- } else {
270
- if (!successResponse.body) {
271
- console.error(`Empty response body from ${successResponse.url}, status: ${successResponse.status}`);
272
- res.writeHead(502).end('Empty Response Body');
273
- return;
282
+ console.error(`Failed to fetch from ${normalizedRegistryUrl}:`, e);
283
+ // 继续尝试下一个注册表
284
+ } finally {
285
+ limiter.release();
274
286
  }
275
- const contentLength = successResponse.headers.get('Content-Length');
276
- const safeHeaders: OutgoingHttpHeaders = {};
277
- safeHeaders["content-type"] = contentType;
278
- if (contentLength && !isNaN(Number(contentLength))) safeHeaders["content-length"] = contentLength;
279
- res.writeHead(successResponse.status, safeHeaders);
280
- successResponse.body.pipe(res).on('error', (err: any) => {
281
- console.error(`Stream error for ${relativePathPrefixedWithSlash}:`, err);
282
- res.writeHead(502).end('Stream Error');
283
- });
284
287
  }
288
+
289
+ // 所有注册表都尝试失败
290
+ console.error(`All registries failed for ${relativePathPrefixedWithSlash}`);
291
+ res.writeHead(404).end('Not Found - All upstream registries failed');
285
292
  };
286
293
 
287
294
  let server: HttpServer | HttpsServer;
@@ -305,7 +312,7 @@ export async function startProxyServer(
305
312
  server = createServer(requestHandler);
306
313
  }
307
314
 
308
- return new Promise((resolve, reject) => {
315
+ const promisedServer: Promise<HttpServer | HttpsServer> = new Promise((resolve, reject) => {
309
316
  server.on('error', (err: NodeJS.ErrnoException) => {
310
317
  if (err.code === 'EADDRINUSE') {
311
318
  console.error(`Port ${port} is in use, please specify a different port or free it.`);
@@ -323,6 +330,8 @@ export async function startProxyServer(
323
330
  resolve(server);
324
331
  });
325
332
  });
333
+
334
+ return promisedServer as Promise<HttpServer | HttpsServer>;
326
335
  }
327
336
 
328
337
  if (import.meta.url === `file://${process.argv[1]}`) {