com.jimuwd.xian.registry-proxy 1.0.123 → 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.
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 优雅退出
3
+ * 本函数是对process.exit的封装,同时执行资源释放动作,程序必须统一调用本方法退出,决不允许直接调用{@link process.exit}来退出。
4
+ */
5
+ export declare function gracefulShutdown(): Promise<void>;
@@ -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
- const config = load(content);
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
- process.exit(1);
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 || `127.0.0.1:${proxyPort}`;
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;
@@ -407,12 +408,12 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
407
408
  server.on('error', errHandler /*this handler will call 'reject'*/);
408
409
  server.on('connection', connectionHandler);
409
410
  // 为了代理服务器的安全性,暂时只监听本机ipv6地址【::1】,不能对本机之外暴露本代理服务地址避免造成安全隐患
411
+ // 注意:截止目前yarn客户端如果通过localhost:<port>来访问本服务,可能会报错ECONNREFUSED错误码,原因是yarn客户端环境解析“localhost”至多个地址,它会尝试轮询每个地址。
410
412
  const listenOptions = { port, host: '::1', ipv6Only: true };
411
- server.listen(listenOptions, () => {
413
+ server.listen(listenOptions, async () => {
412
414
  const addressInfo = server.address();
413
415
  port = addressInfo.port; // 回写上层局部变量
414
- const portFile = join(process.env.PROJECT_ROOT || process.cwd(), '.registry-proxy-port');
415
- writeFile(portFile, addressInfo.port.toString()).catch(e => logger.error(`Failed to write port file: ${portFile}`, e));
416
+ await writePortFile(port);
416
417
  logger.info(`Proxy server running on ${proxyInfo.https ? 'https' : 'http'}://localhost:${addressInfo.port}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`);
417
418
  resolve(server);
418
419
  });
package/dist/port.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ export declare const PORT_FILE_NAME = ".registry-proxy-port";
2
+ export declare const portFile: string;
3
+ export declare function writePortFile(port: number): Promise<void>;
4
+ export declare const deletePortFile: () => Promise<void>;
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.jimuwd.xian.registry-proxy",
3
- "version": "1.0.123",
3
+ "version": "1.0.125",
4
4
  "description": "A lightweight npm registry proxy with fallback support",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",