com.jimuwd.xian.registry-proxy 1.0.53 → 1.0.55
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 +18 -17
- package/package.json +1 -1
- package/src/index.ts +18 -17
- package/src/utils/logger.ts +36 -0
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ 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
|
+
import logger from "./utils/logger";
|
|
10
11
|
const { readFile, writeFile } = fsPromises;
|
|
11
12
|
class ConcurrencyLimiter {
|
|
12
13
|
maxConcurrency;
|
|
@@ -84,13 +85,13 @@ async function readProxyConfig(proxyConfigPath = './.registry-proxy.yml') {
|
|
|
84
85
|
const content = await readFile(resolvedPath, 'utf8');
|
|
85
86
|
const config = load(content);
|
|
86
87
|
if (!config.registries) {
|
|
87
|
-
|
|
88
|
+
logger.error('Missing required "registries" field in config');
|
|
88
89
|
process.exit(1);
|
|
89
90
|
}
|
|
90
91
|
return config;
|
|
91
92
|
}
|
|
92
93
|
catch (e) {
|
|
93
|
-
|
|
94
|
+
logger.error(`Failed to load proxy config from ${resolvedPath}:`, e);
|
|
94
95
|
process.exit(1);
|
|
95
96
|
}
|
|
96
97
|
}
|
|
@@ -100,7 +101,7 @@ async function readYarnConfig(path) {
|
|
|
100
101
|
return load(content);
|
|
101
102
|
}
|
|
102
103
|
catch (e) {
|
|
103
|
-
|
|
104
|
+
logger.warn(`Failed to load Yarn config from ${path}:`, e);
|
|
104
105
|
return {};
|
|
105
106
|
}
|
|
106
107
|
}
|
|
@@ -140,16 +141,16 @@ async function loadProxyInfo(proxyConfigPath = './.registry-proxy.yml', localYar
|
|
|
140
141
|
async function fetchFromRegistry(registry, targetUrl, limiter) {
|
|
141
142
|
await limiter.acquire();
|
|
142
143
|
try {
|
|
143
|
-
|
|
144
|
+
logger.info(`Fetching from: ${targetUrl}`);
|
|
144
145
|
const headers = registry.token ? { Authorization: `Bearer ${registry.token}` } : {};
|
|
145
146
|
headers.Collection = "keep-alive";
|
|
146
147
|
const response = await fetch(targetUrl, { headers });
|
|
147
|
-
|
|
148
|
+
logger.info(`Response from upstream ${targetUrl}: ${response.status} ${response.statusText} content-type=${response.headers.get('content-type')} content-length=${response.headers.get('content-length')} transfer-encoding=${response.headers.get('transfer-encoding')}`);
|
|
148
149
|
return response.ok ? response : null;
|
|
149
150
|
}
|
|
150
151
|
catch (e) {
|
|
151
152
|
if (e instanceof Error) {
|
|
152
|
-
|
|
153
|
+
logger.error(e.code === 'ECONNREFUSED'
|
|
153
154
|
? `Registry ${registry.normalizedRegistryUrl} unreachable [ECONNREFUSED]`
|
|
154
155
|
: `Error from ${registry.normalizedRegistryUrl}: ${e.message}`);
|
|
155
156
|
}
|
|
@@ -200,7 +201,7 @@ async function writeSuccessfulResponse(registryInfo, targetUrl, res, upstreamRes
|
|
|
200
201
|
else {
|
|
201
202
|
// 二进制流处理
|
|
202
203
|
if (!upstreamResponse.body) {
|
|
203
|
-
|
|
204
|
+
logger.error(`Empty response body from ${targetUrl}`);
|
|
204
205
|
res.writeHead(502).end('Empty Upstream Response');
|
|
205
206
|
}
|
|
206
207
|
else {
|
|
@@ -218,7 +219,7 @@ async function writeSuccessfulResponse(registryInfo, targetUrl, res, upstreamRes
|
|
|
218
219
|
}
|
|
219
220
|
}
|
|
220
221
|
catch (err) {
|
|
221
|
-
|
|
222
|
+
logger.error('Failed to write upstreamResponse:', err);
|
|
222
223
|
if (!res.headersSent) {
|
|
223
224
|
res.writeHead(502).end('Internal Server Error');
|
|
224
225
|
}
|
|
@@ -228,9 +229,9 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
228
229
|
const proxyInfo = await loadProxyInfo(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath);
|
|
229
230
|
const registryInfos = proxyInfo.registries;
|
|
230
231
|
const basePathPrefixedWithSlash = removeEndingSlashAndForceStartingSlash(proxyInfo.basePath);
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
232
|
+
logger.info('Active registries:', registryInfos.map(r => r.normalizedRegistryUrl));
|
|
233
|
+
logger.info('Proxy base path:', basePathPrefixedWithSlash);
|
|
234
|
+
logger.info('HTTPS:', !!proxyInfo.https);
|
|
234
235
|
let proxyPort;
|
|
235
236
|
const requestHandler = async (req, res) => {
|
|
236
237
|
if (!req.url || !req.headers.host) {
|
|
@@ -279,7 +280,7 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
279
280
|
await fsPromises.access(certPath);
|
|
280
281
|
}
|
|
281
282
|
catch (e) {
|
|
282
|
-
|
|
283
|
+
logger.error(`HTTPS config error: key or cert file not found`, e);
|
|
283
284
|
process.exit(1);
|
|
284
285
|
}
|
|
285
286
|
const httpsOptions = {
|
|
@@ -294,18 +295,18 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
294
295
|
const promisedServer = new Promise((resolve, reject) => {
|
|
295
296
|
server.on('error', (err) => {
|
|
296
297
|
if (err.code === 'EADDRINUSE') {
|
|
297
|
-
|
|
298
|
+
logger.error(`Port ${port} is in use, please specify a different port or free it.`);
|
|
298
299
|
process.exit(1);
|
|
299
300
|
}
|
|
300
|
-
|
|
301
|
+
logger.error('Server error:', err);
|
|
301
302
|
reject(err);
|
|
302
303
|
});
|
|
303
304
|
server.listen(port, () => {
|
|
304
305
|
const address = server.address();
|
|
305
306
|
proxyPort = address.port;
|
|
306
307
|
const portFile = join(process.env.PROJECT_ROOT || process.cwd(), '.registry-proxy-port');
|
|
307
|
-
writeFile(portFile, proxyPort.toString()).catch(e =>
|
|
308
|
-
|
|
308
|
+
writeFile(portFile, proxyPort.toString()).catch(e => logger.error('Failed to write port file:', e));
|
|
309
|
+
logger.info(`Proxy server running on ${proxyInfo.https ? 'https' : 'http'}://localhost:${proxyPort}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`);
|
|
309
310
|
resolve(server);
|
|
310
311
|
});
|
|
311
312
|
});
|
|
@@ -314,7 +315,7 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
314
315
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
315
316
|
const [, , configPath, localYarnPath, globalYarnPath, port] = process.argv;
|
|
316
317
|
startProxyServer(configPath, localYarnPath, globalYarnPath, parseInt(port, 10) || 0).catch(err => {
|
|
317
|
-
|
|
318
|
+
logger.error('Failed to start server:', err);
|
|
318
319
|
process.exit(1);
|
|
319
320
|
});
|
|
320
321
|
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import fetch, {Response} from 'node-fetch';
|
|
|
8
8
|
import {homedir} from 'os';
|
|
9
9
|
import {join, resolve} from 'path';
|
|
10
10
|
import {URL} from 'url';
|
|
11
|
+
import logger from "./utils/logger";
|
|
11
12
|
|
|
12
13
|
const {readFile, writeFile} = fsPromises;
|
|
13
14
|
|
|
@@ -127,12 +128,12 @@ async function readProxyConfig(proxyConfigPath = './.registry-proxy.yml'): Promi
|
|
|
127
128
|
const content = await readFile(resolvedPath, 'utf8');
|
|
128
129
|
const config = load(content) as ProxyConfig;
|
|
129
130
|
if (!config.registries) {
|
|
130
|
-
|
|
131
|
+
logger.error('Missing required "registries" field in config');
|
|
131
132
|
process.exit(1);
|
|
132
133
|
}
|
|
133
134
|
return config;
|
|
134
135
|
} catch (e) {
|
|
135
|
-
|
|
136
|
+
logger.error(`Failed to load proxy config from ${resolvedPath}:`, e);
|
|
136
137
|
process.exit(1);
|
|
137
138
|
}
|
|
138
139
|
}
|
|
@@ -142,7 +143,7 @@ async function readYarnConfig(path: string): Promise<YarnConfig> {
|
|
|
142
143
|
const content = await readFile(resolvePath(path), 'utf8');
|
|
143
144
|
return load(content) as YarnConfig;
|
|
144
145
|
} catch (e) {
|
|
145
|
-
|
|
146
|
+
logger.warn(`Failed to load Yarn config from ${path}:`, e);
|
|
146
147
|
return {};
|
|
147
148
|
}
|
|
148
149
|
}
|
|
@@ -192,15 +193,15 @@ async function fetchFromRegistry(
|
|
|
192
193
|
): Promise<Response | null> {
|
|
193
194
|
await limiter.acquire();
|
|
194
195
|
try {
|
|
195
|
-
|
|
196
|
+
logger.info(`Fetching from: ${targetUrl}`);
|
|
196
197
|
const headers: {} = registry.token ? {Authorization: `Bearer ${registry.token}`} : {};
|
|
197
198
|
(headers as any).Collection = "keep-alive";
|
|
198
199
|
const response = await fetch(targetUrl, {headers});
|
|
199
|
-
|
|
200
|
+
logger.info(`Response from upstream ${targetUrl}: ${response.status} ${response.statusText} content-type=${response.headers.get('content-type')} content-length=${response.headers.get('content-length')} transfer-encoding=${response.headers.get('transfer-encoding')}`);
|
|
200
201
|
return response.ok ? response : null;
|
|
201
202
|
} catch (e) {
|
|
202
203
|
if (e instanceof Error) {
|
|
203
|
-
|
|
204
|
+
logger.error(
|
|
204
205
|
(e as any).code === 'ECONNREFUSED'
|
|
205
206
|
? `Registry ${registry.normalizedRegistryUrl} unreachable [ECONNREFUSED]`
|
|
206
207
|
: `Error from ${registry.normalizedRegistryUrl}: ${e.message}`
|
|
@@ -266,7 +267,7 @@ async function writeSuccessfulResponse(
|
|
|
266
267
|
} else {
|
|
267
268
|
// 二进制流处理
|
|
268
269
|
if (!upstreamResponse.body) {
|
|
269
|
-
|
|
270
|
+
logger.error(`Empty response body from ${targetUrl}`);
|
|
270
271
|
res.writeHead(502).end('Empty Upstream Response');
|
|
271
272
|
} else {
|
|
272
273
|
// write back to client
|
|
@@ -282,7 +283,7 @@ async function writeSuccessfulResponse(
|
|
|
282
283
|
}
|
|
283
284
|
}
|
|
284
285
|
} catch (err) {
|
|
285
|
-
|
|
286
|
+
logger.error('Failed to write upstreamResponse:', err);
|
|
286
287
|
if (!res.headersSent) {
|
|
287
288
|
res.writeHead(502).end('Internal Server Error');
|
|
288
289
|
}
|
|
@@ -299,9 +300,9 @@ export async function startProxyServer(
|
|
|
299
300
|
const registryInfos = proxyInfo.registries;
|
|
300
301
|
const basePathPrefixedWithSlash: string = removeEndingSlashAndForceStartingSlash(proxyInfo.basePath);
|
|
301
302
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
303
|
+
logger.info('Active registries:', registryInfos.map(r => r.normalizedRegistryUrl));
|
|
304
|
+
logger.info('Proxy base path:', basePathPrefixedWithSlash);
|
|
305
|
+
logger.info('HTTPS:', !!proxyInfo.https);
|
|
305
306
|
|
|
306
307
|
let proxyPort: number;
|
|
307
308
|
|
|
@@ -354,7 +355,7 @@ export async function startProxyServer(
|
|
|
354
355
|
await fsPromises.access(keyPath);
|
|
355
356
|
await fsPromises.access(certPath);
|
|
356
357
|
} catch (e) {
|
|
357
|
-
|
|
358
|
+
logger.error(`HTTPS config error: key or cert file not found`, e);
|
|
358
359
|
process.exit(1);
|
|
359
360
|
}
|
|
360
361
|
const httpsOptions = {
|
|
@@ -369,18 +370,18 @@ export async function startProxyServer(
|
|
|
369
370
|
const promisedServer: Promise<HttpServer | HttpsServer> = new Promise((resolve, reject) => {
|
|
370
371
|
server.on('error', (err: NodeJS.ErrnoException) => {
|
|
371
372
|
if (err.code === 'EADDRINUSE') {
|
|
372
|
-
|
|
373
|
+
logger.error(`Port ${port} is in use, please specify a different port or free it.`);
|
|
373
374
|
process.exit(1);
|
|
374
375
|
}
|
|
375
|
-
|
|
376
|
+
logger.error('Server error:', err);
|
|
376
377
|
reject(err);
|
|
377
378
|
});
|
|
378
379
|
server.listen(port, () => {
|
|
379
380
|
const address = server.address() as AddressInfo;
|
|
380
381
|
proxyPort = address.port;
|
|
381
382
|
const portFile = join(process.env.PROJECT_ROOT || process.cwd(), '.registry-proxy-port');
|
|
382
|
-
writeFile(portFile, proxyPort.toString()).catch(e =>
|
|
383
|
-
|
|
383
|
+
writeFile(portFile, proxyPort.toString()).catch(e => logger.error('Failed to write port file:', e));
|
|
384
|
+
logger.info(`Proxy server running on ${proxyInfo.https ? 'https' : 'http'}://localhost:${proxyPort}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`);
|
|
384
385
|
resolve(server);
|
|
385
386
|
});
|
|
386
387
|
});
|
|
@@ -396,7 +397,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
396
397
|
globalYarnPath,
|
|
397
398
|
parseInt(port, 10) || 0
|
|
398
399
|
).catch(err => {
|
|
399
|
-
|
|
400
|
+
logger.error('Failed to start server:', err);
|
|
400
401
|
process.exit(1);
|
|
401
402
|
});
|
|
402
403
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// utils/logger.ts
|
|
2
|
+
const COLORS = {
|
|
3
|
+
reset: '\x1b[0m',
|
|
4
|
+
proxy: '\x1b[36m', // 青色
|
|
5
|
+
success: '\x1b[32m', // 绿色
|
|
6
|
+
error: '\x1b[31m', // 红色
|
|
7
|
+
warn: '\x1b[33m', // 黄色
|
|
8
|
+
debug: '\x1b[35m' // 紫色
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const PREFIX = {
|
|
12
|
+
proxy: `${COLORS.proxy}[PROXY]${COLORS.reset}`,
|
|
13
|
+
error: `${COLORS.error}[ERROR]${COLORS.reset}`,
|
|
14
|
+
warn: `${COLORS.warn}[WARN]${COLORS.reset}`,
|
|
15
|
+
debug: `${COLORS.debug}[DEBUG]${COLORS.reset}`
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// 代理服务器专用日志
|
|
19
|
+
const log = {
|
|
20
|
+
info: (...args: any[]) =>
|
|
21
|
+
console.log(`${PREFIX.proxy}`, ...args),
|
|
22
|
+
|
|
23
|
+
success: (...args: any[]) =>
|
|
24
|
+
console.log(`${PREFIX.proxy} ${COLORS.success}✓${COLORS.reset}`, ...args),
|
|
25
|
+
|
|
26
|
+
error: (...args: any[]) =>
|
|
27
|
+
console.error(`${PREFIX.error}`, ...args),
|
|
28
|
+
|
|
29
|
+
warn: (...args: any[]) =>
|
|
30
|
+
console.warn(`${PREFIX.warn}`, ...args),
|
|
31
|
+
|
|
32
|
+
debug: (...args: any[]) =>
|
|
33
|
+
process.env.DEBUG && console.debug(`${PREFIX.debug}`, ...args)
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default log;
|