com.jimuwd.xian.registry-proxy 1.0.35 → 1.0.36

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 +66 -24
  2. package/package.json +1 -1
  3. package/src/index.ts +70 -29
package/dist/index.js CHANGED
@@ -161,36 +161,78 @@ async function fetchFromRegistry(registry, path, search, limiter) {
161
161
  limiter.release();
162
162
  }
163
163
  }
164
+ // 修改后的 writeResponse 函数
164
165
  async function writeResponse(res, response, req, proxyInfo, proxyPort, registryInfos) {
165
166
  const contentType = response.headers.get('Content-Type') || 'application/octet-stream';
166
- if (contentType.includes('application/json')) {
167
- const data = await response.json();
168
- if (data.versions) {
169
- const host = req.headers.host || `localhost:${proxyPort}`;
170
- const baseUrl = `${proxyInfo.https ? 'https' : 'http'}://${host}${proxyInfo.basePath === '/' ? '' : proxyInfo.basePath}`;
171
- for (const version in data.versions) {
172
- const tarball = data.versions[version]?.dist?.tarball;
173
- if (tarball) {
174
- const path = removeRegistryPrefix(tarball, registryInfos);
175
- data.versions[version].dist.tarball = `${baseUrl}${path}${new URL(tarball).search || ''}`;
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
+ });
182
+ try {
183
+ if (contentType.includes('application/json')) {
184
+ // JSON 处理逻辑
185
+ const data = await response.json();
186
+ if (data.versions) {
187
+ const host = req.headers.host || `localhost:${proxyPort}`;
188
+ const baseUrl = `${proxyInfo.https ? 'https' : 'http'}://${host}${proxyInfo.basePath === '/' ? '' : proxyInfo.basePath}`;
189
+ for (const version in data.versions) {
190
+ const tarball = data.versions[version]?.dist?.tarball;
191
+ if (tarball) {
192
+ const path = removeRegistryPrefix(tarball, registryInfos);
193
+ data.versions[version].dist.tarball = `${baseUrl}${path}${new URL(tarball).search || ''}`;
194
+ }
176
195
  }
177
196
  }
197
+ res.writeHead(200, safeHeaders);
198
+ res.end(JSON.stringify(data));
199
+ }
200
+ else {
201
+ // 二进制流处理
202
+ 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);
211
+ }
212
+ 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;
226
+ }
227
+ res.writeHead(response.status, safeHeaders);
228
+ await pipeline(nodeStream, res);
178
229
  }
179
- res.writeHead(200, { 'Content-Type': 'application/json' });
180
- res.end(JSON.stringify(data));
181
230
  }
182
- else {
183
- const safeHeaders = {
184
- 'content-type': contentType,
185
- 'connection': 'keep-alive',
186
- };
187
- ['cache-control', 'etag', 'last-modified', 'content-encoding'].forEach(header => {
188
- const value = response.headers.get(header);
189
- if (value)
190
- safeHeaders[header] = value;
191
- });
192
- res.writeHead(response.status, safeHeaders);
193
- await pipeline(Readable.fromWeb(response.body), res);
231
+ catch (err) {
232
+ console.error('Failed to write response:', err);
233
+ if (!res.headersSent) {
234
+ res.writeHead(502).end('Internal Server Error');
235
+ }
194
236
  }
195
237
  }
