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