com.jimuwd.xian.registry-proxy 1.0.36 → 1.0.38
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 +55 -48
- package/package.json +1 -1
- package/src/index.ts +63 -61
package/dist/index.js
CHANGED
|
@@ -139,10 +139,9 @@ async function loadProxyInfo(proxyConfigPath = './.registry-proxy.yml', localYar
|
|
|
139
139
|
const basePath = removeEndingSlashAndForceStartingSlash(proxyConfig.basePath);
|
|
140
140
|
return { registries, https, basePath };
|
|
141
141
|
}
|
|
142
|
-
async function fetchFromRegistry(registry,
|
|
142
|
+
async function fetchFromRegistry(registry, targetUrl, limiter) {
|
|
143
143
|
await limiter.acquire();
|
|
144
144
|
try {
|
|
145
|
-
const targetUrl = `${registry.normalizedRegistryUrl}${path}${search || ''}`;
|
|
146
145
|
console.log(`Fetching from: ${targetUrl}`);
|
|
147
146
|
const headers = registry.token ? { Authorization: `Bearer ${registry.token}` } : undefined;
|
|
148
147
|
const response = await fetch(targetUrl, { headers });
|
|
@@ -161,25 +160,28 @@ async function fetchFromRegistry(registry, path, search, limiter) {
|
|
|
161
160
|
limiter.release();
|
|
162
161
|
}
|
|
163
162
|
}
|
|
164
|
-
// 修改后的
|
|
165
|
-
async function
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const safeHeaders = {
|
|
169
|
-
'content-type': contentType,
|
|
170
|
-
'connection': 'keep-alive'
|
|
171
|
-
};
|
|
172
|
-
// 复制所有可能需要的头信息
|
|
173
|
-
const headersToCopy = [
|
|
174
|
-
'cache-control', 'etag', 'last-modified',
|
|
175
|
-
'content-encoding', 'content-length'
|
|
176
|
-
];
|
|
177
|
-
headersToCopy.forEach(header => {
|
|
178
|
-
const value = response.headers.get(header);
|
|
179
|
-
if (value)
|
|
180
|
-
safeHeaders[header] = value;
|
|
181
|
-
});
|
|
163
|
+
// 修改后的 writeSuccessfulResponse 函数
|
|
164
|
+
async function writeSuccessfulResponse(registryInfo, targetUrl, res, response, req, proxyInfo, proxyPort, registryInfos) {
|
|
165
|
+
if (!response.ok)
|
|
166
|
+
throw new Error("Only 2xx response is supported");
|
|
182
167
|
try {
|
|
168
|
+
const contentType = response.headers.get('Content-Type') || 'application/octet-stream';
|
|
169
|
+
// 准备通用头信息
|
|
170
|
+
const safeHeaders = {
|
|
171
|
+
'content-type': contentType,
|
|
172
|
+
'connection': 'keep-alive'
|
|
173
|
+
};
|
|
174
|
+
// 复制所有可能需要的头信息
|
|
175
|
+
const headersToCopy = [
|
|
176
|
+
'cache-control', 'etag', 'last-modified',
|
|
177
|
+
'content-encoding', 'content-length'
|
|
178
|
+
];
|
|
179
|
+
headersToCopy.forEach(header => {
|
|
180
|
+
const value = response.headers.get(header);
|
|
181
|
+
if (value)
|
|
182
|
+
safeHeaders[header] = value;
|
|
183
|
+
});
|
|
184
|
+
res.writeHead(response.status, safeHeaders);
|
|
183
185
|
if (contentType.includes('application/json')) {
|
|
184
186
|
// JSON 处理逻辑
|
|
185
187
|
const data = await response.json();
|
|
@@ -190,42 +192,42 @@ async function writeResponse(res, response, req, proxyInfo, proxyPort, registryI
|
|
|
190
192
|
const tarball = data.versions[version]?.dist?.tarball;
|
|
191
193
|
if (tarball) {
|
|
192
194
|
const path = removeRegistryPrefix(tarball, registryInfos);
|
|
193
|
-
|
|
195
|
+
const proxiedTarballUrl = `${baseUrl}${path}${new URL(tarball).search || ''}`;
|
|
196
|
+
data.versions[version].dist.tarball = proxiedTarballUrl;
|
|
197
|
+
console.log("proxiedTarballUrl", proxiedTarballUrl);
|
|
194
198
|
}
|
|
195
199
|
}
|
|
196
200
|
}
|
|
197
|
-
res.writeHead(200, safeHeaders);
|
|
198
201
|
res.end(JSON.stringify(data));
|
|
199
202
|
}
|
|
200
203
|
else {
|
|
201
204
|
// 二进制流处理
|
|
202
205
|
if (!response.body) {
|
|
203
|
-
console.error(
|
|
204
|
-
process.exit(1);
|
|
205
|
-
}
|
|
206
|
-
// 修复流类型转换问题
|
|
207
|
-
let nodeStream;
|
|
208
|
-
if (typeof Readable.fromWeb === 'function') {
|
|
209
|
-
// Node.js 17+ 标准方式
|
|
210
|
-
nodeStream = Readable.fromWeb(response.body);
|
|
206
|
+
console.error(`Empty response body from ${targetUrl}`);
|
|
211
207
|
}
|
|
212
208
|
else {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
passThrough
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
209
|
+
let nodeStream;
|
|
210
|
+
if (typeof Readable.fromWeb === 'function') {
|
|
211
|
+
// Node.js 17+ 标准方式
|
|
212
|
+
nodeStream = Readable.fromWeb(response.body);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
// Node.js 16 及以下版本的兼容方案
|
|
216
|
+
const { PassThrough } = await import('stream');
|
|
217
|
+
const passThrough = new PassThrough();
|
|
218
|
+
const reader = response.body.getReader();
|
|
219
|
+
const pump = async () => {
|
|
220
|
+
const { done, value } = await reader.read();
|
|
221
|
+
if (done)
|
|
222
|
+
return passThrough.end();
|
|
223
|
+
passThrough.write(value);
|
|
224
|
+
await pump();
|
|
225
|
+
};
|
|
226
|
+
pump().catch(err => passThrough.destroy(err));
|
|
227
|
+
nodeStream = passThrough;
|
|
228
|
+
}
|
|
229
|
+
await pipeline(nodeStream, res);
|
|
226
230
|
}
|
|
227
|
-
res.writeHead(response.status, safeHeaders);
|
|
228
|
-
await pipeline(nodeStream, res);
|
|
229
231
|
}
|
|
230
232
|
}
|
|
231
233
|
catch (err) {
|
|
@@ -258,10 +260,15 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
258
260
|
: fullUrl.pathname.slice(basePathPrefixedWithSlash.length);
|
|
259
261
|
// 顺序尝试注册表,获取第一个成功响应
|
|
260
262
|
let successfulResponse = null;
|
|
263
|
+
let targetRegistry = null;
|
|
264
|
+
let targetUrl = null;
|
|
261
265
|
for (const registry of registryInfos) {
|
|
262
266
|
if (req.destroyed)
|
|
263
267
|
break;
|
|
264
|
-
|
|
268
|
+
targetRegistry = registry;
|
|
269
|
+
const search = fullUrl.search || '';
|
|
270
|
+
targetUrl = `${registry.normalizedRegistryUrl}${path}${search}`;
|
|
271
|
+
const response = await fetchFromRegistry(registry, targetUrl, limiter);
|
|
265
272
|
if (response) {
|
|
266
273
|
successfulResponse = response;
|
|
267
274
|
break;
|
|
@@ -269,7 +276,7 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
269
276
|
}
|
|
270
277
|
// 统一回写响应
|
|
271
278
|
if (successfulResponse) {
|
|
272
|
-
await
|
|
279
|
+
await writeSuccessfulResponse(targetRegistry, targetUrl, res, successfulResponse, req, proxyInfo, proxyPort, registryInfos);
|
|
273
280
|
}
|
|
274
281
|
else {
|
|
275
282
|
res.writeHead(404).end('All upstream registries failed');
|
|
@@ -311,7 +318,7 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
311
318
|
proxyPort = address.port;
|
|
312
319
|
const portFile = join(process.env.PROJECT_ROOT || process.cwd(), '.registry-proxy-port');
|
|
313
320
|
writeFile(portFile, proxyPort.toString()).catch(e => console.error('Failed to write port file:', e));
|
|
314
|
-
console.log(`Proxy server running on ${proxyInfo.https ? 'https' : 'http'}://localhost:${proxyPort}${basePathPrefixedWithSlash}`);
|
|
321
|
+
console.log(`Proxy server running on ${proxyInfo.https ? 'https' : 'http'}://localhost:${proxyPort}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`);
|
|
315
322
|
resolve(server);
|
|
316
323
|
});
|
|
317
324
|
});
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -189,16 +189,14 @@ async function loadProxyInfo(
|
|
|
189
189
|
|
|
190
190
|
async function fetchFromRegistry(
|
|
191
191
|
registry: RegistryInfo,
|
|
192
|
-
|
|
193
|
-
search: string,
|
|
192
|
+
targetUrl: string,
|
|
194
193
|
limiter: ConcurrencyLimiter
|
|
195
194
|
): Promise<Response | null> {
|
|
196
195
|
await limiter.acquire();
|
|
197
196
|
try {
|
|
198
|
-
const targetUrl = `${registry.normalizedRegistryUrl}${path}${search || ''}`;
|
|
199
197
|
console.log(`Fetching from: ${targetUrl}`);
|
|
200
|
-
const headers = registry.token ? {
|
|
201
|
-
const response = await fetch(targetUrl, {
|
|
198
|
+
const headers = registry.token ? {Authorization: `Bearer ${registry.token}`} : undefined;
|
|
199
|
+
const response = await fetch(targetUrl, {headers});
|
|
202
200
|
console.log(`Response from ${targetUrl}: ${response.status} ${response.statusText}`);
|
|
203
201
|
return response.ok ? response : null;
|
|
204
202
|
} catch (e) {
|
|
@@ -215,8 +213,10 @@ async function fetchFromRegistry(
|
|
|
215
213
|
}
|
|
216
214
|
}
|
|
217
215
|
|
|
218
|
-
// 修改后的
|
|
219
|
-
async function
|
|
216
|
+
// 修改后的 writeSuccessfulResponse 函数
|
|
217
|
+
async function writeSuccessfulResponse(
|
|
218
|
+
registryInfo: RegistryInfo,
|
|
219
|
+
targetUrl: string,
|
|
220
220
|
res: ServerResponse,
|
|
221
221
|
response: Response,
|
|
222
222
|
req: IncomingMessage,
|
|
@@ -224,26 +224,32 @@ async function writeResponse(
|
|
|
224
224
|
proxyPort: number,
|
|
225
225
|
registryInfos: RegistryInfo[]
|
|
226
226
|
): Promise<void> {
|
|
227
|
-
const contentType = response.headers.get('Content-Type') || 'application/octet-stream';
|
|
228
227
|
|
|
229
|
-
|
|
230
|
-
const safeHeaders: OutgoingHttpHeaders = {
|
|
231
|
-
'content-type': contentType,
|
|
232
|
-
'connection': 'keep-alive'
|
|
233
|
-
};
|
|
228
|
+
if (!response.ok) throw new Error("Only 2xx response is supported");
|
|
234
229
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
'cache-control', 'etag', 'last-modified',
|
|
238
|
-
'content-encoding', 'content-length'
|
|
239
|
-
];
|
|
230
|
+
try {
|
|
231
|
+
const contentType = response.headers.get('Content-Type') || 'application/octet-stream';
|
|
240
232
|
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
233
|
+
// 准备通用头信息
|
|
234
|
+
const safeHeaders: OutgoingHttpHeaders = {
|
|
235
|
+
'content-type': contentType,
|
|
236
|
+
'connection': 'keep-alive'
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// 复制所有可能需要的头信息
|
|
240
|
+
const headersToCopy = [
|
|
241
|
+
'cache-control', 'etag', 'last-modified',
|
|
242
|
+
'content-encoding', 'content-length'
|
|
243
|
+
];
|
|
244
|
+
|
|
245
|
+
headersToCopy.forEach(header => {
|
|
246
|
+
const value = response.headers.get(header);
|
|
247
|
+
if (value) safeHeaders[header] = value;
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
res.writeHead(response.status, safeHeaders);
|
|
245
252
|
|
|
246
|
-
try {
|
|
247
253
|
if (contentType.includes('application/json')) {
|
|
248
254
|
// JSON 处理逻辑
|
|
249
255
|
const data = await response.json() as PackageData;
|
|
@@ -255,43 +261,41 @@ async function writeResponse(
|
|
|
255
261
|
const tarball = data.versions[version]?.dist?.tarball;
|
|
256
262
|
if (tarball) {
|
|
257
263
|
const path = removeRegistryPrefix(tarball, registryInfos);
|
|
258
|
-
|
|
264
|
+
const proxiedTarballUrl = `${baseUrl}${path}${new URL(tarball).search || ''}`;
|
|
265
|
+
data.versions[version]!.dist!.tarball = proxiedTarballUrl;
|
|
266
|
+
console.log("proxiedTarballUrl", proxiedTarballUrl);
|
|
259
267
|
}
|
|
260
268
|
}
|
|
261
269
|
}
|
|
262
|
-
res.writeHead(200, safeHeaders);
|
|
263
270
|
res.end(JSON.stringify(data));
|
|
264
271
|
} else {
|
|
265
272
|
// 二进制流处理
|
|
266
273
|
if (!response.body) {
|
|
267
|
-
console.error(
|
|
268
|
-
process.exit(1);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// 修复流类型转换问题
|
|
272
|
-
let nodeStream: NodeJS.ReadableStream;
|
|
273
|
-
if (typeof Readable.fromWeb === 'function') {
|
|
274
|
-
// Node.js 17+ 标准方式
|
|
275
|
-
nodeStream = Readable.fromWeb(response.body as any);
|
|
274
|
+
console.error(`Empty response body from ${targetUrl}`);
|
|
276
275
|
} else {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
276
|
+
let nodeStream: NodeJS.ReadableStream;
|
|
277
|
+
if (typeof Readable.fromWeb === 'function') {
|
|
278
|
+
// Node.js 17+ 标准方式
|
|
279
|
+
nodeStream = Readable.fromWeb(response.body as any);
|
|
280
|
+
} else {
|
|
281
|
+
// Node.js 16 及以下版本的兼容方案
|
|
282
|
+
const {PassThrough} = await import('stream');
|
|
283
|
+
const passThrough = new PassThrough();
|
|
284
|
+
const reader = (response.body as any).getReader();
|
|
285
|
+
|
|
286
|
+
const pump = async () => {
|
|
287
|
+
const {done, value} = await reader.read();
|
|
288
|
+
if (done) return passThrough.end();
|
|
289
|
+
passThrough.write(value);
|
|
290
|
+
await pump();
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
pump().catch(err => passThrough.destroy(err));
|
|
294
|
+
nodeStream = passThrough;
|
|
295
|
+
}
|
|
292
296
|
|
|
293
|
-
|
|
294
|
-
|
|
297
|
+
await pipeline(nodeStream, res);
|
|
298
|
+
}
|
|
295
299
|
}
|
|
296
300
|
} catch (err) {
|
|
297
301
|
console.error('Failed to write response:', err);
|
|
@@ -335,16 +339,14 @@ export async function startProxyServer(
|
|
|
335
339
|
|
|
336
340
|
// 顺序尝试注册表,获取第一个成功响应
|
|
337
341
|
let successfulResponse: Response | null = null;
|
|
342
|
+
let targetRegistry: RegistryInfo | null = null;
|
|
343
|
+
let targetUrl: string | null = null;
|
|
338
344
|
for (const registry of registryInfos) {
|
|
339
345
|
if (req.destroyed) break;
|
|
340
|
-
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
fullUrl.search,
|
|
345
|
-
limiter
|
|
346
|
-
);
|
|
347
|
-
|
|
346
|
+
targetRegistry = registry;
|
|
347
|
+
const search = fullUrl.search || '';
|
|
348
|
+
targetUrl = `${registry.normalizedRegistryUrl}${path}${search}`;
|
|
349
|
+
const response = await fetchFromRegistry(registry, targetUrl, limiter);
|
|
348
350
|
if (response) {
|
|
349
351
|
successfulResponse = response;
|
|
350
352
|
break;
|
|
@@ -353,7 +355,7 @@ export async function startProxyServer(
|
|
|
353
355
|
|
|
354
356
|
// 统一回写响应
|
|
355
357
|
if (successfulResponse) {
|
|
356
|
-
await
|
|
358
|
+
await writeSuccessfulResponse(targetRegistry!, targetUrl!, res, successfulResponse, req, proxyInfo, proxyPort, registryInfos);
|
|
357
359
|
} else {
|
|
358
360
|
res.writeHead(404).end('All upstream registries failed');
|
|
359
361
|
}
|
|
@@ -394,7 +396,7 @@ export async function startProxyServer(
|
|
|
394
396
|
proxyPort = address.port;
|
|
395
397
|
const portFile = join(process.env.PROJECT_ROOT || process.cwd(), '.registry-proxy-port');
|
|
396
398
|
writeFile(portFile, proxyPort.toString()).catch(e => console.error('Failed to write port file:', e));
|
|
397
|
-
console.log(`Proxy server running on ${proxyInfo.https ? 'https' : 'http'}://localhost:${proxyPort}${basePathPrefixedWithSlash}`);
|
|
399
|
+
console.log(`Proxy server running on ${proxyInfo.https ? 'https' : 'http'}://localhost:${proxyPort}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`);
|
|
398
400
|
resolve(server);
|
|
399
401
|
});
|
|
400
402
|
});
|