com.jimuwd.xian.registry-proxy 1.0.132 → 1.0.134
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 +15 -303
- package/dist/client/yarn-install.js +25 -52
- package/dist/server/index.js +22 -34
- package/dist/utils/portTester.d.ts +18 -0
- package/dist/utils/portTester.js +52 -0
- package/package.json +5 -3
- package/src/client/yarn-install.sh +133 -0
package/README.MD
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
## 概述
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
本项目提供了一个本地代理服务器(`registry-proxy`),允许 Yarn 从多个注册表获取包,并支持身份验证令牌。项目还包括一个脚本(`src/client/yarn-install`),用于自动化启动代理服务器、安装依赖和清理资源的过程。该设置确保与 Yarn 无缝集成,开发者只需使用标准的 `yarn` 命令即可通过代理安装依赖。
|
|
10
10
|
|
|
11
11
|
## 功能
|
|
12
12
|
|
|
@@ -63,167 +63,22 @@ registries:
|
|
|
63
63
|
|
|
64
64
|
```yaml
|
|
65
65
|
unsafeHttpWhitelist:
|
|
66
|
-
- "
|
|
66
|
+
- "[::1]"
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
### 4.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
#!/bin/bash
|
|
75
|
-
|
|
76
|
-
# 启用严格模式,但移除 set -e,手动处理错误
|
|
77
|
-
set -u # 未定义变量时退出
|
|
78
|
-
set -o pipefail # 管道中任一命令失败时退出
|
|
79
|
-
|
|
80
|
-
# 动态确定项目根目录(假设 package.json 所在目录为根目录)
|
|
81
|
-
find_project_root() {
|
|
82
|
-
local dir="$PWD"
|
|
83
|
-
while [ "$dir" != "/" ]; do
|
|
84
|
-
if [ -f "$dir/package.json" ]; then
|
|
85
|
-
echo "$dir"
|
|
86
|
-
return 0
|
|
87
|
-
fi
|
|
88
|
-
dir=$(dirname "$dir")
|
|
89
|
-
done
|
|
90
|
-
echo "Error: Could not find project root (package.json not found)" >&2
|
|
91
|
-
exit 1
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
PROJECT_ROOT=$(find_project_root)
|
|
95
|
-
|
|
96
|
-
# 定义锁文件和端口文件路径(固定在项目根目录)
|
|
97
|
-
LOCK_FILE="$PROJECT_ROOT/.registry-proxy-install.lock"
|
|
98
|
-
|
|
99
|
-
# 检查是否已经在运行(通过锁文件)
|
|
100
|
-
if [ -f "$LOCK_FILE" ]; then
|
|
101
|
-
echo "Custom install script is already running (lock file $LOCK_FILE exists)."
|
|
102
|
-
echo "If this is unexpected, please remove $LOCK_FILE and try again."
|
|
103
|
-
exit 0 # 内层脚本直接退出,表示任务已由外层脚本完成
|
|
104
|
-
fi
|
|
105
|
-
|
|
106
|
-
# 创建锁文件
|
|
107
|
-
touch "$LOCK_FILE"
|
|
108
|
-
|
|
109
|
-
# 清理函数,支持不同的退出状态
|
|
110
|
-
# 参数 $1:退出状态(0 表示正常退出,1 表示异常退出)
|
|
111
|
-
cleanup() {
|
|
112
|
-
local exit_code=${1:-1} # 默认退出码为 1(异常退出)
|
|
113
|
-
|
|
114
|
-
# 显式清除 EXIT 信号的 trap,避免潜在的误解
|
|
115
|
-
trap - EXIT
|
|
116
|
-
|
|
117
|
-
if [ "$exit_code" -eq 0 ]; then
|
|
118
|
-
echo "Cleaning up after successful execution..."
|
|
119
|
-
else
|
|
120
|
-
echo "Caught interrupt signal or error, cleaning up..."
|
|
121
|
-
fi
|
|
122
|
-
|
|
123
|
-
# 清理临时文件
|
|
124
|
-
rm -f "$LOCK_FILE" 2>/dev/null
|
|
125
|
-
# PORT_FILE端口临时文件是registry-proxy服务器管理的文件这里不负责清理,服务器退出时会自动清理
|
|
126
|
-
#rm -f "$PORT_FILE" 2>/dev/null
|
|
127
|
-
|
|
128
|
-
# 停止代理服务器
|
|
129
|
-
if [ -n "${PROXY_PID:-}" ]; then
|
|
130
|
-
echo "Stopping proxy server (PID: $PROXY_PID)..."
|
|
131
|
-
kill -TERM "$PROXY_PID" 2>/dev/null
|
|
132
|
-
wait "$PROXY_PID" 2>/dev/null || true
|
|
133
|
-
echo "Proxy server stopped."
|
|
134
|
-
fi
|
|
135
|
-
|
|
136
|
-
# 切换到项目根目录
|
|
137
|
-
# shellcheck disable=SC2164
|
|
138
|
-
cd "$PROJECT_ROOT"
|
|
139
|
-
|
|
140
|
-
# 清理 npmRegistryServer 配置
|
|
141
|
-
yarn config unset npmRegistryServer 2>/dev/null || true
|
|
142
|
-
echo "Cleared npmRegistryServer configuration"
|
|
143
|
-
|
|
144
|
-
# 根据退出状态退出
|
|
145
|
-
exit "$exit_code"
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
# 注册信号处理
|
|
149
|
-
trap 'cleanup 1' SIGINT SIGTERM EXIT # 异常退出时调用 cleanup,退出码为 1
|
|
150
|
-
|
|
151
|
-
# 切换到项目根目录
|
|
152
|
-
# shellcheck disable=SC2164
|
|
153
|
-
cd "$PROJECT_ROOT"
|
|
154
|
-
|
|
155
|
-
# 使用 yarn dlx 直接运行 registry-proxy,可通过环境变量指定registry-proxy版本号,默认是latest,registry-proxy将会被放入后台运行,并在安装结束后自动退出。
|
|
156
|
-
REGISTRY_PROXY_VERSION="${REGISTRY_PROXY_VERSION:-latest}"
|
|
157
|
-
echo "Starting registry-proxy@$REGISTRY_PROXY_VERSION in the background (logs will be displayed below)..."
|
|
158
|
-
# 下载registry-proxy临时可执行程序并运行 因yarn可能会缓存tarball url的缘故(yarn.lock内<package>.resolution值),这里不得已只能写死本地代理端口地址,以便无论是从缓存获取tarball url还是从代理服务提供的元数据获取tarball url地址都能成功下载tarball文件
|
|
159
|
-
# 但是注意 这个端口不能暴露到外部使用,只允许本地使用,避免不必要的安全隐患 事实上registry-proxy server也是只监听着::1本机端口的。
|
|
160
|
-
yarn dlx com.jimuwd.xian.registry-proxy@"$REGISTRY_PROXY_VERSION" .registry-proxy.yml .yarnrc.yml ~/.yarnrc.yml 40061 &
|
|
161
|
-
PROXY_PID=$!
|
|
162
|
-
|
|
163
|
-
# 等待代理服务器启动并写入端口,最多 30 秒
|
|
164
|
-
echo "Waiting for proxy server to start (up to 30 seconds)..."
|
|
165
|
-
PORT_FILE="$PROJECT_ROOT/.registry-proxy-port"
|
|
166
|
-
# shellcheck disable=SC2034
|
|
167
|
-
for i in {1..300}; do # 300 次循环,每次 0.1 秒,总共 30 秒
|
|
168
|
-
if [ -f "$PORT_FILE" ]; then
|
|
169
|
-
PROXY_PORT=$(cat "$PORT_FILE")
|
|
170
|
-
if [ -z "$PROXY_PORT" ]; then
|
|
171
|
-
echo "Error: Port file $PORT_FILE is empty"
|
|
172
|
-
cleanup 1
|
|
173
|
-
fi
|
|
174
|
-
if nc -z localhost "$PROXY_PORT" 2>/dev/null; then
|
|
175
|
-
echo "Proxy server is ready on port $PROXY_PORT!"
|
|
176
|
-
break
|
|
177
|
-
else
|
|
178
|
-
# 检查端口是否被占用
|
|
179
|
-
if netstat -tuln 2>/dev/null | grep -q ":$PROXY_PORT "; then
|
|
180
|
-
echo "Error: Port $PROXY_PORT is already in use by another process"
|
|
181
|
-
cleanup 1
|
|
182
|
-
fi
|
|
183
|
-
fi
|
|
184
|
-
fi
|
|
185
|
-
sleep 0.1
|
|
186
|
-
done
|
|
187
|
-
|
|
188
|
-
# 检查是否成功启动
|
|
189
|
-
if [ -z "${PROXY_PORT:-}" ] || ! nc -z localhost "$PROXY_PORT" 2>/dev/null; then
|
|
190
|
-
echo "Error: Proxy server failed to start after 30 seconds"
|
|
191
|
-
echo "Please check the registry-proxy logs above for more details."
|
|
192
|
-
cleanup 1
|
|
193
|
-
fi
|
|
194
|
-
|
|
195
|
-
# 动态设置 npmRegistryServer 为代理地址 注意:yarn对“localhost”域名不友好,请直接使用 [::1]
|
|
196
|
-
yarn config set npmRegistryServer "http://[::1]:$PROXY_PORT"
|
|
197
|
-
echo "Set npmRegistryServer to http://[::1]:$PROXY_PORT"
|
|
198
|
-
|
|
199
|
-
# 使用动态代理端口运行 yarn install,并捕获错误
|
|
200
|
-
if ! yarn install; then
|
|
201
|
-
echo "Error: yarn install failed"
|
|
202
|
-
cleanup 1
|
|
203
|
-
fi
|
|
204
|
-
|
|
205
|
-
# 正常执行完成,调用 cleanup 并传入退出码 0
|
|
206
|
-
cleanup 0
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### 5. 设置脚本权限
|
|
210
|
-
|
|
211
|
-
确保脚本具有可执行权限,并将权限状态提交到版本控制:
|
|
212
|
-
|
|
213
|
-
```bash
|
|
214
|
-
chmod +x scripts/install-from-proxy-registries.sh
|
|
215
|
-
git add scripts/install-from-proxy-registries.sh
|
|
216
|
-
git commit -m "Add install script with executable permission"
|
|
69
|
+
### 4. 安装命令
|
|
70
|
+
```shell
|
|
71
|
+
yarn dlx -p com.jimuwd.xian.registry-proxy yarn-install
|
|
217
72
|
```
|
|
218
73
|
|
|
219
|
-
###
|
|
74
|
+
### 5. 与 Yarn 集成
|
|
220
75
|
|
|
221
76
|
更新 `package.json`,使 `yarn` 命令自动运行脚本:
|
|
222
77
|
|
|
223
78
|
```json
|
|
224
79
|
{
|
|
225
80
|
"scripts": {
|
|
226
|
-
"preinstall": "
|
|
81
|
+
"preinstall": "yarn dlx -p com.jimuwd.xian.registry-proxy yarn-install",
|
|
227
82
|
"install": "echo 'Custom install script is running via preinstall, skipping default install.'"
|
|
228
83
|
}
|
|
229
84
|
}
|
|
@@ -412,7 +267,7 @@ A lightweight proxy server for Yarn to fetch packages from multiple registries w
|
|
|
412
267
|
|
|
413
268
|
## Overview
|
|
414
269
|
|
|
415
|
-
This project provides a proxy server (`registry-proxy`) that allows Yarn to fetch packages from multiple registries, with support for authentication tokens. It also includes a script (`
|
|
270
|
+
This project provides a proxy server (`registry-proxy`) that allows Yarn to fetch packages from multiple registries, with support for authentication tokens. It also includes a script (`src/client/yarn-install`) to automate the process of starting the proxy server, installing dependencies, and cleaning up resources. The setup ensures seamless integration with Yarn, allowing developers to use the standard `yarn` command to install dependencies via the proxy.
|
|
416
271
|
|
|
417
272
|
## Features
|
|
418
273
|
|
|
@@ -469,167 +324,24 @@ Create a `.yarnrc.yml` file in your project root to allow Yarn to use the local
|
|
|
469
324
|
|
|
470
325
|
```yaml
|
|
471
326
|
unsafeHttpWhitelist:
|
|
472
|
-
- "
|
|
327
|
+
- "[::1]"
|
|
473
328
|
```
|
|
474
329
|
|
|
475
|
-
### 4.
|
|
330
|
+
### 4. install Script
|
|
476
331
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
#!/bin/bash
|
|
481
|
-
|
|
482
|
-
# 启用严格模式,但移除 set -e,手动处理错误
|
|
483
|
-
set -u # 未定义变量时退出
|
|
484
|
-
set -o pipefail # 管道中任一命令失败时退出
|
|
485
|
-
|
|
486
|
-
# 动态确定项目根目录(假设 package.json 所在目录为根目录)
|
|
487
|
-
find_project_root() {
|
|
488
|
-
local dir="$PWD"
|
|
489
|
-
while [ "$dir" != "/" ]; do
|
|
490
|
-
if [ -f "$dir/package.json" ]; then
|
|
491
|
-
echo "$dir"
|
|
492
|
-
return 0
|
|
493
|
-
fi
|
|
494
|
-
dir=$(dirname "$dir")
|
|
495
|
-
done
|
|
496
|
-
echo "Error: Could not find project root (package.json not found)" >&2
|
|
497
|
-
exit 1
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
PROJECT_ROOT=$(find_project_root)
|
|
501
|
-
|
|
502
|
-
# 定义锁文件和端口文件路径(固定在项目根目录)
|
|
503
|
-
LOCK_FILE="$PROJECT_ROOT/.registry-proxy-install.lock"
|
|
504
|
-
|
|
505
|
-
# 检查是否已经在运行(通过锁文件)
|
|
506
|
-
if [ -f "$LOCK_FILE" ]; then
|
|
507
|
-
echo "Custom install script is already running (lock file $LOCK_FILE exists)."
|
|
508
|
-
echo "If this is unexpected, please remove $LOCK_FILE and try again."
|
|
509
|
-
exit 0 # 内层脚本直接退出,表示任务已由外层脚本完成
|
|
510
|
-
fi
|
|
511
|
-
|
|
512
|
-
# 创建锁文件
|
|
513
|
-
touch "$LOCK_FILE"
|
|
514
|
-
|
|
515
|
-
# 清理函数,支持不同的退出状态
|
|
516
|
-
# 参数 $1:退出状态(0 表示正常退出,1 表示异常退出)
|
|
517
|
-
cleanup() {
|
|
518
|
-
local exit_code=${1:-1} # 默认退出码为 1(异常退出)
|
|
519
|
-
|
|
520
|
-
# 显式清除 EXIT 信号的 trap,避免潜在的误解
|
|
521
|
-
trap - EXIT
|
|
522
|
-
|
|
523
|
-
if [ "$exit_code" -eq 0 ]; then
|
|
524
|
-
echo "Cleaning up after successful execution..."
|
|
525
|
-
else
|
|
526
|
-
echo "Caught interrupt signal or error, cleaning up..."
|
|
527
|
-
fi
|
|
528
|
-
|
|
529
|
-
# 清理临时文件
|
|
530
|
-
rm -f "$LOCK_FILE" 2>/dev/null
|
|
531
|
-
# PORT_FILE端口临时文件是registry-proxy服务器管理的文件这里不负责清理,服务器退出时会自动清理
|
|
532
|
-
#rm -f "$PORT_FILE" 2>/dev/null
|
|
533
|
-
|
|
534
|
-
# 停止代理服务器
|
|
535
|
-
if [ -n "${PROXY_PID:-}" ]; then
|
|
536
|
-
echo "Stopping proxy server (PID: $PROXY_PID)..."
|
|
537
|
-
kill -TERM "$PROXY_PID" 2>/dev/null
|
|
538
|
-
wait "$PROXY_PID" 2>/dev/null || true
|
|
539
|
-
echo "Proxy server stopped."
|
|
540
|
-
fi
|
|
541
|
-
|
|
542
|
-
# 切换到项目根目录
|
|
543
|
-
# shellcheck disable=SC2164
|
|
544
|
-
cd "$PROJECT_ROOT"
|
|
545
|
-
|
|
546
|
-
# 清理 npmRegistryServer 配置
|
|
547
|
-
yarn config unset npmRegistryServer 2>/dev/null || true
|
|
548
|
-
echo "Cleared npmRegistryServer configuration"
|
|
549
|
-
|
|
550
|
-
# 根据退出状态退出
|
|
551
|
-
exit "$exit_code"
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
# 注册信号处理
|
|
555
|
-
trap 'cleanup 1' SIGINT SIGTERM EXIT # 异常退出时调用 cleanup,退出码为 1
|
|
556
|
-
|
|
557
|
-
# 切换到项目根目录
|
|
558
|
-
# shellcheck disable=SC2164
|
|
559
|
-
cd "$PROJECT_ROOT"
|
|
560
|
-
|
|
561
|
-
# 使用 yarn dlx 直接运行 registry-proxy,可通过环境变量指定registry-proxy版本号,默认是latest,registry-proxy将会被放入后台运行,并在安装结束后自动退出。
|
|
562
|
-
REGISTRY_PROXY_VERSION="${REGISTRY_PROXY_VERSION:-latest}"
|
|
563
|
-
echo "Starting registry-proxy@$REGISTRY_PROXY_VERSION in the background (logs will be displayed below)..."
|
|
564
|
-
# 下载registry-proxy临时可执行程序并运行 因yarn可能会缓存tarball url的缘故(yarn.lock内<package>.resolution值),这里不得已只能写死本地代理端口地址,以便无论是从缓存获取tarball url还是从代理服务提供的元数据获取tarball url地址都能成功下载tarball文件
|
|
565
|
-
# 但是注意 这个端口不能暴露到外部使用,只允许本地使用,避免不必要的安全隐患 事实上registry-proxy server也是只监听着::1本机端口的。
|
|
566
|
-
yarn dlx com.jimuwd.xian.registry-proxy@"$REGISTRY_PROXY_VERSION" .registry-proxy.yml .yarnrc.yml ~/.yarnrc.yml 40061 &
|
|
567
|
-
PROXY_PID=$!
|
|
568
|
-
|
|
569
|
-
# 等待代理服务器启动并写入端口,最多 30 秒
|
|
570
|
-
echo "Waiting for proxy server to start (up to 30 seconds)..."
|
|
571
|
-
PORT_FILE="$PROJECT_ROOT/.registry-proxy-port"
|
|
572
|
-
# shellcheck disable=SC2034
|
|
573
|
-
for i in {1..300}; do # 300 次循环,每次 0.1 秒,总共 30 秒
|
|
574
|
-
if [ -f "$PORT_FILE" ]; then
|
|
575
|
-
PROXY_PORT=$(cat "$PORT_FILE")
|
|
576
|
-
if [ -z "$PROXY_PORT" ]; then
|
|
577
|
-
echo "Error: Port file $PORT_FILE is empty"
|
|
578
|
-
cleanup 1
|
|
579
|
-
fi
|
|
580
|
-
if nc -z localhost "$PROXY_PORT" 2>/dev/null; then
|
|
581
|
-
echo "Proxy server is ready on port $PROXY_PORT!"
|
|
582
|
-
break
|
|
583
|
-
else
|
|
584
|
-
# 检查端口是否被占用
|
|
585
|
-
if netstat -tuln 2>/dev/null | grep -q ":$PROXY_PORT "; then
|
|
586
|
-
echo "Error: Port $PROXY_PORT is already in use by another process"
|
|
587
|
-
cleanup 1
|
|
588
|
-
fi
|
|
589
|
-
fi
|
|
590
|
-
fi
|
|
591
|
-
sleep 0.1
|
|
592
|
-
done
|
|
593
|
-
|
|
594
|
-
# 检查是否成功启动
|
|
595
|
-
if [ -z "${PROXY_PORT:-}" ] || ! nc -z localhost "$PROXY_PORT" 2>/dev/null; then
|
|
596
|
-
echo "Error: Proxy server failed to start after 30 seconds"
|
|
597
|
-
echo "Please check the registry-proxy logs above for more details."
|
|
598
|
-
cleanup 1
|
|
599
|
-
fi
|
|
600
|
-
|
|
601
|
-
# 动态设置 npmRegistryServer 为代理地址 注意:yarn对“localhost”域名不友好,请直接使用 [::1]
|
|
602
|
-
yarn config set npmRegistryServer "http://[::1]:$PROXY_PORT"
|
|
603
|
-
echo "Set npmRegistryServer to http://[::1]:$PROXY_PORT"
|
|
604
|
-
|
|
605
|
-
# 使用动态代理端口运行 yarn install,并捕获错误
|
|
606
|
-
if ! yarn install; then
|
|
607
|
-
echo "Error: yarn install failed"
|
|
608
|
-
cleanup 1
|
|
609
|
-
fi
|
|
610
|
-
|
|
611
|
-
# 正常执行完成,调用 cleanup 并传入退出码 0
|
|
612
|
-
cleanup 0
|
|
613
|
-
```
|
|
614
|
-
|
|
615
|
-
### 5. Set Script Permissions
|
|
616
|
-
|
|
617
|
-
Ensure the script is executable and commit the permission to version control:
|
|
618
|
-
|
|
619
|
-
```bash
|
|
620
|
-
chmod +x scripts/install-from-proxy-registries.sh
|
|
621
|
-
git add scripts/install-from-proxy-registries.sh
|
|
622
|
-
git commit -m "Add install script with executable permission"
|
|
332
|
+
use below cmd to automate the proxy setup and dependency installation.
|
|
333
|
+
```shell
|
|
334
|
+
yarn dlx -p com.jimuwd.xian.registry-proxy yarn-install
|
|
623
335
|
```
|
|
624
336
|
|
|
625
|
-
###
|
|
337
|
+
### 5. Integrate with Yarn
|
|
626
338
|
|
|
627
339
|
Update your `package.json` to run the script automatically when `yarn` is executed:
|
|
628
340
|
|
|
629
341
|
```json
|
|
630
342
|
{
|
|
631
343
|
"scripts": {
|
|
632
|
-
"preinstall": "
|
|
344
|
+
"preinstall": "yarn dlx -p com.jimuwd.xian.registry-proxy yarn-install",
|
|
633
345
|
"install": "echo 'Custom install script is running via preinstall, skipping default install.'"
|
|
634
346
|
}
|
|
635
347
|
}
|
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
import fs from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { execa } from 'execa';
|
|
6
|
-
import net from 'node:net';
|
|
7
|
-
import { fileURLToPath } from 'node:url';
|
|
8
6
|
import findProjectRoot from "../utils/findProjectRoot.js";
|
|
7
|
+
import { isPortConnectable } from "../utils/portTester.js";
|
|
9
8
|
// Constants
|
|
10
9
|
const REGISTRY_PROXY_VERSION = process.env.REGISTRY_PROXY_VERSION || 'latest';
|
|
11
10
|
const LOCK_FILE_NAME = '.registry-proxy-install.lock';
|
|
@@ -16,18 +15,6 @@ const CHECK_INTERVAL_MS = 100; // 0.1 seconds
|
|
|
16
15
|
let proxyProcess = null;
|
|
17
16
|
let cleanupHandlers = [];
|
|
18
17
|
let signalHandlers = [];
|
|
19
|
-
// Helper functions
|
|
20
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
-
async function isPortAvailable(port) {
|
|
22
|
-
return new Promise(resolve => {
|
|
23
|
-
const server = net.createServer();
|
|
24
|
-
server.unref();
|
|
25
|
-
server.on('error', () => resolve(false));
|
|
26
|
-
server.listen({ port }, () => {
|
|
27
|
-
server.close(() => resolve(true));
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
18
|
async function waitForFile(filePath, timeoutMs) {
|
|
32
19
|
const startTime = Date.now();
|
|
33
20
|
while (Date.now() - startTime < timeoutMs) {
|
|
@@ -53,17 +40,6 @@ async function readPortFile(filePath) {
|
|
|
53
40
|
}
|
|
54
41
|
return port;
|
|
55
42
|
}
|
|
56
|
-
async function checkPortListening(port) {
|
|
57
|
-
return new Promise(resolve => {
|
|
58
|
-
const socket = new net.Socket();
|
|
59
|
-
socket.on('error', () => resolve(false));
|
|
60
|
-
socket.on('connect', () => {
|
|
61
|
-
socket.destroy();
|
|
62
|
-
resolve(true);
|
|
63
|
-
});
|
|
64
|
-
socket.connect({ port, host: '::1' });
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
43
|
// Cleanup management
|
|
68
44
|
async function cleanup(exitCode = 1) {
|
|
69
45
|
// Run all cleanup handlers in reverse order
|
|
@@ -113,8 +89,7 @@ async function main() {
|
|
|
113
89
|
// Start registry proxy
|
|
114
90
|
console.log(`Starting registry-proxy@${REGISTRY_PROXY_VERSION} in the background...`);
|
|
115
91
|
proxyProcess = execa('yarn', [
|
|
116
|
-
'dlx',
|
|
117
|
-
`com.jimuwd.xian.registry-proxy@${REGISTRY_PROXY_VERSION}`,
|
|
92
|
+
'dlx', '-p', `com.jimuwd.xian.registry-proxy@${REGISTRY_PROXY_VERSION}`,
|
|
118
93
|
'registry-proxy',
|
|
119
94
|
'.registry-proxy.yml',
|
|
120
95
|
'.yarnrc.yml',
|
|
@@ -144,15 +119,10 @@ async function main() {
|
|
|
144
119
|
throw new Error(`Proxy server failed to create port file after ${MAX_WAIT_TIME_MS / 1000} seconds`);
|
|
145
120
|
}
|
|
146
121
|
const PROXY_PORT = await readPortFile(PORT_FILE);
|
|
147
|
-
const
|
|
148
|
-
if (!
|
|
149
|
-
throw new Error(`Port ${PROXY_PORT} is already in use by another process`);
|
|
150
|
-
}
|
|
151
|
-
const isListening = await checkPortListening(PROXY_PORT);
|
|
152
|
-
if (!isListening) {
|
|
122
|
+
const portConnectable = await isPortConnectable(PROXY_PORT);
|
|
123
|
+
if (!portConnectable) {
|
|
153
124
|
throw new Error(`Proxy server not listening on port ${PROXY_PORT}`);
|
|
154
125
|
}
|
|
155
|
-
console.log(`Proxy server is ready on port ${PROXY_PORT}!`);
|
|
156
126
|
// Configure yarn
|
|
157
127
|
await execa('yarn', ['config', 'set', 'npmRegistryServer', `http://[::1]:${PROXY_PORT}`]);
|
|
158
128
|
console.log(`Set npmRegistryServer to http://[::1]:${PROXY_PORT}`);
|
|
@@ -180,23 +150,26 @@ async function main() {
|
|
|
180
150
|
await cleanup(1);
|
|
181
151
|
}
|
|
182
152
|
}
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
153
|
+
// 当前模块是否是直接运行的入口文件,而不是被其他模块导入的
|
|
154
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
155
|
+
// Signal handling
|
|
156
|
+
['SIGINT', 'SIGTERM', 'SIGHUP'].forEach(signal => {
|
|
157
|
+
process.on(signal, async () => {
|
|
158
|
+
console.log(`Received ${signal}, cleaning up...`);
|
|
159
|
+
for (const handler of signalHandlers) {
|
|
160
|
+
try {
|
|
161
|
+
await handler();
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
console.error('Signal handler error:', err);
|
|
165
|
+
}
|
|
193
166
|
}
|
|
194
|
-
|
|
195
|
-
|
|
167
|
+
await cleanup(1);
|
|
168
|
+
});
|
|
196
169
|
});
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
170
|
+
// Start the program
|
|
171
|
+
main().catch(err => {
|
|
172
|
+
console.error('Unhandled error:', err);
|
|
173
|
+
cleanup(1);
|
|
174
|
+
});
|
|
175
|
+
}
|
package/dist/server/index.js
CHANGED
|
@@ -160,7 +160,7 @@ async function fetchFromRegistry(registry, targetUrl, reqFromDownstreamClient, l
|
|
|
160
160
|
limiter.release();
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
|
-
async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDownstreamClient, upstreamResponse, reqFromDownstreamClient, proxyInfo,
|
|
163
|
+
async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDownstreamClient, upstreamResponse, reqFromDownstreamClient, proxyInfo, _proxyPort, registryInfos) {
|
|
164
164
|
logger.info(`Writing upstream registry server ${registryInfo.normalizedRegistryUrl}'s ${upstreamResponse.status}${upstreamResponse.statusText ? (' "' + upstreamResponse.statusText + '"') : ''} response to downstream client.`);
|
|
165
165
|
if (!upstreamResponse.ok)
|
|
166
166
|
throw new Error("Only 2xx upstream response is supported");
|
|
@@ -174,7 +174,7 @@ async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDow
|
|
|
174
174
|
const data = await upstreamResponse.json();
|
|
175
175
|
if (data.versions) { // 处理node依赖包元数据
|
|
176
176
|
logger.info("Write package meta data application/json response from upstream to downstream", targetUrl);
|
|
177
|
-
const host = reqFromDownstreamClient.headers.host /*|| `[::1]:${
|
|
177
|
+
const host = reqFromDownstreamClient.headers.host /*|| `[::1]:${_proxyPort}`*/;
|
|
178
178
|
const baseUrl = `${proxyInfo.https ? 'https' : 'http'}://${host}${proxyInfo.basePath === '/' ? '' : proxyInfo.basePath}`;
|
|
179
179
|
for (const versionKey in data.versions) {
|
|
180
180
|
const packageVersion = data.versions[versionKey];
|
|
@@ -190,10 +190,11 @@ async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDow
|
|
|
190
190
|
logger.info("Write none meta data application/json response from upstream to downstream", targetUrl);
|
|
191
191
|
}
|
|
192
192
|
const bodyData = JSON.stringify(data);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
resToDownstreamClient.
|
|
193
|
+
// remove transfer-encoding = chunked header if present, because the content-length is fixed.
|
|
194
|
+
resToDownstreamClient.removeHeader('transfer-encoding');
|
|
195
|
+
/* 默认是 connection: keep-alive 和 keep-alive: timeout=5
|
|
196
|
+
resToDownstreamClient.setHeader('connection', 'close');
|
|
197
|
+
resToDownstreamClient.removeHeader('Keep-Alive');*/
|
|
197
198
|
resToDownstreamClient.setHeader('content-type', contentType);
|
|
198
199
|
resToDownstreamClient.setHeader('content-length', Buffer.byteLength(bodyData));
|
|
199
200
|
logger.info(`Response to downstream client headers`, JSON.stringify(resToDownstreamClient.getHeaders()), targetUrl);
|
|
@@ -207,41 +208,33 @@ async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDow
|
|
|
207
208
|
}
|
|
208
209
|
else {
|
|
209
210
|
// write back to client
|
|
210
|
-
// 准备通用响应头信息
|
|
211
211
|
const safeHeaders = new Map();
|
|
212
|
-
safeHeaders.set("Content-Type", contentType);
|
|
213
212
|
// 复制所有可能需要的头信息(不包含安全相关的敏感头信息,如access-control-allow-origin、set-cookie、server、strict-transport-security等,这意味着代理服务器向下游客户端屏蔽了这些认证等安全数据)
|
|
214
213
|
// 也不能包含cf-cache-status、cf-ray(Cloudflare 特有字段)可能干扰客户端解析。
|
|
215
|
-
const headersToCopy = ['cache-control', '
|
|
214
|
+
const headersToCopy = ['cache-control', 'etag', 'last-modified', 'vary', 'connection', 'keep-alive', 'content-encoding'];
|
|
216
215
|
headersToCopy.forEach(header => {
|
|
217
216
|
const value = upstreamResponse.headers.get(header);
|
|
218
217
|
if (value)
|
|
219
218
|
safeHeaders.set(header, value);
|
|
220
219
|
});
|
|
221
|
-
//
|
|
222
|
-
// 这个坑害我浪费很久事件来调试!
|
|
220
|
+
// 强制设置二进制流传输的响应头,需要使用 ServerResponse.setHeaders(safeHeaders)来覆盖现有headers而不是ServerResponse.writeHead(status,headers)来合并headers,避免不必要的麻烦
|
|
223
221
|
resToDownstreamClient.setHeaders(safeHeaders);
|
|
224
|
-
|
|
225
|
-
resToDownstreamClient.removeHeader('content-encoding');
|
|
226
|
-
resToDownstreamClient.removeHeader('Transfer-Encoding');
|
|
222
|
+
resToDownstreamClient.setHeader("content-type", contentType);
|
|
227
223
|
resToDownstreamClient.removeHeader('content-length');
|
|
228
224
|
resToDownstreamClient.setHeader('transfer-encoding', 'chunked');
|
|
229
|
-
|
|
230
|
-
resToDownstreamClient.removeHeader('connection');
|
|
225
|
+
/* 这两个header使用upstream透传过来的header即可
|
|
231
226
|
resToDownstreamClient.setHeader('connection', 'close');
|
|
232
|
-
resToDownstreamClient.removeHeader('Keep-Alive')
|
|
233
|
-
resToDownstreamClient.removeHeader('content-type');
|
|
234
|
-
resToDownstreamClient.setHeader('content-type', contentType);
|
|
227
|
+
resToDownstreamClient.removeHeader('Keep-Alive');*/
|
|
235
228
|
logger.info(`Response to downstream client headers`, JSON.stringify(resToDownstreamClient.getHeaders()), targetUrl);
|
|
236
229
|
// 不再writeHead()而是设置状态码,然后执行pipe操作
|
|
237
230
|
resToDownstreamClient.statusCode = upstreamResponse.status;
|
|
238
231
|
resToDownstreamClient.statusMessage = upstreamResponse.statusText;
|
|
239
|
-
// resToDownstreamClient.writeHead(upstreamResponse.status);
|
|
240
232
|
// stop pipe when req from client is closed accidentally.
|
|
233
|
+
// this is good when proxying big stream from upstream to downstream.
|
|
241
234
|
const cleanup = () => {
|
|
242
235
|
reqFromDownstreamClient.off('close', cleanup);
|
|
243
236
|
logger.info(`Req from downstream client is closed, stop pipe from upstream ${targetUrl} to downstream client.`);
|
|
244
|
-
|
|
237
|
+
upstreamResponse.body?.unpipe();
|
|
245
238
|
};
|
|
246
239
|
reqFromDownstreamClient.on('close', cleanup);
|
|
247
240
|
reqFromDownstreamClient.on('end', () => logger.info("Req from downstream client ends."));
|
|
@@ -253,20 +246,15 @@ async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDow
|
|
|
253
246
|
};
|
|
254
247
|
resToDownstreamClient.on('close', cleanup0);
|
|
255
248
|
// write back body data (chunked probably)
|
|
256
|
-
// pipe upstream body to downstream
|
|
249
|
+
// pipe upstream body-stream to downstream stream and automatically ends the stream to downstream when upstream stream is ended.
|
|
257
250
|
upstreamResponse.body.pipe(resToDownstreamClient, { end: true });
|
|
258
251
|
upstreamResponse.body
|
|
259
252
|
.on('data', (chunk) => logger.debug(`Chunk transferred from ${targetUrl} to downstream client size=${chunk.length}`))
|
|
260
|
-
.on('end', () => {
|
|
261
|
-
logger.info(`Upstream server ${targetUrl} response.body ended.`);
|
|
262
|
-
// resToDownstreamClient.end();
|
|
263
|
-
})
|
|
253
|
+
.on('end', () => logger.info(`Upstream server ${targetUrl} response.body ended.`))
|
|
264
254
|
// connection will be closed automatically when all chunk data is transferred (after stream ends).
|
|
265
|
-
.on('close', () => {
|
|
266
|
-
logger.info(`Upstream server ${targetUrl} closed connection.`);
|
|
267
|
-
})
|
|
255
|
+
.on('close', () => logger.info(`Upstream server ${targetUrl} closed connection.`))
|
|
268
256
|
.on('error', (err) => {
|
|
269
|
-
const errMsg = `Stream error: ${err.message}`;
|
|
257
|
+
const errMsg = `Stream error between upstream and registry-proxy server: ${err.message}. Upstream url is ${targetUrl}`;
|
|
270
258
|
logger.error(errMsg);
|
|
271
259
|
resToDownstreamClient.destroy(new Error(errMsg, { cause: err, }));
|
|
272
260
|
reqFromDownstreamClient.destroy(new Error(errMsg, { cause: err, }));
|
|
@@ -274,15 +262,15 @@ async function writeResponseToDownstreamClient(registryInfo, targetUrl, resToDow
|
|
|
274
262
|
}
|
|
275
263
|
}
|
|
276
264
|
else {
|
|
277
|
-
logger.warn(`Write unsupported content-type=${contentType} response from upstream to downstream ${targetUrl}`);
|
|
265
|
+
logger.warn(`Write unsupported content-type=${contentType} response from upstream to downstream. Upstream url is ${targetUrl}`);
|
|
278
266
|
const bodyData = await upstreamResponse.text();
|
|
279
|
-
resToDownstreamClient.removeHeader('
|
|
267
|
+
resToDownstreamClient.removeHeader('transfer-encoding');
|
|
280
268
|
// 默认是 connection: keep-alive 和 keep-alive: timeout=5,这里直接给它咔嚓掉
|
|
281
|
-
resToDownstreamClient.setHeader('
|
|
269
|
+
resToDownstreamClient.setHeader('connection', 'close');
|
|
282
270
|
resToDownstreamClient.removeHeader('Keep-Alive');
|
|
283
271
|
resToDownstreamClient.setHeader('content-type', contentType);
|
|
284
272
|
resToDownstreamClient.setHeader('content-length', Buffer.byteLength(bodyData));
|
|
285
|
-
logger.info(`Response to downstream client headers`, JSON.stringify(resToDownstreamClient.getHeaders()), targetUrl);
|
|
273
|
+
logger.info(`Response to downstream client headers`, JSON.stringify(resToDownstreamClient.getHeaders()), `Upstream url is ${targetUrl}`);
|
|
286
274
|
resToDownstreamClient.writeHead(upstreamResponse.status).end(bodyData);
|
|
287
275
|
}
|
|
288
276
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function isPortFree(port: number): Promise<boolean>;
|
|
2
|
+
/**
|
|
3
|
+
* 检查指定端口是否可连接(有服务正在监听)
|
|
4
|
+
* @param port 要检查的端口号
|
|
5
|
+
* @param host 目标主机(默认本地IPv6 ::1)
|
|
6
|
+
* @param timeout 超时时间(毫秒,默认1000ms)
|
|
7
|
+
*/
|
|
8
|
+
export declare function isPortConnectable(port: number, host?: string, timeout?: number): Promise<boolean>;
|
|
9
|
+
/**
|
|
10
|
+
* 简单的检查,若需要更高级的检查,请使用{@link isPortConnectable}
|
|
11
|
+
*/
|
|
12
|
+
declare function checkPortListening(port: number): Promise<boolean>;
|
|
13
|
+
declare const _default: {
|
|
14
|
+
isPortFree: typeof isPortFree;
|
|
15
|
+
isPortConnectable: typeof isPortConnectable;
|
|
16
|
+
checkPortListening: typeof checkPortListening;
|
|
17
|
+
};
|
|
18
|
+
export default _default;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import net from "node:net";
|
|
2
|
+
export async function isPortFree(port) {
|
|
3
|
+
return new Promise(resolve => {
|
|
4
|
+
const server = net.createServer();
|
|
5
|
+
server.unref();
|
|
6
|
+
server.on('error', () => resolve(false));
|
|
7
|
+
server.listen({ port }, () => {
|
|
8
|
+
server.close(() => resolve(true));
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 检查指定端口是否可连接(有服务正在监听)
|
|
14
|
+
* @param port 要检查的端口号
|
|
15
|
+
* @param host 目标主机(默认本地IPv6 ::1)
|
|
16
|
+
* @param timeout 超时时间(毫秒,默认1000ms)
|
|
17
|
+
*/
|
|
18
|
+
export async function isPortConnectable(port, host = '::1', timeout = 1000) {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
const socket = new net.Socket();
|
|
21
|
+
// 设置超时
|
|
22
|
+
socket.setTimeout(timeout);
|
|
23
|
+
socket.on('connect', () => {
|
|
24
|
+
socket.destroy();
|
|
25
|
+
resolve(true);
|
|
26
|
+
});
|
|
27
|
+
socket.on('timeout', () => {
|
|
28
|
+
socket.destroy();
|
|
29
|
+
resolve(false);
|
|
30
|
+
});
|
|
31
|
+
socket.on('error', () => {
|
|
32
|
+
socket.destroy();
|
|
33
|
+
resolve(false);
|
|
34
|
+
});
|
|
35
|
+
socket.connect(port, host);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 简单的检查,若需要更高级的检查,请使用{@link isPortConnectable}
|
|
40
|
+
*/
|
|
41
|
+
async function checkPortListening(port) {
|
|
42
|
+
return new Promise(resolve => {
|
|
43
|
+
const socket = new net.Socket();
|
|
44
|
+
socket.on('error', () => resolve(false));
|
|
45
|
+
socket.on('connect', () => {
|
|
46
|
+
socket.destroy();
|
|
47
|
+
resolve(true);
|
|
48
|
+
});
|
|
49
|
+
socket.connect({ port, host: '::1' });
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export default { isPortFree, isPortConnectable, checkPortListening };
|
package/package.json
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "com.jimuwd.xian.registry-proxy",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.134",
|
|
4
4
|
"description": "A lightweight npm registry proxy with fallback support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/server/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"registry-proxy": "dist/server/index.js",
|
|
9
|
-
"yarn-install": "dist/client/yarn-install.js"
|
|
9
|
+
"yarn-install": "dist/client/yarn-install.js",
|
|
10
|
+
"yarn-install-bash": "src/client/yarn-install.sh"
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
12
|
-
"dist"
|
|
13
|
+
"dist",
|
|
14
|
+
"src/client/yarn-install.sh"
|
|
13
15
|
],
|
|
14
16
|
"scripts": {
|
|
15
17
|
"build": "tsc",
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# 启用严格模式,但移除 set -e,手动处理错误
|
|
4
|
+
set -u # 未定义变量时退出
|
|
5
|
+
set -o pipefail # 管道中任一命令失败时退出
|
|
6
|
+
|
|
7
|
+
# 动态确定项目根目录(假设 package.json 所在目录为根目录)
|
|
8
|
+
find_project_root() {
|
|
9
|
+
local dir="$PWD"
|
|
10
|
+
while [ "$dir" != "/" ]; do
|
|
11
|
+
if [ -f "$dir/package.json" ]; then
|
|
12
|
+
echo "$dir"
|
|
13
|
+
return 0
|
|
14
|
+
fi
|
|
15
|
+
dir=$(dirname "$dir")
|
|
16
|
+
done
|
|
17
|
+
echo "Error: Could not find project root (package.json not found)" >&2
|
|
18
|
+
exit 1
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
PROJECT_ROOT=$(find_project_root)
|
|
22
|
+
|
|
23
|
+
# 定义锁文件和端口文件路径(固定在项目根目录)
|
|
24
|
+
LOCK_FILE="$PROJECT_ROOT/.registry-proxy-install.lock"
|
|
25
|
+
|
|
26
|
+
# 检查是否已经在运行(通过锁文件)
|
|
27
|
+
if [ -f "$LOCK_FILE" ]; then
|
|
28
|
+
echo "Custom install script is already running (lock file $LOCK_FILE exists)."
|
|
29
|
+
echo "If this is unexpected, please remove $LOCK_FILE and try again."
|
|
30
|
+
exit 0 # 内层脚本直接退出,表示任务已由外层脚本完成
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# 创建锁文件
|
|
34
|
+
touch "$LOCK_FILE"
|
|
35
|
+
|
|
36
|
+
# 清理函数,支持不同的退出状态
|
|
37
|
+
# 参数 $1:退出状态(0 表示正常退出,1 表示异常退出)
|
|
38
|
+
cleanup() {
|
|
39
|
+
local exit_code=${1:-1} # 默认退出码为 1(异常退出)
|
|
40
|
+
|
|
41
|
+
# 显式清除 EXIT 信号的 trap,避免潜在的误解
|
|
42
|
+
trap - EXIT
|
|
43
|
+
|
|
44
|
+
if [ "$exit_code" -eq 0 ]; then
|
|
45
|
+
echo "Cleaning up after successful execution..."
|
|
46
|
+
else
|
|
47
|
+
echo "Caught interrupt signal or error, cleaning up..."
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# 清理临时文件
|
|
51
|
+
rm -f "$LOCK_FILE" 2>/dev/null
|
|
52
|
+
# PORT_FILE端口临时文件是registry-proxy服务器管理的文件这里不负责清理,服务器退出时会自动清理
|
|
53
|
+
#rm -f "$PORT_FILE" 2>/dev/null
|
|
54
|
+
|
|
55
|
+
# 停止代理服务器
|
|
56
|
+
if [ -n "${PROXY_PID:-}" ]; then
|
|
57
|
+
echo "Stopping proxy server (PID: $PROXY_PID)..."
|
|
58
|
+
kill -TERM "$PROXY_PID" 2>/dev/null
|
|
59
|
+
wait "$PROXY_PID" 2>/dev/null || true
|
|
60
|
+
echo "Proxy server stopped."
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# 切换到项目根目录
|
|
64
|
+
# shellcheck disable=SC2164
|
|
65
|
+
cd "$PROJECT_ROOT"
|
|
66
|
+
|
|
67
|
+
# 清理 npmRegistryServer 配置
|
|
68
|
+
yarn config unset npmRegistryServer 2>/dev/null || true
|
|
69
|
+
echo "Cleared npmRegistryServer configuration"
|
|
70
|
+
|
|
71
|
+
# 根据退出状态退出
|
|
72
|
+
exit "$exit_code"
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# 注册信号处理
|
|
76
|
+
trap 'cleanup 1' SIGINT SIGTERM EXIT # 异常退出时调用 cleanup,退出码为 1
|
|
77
|
+
|
|
78
|
+
# 切换到项目根目录
|
|
79
|
+
# shellcheck disable=SC2164
|
|
80
|
+
cd "$PROJECT_ROOT"
|
|
81
|
+
|
|
82
|
+
# 使用 yarn dlx 直接运行 registry-proxy,可通过环境变量指定registry-proxy版本号,默认是latest,registry-proxy将会被放入后台运行,并在安装结束后自动退出。
|
|
83
|
+
REGISTRY_PROXY_VERSION="${REGISTRY_PROXY_VERSION:-latest}"
|
|
84
|
+
echo "Starting registry-proxy@$REGISTRY_PROXY_VERSION in the background (logs will be displayed below)..."
|
|
85
|
+
# 下载registry-proxy临时可执行程序并运行 因yarn可能会缓存tarball url的缘故(yarn.lock内<package>.resolution值),这里不得已只能写死本地代理端口地址,以便无论是从缓存获取tarball url还是从代理服务提供的元数据获取tarball url地址都能成功下载tarball文件
|
|
86
|
+
# 但是注意 这个端口不能暴露到外部使用,只允许本地使用,避免不必要的安全隐患 事实上registry-proxy server也是只监听着::1本机端口的。
|
|
87
|
+
yarn dlx -p com.jimuwd.xian.registry-proxy@"$REGISTRY_PROXY_VERSION" registry-proxy .registry-proxy.yml .yarnrc.yml ~/.yarnrc.yml 40061 &
|
|
88
|
+
PROXY_PID=$!
|
|
89
|
+
|
|
90
|
+
# 等待代理服务器启动并写入端口,最多 30 秒
|
|
91
|
+
echo "Waiting for proxy server to start (up to 30 seconds)..."
|
|
92
|
+
PORT_FILE="$PROJECT_ROOT/.registry-proxy-port"
|
|
93
|
+
# shellcheck disable=SC2034
|
|
94
|
+
for i in {1..300}; do # 300 次循环,每次 0.1 秒,总共 30 秒
|
|
95
|
+
if [ -f "$PORT_FILE" ]; then
|
|
96
|
+
PROXY_PORT=$(cat "$PORT_FILE")
|
|
97
|
+
if [ -z "$PROXY_PORT" ]; then
|
|
98
|
+
echo "Error: Port file $PORT_FILE is empty"
|
|
99
|
+
cleanup 1
|
|
100
|
+
fi
|
|
101
|
+
if nc -z localhost "$PROXY_PORT" 2>/dev/null; then
|
|
102
|
+
echo "Proxy server is ready on port $PROXY_PORT!"
|
|
103
|
+
break
|
|
104
|
+
else
|
|
105
|
+
# 检查端口是否被占用
|
|
106
|
+
if netstat -tuln 2>/dev/null | grep -q ":$PROXY_PORT "; then
|
|
107
|
+
echo "Error: Port $PROXY_PORT is already in use by another process"
|
|
108
|
+
cleanup 1
|
|
109
|
+
fi
|
|
110
|
+
fi
|
|
111
|
+
fi
|
|
112
|
+
sleep 0.1
|
|
113
|
+
done
|
|
114
|
+
|
|
115
|
+
# 检查是否成功启动
|
|
116
|
+
if [ -z "${PROXY_PORT:-}" ] || ! nc -z localhost "$PROXY_PORT" 2>/dev/null; then
|
|
117
|
+
echo "Error: Proxy server failed to start after 30 seconds"
|
|
118
|
+
echo "Please check the registry-proxy logs above for more details."
|
|
119
|
+
cleanup 1
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# 动态设置 npmRegistryServer 为代理地址 注意:yarn对“localhost”域名不友好,请直接使用 [::1]
|
|
123
|
+
yarn config set npmRegistryServer "http://[::1]:$PROXY_PORT"
|
|
124
|
+
echo "Set npmRegistryServer to http://[::1]:$PROXY_PORT"
|
|
125
|
+
|
|
126
|
+
# 使用动态代理端口运行 yarn install,并捕获错误
|
|
127
|
+
if ! yarn install; then
|
|
128
|
+
echo "Error: yarn install failed"
|
|
129
|
+
cleanup 1
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# 正常执行完成,调用 cleanup 并传入退出码 0
|
|
133
|
+
cleanup 0
|