com.jimuwd.xian.registry-proxy 1.0.12 → 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 +91 -17
  2. package/package.json +1 -1
  3. package/src/index.ts +104 -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,13 +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
- // Handle base path
114
159
  const fullUrl = new URL(req.url, `${proxyConfig.https ? 'https' : 'http'}://${req.headers.host}`);
160
+ console.debug(`Full URL: ${fullUrl.toString()}`);
115
161
  if (basePath && !fullUrl.pathname.startsWith(basePath)) {
162
+ console.error(`Path ${fullUrl.pathname} does not match basePath ${basePath}`);
116
163
  res.writeHead(404).end('Not Found');
117
164
  return;
118
165
  }
@@ -120,39 +167,56 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
120
167
  ? fullUrl.pathname.slice(basePath.length)
121
168
  : fullUrl.pathname;
122
169
  console.log(`Proxying: ${relativePath}`);
123
- const responses = await Promise.all(registries.map(async ({ url, token }) => {
170
+ const fetchPromises = registries.map(async ({ url, token }) => {
171
+ await limiter.acquire(); // 获取并发许可
124
172
  try {
125
- const targetUrl = `${url}${relativePath}${fullUrl.search || ''}`;
173
+ const cleanRelativePath = relativePath.replace(/\/+$/, '');
174
+ const targetUrl = `${url}${cleanRelativePath}${fullUrl.search || ''}`;
175
+ console.log(`Fetching from: ${targetUrl}`);
126
176
  const headers = token ? { Authorization: `Bearer ${token}` } : undefined;
127
177
  const response = await fetch(targetUrl, { headers });
178
+ console.log(`Response from ${url}: ${response.status} ${response.statusText}`);
179
+ console.debug(`Response headers from ${url}:`, Object.fromEntries(response.headers.entries()));
128
180
  return response.ok ? response : null;
129
181
  }
130
182
  catch (e) {
131
183
  console.error(`Failed to fetch from ${url}:`, e);
132
184
  return null;
133
185
  }
134
- }));
186
+ finally {
187
+ limiter.release(); // 释放并发许可
188
+ }
189
+ });
190
+ const responses = await Promise.all(fetchPromises);
135
191
  const successResponse = responses.find((r) => r !== null);
136
192
  if (!successResponse) {
137
- res.writeHead(404).end('Not Found');
193
+ console.error(`All registries failed for ${relativePath}`);
194
+ res.writeHead(404).end('Not Found - All upstream registries failed');
138
195
  return;
139
196
  }
140
197
  const contentType = successResponse.headers.get('Content-Type') || 'application/octet-stream';
198
+ console.debug(`Content-Type: ${contentType}`);
141
199
  if (contentType.includes('application/json')) {
142
200
  try {
143
201
  const data = await successResponse.json();
202
+ console.debug('JSON response data:', JSON.stringify(data, null, 2));
144
203
  if (data.versions) {
145
204
  const proxyBase = `${proxyConfig.https ? 'https' : 'http'}://${req.headers.host || 'localhost:' + proxyPort}${basePath}`;
205
+ console.debug(`Rewriting tarball URLs with proxy base: ${proxyBase}`);
146
206
  for (const version in data.versions) {
147
207
  const dist = data.versions[version]?.dist;
148
208
  if (dist?.tarball) {
149
209
  const originalUrl = new URL(dist.tarball);
150
210
  const tarballPath = removeRegistryPrefix(dist.tarball, registries);
151
211
  dist.tarball = `${proxyBase}${tarballPath}${originalUrl.search || ''}`;
212
+ console.debug(`Rewrote tarball: ${originalUrl} -> ${dist.tarball}`);
152
213
  }
153
214
  }
154
215
  }
155
- 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);
156
220
  }
157
221
  catch (e) {
158
222
  console.error('Failed to parse JSON response:', e);
@@ -169,24 +233,31 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
169
233
  'Content-Type': successResponse.headers.get('Content-Type'),
170
234
  'Content-Length': successResponse.headers.get('Content-Length'),
171
235
  };
236
+ console.debug(`Streaming response with headers:`, safeHeaders);
172
237
  res.writeHead(successResponse.status, safeHeaders);
