ccman 0.0.4 → 0.1.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 +279 -0
- package/scripts/modules/monitor-release.sh +268 -0
- package/scripts/modules/version-bump.sh +262 -0
- package/scripts/smart-release-v3.sh +289 -0
- package/scripts/smart-release.sh +322 -0
- package/src/shell/ShellManager.ts +143 -33
|
@@ -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,
|