com.jimuwd.xian.registry-proxy 1.1.7 → 1.1.9

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/README.MD CHANGED
@@ -63,7 +63,7 @@ registries:
63
63
 
64
64
  ```yaml
65
65
  unsafeHttpWhitelist:
66
- - "[::1]"
66
+ - "127.0.0.1"
67
67
  ```
68
68
 
69
69
  ### 4. 安装命令
@@ -324,7 +324,7 @@ Create a `.yarnrc.yml` file in your project root to allow Yarn to use the local
324
324
 
325
325
  ```yaml
326
326
  unsafeHttpWhitelist:
327
- - "[::1]"
327
+ - "127.0.0.1"
328
328
  ```
329
329
 
330
330
  ### 4. install Script
@@ -124,8 +124,8 @@ async function main() {
124
124
  throw new Error(`Proxy server not listening on port ${PROXY_PORT}`);
125
125
  }
126
126
  // Configure yarn
127
- await execa('yarn', ['config', 'set', 'npmRegistryServer', `http://[::1]:${PROXY_PORT}`]);
128
- console.log(`Set npmRegistryServer to http://[::1]:${PROXY_PORT}`);
127
+ await execa('yarn', ['config', 'set', 'npmRegistryServer', `http://127.0.0.1:${PROXY_PORT}`]);
128
+ console.log(`Set npmRegistryServer to http://127.0.0.1:${PROXY_PORT}`);
129
129
  registerCleanup(async () => {
130
130
  try {
131
131
  await execa('yarn', ['config', 'unset', 'npmRegistryServer']);
@@ -166,13 +166,24 @@ async function fetchFromRegistry(registry, targetUrl, reqFromDownstreamClient, l
166
166
  }
167
167
  }
168
168
  catch (e) {
169
+ // Fetch form one of the confiured upstream registries failed, this is expected, not error.
169
170
  if (e instanceof Error) {
170
- logger.error(e.code === 'ECONNREFUSED' ? `Upstream ${targetUrl} unreachable [ECONNREFUSED]`
171
- : `Error fetching from ${targetUrl}, ${e.message}`, e);
171
+ const errCode = e.code;
172
+ if (errCode === 'ECONNREFUSED') {
173
+ logger.info(`Upstream ${targetUrl} refused connection [ECONNREFUSED], skip fetching from registry ${registry.normalizedRegistryUrl}`);
174
+ }
175
+ else if (errCode === 'ENOTFOUND') {
176
+ logger.info(`Unknown upstream domain name in ${targetUrl} [ENOTFOUND], skip fetching from registry ${registry.normalizedRegistryUrl}.`);
177
+ }
178
+ else {
179
+ // other net error code, pring log with stacktrace
180
+ logger.warn(`Failed to fetch from ${targetUrl}, ${e.message}`, e);
181
+ }
172
182
  }
173
183
  else {
174
184
  logger.error("Unknown error", e);
175
185
  }
186
+ // return null means skipping current upstream registry.
176
187
  return null;
177
188
  }
178
189
  finally {
@@ -193,7 +204,7 @@ async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDow
193
204
  const data = await upstreamResponse.json();
194
205
  if (data.versions) { // 处理node依赖包元数据
195
206
  logger.debug(() => "Write package meta data application/json response from upstream to downstream", targetUrl);
196
- const host = reqFromDownstreamClient.headers.host /*|| `[::1]:${_proxyPort}`*/;
207
+ const host = reqFromDownstreamClient.headers.host;
197
208
  const baseUrl = `${proxyInfo.https ? 'https' : 'http'}://${host}${proxyInfo.basePath === '/' ? '' : proxyInfo.basePath}`;
198
209
  for (const versionKey in data.versions) {
199
210
  const packageVersion = data.versions[versionKey];
@@ -414,15 +425,17 @@ export async function startProxyServer(proxyConfigPath, localYarnConfigPath, glo
414
425
  };
415
426
  server.on('error', errHandler /*this handler will call 'reject'*/);
416
427
  server.on('connection', connectionHandler);
417
- // 为了代理服务器的安全性,暂时只监听本机ipv6地址【::1】,不能对本机之外暴露本代理服务地址避免造成安全隐患
418
- // 注意:截止目前yarn客户端如果通过localhost:<port>来访问本服务,可能会报错ECONNREFUSED错误码,原因是yarn客户端环境解析“localhost”至多个地址,它会尝试轮询每个地址。
419
- const ipv6OnlyHost = '::1';
420
- const listenOptions = { port, host: ipv6OnlyHost, ipv6Only: true };
428
+ // 安全提示:为了代理服务器的安全性,暂时只监听本机ipv6地址【127.0.0.1】,不能对本机之外暴露本代理服务地址避免造成安全隐患。
429
+ // 兼容性说明:
430
+ // 1、不使用ipv6地址因为旧的docker容器内部并未开放ipv6,为了最大成都兼容容器内运行registry-proxy,只使用ipv4了。
431
+ // 2、yarn客户端应当通过127.0.0.1:<port>而非localhost:<port>来访问本服务,原因是yarn客户端环境解析“localhost”至多个地址,它会尝试轮询每个地址,其中某地址可能会报错ECONNREFUSED错误码,会导致yarn install执行失败。
432
+ const ipv4LocalhostIp = '127.0.0.1';
433
+ const listenOptions = { port, host: ipv4LocalhostIp, ipv6Only: false };
421
434
  server.listen(listenOptions, async () => {
422
435
  const addressInfo = server.address();
423
436
  port = addressInfo.port; // 回写上层局部变量
424
437
  await writePortFile(port);
425
- logger.info(`Proxy server running on ${proxyInfo.https ? 'https' : 'http'}://[${ipv6OnlyHost}]:${port}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`);
438
+ logger.info(`Proxy server running on ${proxyInfo.https ? 'https' : 'http'}://${ipv4LocalhostIp}:${port}${basePathPrefixedWithSlash === '/' ? '' : basePathPrefixedWithSlash}`);
426
439
  resolve(server);
427
440
  });
428
441
  });
@@ -433,7 +446,7 @@ if (import.meta.url === `file://${process.argv[1]}`) {
433
446
  registerProcessShutdownHook();
434
447
  const [, , configPath, localYarnPath, globalYarnPath, port] = process.argv;
435
448
  startProxyServer(configPath, localYarnPath, globalYarnPath, parseInt(port, 10) || 0).catch(async (err) => {
436
- logger.error('Failed to start server:', err);
449
+ logger.error('Failed to start server', err);
437
450
  await gracefulShutdown();
438
451
  });
439
452
  }
@@ -2,7 +2,7 @@ export declare function isPortFree(port: number): Promise<boolean>;
2
2
  /**
3
3
  * 检查指定端口是否可连接(有服务正在监听)
4
4
  * @param port 要检查的端口号
5
- * @param host 目标主机(默认本地IPv6 ::1)
5
+ * @param host 目标主机(默认本地IPv4 127.0.0.1)
6
6
  * @param timeout 超时时间(毫秒,默认1000ms)
7
7
  */
8
8
  export declare function isPortConnectable(port: number, host?: string, timeout?: number): Promise<boolean>;
@@ -12,10 +12,10 @@ export async function isPortFree(port) {
12
12
  /**
13
13
  * 检查指定端口是否可连接(有服务正在监听)
14
14
  * @param port 要检查的端口号
15
- * @param host 目标主机(默认本地IPv6 ::1)
15
+ * @param host 目标主机(默认本地IPv4 127.0.0.1)
16
16
  * @param timeout 超时时间(毫秒,默认1000ms)
17
17
  */
18
- export async function isPortConnectable(port, host = '::1', timeout = 1000) {
18
+ export async function isPortConnectable(port, host = '127.0.0.1', timeout = 1000) {
19
19
  return new Promise((resolve) => {
20
20
  const socket = new net.Socket();
21
21
  // 设置超时
@@ -46,7 +46,7 @@ async function checkPortListening(port) {
46
46
  socket.destroy();
47
47
  resolve(true);
48
48
  });
49
- socket.connect({ port, host: '::1' });
49
+ socket.connect({ port, host: '127.0.0.1' });
50
50
  });
51
51
  }
52
52
  export default { isPortFree, isPortConnectable, checkPortListening };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.jimuwd.xian.registry-proxy",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
4
4
  "description": "A lightweight npm registry proxy with fallback support",
5
5
  "type": "module",
6
6
  "main": "dist/server/index.js",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "files": [
12
12
  "dist",
13
- "src/client/yarn-install.sh"
13
+ "LICENSE"
14
14
  ],
15
15
  "scripts": {
16
16
  "build": "tsc",
@@ -1,136 +0,0 @@
1
- #!/bin/bash
2
-
3
- eco "@@Deprecated@@"
4
- eco "use yarn-install.ts instead!"
5
-
6
- # 启用严格模式,但移除 set -e,手动处理错误
7
- set -u # 未定义变量时退出
8
- set -o pipefail # 管道中任一命令失败时退出
9
-
10
- # 动态确定项目根目录(假设 package.json 所在目录为根目录)
11
- find_project_root() {
12
- local dir="$PWD"
13
- while [ "$dir" != "/" ]; do
14
- if [ -f "$dir/package.json" ]; then
15
- echo "$dir"
16
- return 0
17
- fi
18
- dir=$(dirname "$dir")
19
- done
20
- echo "Error: Could not find project root (package.json not found)" >&2
21
- exit 1
22
- }
23
-
24
- PROJECT_ROOT=$(find_project_root)
25
-
26
- # 定义锁文件和端口文件路径(固定在项目根目录)
27
- LOCK_FILE="$PROJECT_ROOT/.registry-proxy-install.lock"
28
-
29
- # 检查是否已经在运行(通过锁文件)
30
- if [ -f "$LOCK_FILE" ]; then
31
- echo "Custom install script is already running (lock file $LOCK_FILE exists)."
32
- echo "If this is unexpected, please remove $LOCK_FILE and try again."
33
- exit 0 # 内层脚本直接退出,表示任务已由外层脚本完成
34
- fi
35
-
36
- # 创建锁文件
37
- touch "$LOCK_FILE"
38
-
39
- # 清理函数,支持不同的退出状态
40
- # 参数 $1:退出状态(0 表示正常退出,1 表示异常退出)
41
- cleanup() {
42
- local exit_code=${1:-1} # 默认退出码为 1(异常退出)
43
-
44
- # 显式清除 EXIT 信号的 trap,避免潜在的误解
45
- trap - EXIT
46
-
47
- if [ "$exit_code" -eq 0 ]; then
48
- echo "Cleaning up after successful execution..."
49
- else
50
- echo "Caught interrupt signal or error, cleaning up..."
51
- fi
52
-
53
- # 清理临时文件
54
- rm -f "$LOCK_FILE" 2>/dev/null
55
- # PORT_FILE端口临时文件是registry-proxy服务器管理的文件这里不负责清理,服务器退出时会自动清理
56
- #rm -f "$PORT_FILE" 2>/dev/null
57
-
58
- # 停止代理服务器
59
- if [ -n "${PROXY_PID:-}" ]; then
60
- echo "Stopping proxy server (PID: $PROXY_PID)..."
61
- kill -TERM "$PROXY_PID" 2>/dev/null
62
- wait "$PROXY_PID" 2>/dev/null || true
63
- echo "Proxy server stopped."
64
- fi
65
-
66
- # 切换到项目根目录
67
- # shellcheck disable=SC2164
68
- cd "$PROJECT_ROOT"
69
-
70
- # 清理 npmRegistryServer 配置
71
- yarn config unset npmRegistryServer 2>/dev/null || true
72
- echo "Cleared npmRegistryServer configuration"
73
-
74
- # 根据退出状态退出
75
- exit "$exit_code"
76
- }
77
-
78
- # 注册信号处理
79
- trap 'cleanup 1' SIGINT SIGTERM EXIT # 异常退出时调用 cleanup,退出码为 1
80
-
81
- # 切换到项目根目录
82
- # shellcheck disable=SC2164
83
- cd "$PROJECT_ROOT"
84
-
85
- # 使用 yarn dlx 直接运行 registry-proxy,可通过环境变量指定registry-proxy版本号,默认是latest,registry-proxy将会被放入后台运行,并在安装结束后自动退出。
86
- REGISTRY_PROXY_VERSION="${REGISTRY_PROXY_VERSION:-latest}"
87
- echo "Starting registry-proxy@$REGISTRY_PROXY_VERSION in the background (logs will be displayed below)..."
88
- # 下载registry-proxy临时可执行程序并运行 因yarn可能会缓存tarball url的缘故(yarn.lock内<package>.resolution值),这里不得已只能写死本地代理端口地址,以便无论是从缓存获取tarball url还是从代理服务提供的元数据获取tarball url地址都能成功下载tarball文件
89
- # 但是注意 这个端口不能暴露到外部使用,只允许本地使用,避免不必要的安全隐患 事实上registry-proxy server也是只监听着::1本机端口的。
90
- yarn dlx -p com.jimuwd.xian.registry-proxy@"$REGISTRY_PROXY_VERSION" registry-proxy .registry-proxy.yml .yarnrc.yml ~/.yarnrc.yml 40061 &
91
- PROXY_PID=$!
92
-
93
- # 等待代理服务器启动并写入端口,最多 30 秒
94
- echo "Waiting for proxy server to start (up to 30 seconds)..."
95
- PORT_FILE="$PROJECT_ROOT/.registry-proxy-port"
96
- # shellcheck disable=SC2034
97
- for i in {1..300}; do # 300 次循环,每次 0.1 秒,总共 30 秒
98
- if [ -f "$PORT_FILE" ]; then
99
- PROXY_PORT=$(cat "$PORT_FILE")
100
- if [ -z "$PROXY_PORT" ]; then
101
- echo "Error: Port file $PORT_FILE is empty"
102
- cleanup 1
103
- fi
104
- if nc -z localhost "$PROXY_PORT" 2>/dev/null; then
105
- echo "Proxy server is ready on port $PROXY_PORT!"
106
- break
107
- else
108
- # 检查端口是否被占用
109
- if netstat -tuln 2>/dev/null | grep -q ":$PROXY_PORT "; then
110
- echo "Error: Port $PROXY_PORT is already in use by another process"
111
- cleanup 1
112
- fi
113
- fi
114
- fi
115
- sleep 0.1
116
- done
117
-
118
- # 检查是否成功启动
119
- if [ -z "${PROXY_PORT:-}" ] || ! nc -z localhost "$PROXY_PORT" 2>/dev/null; then
120
- echo "Error: Proxy server failed to start after 30 seconds"
121
- echo "Please check the registry-proxy logs above for more details."
122
- cleanup 1
123
- fi
124
-
125
- # 动态设置 npmRegistryServer 为代理地址 注意:yarn对“localhost”域名不友好,请直接使用 [::1]
126
- yarn config set npmRegistryServer "http://[::1]:$PROXY_PORT"
127
- echo "Set npmRegistryServer to http://[::1]:$PROXY_PORT"
128
-
129
- # 使用动态代理端口运行 yarn install,并捕获错误
130
- if ! yarn install; then
131
- echo "Error: yarn install failed"
132
- cleanup 1
133
- fi
134
-
135
- # 正常执行完成,调用 cleanup 并传入退出码 0
136
- cleanup 0