173
- 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
+ });
174
242
  }
175
243
  };
176
244
  let server;
177
245
  if (proxyConfig.https) {
178
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}`);
179
250
  try {
180
- await fsPromises.access(resolvePath(key));
181
- await fsPromises.access(resolvePath(cert));
251
+ await fsPromises.access(keyPath);
252
+ await fsPromises.access(certPath);
182
253
  }
183
254
  catch (e) {
184
255
  console.error(`HTTPS config error: key or cert file not found`, e);
185
256
  process.exit(1);
186
257
  }
187
258
  const httpsOptions = {
188
- key: readFileSync(resolvePath(key)),
189
- cert: readFileSync(resolvePath(cert)),
259
+ key: readFileSync(keyPath),
260
+ cert: readFileSync(certPath),
190
261
  };
191
262
  server = createHttpsServer(httpsOptions, requestHandler);
192
263
  }
@@ -199,12 +270,14 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
199
270
  console.error(`Port ${port} is in use, please specify a different port or free it.`);
200
271
  process.exit(1);
201
272
  }
273
+ console.error('Server error:', err);
202
274
  reject(err);
203
275
  });
204
276
  server.listen(port, () => {
205
277
  const address = server.address();
206
278
  proxyPort = address.port;
207
279
  const portFile = join(process.env.PROJECT_ROOT || process.cwd(), '.registry-proxy-port');
280
+ console.debug(`Writing port ${proxyPort} to file: ${portFile}`);
208
281
  writeFile(portFile, proxyPort.toString()).catch(e => console.error('Failed to write port file:', e));
209
282
  console.log(`Proxy server running on ${proxyConfig.https ? 'https' : 'http'}://localhost:${proxyPort}${basePath}`);
210
283
  resolve(server);
@@ -213,6 +286,7 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
213
286
  }
214
287
  if (import.meta.url === `file://${process.argv[1]}`) {
215
288
  const [, , configPath, localYarnPath, globalYarnPath, port] = process.argv;
289
+ console.debug(`Starting server with args: configPath=${configPath}, localYarnPath=${localYarnPath}, globalYarnPath=${globalYarnPath}, port=${port}`);
216
290
  startProxyServer(configPath, localYarnPath, globalYarnPath, parseInt(port, 10) || 0).catch(err => {
217
291
  console.error('Failed to start server:', err);
218
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.12",
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,14 +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
- // Handle base path
148
196
  const fullUrl = new URL(req.url, `${proxyConfig.https ? 'https' : 'http'}://${req.headers.host}`);
197
+ console.debug(`Full URL: ${fullUrl.toString()}`);
149
198
  if (basePath && !fullUrl.pathname.startsWith(basePath)) {
199
+ console.error(`Path ${fullUrl.pathname} does not match basePath ${basePath}`);
150
200
  res.writeHead(404).end('Not Found');
151
201
  return;
152
202
  }
@@ -154,45 +204,58 @@ export async function startProxyServer(
154
204
  const relativePath = basePath
155
205
  ? fullUrl.pathname.slice(basePath.length)
156
206
  : fullUrl.pathname;
157
-
158
207
  console.log(`Proxying: ${relativePath}`);
159
208
 
160
- const responses = await Promise.all(
161
- registries.map(async ({ url, token }) => {
162
- try {
163
- const targetUrl = `${url}${relativePath}${fullUrl.search || ''}`;
164
- const headers = token ? { Authorization: `Bearer ${token}` } : undefined;
165
- const response = await fetch(targetUrl, { headers });
166
- return response.ok ? response : null;
167
- } catch (e) {
168
- console.error(`Failed to fetch from ${url}:`, e);
169
- return null;
170
- }
171
- })
172
- );
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
+ });
173
227
 
228
+ const responses = await Promise.all(fetchPromises);
174
229
  const successResponse = responses.find((r): r is Response => r !== null);
175
230
  if (!successResponse) {
176
- res.writeHead(404).end('Not Found');
231
+ console.error(`All registries failed for ${relativePath}`);
232
+ res.writeHead(404).end('Not Found - All upstream registries failed');
177
233
  return;
178
234
  }
179
235
 
180
236
  const contentType = successResponse.headers.get('Content-Type') || 'application/octet-stream';
237
+ console.debug(`Content-Type: ${contentType}`);
181
238
  if (contentType.includes('application/json')) {
182
239
  try {
183
240
  const data = await successResponse.json() as PackageData;
241
+ console.debug('JSON response data:', JSON.stringify(data, null, 2));
184
242
  if (data.versions) {
185
243
  const proxyBase = `${proxyConfig.https ? 'https' : 'http'}://${req.headers.host || 'localhost:' + proxyPort}${basePath}`;
244
+ console.debug(`Rewriting tarball URLs with proxy base: ${proxyBase}`);
186
245
  for (const version in data.versions) {
187
246
  const dist = data.versions[version]?.dist;
188
247
  if (dist?.tarball) {
189
248
  const originalUrl = new URL(dist.tarball);
190
249
  const tarballPath = removeRegistryPrefix(dist.tarball, registries);
191
250
  dist.tarball = `${proxyBase}${tarballPath}${originalUrl.search || ''}`;
251
+ console.debug(`Rewrote tarball: ${originalUrl} -> ${dist.tarball}`);
192
252
  }
193
253
  }
194
254
  }
195
- 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);
196
259
  } catch (e) {
197
260
  console.error('Failed to parse JSON response:', e);
198
261
  res.writeHead(502).end('Invalid Upstream Response');
@@ -207,24 +270,31 @@ export async function startProxyServer(
207
270
  'Content-Type': successResponse.headers.get('Content-Type'),
208
271
  'Content-Length': successResponse.headers.get('Content-Length'),
209
272
  };
273
+ console.debug(`Streaming response with headers:`, safeHeaders);
210
274
  res.writeHead(successResponse.status, safeHeaders);
211
- 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
+ });
212
279
  }
