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.
- package/dist/index.js +80 -73
- package/package.json +1 -1
- 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.
|
|
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 [
|
|
115
|
-
const
|
|
116
|
-
let token =
|
|
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
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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(
|
|
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
|
|
142
|
+
const registryInfos = proxyInfo.registries;
|
|
138
143
|
const basePathPrefixedWithSlash = removeEndingSlashAndForceStartingSlash(proxyInfo.basePath);
|
|
139
|
-
console.log('Active registries:',
|
|
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
|
-
|
|
163
|
+
// 修改为按顺序尝试注册表,找到第一个成功响应即返回
|
|
164
|
+
for (const { normalizedRegistryUrl, token } of registryInfos) {
|
|
159
165
|
await limiter.acquire();
|
|
160
166
|
try {
|
|
161
|
-
const
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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(
|
|
210
|
-
|
|
226
|
+
console.error(`Failed to fetch from ${normalizedRegistryUrl}:`, e);
|
|
227
|
+
// 继续尝试下一个注册表
|
|
211
228
|
}
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
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
package/src/index.ts
CHANGED
|
@@ -31,7 +31,7 @@ interface YarnConfig {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
interface RegistryInfo {
|
|
34
|
-
|
|
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.
|
|
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 [
|
|
162
|
-
const
|
|
163
|
-
let token =
|
|
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
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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(
|
|
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
|
|
195
|
+
const registryInfos = proxyInfo.registries;
|
|
191
196
|
const basePathPrefixedWithSlash: string = removeEndingSlashAndForceStartingSlash(proxyInfo.basePath);
|
|
192
197
|
|
|
193
|
-
console.log('Active registries:',
|
|
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
|
-
|
|
222
|
+
// 修改为按顺序尝试注册表,找到第一个成功响应即返回
|
|
223
|
+
for (const {normalizedRegistryUrl, token} of registryInfos) {
|
|
218
224
|
await limiter.acquire();
|
|
219
225
|
try {
|
|
220
|
-
const
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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(
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
|
|
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
|
-
|
|
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]}`) {
|