com.jimuwd.xian.registry-proxy 1.0.13 → 1.0.14

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 +85 -14
  2. package/package.json +1 -1
  3. package/src/index.ts +102 -31
package/dist/index.js CHANGED
@@ -6,8 +6,36 @@ import { load } from 'js-yaml';
6
6
  import fetch from 'node-fetch';
7
7
  import { homedir } from 'os';
8
8
  import { join, resolve } from 'path';
9
- import { URL } from 'url'; // 显式导入URL
9
+ import { URL } from 'url';
10
10
  const { readFile, writeFile } = fsPromises;
11
+ // 并发控制队列
12
+ class ConcurrencyLimiter {
13
+ maxConcurrency;
14
+ current = 0;
15
+ queue = [];
16
+ constructor(maxConcurrency) {
17
+ this.maxConcurrency = maxConcurrency;
18
+ }
19
+ async acquire() {
20
+ if (this.current < this.maxConcurrency) {
21
+ this.current++;
22
+ return Promise.resolve();
23
+ }
24
+ return new Promise((resolve) => {
25
+ this.queue.push(resolve);
26
+ });
27
+ }
28
+ release() {
29
+ this.current--;
30
+ const next = this.queue.shift();
31
+ if (next) {
32
+ this.current++;
33
+ next();
34
+ }
35
+ }
36
+ }
37
+ // 设置最大并发数(可调整)
38
+ const limiter = new ConcurrencyLimiter(5); // 限制为 5 个并发请求
11
39
  function normalizeUrl(url) {
12
40
  try {
13
41
  const urlObj = new URL(url);
@@ -20,6 +48,7 @@ function normalizeUrl(url) {
20
48
  if (!urlObj.pathname.endsWith('/')) {
21
49
  urlObj.pathname += '/';
22
50
  }
51
+ console.debug(`Normalized URL: ${url} -> ${urlObj.toString()}`);
23
52
  return urlObj.toString();
24
53
  }
25
54
  catch (e) {
@@ -28,7 +57,9 @@ function normalizeUrl(url) {
28
57
  }
29
58
  }
30
59
  function resolvePath(path) {
31
- return path.startsWith('~/') ? join(homedir(), path.slice(2)) : resolve(path);
60
+ const resolved = path.startsWith('~/') ? join(homedir(), path.slice(2)) : resolve(path);
61
+ console.debug(`Resolved path: ${path} -> ${resolved}`);
62
+ return resolved;
32
63
  }
33
64
  function removeRegistryPrefix(tarballUrl, registries) {
34
65
  try {
@@ -36,25 +67,31 @@ function removeRegistryPrefix(tarballUrl, registries) {
36
67
  const normalizedRegistries = registries
37
68
  .map(r => normalizeUrl(r.url))
38
69
  .sort((a, b) => b.length - a.length);
70
+ console.debug(`Removing registry prefix from tarball: ${normalizedTarball}`);
39
71
  for (const registry of normalizedRegistries) {
40
72
  if (normalizedTarball.startsWith(registry)) {
41
- return normalizedTarball.slice(registry.length - 1) || '/';
73
+ const result = normalizedTarball.slice(registry.length - 1) || '/';
74
+ console.debug(`Matched registry ${registry}, result: ${result}`);
75
+ return result;
42
76
  }
43
77
  }
78
+ console.debug(`No registry prefix matched for ${normalizedTarball}`);
44
79
  }
45
80
  catch (e) {
46
- console.error(`Invalid URL: ${tarballUrl}`, e);
81
+ console.error(`Invalid URL in removeRegistryPrefix: ${tarballUrl}`, e);
47
82
  }
48
83
  return tarballUrl;
49
84
  }
50
85
  async function loadProxyConfig(proxyConfigPath = './.registry-proxy.yml') {
51
86
  const resolvedPath = resolvePath(proxyConfigPath);
87
+ console.debug(`Loading proxy config from: ${resolvedPath}`);
52
88
  try {
53
89
  const content = await readFile(resolvedPath, 'utf8');
54
90
  const config = load(content);
55
91
  if (!config.registries) {
56
92
  throw new Error('Missing required "registries" field in config');
57
93
  }
94
+ console.debug('Loaded proxy config:', JSON.stringify(config, null, 2));
58
95
  return config;
59
96
  }
60
97
  catch (e) {
@@ -63,9 +100,12 @@ async function loadProxyConfig(proxyConfigPath = './.registry-proxy.yml') {
63
100
  }
64
101
  }
65
102
  async function loadYarnConfig(path) {
103
+ console.debug(`Loading Yarn config from: ${path}`);
66
104
  try {
67
105
  const content = await readFile(resolvePath(path), 'utf8');
68
- return load(content);
106
+ const config = load(content);
107
+ console.debug(`Loaded Yarn config from ${path}:`, JSON.stringify(config, null, 2));
108
+ return config;
69
109
  }
70
110
  catch (e) {
71
111
  console.warn(`Failed to load Yarn config from ${path}:`, e);
@@ -73,6 +113,7 @@ async function loadYarnConfig(path) {
73
113
  }
74
114
  }
75
115
  async function loadRegistries(proxyConfigPath = './.registry-proxy.yml', localYarnConfigPath = './.yarnrc.yml', globalYarnConfigPath = join(homedir(), '.yarnrc.yml')) {
116
+ console.debug('Loading registries...');
76
117
  const [proxyConfig, localYarnConfig, globalYarnConfig] = await Promise.all([
77
118
  loadProxyConfig(proxyConfigPath),
78
119
  loadYarnConfig(localYarnConfigPath),
@@ -89,13 +130,16 @@ async function loadRegistries(proxyConfigPath = './.registry-proxy.yml', localYa
89
130
  config.npmRegistries?.[url];
90
131
  if (registryConfig?.npmAuthToken) {
91
132
  token = registryConfig.npmAuthToken;
133
+ console.debug(`Found token for ${normalizedUrl} in Yarn config`);
92
134
  break;
93
135
  }
94
136
  }
95
137
  }
96
138
  registryMap.set(normalizedUrl, { url: normalizedUrl, token });
97
139
  }
98
- return Array.from(registryMap.values());
140
+ const registries = Array.from(registryMap.values());
141
+ console.log('Loaded registries:', registries);
142
+ return registries;
99
143
  }
100
144
  export async function startProxyServer(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath, port = 0) {
101
145
  const proxyConfig = await loadProxyConfig(proxyConfigPath);
@@ -106,12 +150,16 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
106
150
  console.log('HTTPS:', !!proxyConfig.https);
107
151
  let proxyPort;
108
152
  const requestHandler = async (req, res) => {
153
+ console.debug(`Received request: ${req.method} ${req.url}`);
109
154
  if (!req.url || !req.headers.host) {
155
+ console.error('Invalid request: missing URL or host header');
110
156
  res.writeHead(400).end('Invalid Request');
111
157
  return;
112
158
  }
113
159
  const fullUrl = new URL(req.url, `${proxyConfig.https ? 'https' : 'http'}://${req.headers.host}`);
160
+ console.debug(`Full URL: ${fullUrl.toString()}`);
114
161
  if (basePath && !fullUrl.pathname.startsWith(basePath)) {
162
+ console.error(`Path ${fullUrl.pathname} does not match basePath ${basePath}`);
115
163
  res.writeHead(404).end('Not Found');
116
164
  return;
117
165
  }
@@ -119,7 +167,8 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
119
167
  ? fullUrl.pathname.slice(basePath.length)
120
168
  : fullUrl.pathname;
121
169
  console.log(`Proxying: ${relativePath}`);
122
- const responses = await Promise.all(registries.map(async ({ url, token }) => {
170
+ const fetchPromises = registries.map(async ({ url, token }) => {
171
+ await limiter.acquire(); // 获取并发许可
123
172
  try {
124
173
  const cleanRelativePath = relativePath.replace(/\/+$/, '');
125
174
  const targetUrl = `${url}${cleanRelativePath}${fullUrl.search || ''}`;
@@ -127,13 +176,18 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
127
176
  const headers = token ? { Authorization: `Bearer ${token}` } : undefined;
128
177
  const response = await fetch(targetUrl, { headers });
129
178
  console.log(`Response from ${url}: ${response.status} ${response.statusText}`);
179
+ console.debug(`Response headers from ${url}:`, Object.fromEntries(response.headers.entries()));
130
180
  return response.ok ? response : null;
131
181
  }
132
182
  catch (e) {
133
183
  console.error(`Failed to fetch from ${url}:`, e);
134
184
  return null;
135
185
  }
136
- }));
186
+ finally {
187
+ limiter.release(); // 释放并发许可
188
+ }
189
+ });
190
+ const responses = await Promise.all(fetchPromises);
137
191
  const successResponse = responses.find((r) => r !== null);
138
192
  if (!successResponse) {
139
193
  console.error(`All registries failed for ${relativePath}`);
@@ -141,21 +195,28 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
141
195
  return;
142
196
  }
143
197
  const contentType = successResponse.headers.get('Content-Type') || 'application/octet-stream';
198
+ console.debug(`Content-Type: ${contentType}`);
144
199
  if (contentType.includes('application/json')) {
145
200
  try {
146
201
  const data = await successResponse.json();
202
+ console.debug('JSON response data:', JSON.stringify(data, null, 2));
147
203
  if (data.versions) {
148
204
  const proxyBase = `${proxyConfig.https ? 'https' : 'http'}://${req.headers.host || 'localhost:' + proxyPort}${basePath}`;
205
+ console.debug(`Rewriting tarball URLs with proxy base: ${proxyBase}`);
149
206
  for (const version in data.versions) {
150
207
  const dist = data.versions[version]?.dist;
151
208
  if (dist?.tarball) {
152
209
  const originalUrl = new URL(dist.tarball);
153
210
  const tarballPath = removeRegistryPrefix(dist.tarball, registries);
154
211
  dist.tarball = `${proxyBase}${tarballPath}${originalUrl.search || ''}`;
212
+ console.debug(`Rewrote tarball: ${originalUrl} -> ${dist.tarball}`);
155
213
  }
156
214
  }
157
215
  }
158
- res.writeHead(200, { 'Content-Type': 'application/json' }).end(JSON.stringify(data));
216
+ res.writeHead(200, { 'Content-Type': 'application/json' });
217
+ const jsonResponse = JSON.stringify(data);
218
+ console.debug(`Sending JSON response: ${jsonResponse}`);
219
+ res.end(jsonResponse);
159
220
  }
160
221
  catch (e) {
161
222
  console.error('Failed to parse JSON response:', e);
@@ -172,24 +233,31 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
172
233
  'Content-Type': successResponse.headers.get('Content-Type'),
173
234
  'Content-Length': successResponse.headers.get('Content-Length'),
174
235
  };
236
+ console.debug(`Streaming response with headers:`, safeHeaders);
175
237
  res.writeHead(successResponse.status, safeHeaders);
176
- successResponse.body.pipe(res);
238
+ successResponse.body.pipe(res).on('error', (err) => {
239
+ console.error(`Stream error for ${relativePath}:`, err);
240
+ res.writeHead(502).end('Stream Error');
241
+ });
177
242
  }
178
243
  };
179
244
  let server;
180
245
  if (proxyConfig.https) {
181
246
  const { key, cert } = proxyConfig.https;
247
+ const keyPath = resolvePath(key);
248
+ const certPath = resolvePath(cert);
249
+ console.debug(`Loading HTTPS key: ${keyPath}, cert: ${certPath}`);
182
250
  try {
183
- await fsPromises.access(resolvePath(key));
184
- await fsPromises.access(resolvePath(cert));
251
+ await fsPromises.access(keyPath);
252
+ await fsPromises.access(certPath);
185
253
  }
186
254
  catch (e) {
187
255
  console.error(`HTTPS config error: key or cert file not found`, e);
188
256
  process.exit(1);
189
257
  }
190
258
  const httpsOptions = {
191
- key: readFileSync(resolvePath(key)),
192
- cert: readFileSync(resolvePath(cert)),
259
+ key: readFileSync(keyPath),
260
+ cert: readFileSync(certPath),
193
261
  };
194
262
  server = createHttpsServer(httpsOptions, requestHandler);
195
263
  }
@@ -202,12 +270,14 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
202
270
  console.error(`Port ${port} is in use, please specify a different port or free it.`);
203
271
  process.exit(1);
204
272
  }
273
+ console.error('Server error:', err);
205
274
  reject(err);
206
275
  });
207
276
  server.listen(port, () => {
208
277
  const address = server.address();
209
278
  proxyPort = address.port;
210
279
  const portFile = join(process.env.PROJECT_ROOT || process.cwd(), '.registry-proxy-port');
280
+ console.debug(`Writing port ${proxyPort} to file: ${portFile}`);
211
281
  writeFile(portFile, proxyPort.toString()).catch(e => console.error('Failed to write port file:', e));
212
282
  console.log(`Proxy server running on ${proxyConfig.https ? 'https' : 'http'}://localhost:${proxyPort}${basePath}`);
213
283
  resolve(server);
@@ -216,6 +286,7 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
216
286
  }
217
287
  if (import.meta.url === `file://${process.argv[1]}`) {
218
288
  const [, , configPath, localYarnPath, globalYarnPath, port] = process.argv;
289
+ console.debug(`Starting server with args: configPath=${configPath}, localYarnPath=${localYarnPath}, globalYarnPath=${globalYarnPath}, port=${port}`);
219
290
  startProxyServer(configPath, localYarnPath, globalYarnPath, parseInt(port, 10) || 0).catch(err => {
220
291
  console.error('Failed to start server:', err);
221
292
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.jimuwd.xian.registry-proxy",
3
- "version": "1.0.13",
3
+ "version": "1.0.14",
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
@@ -7,7 +7,7 @@ import { load } from 'js-yaml';
7
7
  import fetch, { Response } from 'node-fetch';
8
8
  import { homedir } from 'os';
9
9
  import { join, resolve } from 'path';
10
- import { URL } from 'url'; // 显式导入URL
10
+ import { URL } from 'url';
11
11
 
12
12
  const { readFile, writeFile } = fsPromises;
13
13
 
@@ -23,6 +23,39 @@ interface RegistryInfo { url: string; token?: string; }
23
23
  interface PackageVersion { dist?: { tarball?: string }; }
24
24
  interface PackageData { versions?: Record<string, PackageVersion>; }
25
25
 
26
+ // 并发控制队列
27
+ class ConcurrencyLimiter {
28
+ private maxConcurrency: number;
29
+ private current: number = 0;
30
+ private queue: Array<() => void> = [];
31
+
32
+ constructor(maxConcurrency: number) {
33
+ this.maxConcurrency = maxConcurrency;
34
+ }
35
+
36
+ async acquire(): Promise<void> {
37
+ if (this.current < this.maxConcurrency) {
38
+ this.current++;
39
+ return Promise.resolve();
40
+ }
41
+ return new Promise((resolve) => {
42
+ this.queue.push(resolve);
43
+ });
44
+ }
45
+
46
+ release(): void {
47
+ this.current--;
48
+ const next = this.queue.shift();
49
+ if (next) {
50
+ this.current++;
51
+ next();
52
+ }
53
+ }
54
+ }
55
+
56
+ // 设置最大并发数(可调整)
57
+ const limiter = new ConcurrencyLimiter(5); // 限制为 5 个并发请求
58
+
26
59
  function normalizeUrl(url: string): string {
27
60
  try {
28
61
  const urlObj = new URL(url);
@@ -34,6 +67,7 @@ function normalizeUrl(url: string): string {
34
67
  if (!urlObj.pathname.endsWith('/')) {
35
68
  urlObj.pathname += '/';
36
69
  }
70
+ console.debug(`Normalized URL: ${url} -> ${urlObj.toString()}`);
37
71
  return urlObj.toString();
38
72
  } catch (e) {
39
73
  console.error(`Invalid URL: ${url}`, e);
@@ -42,7 +76,9 @@ function normalizeUrl(url: string): string {
42
76
  }
43
77
 
44
78
  function resolvePath(path: string): string {
45
- return path.startsWith('~/') ? join(homedir(), path.slice(2)) : resolve(path);
79
+ const resolved = path.startsWith('~/') ? join(homedir(), path.slice(2)) : resolve(path);
80
+ console.debug(`Resolved path: ${path} -> ${resolved}`);
81
+ return resolved;
46
82
  }
47
83
 
48
84
  function removeRegistryPrefix(tarballUrl: string, registries: RegistryInfo[]): string {
@@ -52,27 +88,31 @@ function removeRegistryPrefix(tarballUrl: string, registries: RegistryInfo[]): s
52
88
  .map(r => normalizeUrl(r.url))
53
89
  .sort((a, b) => b.length - a.length);
54
90
 
91
+ console.debug(`Removing registry prefix from tarball: ${normalizedTarball}`);
55
92
  for (const registry of normalizedRegistries) {
56
93
  if (normalizedTarball.startsWith(registry)) {
57
- return normalizedTarball.slice(registry.length - 1) || '/';
94
+ const result = normalizedTarball.slice(registry.length - 1) || '/';
95
+ console.debug(`Matched registry ${registry}, result: ${result}`);
96
+ return result;
58
97
  }
59
98
  }
99
+ console.debug(`No registry prefix matched for ${normalizedTarball}`);
60
100
  } catch (e) {
61
- console.error(`Invalid URL: ${tarballUrl}`, e);
101
+ console.error(`Invalid URL in removeRegistryPrefix: ${tarballUrl}`, e);
62
102
  }
63
103
  return tarballUrl;
64
104
  }
65
105
 
66
- async function loadProxyConfig(
67
- proxyConfigPath = './.registry-proxy.yml'
68
- ): Promise<ProxyConfig> {
106
+ async function loadProxyConfig(proxyConfigPath = './.registry-proxy.yml'): Promise<ProxyConfig> {
69
107
  const resolvedPath = resolvePath(proxyConfigPath);
108
+ console.debug(`Loading proxy config from: ${resolvedPath}`);
70
109
  try {
71
110
  const content = await readFile(resolvedPath, 'utf8');
72
111
  const config = load(content) as ProxyConfig;
73
112
  if (!config.registries) {
74
113
  throw new Error('Missing required "registries" field in config');
75
114
  }
115
+ console.debug('Loaded proxy config:', JSON.stringify(config, null, 2));
76
116
  return config;
77
117
  } catch (e) {
78
118
  console.error(`Failed to load proxy config from ${resolvedPath}:`, e);
@@ -81,9 +121,12 @@ async function loadProxyConfig(
81
121
  }
82
122
 
83
123
  async function loadYarnConfig(path: string): Promise<YarnConfig> {
124
+ console.debug(`Loading Yarn config from: ${path}`);
84
125
  try {
85
126
  const content = await readFile(resolvePath(path), 'utf8');
86
- return load(content) as YarnConfig;
127
+ const config = load(content) as YarnConfig;
128
+ console.debug(`Loaded Yarn config from ${path}:`, JSON.stringify(config, null, 2));
129
+ return config;
87
130
  } catch (e) {
88
131
  console.warn(`Failed to load Yarn config from ${path}:`, e);
89
132
  return {};
@@ -95,6 +138,7 @@ async function loadRegistries(
95
138
  localYarnConfigPath = './.yarnrc.yml',
96
139
  globalYarnConfigPath = join(homedir(), '.yarnrc.yml')
97
140
  ): Promise<RegistryInfo[]> {
141
+ console.debug('Loading registries...');
98
142
  const [proxyConfig, localYarnConfig, globalYarnConfig] = await Promise.all([
99
143
  loadProxyConfig(proxyConfigPath),
100
144
  loadYarnConfig(localYarnConfigPath),
@@ -113,13 +157,16 @@ async function loadRegistries(
113
157
  config.npmRegistries?.[url];
114
158
  if (registryConfig?.npmAuthToken) {
115
159
  token = registryConfig.npmAuthToken;
160
+ console.debug(`Found token for ${normalizedUrl} in Yarn config`);
116
161
  break;
117
162
  }
118
163
  }
119
164
  }
120
165
  registryMap.set(normalizedUrl, { url: normalizedUrl, token });
121
166
  }
122
- return Array.from(registryMap.values());
167
+ const registries = Array.from(registryMap.values());
168
+ console.log('Loaded registries:', registries);
169
+ return registries;
123
170
  }
124
171
 
125
172
  export async function startProxyServer(
@@ -139,13 +186,17 @@ export async function startProxyServer(
139
186
  let proxyPort: number;
140
187
 
141
188
  const requestHandler = async (req: any, res: any) => {
189
+ console.debug(`Received request: ${req.method} ${req.url}`);
142
190
  if (!req.url || !req.headers.host) {
191
+ console.error('Invalid request: missing URL or host header');
143
192
  res.writeHead(400).end('Invalid Request');
144
193
  return;
145
194
  }
146
195
 
147
196
  const fullUrl = new URL(req.url, `${proxyConfig.https ? 'https' : 'http'}://${req.headers.host}`);
197
+ console.debug(`Full URL: ${fullUrl.toString()}`);
148
198
  if (basePath && !fullUrl.pathname.startsWith(basePath)) {
199
+ console.error(`Path ${fullUrl.pathname} does not match basePath ${basePath}`);
149
200
  res.writeHead(404).end('Not Found');
150
201
  return;
151
202
  }
@@ -155,23 +206,26 @@ export async function startProxyServer(
155
206
  : fullUrl.pathname;
156
207
  console.log(`Proxying: ${relativePath}`);
157
208
 
158
- const responses = await Promise.all(
159
- registries.map(async ({ url, token }) => {
160
- try {
161
- const cleanRelativePath = relativePath.replace(/\/+$/, '');
162
- const targetUrl = `${url}${cleanRelativePath}${fullUrl.search || ''}`;
163
- console.log(`Fetching from: ${targetUrl}`);
164
- const headers = token ? { Authorization: `Bearer ${token}` } : undefined;
165
- const response = await fetch(targetUrl, { headers });
166
- console.log(`Response from ${url}: ${response.status} ${response.statusText}`);
167
- return response.ok ? response : null;
168
- } catch (e) {
169
- console.error(`Failed to fetch from ${url}:`, e);
170
- return null;
171
- }
172
- })
173
- );
209
+ const fetchPromises = registries.map(async ({ url, token }) => {
210
+ await limiter.acquire(); // 获取并发许可
211
+ try {
212
+ const cleanRelativePath = relativePath.replace(/\/+$/, '');
213
+ const targetUrl = `${url}${cleanRelativePath}${fullUrl.search || ''}`;
214
+ console.log(`Fetching from: ${targetUrl}`);
215
+ const headers = token ? { Authorization: `Bearer ${token}` } : undefined;
216
+ const response = await fetch(targetUrl, { headers });
217
+ console.log(`Response from ${url}: ${response.status} ${response.statusText}`);
218
+ console.debug(`Response headers from ${url}:`, Object.fromEntries(response.headers.entries()));
219
+ return response.ok ? response : null;
220
+ } catch (e) {
221
+ console.error(`Failed to fetch from ${url}:`, e);
222
+ return null;
223
+ } finally {
224
+ limiter.release(); // 释放并发许可
225
+ }
226
+ });
174
227
 
228
+ const responses = await Promise.all(fetchPromises);
175
229
  const successResponse = responses.find((r): r is Response => r !== null);
176
230
  if (!successResponse) {
177
231
  console.error(`All registries failed for ${relativePath}`);
@@ -180,21 +234,28 @@ export async function startProxyServer(
180
234
  }
181
235
 
182
236
  const contentType = successResponse.headers.get('Content-Type') || 'application/octet-stream';
237
+ console.debug(`Content-Type: ${contentType}`);
183
238
  if (contentType.includes('application/json')) {
184
239
  try {
185
240
  const data = await successResponse.json() as PackageData;
241
+ console.debug('JSON response data:', JSON.stringify(data, null, 2));
186
242
  if (data.versions) {
187
243
  const proxyBase = `${proxyConfig.https ? 'https' : 'http'}://${req.headers.host || 'localhost:' + proxyPort}${basePath}`;
244
+ console.debug(`Rewriting tarball URLs with proxy base: ${proxyBase}`);
188
245
  for (const version in data.versions) {
189
246
  const dist = data.versions[version]?.dist;
190
247
  if (dist?.tarball) {
191
248
  const originalUrl = new URL(dist.tarball);
192
249
  const tarballPath = removeRegistryPrefix(dist.tarball, registries);
193
250
  dist.tarball = `${proxyBase}${tarballPath}${originalUrl.search || ''}`;
251
+ console.debug(`Rewrote tarball: ${originalUrl} -> ${dist.tarball}`);
194
252
  }
195
253
  }
196
254
  }
197
- res.writeHead(200, { 'Content-Type': 'application/json' }).end(JSON.stringify(data));
255
+ res.writeHead(200, { 'Content-Type': 'application/json' });
256
+ const jsonResponse = JSON.stringify(data);
257
+ console.debug(`Sending JSON response: ${jsonResponse}`);
258
+ res.end(jsonResponse);
198
259
  } catch (e) {
199
260
  console.error('Failed to parse JSON response:', e);
200
261
  res.writeHead(502).end('Invalid Upstream Response');
@@ -209,24 +270,31 @@ export async function startProxyServer(
209
270
  'Content-Type': successResponse.headers.get('Content-Type'),
210
271
  'Content-Length': successResponse.headers.get('Content-Length'),
211
272
  };
273
+ console.debug(`Streaming response with headers:`, safeHeaders);
212
274
  res.writeHead(successResponse.status, safeHeaders);
213
- successResponse.body.pipe(res);
275
+ successResponse.body.pipe(res).on('error', (err:any) => {
276
+ console.error(`Stream error for ${relativePath}:`, err);
277
+ res.writeHead(502).end('Stream Error');
278
+ });
214
279
  }
215
280
  };
216
281
 
217
282
  let server: HttpServer | HttpsServer;
218
283
  if (proxyConfig.https) {
219
284
  const { key, cert } = proxyConfig.https;
285
+ const keyPath = resolvePath(key);
286
+ const certPath = resolvePath(cert);
287
+ console.debug(`Loading HTTPS key: ${keyPath}, cert: ${certPath}`);
220
288
  try {
221
- await fsPromises.access(resolvePath(key));
222
- await fsPromises.access(resolvePath(cert));
289
+ await fsPromises.access(keyPath);
290
+ await fsPromises.access(certPath);
223
291
  } catch (e) {
224
292
  console.error(`HTTPS config error: key or cert file not found`, e);
225
293
  process.exit(1);
226
294
  }
227
295
  const httpsOptions = {
228
- key: readFileSync(resolvePath(key)),
229
- cert: readFileSync(resolvePath(cert)),
296
+ key: readFileSync(keyPath),
297
+ cert: readFileSync(certPath),
230
298
  };
231
299
  server = createHttpsServer(httpsOptions, requestHandler);
232
300
  } else {
@@ -239,12 +307,14 @@ export async function startProxyServer(
239
307
  console.error(`Port ${port} is in use, please specify a different port or free it.`);
240
308
  process.exit(1);
241
309
  }
310
+ console.error('Server error:', err);
242
311
  reject(err);
243
312
  });
244
313
  server.listen(port, () => {
245
314
  const address = server.address() as AddressInfo;
246
315
  proxyPort = address.port;
247
316
  const portFile = join(process.env.PROJECT_ROOT || process.cwd(), '.registry-proxy-port');
317
+ console.debug(`Writing port ${proxyPort} to file: ${portFile}`);
248
318
  writeFile(portFile, proxyPort.toString()).catch(e => console.error('Failed to write port file:', e));
249
319
  console.log(`Proxy server running on ${proxyConfig.https ? 'https' : 'http'}://localhost:${proxyPort}${basePath}`);
250
320
  resolve(server);
@@ -254,6 +324,7 @@ export async function startProxyServer(
254
324
 
255
325
  if (import.meta.url === `file://${process.argv[1]}`) {
256
326
  const [,, configPath, localYarnPath, globalYarnPath, port] = process.argv;
327
+ console.debug(`Starting server with args: configPath=${configPath}, localYarnPath=${localYarnPath}, globalYarnPath=${globalYarnPath}, port=${port}`);
257
328
  startProxyServer(
258
329
  configPath,
259
330
  localYarnPath,