213
280
  };
214
281
 
215
282
  let server: HttpServer | HttpsServer;
216
283
  if (proxyConfig.https) {
217
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}`);
218
288
  try {
219
- await fsPromises.access(resolvePath(key));
220
- await fsPromises.access(resolvePath(cert));
289
+ await fsPromises.access(keyPath);
290
+ await fsPromises.access(certPath);
221
291
  } catch (e) {
222
292
  console.error(`HTTPS config error: key or cert file not found`, e);
223
293
  process.exit(1);
224
294
  }
225
295
  const httpsOptions = {
226
- key: readFileSync(resolvePath(key)),
227
- cert: readFileSync(resolvePath(cert)),
296
+ key: readFileSync(keyPath),
297
+ cert: readFileSync(certPath),
228
298
  };
229
299
  server = createHttpsServer(httpsOptions, requestHandler);
230
300
  } else {
@@ -237,12 +307,14 @@ export async function startProxyServer(
237
307
  console.error(`Port ${port} is in use, please specify a different port or free it.`);
238
308
  process.exit(1);
239
309
  }
310
+ console.error('Server error:', err);
240
311
  reject(err);
241
312
  });
242
313
  server.listen(port, () => {
243
314
  const address = server.address() as AddressInfo;
244
315
  proxyPort = address.port;
245
316
  const portFile = join(process.env.PROJECT_ROOT || process.cwd(), '.registry-proxy-port');
317
+ console.debug(`Writing port ${proxyPort} to file: ${portFile}`);
246
318
  writeFile(portFile, proxyPort.toString()).catch(e => console.error('Failed to write port file:', e));
247
319
  console.log(`Proxy server running on ${proxyConfig.https ? 'https' : 'http'}://localhost:${proxyPort}${basePath}`);
248
320
  resolve(server);
@@ -252,6 +324,7 @@ export async function startProxyServer(
252
324
 
253
325
  if (import.meta.url === `file://${process.argv[1]}`) {
254
326
  const [,, configPath, localYarnPath, globalYarnPath, port] = process.argv;
327
+ console.debug(`Starting server with args: configPath=${configPath}, localYarnPath=${localYarnPath}, globalYarnPath=${globalYarnPath}, port=${port}`);
255
328
  startProxyServer(
256
329
  configPath,
257
330
  localYarnPath,