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.
Files changed (3) hide show
  1. package/dist/index.js +55 -48
  2. package/package.json +1 -1
  3. 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, path, search, limiter) {
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
- // 修改后的 writeResponse 函数
165
- async function writeResponse(res, response, req, proxyInfo, proxyPort, registryInfos) {
166
- const contentType = response.headers.get('Content-Type') || 'application/octet-stream';
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
- data.versions[version].dist.tarball = `${baseUrl}${path}${new URL(tarball).search || ''}`;
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('Empty response body');
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
- // Node.js 16 及以下版本的兼容方案
214
- const { PassThrough } = await import('stream');
215
- const passThrough = new PassThrough();
216
- const reader = response.body.getReader();
217
- const pump = async () => {
218
- const { done, value } = await reader.read();
219
- if (done)
220
- return passThrough.end();
221
- passThrough.write(value);
222
- await pump();
223
- };
224
- pump().catch(err => passThrough.destroy(err));
225
- nodeStream = passThrough;
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
- const response = await fetchFromRegistry(registry, path, fullUrl.search, limiter);
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 writeResponse(res, successfulResponse, req, proxyInfo, proxyPort, registryInfos);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.jimuwd.xian.registry-proxy",
3
- "version": "1.0.36",
3
+ "version": "1.0.38",
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
@@ -189,16 +189,14 @@ async function loadProxyInfo(
189
189
 
190
190
  async function fetchFromRegistry(
191
191
  registry: RegistryInfo,
192
- path: string,
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 ? { Authorization: `Bearer ${registry.token}` } : undefined;
201
- const response = await fetch(targetUrl, { headers });
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
- // 修改后的 writeResponse 函数
219
- async function writeResponse(
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
- const headersToCopy = [
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
- headersToCopy.forEach(header => {
242
- const value = response.headers.get(header);
243
- if (value) safeHeaders[header] = value;
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
- data.versions[version]!.dist!.tarball = `${baseUrl}${path}${new URL(tarball).search || ''}`;
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('Empty response body');
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
- // Node.js 16 及以下版本的兼容方案
278
- const { PassThrough } = await import('stream');
279
- const passThrough = new PassThrough();
280
- const reader = (response.body as any).getReader();
281
-
282
- const pump = async () => {
283
- const { done, value } = await reader.read();
284
- if (done) return passThrough.end();
285
- passThrough.write(value);
286
- await pump();
287
- };
288
-
289
- pump().catch(err => passThrough.destroy(err));
290
- nodeStream = passThrough;
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
- res.writeHead(response.status, safeHeaders);
294
- await pipeline(nodeStream, res);
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 response = await fetchFromRegistry(
342
- registry,
343
- path,
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 writeResponse(res, successfulResponse, req, proxyInfo, proxyPort, registryInfos);
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
  });