com.jimuwd.xian.registry-proxy 1.0.31 → 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 +66 -64
- package/package.json +1 -1
- package/src/index.ts +66 -62
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)) {
|
|
@@ -130,7 +130,7 @@ async function loadProxyInfo(proxyConfigPath = './.registry-proxy.yml', localYar
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
-
registryMap.set(normalizedProxiedRegUrl, {
|
|
133
|
+
registryMap.set(normalizedProxiedRegUrl, { normalizedRegistryUrl: normalizedProxiedRegUrl, token });
|
|
134
134
|
}
|
|
135
135
|
const registries = Array.from(registryMap.values());
|
|
136
136
|
const https = proxyConfig.https;
|
|
@@ -139,9 +139,9 @@ async function loadProxyInfo(proxyConfigPath = './.registry-proxy.yml', localYar
|
|
|
139
139
|
}
|
|
140
140
|
export async function startProxyServer(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath, port = 0) {
|
|
141
141
|
const proxyInfo = await loadProxyInfo(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath);
|
|
142
|
-
const
|
|
142
|
+
const registryInfos = proxyInfo.registries;
|
|
143
143
|
const basePathPrefixedWithSlash = removeEndingSlashAndForceStartingSlash(proxyInfo.basePath);
|
|
144
|
-
console.log('Active registries:',
|
|
144
|
+
console.log('Active registries:', registryInfos.map(r => r.normalizedRegistryUrl));
|
|
145
145
|
console.log('Proxy base path:', basePathPrefixedWithSlash);
|
|
146
146
|
console.log('HTTPS:', !!proxyInfo.https);
|
|
147
147
|
let proxyPort;
|
|
@@ -160,78 +160,79 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
160
160
|
}
|
|
161
161
|
const relativePathPrefixedWithSlash = basePathPrefixedWithSlash === '/' ? fullUrl.pathname : fullUrl.pathname.slice(basePathPrefixedWithSlash.length);
|
|
162
162
|
console.log(`Proxying: ${relativePathPrefixedWithSlash}`);
|
|
163
|
-
|
|
163
|
+
// 修改为按顺序尝试注册表,找到第一个成功响应即返回
|
|
164
|
+
for (const { normalizedRegistryUrl, token } of registryInfos) {
|
|
164
165
|
await limiter.acquire();
|
|
165
166
|
try {
|
|
166
|
-
const
|
|
167
|
-
const targetUrl = `${url}/${cleanRelativePath}${fullUrl.search || ''}`;
|
|
167
|
+
const targetUrl = `${normalizedRegistryUrl}${relativePathPrefixedWithSlash}${fullUrl.search || ''}`;
|
|
168
168
|
console.log(`Fetching from: ${targetUrl}`);
|
|
169
169
|
const headers = token ? { Authorization: `Bearer ${token}` } : undefined;
|
|
170
|
-
const response = await fetch(targetUrl, { headers
|
|
170
|
+
const response = await fetch(targetUrl, { headers });
|
|
171
171
|
console.log(`Response from ${targetUrl}: ${response.status} ${response.statusText}`);
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
+
}
|
|
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;
|
|
207
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;
|
|
208
222
|
}
|
|
209
223
|
}
|
|
210
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
211
|
-
res.end(JSON.stringify(data));
|
|
212
224
|
}
|
|
213
225
|
catch (e) {
|
|
214
|
-
console.error(
|
|
215
|
-
|
|
226
|
+
console.error(`Failed to fetch from ${normalizedRegistryUrl}:`, e);
|
|
227
|
+
// 继续尝试下一个注册表
|
|
216
228
|
}
|
|
217
|
-
|
|
218
|
-
|
|
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;
|
|
229
|
+
finally {
|
|
230
|
+
limiter.release();
|
|
223
231
|
}
|
|
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
232
|
}
|
|
233
|
+
// 所有注册表都尝试失败
|
|
234
|
+
console.error(`All registries failed for ${relativePathPrefixedWithSlash}`);
|
|
235
|
+
res.writeHead(404).end('Not Found - All upstream registries failed');
|
|
235
236
|
};
|
|
236
237
|
let server;
|
|
237
238
|
if (proxyInfo.https) {
|
|
@@ -255,7 +256,7 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
255
256
|
else {
|
|
256
257
|
server = createServer(requestHandler);
|
|
257
258
|
}
|
|
258
|
-
|
|
259
|
+
const promisedServer = new Promise((resolve, reject) => {
|
|
259
260
|
server.on('error', (err) => {
|
|
260
261
|
if (err.code === 'EADDRINUSE') {
|
|
261
262
|
console.error(`Port ${port} is in use, please specify a different port or free it.`);
|
|
@@ -273,6 +274,7 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
273
274
|
resolve(server);
|
|
274
275
|
});
|
|
275
276
|
});
|
|
277
|
+
return promisedServer;
|
|
276
278
|
}
|
|
277
279
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
278
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)) {
|
|
@@ -177,7 +177,7 @@ async function loadProxyInfo(
|
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
|
-
registryMap.set(normalizedProxiedRegUrl, {
|
|
180
|
+
registryMap.set(normalizedProxiedRegUrl, {normalizedRegistryUrl: normalizedProxiedRegUrl, token});
|
|
181
181
|
}
|
|
182
182
|
const registries = Array.from(registryMap.values());
|
|
183
183
|
const https = proxyConfig.https;
|
|
@@ -192,10 +192,10 @@ export async function startProxyServer(
|
|
|
192
192
|
port: number = 0
|
|
193
193
|
): Promise<HttpServer | HttpsServer> {
|
|
194
194
|
const proxyInfo = await loadProxyInfo(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath);
|
|
195
|
-
const
|
|
195
|
+
const registryInfos = proxyInfo.registries;
|
|
196
196
|
const basePathPrefixedWithSlash: string = removeEndingSlashAndForceStartingSlash(proxyInfo.basePath);
|
|
197
197
|
|
|
198
|
-
console.log('Active registries:',
|
|
198
|
+
console.log('Active registries:', registryInfos.map(r => r.normalizedRegistryUrl));
|
|
199
199
|
console.log('Proxy base path:', basePathPrefixedWithSlash);
|
|
200
200
|
console.log('HTTPS:', !!proxyInfo.https);
|
|
201
201
|
|
|
@@ -219,74 +219,76 @@ export async function startProxyServer(
|
|
|
219
219
|
const relativePathPrefixedWithSlash = basePathPrefixedWithSlash === '/' ? fullUrl.pathname : fullUrl.pathname.slice(basePathPrefixedWithSlash.length);
|
|
220
220
|
console.log(`Proxying: ${relativePathPrefixedWithSlash}`);
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
// 修改为按顺序尝试注册表,找到第一个成功响应即返回
|
|
223
|
+
for (const {normalizedRegistryUrl, token} of registryInfos) {
|
|
223
224
|
await limiter.acquire();
|
|
224
225
|
try {
|
|
225
|
-
const
|
|
226
|
-
const targetUrl = `${url}/${cleanRelativePath}${fullUrl.search || ''}`;
|
|
226
|
+
const targetUrl = `${normalizedRegistryUrl}${relativePathPrefixedWithSlash}${fullUrl.search || ''}`;
|
|
227
227
|
console.log(`Fetching from: ${targetUrl}`);
|
|
228
228
|
const headers = token ? {Authorization: `Bearer ${token}`} : undefined;
|
|
229
|
-
const response = await fetch(targetUrl, {headers
|
|
229
|
+
const response = await fetch(targetUrl, {headers});
|
|
230
230
|
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
|
-
|
|
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
|
-
}
|
|
247
231
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
+
// 继续尝试下一个注册表
|
|
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;
|
|
265
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;
|
|
266
279
|
}
|
|
267
280
|
}
|
|
268
|
-
res.writeHead(200, {'Content-Type': 'application/json'});
|
|
269
|
-
res.end(JSON.stringify(data));
|
|
270
281
|
} catch (e) {
|
|
271
|
-
console.error(
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
|
|
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;
|
|
282
|
+
console.error(`Failed to fetch from ${normalizedRegistryUrl}:`, e);
|
|
283
|
+
// 继续尝试下一个注册表
|
|
284
|
+
} finally {
|
|
285
|
+
limiter.release();
|
|
279
286
|
}
|
|
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
287
|
}
|
|
288
|
+
|
|
289
|
+
// 所有注册表都尝试失败
|
|
290
|
+
console.error(`All registries failed for ${relativePathPrefixedWithSlash}`);
|
|
291
|
+
res.writeHead(404).end('Not Found - All upstream registries failed');
|
|
290
292
|
};
|
|
291
293
|
|
|
292
294
|
let server: HttpServer | HttpsServer;
|
|
@@ -310,7 +312,7 @@ export async function startProxyServer(
|
|
|
310
312
|
server = createServer(requestHandler);
|
|
311
313
|
}
|
|
312
314
|
|
|
313
|
-
|
|
315
|
+
const promisedServer: Promise<HttpServer | HttpsServer> = new Promise((resolve, reject) => {
|
|
314
316
|
server.on('error', (err: NodeJS.ErrnoException) => {
|
|
315
317
|
if (err.code === 'EADDRINUSE') {
|
|
316
318
|
console.error(`Port ${port} is in use, please specify a different port or free it.`);
|
|
@@ -328,6 +330,8 @@ export async function startProxyServer(
|
|
|
328
330
|
resolve(server);
|
|
329
331
|
});
|
|
330
332
|
});
|
|
333
|
+
|
|
334
|
+
return promisedServer as Promise<HttpServer | HttpsServer>;
|
|
331
335
|
}
|
|
332
336
|
|
|
333
337
|
if (import.meta.url === `file://${process.argv[1]}`) {
|