com.jimuwd.xian.registry-proxy 1.0.124 → 1.0.125
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/gracefullShutdown.d.ts +5 -0
- package/dist/gracefullShutdown.js +34 -0
- package/dist/index.js +14 -14
- package/dist/port.d.ts +4 -0
- package/dist/port.js +26 -0
- package/package.json +1 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { deletePortFile } from "./port.js";
|
|
2
|
+
import logger from "./utils/logger.js";
|
|
3
|
+
/**
|
|
4
|
+
* 优雅退出
|
|
5
|
+
* 本函数是对process.exit的封装,同时执行资源释放动作,程序必须统一调用本方法退出,决不允许直接调用{@link process.exit}来退出。
|
|
6
|
+
*/
|
|
7
|
+
export async function gracefulShutdown() {
|
|
8
|
+
try {
|
|
9
|
+
logger.info('Shutdown...');
|
|
10
|
+
await doCleanup();
|
|
11
|
+
logger.info('Shutdown completed.');
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
logger.error('Failed to clean:', err);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function doCleanup() {
|
|
20
|
+
await deletePortFile();
|
|
21
|
+
}
|
|
22
|
+
// 捕获信号或异常
|
|
23
|
+
process.on('SIGINT', () => {
|
|
24
|
+
logger.info('收到 SIGINT(Ctrl+C)');
|
|
25
|
+
process.exit(0); // 触发 exit 事件
|
|
26
|
+
});
|
|
27
|
+
process.on('SIGTERM', () => {
|
|
28
|
+
logger.info('收到 SIGTERM');
|
|
29
|
+
process.exit(0); // 触发 exit 事件
|
|
30
|
+
});
|
|
31
|
+
process.on('uncaughtException', (err) => {
|
|
32
|
+
logger.info('uncaughtException:', err);
|
|
33
|
+
process.exit(1); // 触发 exit 事件
|
|
34
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import http, { createServer } from 'node:http';
|
|
3
3
|
import https, { createServer as createHttpsServer } from 'node:https';
|
|
4
|
-
import { promises as fsPromises, readFileSync } from 'fs';
|
|
4
|
+
import { promises as fsPromises, readFileSync } from 'node:fs';
|
|
5
5
|
import { load } from 'js-yaml';
|
|
6
6
|
import fetch from 'node-fetch';
|
|
7
7
|
import { homedir } from 'os';
|
|
@@ -9,6 +9,8 @@ import { join, resolve } from 'path';
|
|
|
9
9
|
import { URL } from 'url';
|
|
10
10
|
import logger from "./utils/logger.js";
|
|
11
11
|
import ConcurrencyLimiter from "./utils/ConcurrencyLimiter.js";
|
|
12
|
+
import { gracefulShutdown } from "./gracefullShutdown.js";
|
|
13
|
+
import { writePortFile } from "./port.js";
|
|
12
14
|
const { readFile, writeFile } = fsPromises;
|
|
13
15
|
const limiter = new ConcurrencyLimiter(Infinity);
|
|
14
16
|
function removeEndingSlashAndForceStartingSlash(str) {
|
|
@@ -56,20 +58,21 @@ function removeRegistryPrefix(tarballUrl, registries) {
|
|
|
56
58
|
throw new Error(`Can't find tarball url ${tarballUrl} does not match given registries ${normalizedRegistries}`);
|
|
57
59
|
}
|
|
58
60
|
async function readProxyConfig(proxyConfigPath = './.registry-proxy.yml') {
|
|
61
|
+
let config = undefined;
|
|
59
62
|
const resolvedPath = resolvePath(proxyConfigPath);
|
|
60
63
|
try {
|
|
61
64
|
const content = await readFile(resolvedPath, 'utf8');
|
|
62
|
-
|
|
63
|
-
if (!config.registries) {
|
|
64
|
-
logger.error('Missing required "registries" field in config');
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
return config;
|
|
65
|
+
config = load(content);
|
|
68
66
|
}
|
|
69
67
|
catch (e) {
|
|
70
68
|
logger.error(`Failed to load proxy config from ${resolvedPath}:`, e);
|
|
71
|
-
|
|
69
|
+
await gracefulShutdown();
|
|
70
|
+
}
|
|
71
|
+
if (!config?.registries) {
|
|
72
|
+
logger.error('Missing required "registries" field in config');
|
|
73
|
+
await gracefulShutdown();
|
|
72
74
|
}
|
|
75
|
+
return config;
|
|
73
76
|
}
|
|
74
77
|
async function readYarnConfig(path) {
|
|
75
78
|
try {
|
|
@@ -171,7 +174,7 @@ async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDow
|
|
|
171
174
|
const data = await upstreamResponse.json();
|
|
172
175
|
if (data.versions) { // 处理node依赖包元数据
|
|
173
176
|
logger.info("Write package meta data application/json response from upstream to downstream", targetUrl);
|
|
174
|
-
const host = reqFromDownstreamClient.headers.host
|
|
177
|
+
const host = reqFromDownstreamClient.headers.host /*|| `[::1]:${proxyPort}`*/;
|
|
175
178
|
const baseUrl = `${proxyInfo.https ? 'https' : 'http'}://${host}${proxyInfo.basePath === '/' ? '' : proxyInfo.basePath}`;
|
|
176
179
|
for (const versionKey in data.versions) {
|
|
177
180
|
const packageVersion = data.versions[versionKey];
|
|
@@ -299,7 +302,6 @@ function getDownstreamClientIp(req) {
|
|
|
299
302
|
// 直接连接时,取 socket.remoteAddress
|
|
300
303
|
return req.socket.remoteAddress;
|
|
301
304
|
}
|
|
302
|
-
// deprecated 出于安全考虑只监听::1地址,废弃本注释:同时启动ipv6,ipv4监听,比如当客户端访问http://localhost:port时,无论客户端DNS解析到IPV4-127.0.0.1还是IPV6-::1地址,咱server都能轻松应对!
|
|
303
305
|
export async function startProxyServer(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath, port = 0) {
|
|
304
306
|
const proxyInfo = await loadProxyInfo(proxyConfigPath, localYarnConfigPath, globalYarnConfigPath);
|
|
305
307
|
const registryInfos = proxyInfo.registries;
|
|
@@ -358,7 +360,6 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
358
360
|
resToDownstreamClient.writeHead(404).end('All upstream registries failed');
|
|
359
361
|
}
|
|
360
362
|
};
|
|
361
|
-
// deprecated 废弃本注释:需要同时启动ipv6,ipv4监听,比如当客户端访问http://localhost:port时,无论客户端DNS解析到IPV4-127.0.0.1还是IPV6-::1地址,咱server都能轻松应对!
|
|
362
363
|
let server;
|
|
363
364
|
if (proxyInfo.https) {
|
|
364
365
|
const { key, cert } = proxyInfo.https;
|
|
@@ -409,11 +410,10 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
|
|
|
409
410
|
// 为了代理服务器的安全性,暂时只监听本机ipv6地址【::1】,不能对本机之外暴露本代理服务地址避免造成安全隐患
|
|
410
411
|
// 注意:截止目前yarn客户端如果通过localhost:<port>来访问本服务,可能会报错ECONNREFUSED错误码,原因是yarn客户端环境解析“localhost”至多个地址,它会尝试轮询每个地址。
|
|
411
412
|
const listenOptions = { port, host: '::1', ipv6Only: true };
|
|
412
|
-
server.listen(listenOptions, () => {
|
|
413
|
+
server.listen(listenOptions, async () => {
|
|
413
414
|
const addressInfo = server.address();
|
|
414
415
|
port = addressInfo.port; // 回写上层局部变量
|
|
415
|
-
|
|
416
|
-
writeFile(portFile, addressInfo.port.toString()).catch(e => logger.error(`Failed to write port file: ${portFile}`, e));
|
|
416
|
+
await writePortFile(port);
|
|
417
417
|
logger.info(`Proxy server running on ${proxyInfo.https ? 'https' : 'http'}://localhost:${addressInfo.port}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`);
|
|
418
418
|
resolve(server);
|
|
419
419
|
});
|
package/dist/port.d.ts
ADDED
package/dist/port.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { basename, join } from "node:path";
|
|
2
|
+
import { promises as nodeFsPromises } from 'node:fs';
|
|
3
|
+
import logger from "./utils/logger.js";
|
|
4
|
+
const { unlink, writeFile, } = nodeFsPromises;
|
|
5
|
+
export const PORT_FILE_NAME = '.registry-proxy-port';
|
|
6
|
+
export const portFile = join(process.env.PROJECT_ROOT || process.cwd(), PORT_FILE_NAME);
|
|
7
|
+
export async function writePortFile(port) {
|
|
8
|
+
await writeFile(portFile, port.toString()).catch(e => logger.error(`Failed to write port file: ${portFile}`, e));
|
|
9
|
+
}
|
|
10
|
+
export const deletePortFile = async () => {
|
|
11
|
+
await deleteFile(portFile);
|
|
12
|
+
};
|
|
13
|
+
async function deleteFile(filePath) {
|
|
14
|
+
try {
|
|
15
|
+
await unlink(filePath);
|
|
16
|
+
console.log(`文件 ${basename(filePath)} 已删除`);
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
if (err.code === 'ENOENT') {
|
|
20
|
+
console.log('文件不存在');
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.error('删除失败:', err.message);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|