ccman 0.0.4 → 1.0.1
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/shell/ShellManager.d.ts +8 -0
- package/dist/shell/ShellManager.d.ts.map +1 -1
- package/dist/shell/ShellManager.js +132 -33
- package/dist/shell/ShellManager.js.map +1 -1
- package/docs/release-guide.md +68 -10
- package/docs/scripts-guide.md +221 -0
- package/package.json +5 -2
- package/release-temp/package.json +5 -2
- package/scripts/modules/check-uncommitted.sh +109 -0
- package/scripts/modules/create-tag.sh +236 -0
- package/scripts/modules/monitor-release.sh +268 -0
- package/scripts/modules/version-bump.sh +265 -0
- package/scripts/smart-release-v3.sh +289 -0
- package/scripts/smart-release.sh +322 -0
- package/src/shell/ShellManager.ts +143 -33
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# CCM Enhanced Release Script
|
|
4
|
+
# 增强版快速发布脚本 - 支持智能代码处理和发布状态监控
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
# 颜色定义
|
|
9
|
+
GREEN='\033[0;32m'
|
|
10
|
+
YELLOW='\033[1;33m'
|
|
11
|
+
RED='\033[0;31m'
|
|
12
|
+
BLUE='\033[0;34m'
|
|
13
|
+
CYAN='\033[0;36m'
|
|
14
|
+
NC='\033[0m'
|
|
15
|
+
|
|
16
|
+
print_success() { echo -e "${GREEN}✅ $1${NC}"; }
|
|
17
|
+
print_warning() { echo -e "${YELLOW}⚠️ $1${NC}"; }
|
|
18
|
+
print_error() { echo -e "${RED}❌ $1${NC}"; exit 1; }
|
|
19
|
+
print_info() { echo -e "${BLUE}ℹ️ $1${NC}"; }
|
|
20
|
+
print_check() { echo -e "${CYAN}🔍 $1${NC}"; }
|
|
21
|
+
|
|
22
|
+
# 检查基本环境
|
|
23
|
+
check_environment() {
|
|
24
|
+
[ ! -f "package.json" ] && print_error "package.json 未找到"
|
|
25
|
+
[ ! -d ".git" ] && print_error "不在 Git 仓库中"
|
|
26
|
+
|
|
27
|
+
if ! command -v curl &> /dev/null; then
|
|
28
|
+
print_warning "curl 未安装,将跳过发布状态检查"
|
|
29
|
+
SKIP_STATUS_CHECK=true
|
|
30
|
+
fi
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# 智能处理未提交代码
|
|
34
|
+
handle_uncommitted_changes() {
|
|
35
|
+
print_info "检查工作目录状态..."
|
|
36
|
+
|
|
37
|
+
if git diff-index --quiet HEAD --; then
|
|
38
|
+
print_success "工作目录干净"
|
|
39
|
+
return 0
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
print_warning "发现未提交的更改:"
|
|
43
|
+
git status --short
|
|
44
|
+
echo ""
|
|
45
|
+
|
|
46
|
+
echo "请选择处理方式:"
|
|
47
|
+
echo "1) 提交所有更改并继续发布"
|
|
48
|
+
echo "2) 暂存所有更改并继续发布"
|
|
49
|
+
echo "3) 取消发布,手动处理"
|
|
50
|
+
echo ""
|
|
51
|
+
|
|
52
|
+
read -p "请选择 (1-3): " choice
|
|
53
|
+
|
|
54
|
+
case $choice in
|
|
55
|
+
1)
|
|
56
|
+
read -p "输入提交信息: " commit_msg
|
|
57
|
+
[ -z "$commit_msg" ] && commit_msg="chore: 发布前提交未完成更改"
|
|
58
|
+
|
|
59
|
+
git add .
|
|
60
|
+
git commit -m "$commit_msg"
|
|
61
|
+
print_success "所有更改已提交"
|
|
62
|
+
;;
|
|
63
|
+
2)
|
|
64
|
+
git add .
|
|
65
|
+
print_success "所有更改已暂存"
|
|
66
|
+
;;
|
|
67
|
+
3)
|
|
68
|
+
print_info "发布已取消,请手动处理更改后重新运行"
|
|
69
|
+
exit 0
|
|
70
|
+
;;
|
|
71
|
+
*)
|
|
72
|
+
print_error "无效选择"
|
|
73
|
+
;;
|
|
74
|
+
esac
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# 获取和选择版本
|
|
78
|
+
get_and_select_version() {
|
|
79
|
+
current_version=$(node -p "require('./package.json').version")
|
|
80
|
+
version_type=${1:-""}
|
|
81
|
+
|
|
82
|
+
echo "🚀 CCM 智能发布"
|
|
83
|
+
echo "当前版本: $current_version"
|
|
84
|
+
echo ""
|
|
85
|
+
|
|
86
|
+
if [ -z "$version_type" ]; then
|
|
87
|
+
print_info "选择版本升级类型:"
|
|
88
|
+
echo "1) patch (修订版本): $current_version → $(pnpm version patch --dry-run 2>/dev/null | cut -d'v' -f2 || echo '计算中...')"
|
|
89
|
+
echo "2) minor (次版本): $current_version → $(pnpm version minor --dry-run 2>/dev/null | cut -d'v' -f2 || echo '计算中...')"
|
|
90
|
+
echo "3) major (主版本): $current_version → $(pnpm version major --dry-run 2>/dev/null | cut -d'v' -f2 || echo '计算中...')"
|
|
91
|
+
echo "4) 跳过版本升级,仅重新发布当前版本"
|
|
92
|
+
echo ""
|
|
93
|
+
|
|
94
|
+
read -p "请选择 (1-4, 回车默认选择 patch): " choice
|
|
95
|
+
|
|
96
|
+
case ${choice:-1} in
|
|
97
|
+
1|"") version_type="patch" ;;
|
|
98
|
+
2) version_type="minor" ;;
|
|
99
|
+
3) version_type="major" ;;
|
|
100
|
+
4) version_type="skip" ;;
|
|
101
|
+
*) print_error "无效选择" ;;
|
|
102
|
+
esac
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
print_info "选择: $version_type"
|
|
106
|
+
|
|
107
|
+
if [ "$version_type" != "skip" ]; then
|
|
108
|
+
echo ""
|
|
109
|
+
read -p "确认升级版本? (y/N): " -n 1 -r
|
|
110
|
+
echo
|
|
111
|
+
[[ ! $REPLY =~ ^[Yy]$ ]] && { print_warning "取消发布"; exit 0; }
|
|
112
|
+
fi
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# 执行发布流程
|
|
116
|
+
execute_release() {
|
|
117
|
+
print_success "开始发布流程..."
|
|
118
|
+
|
|
119
|
+
# 1. 构建和测试
|
|
120
|
+
print_info "运行构建和代码检查..."
|
|
121
|
+
pnpm run build
|
|
122
|
+
pnpm run lint
|
|
123
|
+
|
|
124
|
+
# 2. 更新版本号
|
|
125
|
+
if [ "$version_type" != "skip" ]; then
|
|
126
|
+
print_info "更新版本号..."
|
|
127
|
+
new_version=$(pnpm version $version_type --no-git-tag-version)
|
|
128
|
+
new_version=${new_version#v}
|
|
129
|
+
print_success "版本已更新: $current_version → $new_version"
|
|
130
|
+
else
|
|
131
|
+
new_version=$current_version
|
|
132
|
+
print_info "跳过版本更新,使用当前版本: $new_version"
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# 3. 先创建tag,再提交(按你的需求顺序)
|
|
136
|
+
tag_name="v$new_version"
|
|
137
|
+
|
|
138
|
+
if [ "$version_type" != "skip" ]; then
|
|
139
|
+
print_info "创建标签: $tag_name"
|
|
140
|
+
git tag -a "$tag_name" -m "Release v$new_version
|
|
141
|
+
|
|
142
|
+
🚀 智能发布 $version_type 版本
|
|
143
|
+
⏰ $(date '+%Y-%m-%d %H:%M:%S')
|
|
144
|
+
|
|
145
|
+
🤖 Generated with [Claude Code](https://claude.ai/code)
|
|
146
|
+
|
|
147
|
+
Co-Authored-By: Claude <noreply@anthropic.com>"
|
|
148
|
+
|
|
149
|
+
print_info "提交版本更改..."
|
|
150
|
+
git add .
|
|
151
|
+
git commit -m "chore: 发布版本 v$new_version
|
|
152
|
+
|
|
153
|
+
🚀 智能发布 $version_type 版本,标签 $tag_name 已创建
|
|
154
|
+
⏰ $(date '+%Y-%m-%d %H:%M:%S')
|
|
155
|
+
|
|
156
|
+
🤖 Generated with [Claude Code](https://claude.ai/code)
|
|
157
|
+
|
|
158
|
+
Co-Authored-By: Claude <noreply@anthropic.com>"
|
|
159
|
+
|
|
160
|
+
print_success "标签 $tag_name 已创建,更改已提交"
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
# 4. 推送
|
|
164
|
+
print_info "推送到远程仓库..."
|
|
165
|
+
git push origin $(git branch --show-current)
|
|
166
|
+
git push origin "$tag_name"
|
|
167
|
+
|
|
168
|
+
print_success "版本 v$new_version 已推送,GitHub Actions 已触发"
|
|
169
|
+
|
|
170
|
+
# 输出监控链接(按你的需求)
|
|
171
|
+
print_info "📊 监控链接:"
|
|
172
|
+
echo " 🔗 GitHub Actions: https://github.com/2ue/ccm/actions"
|
|
173
|
+
echo " 🔗 NPM Registry: https://www.npmjs.com/package/ccman"
|
|
174
|
+
echo " 🔗 GitHub Releases: https://github.com/2ue/ccm/releases"
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
# 监控发布状态
|
|
178
|
+
monitor_release_status() {
|
|
179
|
+
if [ "$SKIP_STATUS_CHECK" = true ]; then
|
|
180
|
+
print_warning "跳过发布状态检查"
|
|
181
|
+
show_manual_links
|
|
182
|
+
return 0
|
|
183
|
+
fi
|
|
184
|
+
|
|
185
|
+
print_info "开始监控发布状态..."
|
|
186
|
+
echo ""
|
|
187
|
+
|
|
188
|
+
# GitHub Actions 状态检查
|
|
189
|
+
print_check "检查 GitHub Actions 状态..."
|
|
190
|
+
|
|
191
|
+
local max_attempts=30 # 最多检查5分钟
|
|
192
|
+
local attempt=0
|
|
193
|
+
local actions_success=false
|
|
194
|
+
|
|
195
|
+
while [ $attempt -lt $max_attempts ]; do
|
|
196
|
+
# 检查最新的 workflow run
|
|
197
|
+
local run_status=$(curl -s -H "Accept: application/vnd.github.v3+json" \
|
|
198
|
+
"https://api.github.com/repos/2ue/ccm/actions/runs?per_page=1" \
|
|
199
|
+
| grep -o '"status":"[^"]*"' | head -1 | cut -d'"' -f4 2>/dev/null || echo "unknown")
|
|
200
|
+
|
|
201
|
+
local run_conclusion=$(curl -s -H "Accept: application/vnd.github.v3+json" \
|
|
202
|
+
"https://api.github.com/repos/2ue/ccm/actions/runs?per_page=1" \
|
|
203
|
+
| grep -o '"conclusion":"[^"]*"' | head -1 | cut -d'"' -f4 2>/dev/null || echo "null")
|
|
204
|
+
|
|
205
|
+
case "$run_status" in
|
|
206
|
+
"completed")
|
|
207
|
+
if [ "$run_conclusion" = "success" ]; then
|
|
208
|
+
print_success "GitHub Actions 构建成功!"
|
|
209
|
+
actions_success=true
|
|
210
|
+
break
|
|
211
|
+
else
|
|
212
|
+
print_error "GitHub Actions 构建失败: $run_conclusion"
|
|
213
|
+
return 1
|
|
214
|
+
fi
|
|
215
|
+
;;
|
|
216
|
+
"in_progress"|"queued")
|
|
217
|
+
echo -ne "\r${CYAN}🔍 GitHub Actions 运行中... (${attempt}/${max_attempts})${NC}"
|
|
218
|
+
;;
|
|
219
|
+
*)
|
|
220
|
+
echo -ne "\r${YELLOW}⚠️ 等待 GitHub Actions 开始... (${attempt}/${max_attempts})${NC}"
|
|
221
|
+
;;
|
|
222
|
+
esac
|
|
223
|
+
|
|
224
|
+
sleep 10
|
|
225
|
+
((attempt++))
|
|
226
|
+
done
|
|
227
|
+
|
|
228
|
+
echo # 换行
|
|
229
|
+
|
|
230
|
+
if [ "$actions_success" != true ]; then
|
|
231
|
+
print_warning "GitHub Actions 检查超时,请手动确认"
|
|
232
|
+
show_manual_links
|
|
233
|
+
return 0
|
|
234
|
+
fi
|
|
235
|
+
|
|
236
|
+
# NPM 发布状态检查
|
|
237
|
+
print_check "检查 NPM 包发布状态..."
|
|
238
|
+
|
|
239
|
+
local npm_attempts=20
|
|
240
|
+
local npm_attempt=0
|
|
241
|
+
local npm_success=false
|
|
242
|
+
|
|
243
|
+
while [ $npm_attempt -lt $npm_attempts ]; do
|
|
244
|
+
local npm_response=$(curl -s "https://registry.npmjs.org/ccman" | grep -o "\"$new_version\"" 2>/dev/null || echo "")
|
|
245
|
+
|
|
246
|
+
if [ -n "$npm_response" ]; then
|
|
247
|
+
print_success "NPM 包 v$new_version 发布成功!"
|
|
248
|
+
npm_success=true
|
|
249
|
+
break
|
|
250
|
+
else
|
|
251
|
+
echo -ne "\r${CYAN}🔍 等待 NPM 包发布... (${npm_attempt}/${npm_attempts})${NC}"
|
|
252
|
+
sleep 15
|
|
253
|
+
((npm_attempt++))
|
|
254
|
+
fi
|
|
255
|
+
done
|
|
256
|
+
|
|
257
|
+
echo # 换行
|
|
258
|
+
|
|
259
|
+
if [ "$npm_success" != true ]; then
|
|
260
|
+
print_warning "NPM 包检查超时,可能仍在发布中"
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
# GitHub Release 检查
|
|
264
|
+
print_check "检查 GitHub Release..."
|
|
265
|
+
|
|
266
|
+
local release_response=$(curl -s "https://api.github.com/repos/2ue/ccm/releases/tags/v$new_version" \
|
|
267
|
+
| grep -o '"tag_name":"[^"]*"' 2>/dev/null || echo "")
|
|
268
|
+
|
|
269
|
+
if [ -n "$release_response" ]; then
|
|
270
|
+
print_success "GitHub Release v$new_version 创建成功!"
|
|
271
|
+
else
|
|
272
|
+
print_warning "GitHub Release 可能仍在创建中"
|
|
273
|
+
fi
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
# 显示手动检查链接
|
|
277
|
+
show_manual_links() {
|
|
278
|
+
print_info "请手动检查以下链接:"
|
|
279
|
+
echo " GitHub Actions: https://github.com/2ue/ccm/actions"
|
|
280
|
+
echo " GitHub Release: https://github.com/2ue/ccm/releases/tag/v$new_version"
|
|
281
|
+
echo " NPM 包: https://www.npmjs.com/package/ccman"
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
# 显示发布总结
|
|
285
|
+
show_release_summary() {
|
|
286
|
+
echo ""
|
|
287
|
+
print_success "🎉 发布流程完成!"
|
|
288
|
+
echo ""
|
|
289
|
+
print_info "📋 发布总结:"
|
|
290
|
+
echo " 版本: v$new_version"
|
|
291
|
+
echo " NPM 包: ccman@$new_version"
|
|
292
|
+
echo ""
|
|
293
|
+
print_info "📦 安装命令:"
|
|
294
|
+
echo " npm install -g ccman@$new_version"
|
|
295
|
+
echo ""
|
|
296
|
+
print_info "🔗 相关链接:"
|
|
297
|
+
echo " NPM: https://www.npmjs.com/package/ccman/v/$new_version"
|
|
298
|
+
echo " GitHub: https://github.com/2ue/ccm/releases/tag/v$new_version"
|
|
299
|
+
echo ""
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
# 主函数
|
|
303
|
+
main() {
|
|
304
|
+
print_info "🚀 CCM 智能发布脚本 v2.0"
|
|
305
|
+
print_info "=================================="
|
|
306
|
+
echo ""
|
|
307
|
+
|
|
308
|
+
check_environment
|
|
309
|
+
handle_uncommitted_changes
|
|
310
|
+
get_and_select_version "$1"
|
|
311
|
+
execute_release
|
|
312
|
+
monitor_release_status
|
|
313
|
+
show_release_summary
|
|
314
|
+
|
|
315
|
+
print_success "✨ 发布成功完成!"
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
# 错误处理
|
|
319
|
+
trap 'print_error "发布过程中出现错误,请检查输出信息"' ERR
|
|
320
|
+
|
|
321
|
+
# 运行主函数
|
|
322
|
+
main "$@"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as fse from 'fs-extra';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as os from 'os';
|
|
4
4
|
import { ShellEnvVars, ShellWriteResult, ShellType } from '../types';
|
|
@@ -20,38 +20,136 @@ export class ShellManager {
|
|
|
20
20
|
*/
|
|
21
21
|
async writeToShell(envVars: ShellEnvVars, envName?: string): Promise<ShellWriteResult> {
|
|
22
22
|
try {
|
|
23
|
-
// 1. 写入环境变量到独立的 ccmanrc
|
|
23
|
+
// 1. 写入环境变量到独立的 ccmanrc 文件(通常不会有权限问题)
|
|
24
24
|
await this.writeCCMANRC(envVars, envName);
|
|
25
25
|
|
|
26
|
-
// 2.
|
|
27
|
-
const
|
|
26
|
+
// 2. 检查shell配置文件权限
|
|
27
|
+
const shellPermissionCheck = this.checkShellWritePermissions();
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
// 3. 尝试更新 shell 配置文件引用
|
|
30
|
+
if (shellPermissionCheck.hasWritableShellConfig) {
|
|
31
|
+
try {
|
|
32
|
+
const shellUpdateResult = await this.ensureShellReference();
|
|
33
|
+
return {
|
|
34
|
+
success: true,
|
|
35
|
+
filePath: this.ccmanrcPath,
|
|
36
|
+
message: `环境变量已写入 ${this.ccmanrcPath}${shellUpdateResult.updated ? ` 并${shellUpdateResult.action}shell引用` : ''}`
|
|
37
|
+
};
|
|
38
|
+
} catch (error) {
|
|
39
|
+
// Shell引用失败但有可写配置文件时,提供具体的手动指导
|
|
40
|
+
return this.createManualConfigResult(shellPermissionCheck, String(error));
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
// 没有可写的shell配置文件,提供完整的手动配置指导
|
|
44
|
+
return this.createManualConfigResult(shellPermissionCheck);
|
|
45
|
+
}
|
|
34
46
|
} catch (error) {
|
|
35
47
|
return {
|
|
36
48
|
success: false,
|
|
37
49
|
filePath: this.ccmanrcPath,
|
|
38
|
-
message: '
|
|
50
|
+
message: '写入环境变量失败',
|
|
39
51
|
error: String(error)
|
|
40
52
|
};
|
|
41
53
|
}
|
|
42
54
|
}
|
|
43
55
|
|
|
56
|
+
/**
|
|
57
|
+
* 创建手动配置结果
|
|
58
|
+
*/
|
|
59
|
+
private createManualConfigResult(
|
|
60
|
+
shellPermissionCheck: { shellConfigAccess: { file: string; writable: boolean; error?: string }[] },
|
|
61
|
+
shellError?: string
|
|
62
|
+
): ShellWriteResult {
|
|
63
|
+
const reference = this.generateShellReference().trim();
|
|
64
|
+
const writableFiles = shellPermissionCheck.shellConfigAccess.filter(f => f.writable);
|
|
65
|
+
const nonWritableFiles = shellPermissionCheck.shellConfigAccess.filter(f => !f.writable);
|
|
66
|
+
|
|
67
|
+
let message = `环境变量已写入 ${this.ccmanrcPath},但需要手动配置shell引用。\n\n`;
|
|
68
|
+
|
|
69
|
+
if (writableFiles.length > 0) {
|
|
70
|
+
message += `推荐添加到以下文件之一:\n`;
|
|
71
|
+
writableFiles.forEach(f => {
|
|
72
|
+
message += ` ✅ ${f.file}\n`;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (nonWritableFiles.length > 0) {
|
|
77
|
+
message += `以下文件无写入权限:\n`;
|
|
78
|
+
nonWritableFiles.forEach(f => {
|
|
79
|
+
message += ` ❌ ${f.file} (${f.error})\n`;
|
|
80
|
+
});
|
|
81
|
+
message += `\n可尝试修复权限:\n`;
|
|
82
|
+
nonWritableFiles.forEach(f => {
|
|
83
|
+
if (f.error === '无写入权限') {
|
|
84
|
+
message += ` chmod 644 ${f.file}\n`;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
message += `\n需要手动添加的内容:\n${reference}`;
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
success: true, // ccmanrc写入成功,只是需要手动配置
|
|
93
|
+
filePath: this.ccmanrcPath,
|
|
94
|
+
message,
|
|
95
|
+
error: shellError ? `Shell配置自动更新失败: ${shellError}` : '所有shell配置文件都无写入权限'
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 检查shell配置文件写入权限
|
|
101
|
+
*/
|
|
102
|
+
private checkShellWritePermissions(): {
|
|
103
|
+
hasWritableShellConfig: boolean;
|
|
104
|
+
shellConfigAccess: { file: string; writable: boolean; error?: string }[];
|
|
105
|
+
} {
|
|
106
|
+
const result = {
|
|
107
|
+
hasWritableShellConfig: false,
|
|
108
|
+
shellConfigAccess: [] as { file: string; writable: boolean; error?: string }[]
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const shellType = this.detectShell();
|
|
112
|
+
const configFiles = this.getShellConfigFiles(shellType);
|
|
113
|
+
|
|
114
|
+
for (const configFile of configFiles) {
|
|
115
|
+
const fileCheck = { file: configFile, writable: false, error: undefined as string | undefined };
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
if (fse.pathExistsSync(configFile)) {
|
|
119
|
+
// 文件存在,检查写入权限
|
|
120
|
+
fse.accessSync(configFile, fse.constants.W_OK);
|
|
121
|
+
fileCheck.writable = true;
|
|
122
|
+
result.hasWritableShellConfig = true;
|
|
123
|
+
} else {
|
|
124
|
+
// 文件不存在,检查父目录权限(能否创建文件)
|
|
125
|
+
const dir = path.dirname(configFile);
|
|
126
|
+
if (fse.pathExistsSync(dir)) {
|
|
127
|
+
fse.accessSync(dir, fse.constants.W_OK);
|
|
128
|
+
fileCheck.writable = true;
|
|
129
|
+
result.hasWritableShellConfig = true;
|
|
130
|
+
} else {
|
|
131
|
+
fileCheck.error = '目录不存在';
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
} catch (error: any) {
|
|
135
|
+
fileCheck.error = `无写入权限`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
result.shellConfigAccess.push(fileCheck);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
|
|
44
144
|
/**
|
|
45
145
|
* 写入 ccmanrc 文件
|
|
46
146
|
*/
|
|
47
147
|
private async writeCCMANRC(envVars: ShellEnvVars, envName?: string): Promise<void> {
|
|
48
148
|
// 确保 .ccman 目录存在
|
|
49
|
-
|
|
50
|
-
fs.mkdirSync(this.ccmanDir, { recursive: true });
|
|
51
|
-
}
|
|
149
|
+
fse.ensureDirSync(this.ccmanDir);
|
|
52
150
|
|
|
53
151
|
const content = this.generateExportStatements(envVars, envName);
|
|
54
|
-
|
|
152
|
+
await fse.writeFile(this.ccmanrcPath, content, 'utf8');
|
|
55
153
|
}
|
|
56
154
|
|
|
57
155
|
/**
|
|
@@ -63,8 +161,8 @@ export class ShellManager {
|
|
|
63
161
|
|
|
64
162
|
// 检查是否已经有引用
|
|
65
163
|
for (const configFile of configFiles) {
|
|
66
|
-
if (
|
|
67
|
-
const content =
|
|
164
|
+
if (fse.pathExistsSync(configFile)) {
|
|
165
|
+
const content = fse.readFileSync(configFile, 'utf8');
|
|
68
166
|
if (this.hasShellReference(content)) {
|
|
69
167
|
return { updated: false, action: 'already exists' };
|
|
70
168
|
}
|
|
@@ -104,20 +202,32 @@ export class ShellManager {
|
|
|
104
202
|
private async addShellReference(configFilePath: string): Promise<void> {
|
|
105
203
|
// 确保目录存在
|
|
106
204
|
const dir = path.dirname(configFilePath);
|
|
107
|
-
|
|
108
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
109
|
-
}
|
|
205
|
+
fse.ensureDirSync(dir);
|
|
110
206
|
|
|
111
207
|
let content = '';
|
|
112
|
-
if (
|
|
113
|
-
|
|
208
|
+
if (fse.pathExistsSync(configFilePath)) {
|
|
209
|
+
try {
|
|
210
|
+
content = fse.readFileSync(configFilePath, 'utf8');
|
|
211
|
+
} catch (error: any) {
|
|
212
|
+
if (error.code === 'EACCES' || error.code === 'EPERM') {
|
|
213
|
+
throw new Error(`无权限读取shell配置文件 ${configFilePath}`);
|
|
214
|
+
}
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
114
217
|
}
|
|
115
218
|
|
|
116
219
|
// 添加对 ccmanrc 的引用
|
|
117
220
|
const reference = this.generateShellReference();
|
|
118
221
|
content += reference;
|
|
119
222
|
|
|
120
|
-
|
|
223
|
+
try {
|
|
224
|
+
await fse.writeFile(configFilePath, content, 'utf8');
|
|
225
|
+
} catch (error: any) {
|
|
226
|
+
if (error.code === 'EACCES' || error.code === 'EPERM') {
|
|
227
|
+
throw new Error(`无权限修改shell配置文件 ${configFilePath}。\n建议:\n 1. 检查文件权限:chmod 644 ${configFilePath}\n 2. 或手动添加以下内容到该文件:\n${reference.trim()}`);
|
|
228
|
+
}
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
121
231
|
}
|
|
122
232
|
|
|
123
233
|
/**
|
|
@@ -148,9 +258,9 @@ export class ShellManager {
|
|
|
148
258
|
let lastError: string | undefined;
|
|
149
259
|
|
|
150
260
|
// 1. 删除 ccmanrc 文件
|
|
151
|
-
if (
|
|
261
|
+
if (fse.pathExistsSync(this.ccmanrcPath)) {
|
|
152
262
|
try {
|
|
153
|
-
|
|
263
|
+
fse.removeSync(this.ccmanrcPath);
|
|
154
264
|
clearedAny = true;
|
|
155
265
|
} catch (error) {
|
|
156
266
|
lastError = String(error);
|
|
@@ -163,7 +273,7 @@ export class ShellManager {
|
|
|
163
273
|
|
|
164
274
|
for (const configFile of configFiles) {
|
|
165
275
|
try {
|
|
166
|
-
if (
|
|
276
|
+
if (fse.pathExistsSync(configFile)) {
|
|
167
277
|
await this.removeShellReference(configFile);
|
|
168
278
|
clearedAny = true;
|
|
169
279
|
}
|
|
@@ -192,14 +302,14 @@ export class ShellManager {
|
|
|
192
302
|
* 从配置文件中移除 shell 引用
|
|
193
303
|
*/
|
|
194
304
|
private async removeShellReference(filePath: string): Promise<void> {
|
|
195
|
-
if (!
|
|
305
|
+
if (!fse.pathExistsSync(filePath)) {
|
|
196
306
|
return;
|
|
197
307
|
}
|
|
198
308
|
|
|
199
|
-
const content =
|
|
309
|
+
const content = fse.readFileSync(filePath, 'utf8');
|
|
200
310
|
const cleanedContent = this.removeShellReferenceFromContent(content);
|
|
201
311
|
|
|
202
|
-
|
|
312
|
+
await fse.writeFile(filePath, cleanedContent, 'utf8');
|
|
203
313
|
}
|
|
204
314
|
|
|
205
315
|
/**
|
|
@@ -313,7 +423,7 @@ export ${CONFIG.ENV_VARS.AUTH_TOKEN}="${envVars.ANTHROPIC_AUTH_TOKEN}"
|
|
|
313
423
|
*/
|
|
314
424
|
hasEnvVarsInShell(): boolean {
|
|
315
425
|
// 检查 ccmanrc 文件是否存在
|
|
316
|
-
if (
|
|
426
|
+
if (fse.pathExistsSync(this.ccmanrcPath)) {
|
|
317
427
|
return true;
|
|
318
428
|
}
|
|
319
429
|
|
|
@@ -322,8 +432,8 @@ export ${CONFIG.ENV_VARS.AUTH_TOKEN}="${envVars.ANTHROPIC_AUTH_TOKEN}"
|
|
|
322
432
|
const configFiles = this.getShellConfigFiles(shellType);
|
|
323
433
|
|
|
324
434
|
for (const configFile of configFiles) {
|
|
325
|
-
if (
|
|
326
|
-
const content =
|
|
435
|
+
if (fse.pathExistsSync(configFile)) {
|
|
436
|
+
const content = fse.readFileSync(configFile, 'utf8');
|
|
327
437
|
if (this.hasShellReference(content)) {
|
|
328
438
|
return true;
|
|
329
439
|
}
|
|
@@ -341,7 +451,7 @@ export ${CONFIG.ENV_VARS.AUTH_TOKEN}="${envVars.ANTHROPIC_AUTH_TOKEN}"
|
|
|
341
451
|
const configFiles = this.getShellConfigFiles(shellType);
|
|
342
452
|
|
|
343
453
|
// 找到第一个存在的配置文件
|
|
344
|
-
const activeConfigFile = configFiles.find(file =>
|
|
454
|
+
const activeConfigFile = configFiles.find(file => fse.pathExistsSync(file));
|
|
345
455
|
|
|
346
456
|
if (!activeConfigFile) {
|
|
347
457
|
return {
|
|
@@ -404,7 +514,7 @@ export ${CONFIG.ENV_VARS.AUTH_TOKEN}="${envVars.ANTHROPIC_AUTH_TOKEN}"
|
|
|
404
514
|
const configFiles = this.getShellConfigFiles(shellType);
|
|
405
515
|
|
|
406
516
|
// 找到第一个存在的配置文件作为活动配置文件
|
|
407
|
-
const activeConfigFile = configFiles.find(file =>
|
|
517
|
+
const activeConfigFile = configFiles.find(file => fse.pathExistsSync(file));
|
|
408
518
|
|
|
409
519
|
return {
|
|
410
520
|
shellType,
|