196
238
  export async function startProxyServer(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath, port = 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.jimuwd.xian.registry-proxy",
3
- "version": "1.0.35",
3
+ "version": "1.0.36",
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
@@ -52,7 +52,7 @@ interface PackageData {
52
52
  }
53
53
 
54
54
  class ConcurrencyLimiter {
55
- private maxConcurrency: number;
55
+ private readonly maxConcurrency: number;
56
56
  private current: number = 0;
57
57
  private queue: Array<() => void> = [];
58
58
 
@@ -215,6 +215,7 @@ async function fetchFromRegistry(
215
215
  }
216
216
  }
217
217
 
218
+ // 修改后的 writeResponse 函数
218
219
  async function writeResponse(
219
220
  res: ServerResponse,
220
221
  response: Response,
@@ -225,38 +226,78 @@ async function writeResponse(
225
226
  ): Promise<void> {
226
227
  const contentType = response.headers.get('Content-Type') || 'application/octet-stream';
227
228
 
228
- if (contentType.includes('application/json')) {
229
- const data = await response.json() as PackageData;
230
- if (data.versions) {
231
- const host = req.headers.host || `localhost:${proxyPort}`;
232
- const baseUrl = `${proxyInfo.https ? 'https' : 'http'}://${host}${proxyInfo.basePath === '/' ? '' : proxyInfo.basePath}`;
233
-
234
- for (const version in data.versions) {
235
- const tarball = data.versions[version]?.dist?.tarball;
236
- if (tarball) {
237
- const path = removeRegistryPrefix(tarball, registryInfos);
238
- data.versions[version]!.dist!.tarball = `${baseUrl}${path}${new URL(tarball).search || ''}`;
229
+ // 准备通用头信息
230
+ const safeHeaders: OutgoingHttpHeaders = {
231
+ 'content-type': contentType,
232
+ 'connection': 'keep-alive'
233
+ };
234
+
235
+ // 复制所有可能需要的头信息
236
+ const headersToCopy = [
237
+ 'cache-control', 'etag', 'last-modified',
238
+ 'content-encoding', 'content-length'
239
+ ];
240
+
241
+ headersToCopy.forEach(header => {
242
+ const value = response.headers.get(header);
243
+ if (value) safeHeaders[header] = value;
244
+ });
245
+
246
+ try {
247
+ if (contentType.includes('application/json')) {
248
+ // JSON 处理逻辑
249
+ const data = await response.json() as PackageData;
250
+ if (data.versions) {
251
+ const host = req.headers.host || `localhost:${proxyPort}`;
252
+ const baseUrl = `${proxyInfo.https ? 'https' : 'http'}://${host}${proxyInfo.basePath === '/' ? '' : proxyInfo.basePath}`;
253
+
254
+ for (const version in data.versions) {
255
+ const tarball = data.versions[version]?.dist?.tarball;
256
+ if (tarball) {
257
+ const path = removeRegistryPrefix(tarball, registryInfos);
258
+ data.versions[version]!.dist!.tarball = `${baseUrl}${path}${new URL(tarball).search || ''}`;
259
+ }
239
260
  }
240
261
  }
241
- }
242
- res.writeHead(200, { 'Content-Type': 'application/json' });
243
- res.end(JSON.stringify(data));
244
- } else {
245
- const safeHeaders: OutgoingHttpHeaders = {
246
- 'content-type': contentType,
247
- 'connection': 'keep-alive',
248
- };
262
+ res.writeHead(200, safeHeaders);
263
+ res.end(JSON.stringify(data));
264
+ } else {
265
+ // 二进制流处理
266
+ if (!response.body) {
267
+ console.error('Empty response body');
268
+ process.exit(1);
269
+ }
249
270
 
250
- ['cache-control', 'etag', 'last-modified', 'content-encoding'].forEach(header => {
251
- const value = response.headers.get(header);
252
- if (value) safeHeaders[header] = value;
253
- });
271
+ // 修复流类型转换问题
272
+ let nodeStream: NodeJS.ReadableStream;
273
+ if (typeof Readable.fromWeb === 'function') {
274
+ // Node.js 17+ 标准方式
275
+ nodeStream = Readable.fromWeb(response.body as any);
276
+ } 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
+ }
254
292
 
255
- res.writeHead(response.status, safeHeaders);
256
- await pipeline(
257
- Readable.fromWeb(response.body as any),
258
- res
259
- );
293
+ res.writeHead(response.status, safeHeaders);
294
+ await pipeline(nodeStream, res);
295
+ }
296
+ } catch (err) {
297
+ console.error('Failed to write response:', err);
298
+ if (!res.headersSent) {
299
+ res.writeHead(502).end('Internal Server Error');
300
+ }
260
301
  }
261
302
  }
262
303