ccman 0.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/.editorconfig +15 -0
- package/.eslintrc.js +28 -0
- package/.github/workflows/release.yml +213 -0
- package/.prettierrc +10 -0
- package/CLAUDE.md +215 -0
- package/README.md +361 -0
- package/README_zh.md +361 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +476 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/ConfigManager.d.ts +67 -0
- package/dist/config/ConfigManager.d.ts.map +1 -0
- package/dist/config/ConfigManager.js +226 -0
- package/dist/config/ConfigManager.js.map +1 -0
- package/dist/config/EnvironmentManager.d.ts +83 -0
- package/dist/config/EnvironmentManager.d.ts.map +1 -0
- package/dist/config/EnvironmentManager.js +280 -0
- package/dist/config/EnvironmentManager.js.map +1 -0
- package/dist/config/constants.d.ts +40 -0
- package/dist/config/constants.d.ts.map +1 -0
- package/dist/config/constants.js +97 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/shell/ShellManager.d.ts +73 -0
- package/dist/shell/ShellManager.d.ts.map +1 -0
- package/dist/shell/ShellManager.js +391 -0
- package/dist/shell/ShellManager.js.map +1 -0
- package/dist/types/index.d.ts +55 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/version.d.ts +67 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +199 -0
- package/dist/utils/version.js.map +1 -0
- package/docs/npm-publish-guide.md +71 -0
- package/docs/release-guide.md +86 -0
- package/docs/version-management.md +64 -0
- package/jest.config.js +22 -0
- package/package.json +57 -0
- package/release-temp/README.md +361 -0
- package/release-temp/package.json +57 -0
- package/scripts/publish-local.sh +91 -0
- package/scripts/quick-release.sh +100 -0
- package/scripts/release.sh +430 -0
- package/src/cli.ts +510 -0
- package/src/config/ConfigManager.ts +227 -0
- package/src/config/EnvironmentManager.ts +327 -0
- package/src/config/constants.ts +64 -0
- package/src/index.ts +5 -0
- package/src/shell/ShellManager.ts +416 -0
- package/src/types/index.ts +60 -0
- package/src/utils/version.ts +189 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import { ShellEnvVars, ShellWriteResult, ShellType } from '../types';
|
|
5
|
+
import { CONFIG, getConfigDir, getShellRCFile } from '../config/constants';
|
|
6
|
+
|
|
7
|
+
export class ShellManager {
|
|
8
|
+
private readonly homeDir: string;
|
|
9
|
+
private readonly ccmanDir: string;
|
|
10
|
+
private readonly ccmanrcPath: string;
|
|
11
|
+
|
|
12
|
+
constructor() {
|
|
13
|
+
this.homeDir = os.homedir();
|
|
14
|
+
this.ccmanDir = getConfigDir();
|
|
15
|
+
this.ccmanrcPath = getShellRCFile();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 写入环境变量到 CCMan 配置文件并更新 shell 引用
|
|
20
|
+
*/
|
|
21
|
+
async writeToShell(envVars: ShellEnvVars, envName?: string): Promise<ShellWriteResult> {
|
|
22
|
+
try {
|
|
23
|
+
// 1. 写入环境变量到独立的 ccmanrc 文件
|
|
24
|
+
await this.writeCCMANRC(envVars, envName);
|
|
25
|
+
|
|
26
|
+
// 2. 确保 shell 配置文件中有对 ccmanrc 的引用
|
|
27
|
+
const shellUpdateResult = await this.ensureShellReference();
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
success: true,
|
|
31
|
+
filePath: this.ccmanrcPath,
|
|
32
|
+
message: `Environment variables written to ${this.ccmanrcPath}${shellUpdateResult.updated ? ` and shell reference ${shellUpdateResult.action}` : ''}`
|
|
33
|
+
};
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return {
|
|
36
|
+
success: false,
|
|
37
|
+
filePath: this.ccmanrcPath,
|
|
38
|
+
message: 'Failed to write environment variables',
|
|
39
|
+
error: String(error)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 写入 ccmanrc 文件
|
|
46
|
+
*/
|
|
47
|
+
private async writeCCMANRC(envVars: ShellEnvVars, envName?: string): Promise<void> {
|
|
48
|
+
// 确保 .ccman 目录存在
|
|
49
|
+
if (!fs.existsSync(this.ccmanDir)) {
|
|
50
|
+
fs.mkdirSync(this.ccmanDir, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const content = this.generateExportStatements(envVars, envName);
|
|
54
|
+
fs.writeFileSync(this.ccmanrcPath, content);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 确保 shell 配置文件中有对 ccmanrc 的引用
|
|
59
|
+
*/
|
|
60
|
+
private async ensureShellReference(): Promise<{ updated: boolean; action: string; filePath?: string }> {
|
|
61
|
+
const shellType = this.detectShell();
|
|
62
|
+
const configFiles = this.getShellConfigFiles(shellType);
|
|
63
|
+
|
|
64
|
+
// 检查是否已经有引用
|
|
65
|
+
for (const configFile of configFiles) {
|
|
66
|
+
if (fs.existsSync(configFile)) {
|
|
67
|
+
const content = fs.readFileSync(configFile, 'utf8');
|
|
68
|
+
if (this.hasShellReference(content)) {
|
|
69
|
+
return { updated: false, action: 'already exists' };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 添加引用到主配置文件
|
|
75
|
+
const primaryConfigFile = configFiles[0];
|
|
76
|
+
try {
|
|
77
|
+
await this.addShellReference(primaryConfigFile);
|
|
78
|
+
return {
|
|
79
|
+
updated: true,
|
|
80
|
+
action: 'added',
|
|
81
|
+
filePath: primaryConfigFile
|
|
82
|
+
};
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// 尝试其他配置文件
|
|
85
|
+
for (let i = 1; i < configFiles.length; i++) {
|
|
86
|
+
try {
|
|
87
|
+
await this.addShellReference(configFiles[i]);
|
|
88
|
+
return {
|
|
89
|
+
updated: true,
|
|
90
|
+
action: 'added (fallback)',
|
|
91
|
+
filePath: configFiles[i]
|
|
92
|
+
};
|
|
93
|
+
} catch (fallbackError) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
throw new Error('Failed to add shell reference to any configuration file');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 添加 shell 引用到配置文件
|
|
103
|
+
*/
|
|
104
|
+
private async addShellReference(configFilePath: string): Promise<void> {
|
|
105
|
+
// 确保目录存在
|
|
106
|
+
const dir = path.dirname(configFilePath);
|
|
107
|
+
if (!fs.existsSync(dir)) {
|
|
108
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let content = '';
|
|
112
|
+
if (fs.existsSync(configFilePath)) {
|
|
113
|
+
content = fs.readFileSync(configFilePath, 'utf8');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 添加对 ccmanrc 的引用
|
|
117
|
+
const reference = this.generateShellReference();
|
|
118
|
+
content += reference;
|
|
119
|
+
|
|
120
|
+
fs.writeFileSync(configFilePath, content);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 生成 shell 引用代码
|
|
125
|
+
*/
|
|
126
|
+
private generateShellReference(): string {
|
|
127
|
+
return `
|
|
128
|
+
# ${CONFIG.APP_FULL_NAME} - Auto Generated Reference
|
|
129
|
+
# This line sources ${CONFIG.APP_NAME} environment variables from ${this.ccmanrcPath}
|
|
130
|
+
[ -f "${this.ccmanrcPath}" ] && source "${this.ccmanrcPath}"
|
|
131
|
+
# End ${CONFIG.APP_NAME} Reference
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 检查是否已经有 shell 引用
|
|
137
|
+
*/
|
|
138
|
+
private hasShellReference(content: string): boolean {
|
|
139
|
+
return content.includes(`# ${CONFIG.APP_FULL_NAME} - Auto Generated Reference`) ||
|
|
140
|
+
content.includes(this.ccmanrcPath);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* 从 shell 配置文件中清除 ccmanrc 引用和 ccmanrc 文件
|
|
145
|
+
*/
|
|
146
|
+
async clearFromShell(): Promise<ShellWriteResult> {
|
|
147
|
+
let clearedAny = false;
|
|
148
|
+
let lastError: string | undefined;
|
|
149
|
+
|
|
150
|
+
// 1. 删除 ccmanrc 文件
|
|
151
|
+
if (fs.existsSync(this.ccmanrcPath)) {
|
|
152
|
+
try {
|
|
153
|
+
fs.unlinkSync(this.ccmanrcPath);
|
|
154
|
+
clearedAny = true;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
lastError = String(error);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 2. 从 shell 配置文件中移除引用
|
|
161
|
+
const shellType = this.detectShell();
|
|
162
|
+
const configFiles = this.getShellConfigFiles(shellType);
|
|
163
|
+
|
|
164
|
+
for (const configFile of configFiles) {
|
|
165
|
+
try {
|
|
166
|
+
if (fs.existsSync(configFile)) {
|
|
167
|
+
await this.removeShellReference(configFile);
|
|
168
|
+
clearedAny = true;
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
lastError = String(error);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (clearedAny) {
|
|
176
|
+
return {
|
|
177
|
+
success: true,
|
|
178
|
+
filePath: this.ccmanrcPath,
|
|
179
|
+
message: 'Environment variables and shell references cleared'
|
|
180
|
+
};
|
|
181
|
+
} else {
|
|
182
|
+
return {
|
|
183
|
+
success: false,
|
|
184
|
+
filePath: this.ccmanrcPath,
|
|
185
|
+
message: 'Failed to clear environment variables',
|
|
186
|
+
error: lastError
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 从配置文件中移除 shell 引用
|
|
193
|
+
*/
|
|
194
|
+
private async removeShellReference(filePath: string): Promise<void> {
|
|
195
|
+
if (!fs.existsSync(filePath)) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
200
|
+
const cleanedContent = this.removeShellReferenceFromContent(content);
|
|
201
|
+
|
|
202
|
+
fs.writeFileSync(filePath, cleanedContent);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 从内容中移除 shell 引用部分
|
|
207
|
+
*/
|
|
208
|
+
private removeShellReferenceFromContent(content: string): string {
|
|
209
|
+
const startMarker = `# ${CONFIG.APP_FULL_NAME} - Auto Generated Reference`;
|
|
210
|
+
const endMarker = `# End ${CONFIG.APP_NAME} Reference`;
|
|
211
|
+
|
|
212
|
+
const lines = content.split('\n');
|
|
213
|
+
const filteredLines: string[] = [];
|
|
214
|
+
let inCCMSection = false;
|
|
215
|
+
|
|
216
|
+
for (const line of lines) {
|
|
217
|
+
if (line.includes(startMarker)) {
|
|
218
|
+
inCCMSection = true;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (line.includes(endMarker)) {
|
|
223
|
+
inCCMSection = false;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!inCCMSection) {
|
|
228
|
+
filteredLines.push(line);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return filteredLines.join('\n').replace(/\n{3,}/g, '\n\n');
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* 检测当前使用的 shell 类型
|
|
237
|
+
*/
|
|
238
|
+
detectShell(): ShellType {
|
|
239
|
+
const shell = process.env.SHELL || '';
|
|
240
|
+
|
|
241
|
+
if (shell.includes('zsh')) {
|
|
242
|
+
return 'zsh';
|
|
243
|
+
} else if (shell.includes('bash')) {
|
|
244
|
+
return 'bash';
|
|
245
|
+
} else if (shell.includes('fish')) {
|
|
246
|
+
return 'fish';
|
|
247
|
+
} else {
|
|
248
|
+
return 'unknown';
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* 获取 shell 配置文件路径列表
|
|
254
|
+
*/
|
|
255
|
+
getShellConfigFiles(shellType: ShellType): string[] {
|
|
256
|
+
const configFiles: string[] = [];
|
|
257
|
+
|
|
258
|
+
switch (shellType) {
|
|
259
|
+
case 'zsh':
|
|
260
|
+
configFiles.push(
|
|
261
|
+
path.join(this.homeDir, '.zshrc'),
|
|
262
|
+
path.join(this.homeDir, '.zprofile')
|
|
263
|
+
);
|
|
264
|
+
break;
|
|
265
|
+
case 'bash':
|
|
266
|
+
configFiles.push(
|
|
267
|
+
path.join(this.homeDir, '.bashrc'),
|
|
268
|
+
path.join(this.homeDir, '.bash_profile'),
|
|
269
|
+
path.join(this.homeDir, '.profile')
|
|
270
|
+
);
|
|
271
|
+
break;
|
|
272
|
+
case 'fish':
|
|
273
|
+
configFiles.push(
|
|
274
|
+
path.join(this.homeDir, '.config/fish/config.fish')
|
|
275
|
+
);
|
|
276
|
+
break;
|
|
277
|
+
default:
|
|
278
|
+
// 默认尝试常见的配置文件
|
|
279
|
+
configFiles.push(
|
|
280
|
+
path.join(this.homeDir, '.zshrc'),
|
|
281
|
+
path.join(this.homeDir, '.bashrc'),
|
|
282
|
+
path.join(this.homeDir, '.profile')
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return configFiles;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* 生成环境变量导出语句
|
|
291
|
+
*/
|
|
292
|
+
generateExportStatements(envVars: ShellEnvVars, envName?: string): string {
|
|
293
|
+
const now = new Date();
|
|
294
|
+
const timestamp = now.getFullYear() + '-' +
|
|
295
|
+
String(now.getMonth() + 1).padStart(2, '0') + '-' +
|
|
296
|
+
String(now.getDate()).padStart(2, '0') + ' ' +
|
|
297
|
+
String(now.getHours()).padStart(2, '0') + ':' +
|
|
298
|
+
String(now.getMinutes()).padStart(2, '0') + ':' +
|
|
299
|
+
String(now.getSeconds()).padStart(2, '0');
|
|
300
|
+
const nameComment = envName ? `# Environment: ${envName}` : '';
|
|
301
|
+
|
|
302
|
+
return `
|
|
303
|
+
# ${CONFIG.APP_FULL_NAME} Environment Variables - Auto Generated
|
|
304
|
+
# Generated at: ${timestamp}${nameComment ? '\n' + nameComment : ''}
|
|
305
|
+
export ${CONFIG.ENV_VARS.BASE_URL}="${envVars.ANTHROPIC_BASE_URL}"
|
|
306
|
+
export ${CONFIG.ENV_VARS.AUTH_TOKEN}="${envVars.ANTHROPIC_AUTH_TOKEN}"
|
|
307
|
+
# End ${CONFIG.APP_NAME} Environment Variables
|
|
308
|
+
`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* 检查是否已经写入了环境变量
|
|
313
|
+
*/
|
|
314
|
+
hasEnvVarsInShell(): boolean {
|
|
315
|
+
// 检查 ccmanrc 文件是否存在
|
|
316
|
+
if (fs.existsSync(this.ccmanrcPath)) {
|
|
317
|
+
return true;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// 检查 shell 配置文件中是否有引用
|
|
321
|
+
const shellType = this.detectShell();
|
|
322
|
+
const configFiles = this.getShellConfigFiles(shellType);
|
|
323
|
+
|
|
324
|
+
for (const configFile of configFiles) {
|
|
325
|
+
if (fs.existsSync(configFile)) {
|
|
326
|
+
const content = fs.readFileSync(configFile, 'utf8');
|
|
327
|
+
if (this.hasShellReference(content)) {
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* 自动 source shell 配置文件
|
|
338
|
+
*/
|
|
339
|
+
async autoSourceShell(): Promise<ShellWriteResult> {
|
|
340
|
+
const shellType = this.detectShell();
|
|
341
|
+
const configFiles = this.getShellConfigFiles(shellType);
|
|
342
|
+
|
|
343
|
+
// 找到第一个存在的配置文件
|
|
344
|
+
const activeConfigFile = configFiles.find(file => fs.existsSync(file));
|
|
345
|
+
|
|
346
|
+
if (!activeConfigFile) {
|
|
347
|
+
return {
|
|
348
|
+
success: false,
|
|
349
|
+
filePath: configFiles.join(', '),
|
|
350
|
+
message: 'No shell configuration file found to source',
|
|
351
|
+
error: 'Configuration file not found'
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
try {
|
|
356
|
+
// 使用子进程执行 source 命令
|
|
357
|
+
const { exec } = await import('child_process');
|
|
358
|
+
const { promisify } = await import('util');
|
|
359
|
+
const execAsync = promisify(exec);
|
|
360
|
+
|
|
361
|
+
// 根据不同 shell 类型使用不同的 source 命令
|
|
362
|
+
let sourceCommand: string;
|
|
363
|
+
switch (shellType) {
|
|
364
|
+
case 'zsh':
|
|
365
|
+
sourceCommand = `zsh -c "source ${activeConfigFile}"`;
|
|
366
|
+
break;
|
|
367
|
+
case 'bash':
|
|
368
|
+
sourceCommand = `bash -c "source ${activeConfigFile}"`;
|
|
369
|
+
break;
|
|
370
|
+
case 'fish':
|
|
371
|
+
sourceCommand = `fish -c "source ${activeConfigFile}"`;
|
|
372
|
+
break;
|
|
373
|
+
default:
|
|
374
|
+
sourceCommand = `bash -c "source ${activeConfigFile}"`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
await execAsync(sourceCommand);
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
success: true,
|
|
381
|
+
filePath: activeConfigFile,
|
|
382
|
+
message: `Successfully sourced ${activeConfigFile}`
|
|
383
|
+
};
|
|
384
|
+
} catch (error) {
|
|
385
|
+
return {
|
|
386
|
+
success: false,
|
|
387
|
+
filePath: activeConfigFile,
|
|
388
|
+
message: 'Failed to source shell configuration file',
|
|
389
|
+
error: String(error)
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* 获取当前 shell 信息
|
|
396
|
+
*/
|
|
397
|
+
getShellInfo(): {
|
|
398
|
+
shellType: ShellType;
|
|
399
|
+
shellPath: string;
|
|
400
|
+
configFiles: string[];
|
|
401
|
+
activeConfigFile?: string;
|
|
402
|
+
} {
|
|
403
|
+
const shellType = this.detectShell();
|
|
404
|
+
const configFiles = this.getShellConfigFiles(shellType);
|
|
405
|
+
|
|
406
|
+
// 找到第一个存在的配置文件作为活动配置文件
|
|
407
|
+
const activeConfigFile = configFiles.find(file => fs.existsSync(file));
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
shellType,
|
|
411
|
+
shellPath: process.env.SHELL || 'unknown',
|
|
412
|
+
configFiles,
|
|
413
|
+
activeConfigFile
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Manager 类型定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ClaudeEnv {
|
|
6
|
+
/** 环境名称 */
|
|
7
|
+
name: string;
|
|
8
|
+
/** API 基础 URL */
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
/** API 密钥 */
|
|
11
|
+
apiKey: string;
|
|
12
|
+
/** 创建时间 */
|
|
13
|
+
createdAt: string;
|
|
14
|
+
/** 最后使用时间 */
|
|
15
|
+
lastUsed?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Config {
|
|
19
|
+
/** 当前使用的环境名称 */
|
|
20
|
+
current: string | null;
|
|
21
|
+
/** 环境配置列表 */
|
|
22
|
+
environments: { [name: string]: ClaudeEnv };
|
|
23
|
+
/** 全局设置 */
|
|
24
|
+
settings: GlobalSettings;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface GlobalSettings {
|
|
28
|
+
/** 是否自动写入 shell 配置文件 */
|
|
29
|
+
autoWriteShell: boolean;
|
|
30
|
+
/** 首选的 shell 类型 */
|
|
31
|
+
preferredShell: 'bash' | 'zsh' | 'auto';
|
|
32
|
+
/** shell 配置文件路径(可自定义) */
|
|
33
|
+
shellConfigPath?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ShellEnvVars {
|
|
37
|
+
ANTHROPIC_BASE_URL: string;
|
|
38
|
+
ANTHROPIC_AUTH_TOKEN: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AddEnvOptions {
|
|
42
|
+
name: string;
|
|
43
|
+
baseUrl: string;
|
|
44
|
+
apiKey: string;
|
|
45
|
+
autoWriteShell?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ShellWriteResult {
|
|
49
|
+
success: boolean;
|
|
50
|
+
filePath: string;
|
|
51
|
+
message: string;
|
|
52
|
+
error?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface EnvironmentListItem extends ClaudeEnv {
|
|
56
|
+
/** 是否为当前使用的环境 */
|
|
57
|
+
isCurrent: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type ShellType = 'bash' | 'zsh' | 'fish' | 'unknown';
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 版本信息接口
|
|
6
|
+
*/
|
|
7
|
+
export interface VersionInfo {
|
|
8
|
+
version: string;
|
|
9
|
+
major: number;
|
|
10
|
+
minor: number;
|
|
11
|
+
patch: number;
|
|
12
|
+
prerelease?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 版本工具类
|
|
17
|
+
*/
|
|
18
|
+
export class VersionManager {
|
|
19
|
+
private static instance: VersionManager;
|
|
20
|
+
private cachedVersion?: VersionInfo;
|
|
21
|
+
|
|
22
|
+
private constructor() {}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 获取单例实例
|
|
26
|
+
*/
|
|
27
|
+
static getInstance(): VersionManager {
|
|
28
|
+
if (!VersionManager.instance) {
|
|
29
|
+
VersionManager.instance = new VersionManager();
|
|
30
|
+
}
|
|
31
|
+
return VersionManager.instance;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 获取当前版本信息
|
|
36
|
+
*/
|
|
37
|
+
getCurrentVersion(): VersionInfo {
|
|
38
|
+
if (this.cachedVersion) {
|
|
39
|
+
return this.cachedVersion;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const packageJsonPath = this.getPackageJsonPath();
|
|
44
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
45
|
+
const version = packageJson.version;
|
|
46
|
+
|
|
47
|
+
this.cachedVersion = this.parseVersion(version);
|
|
48
|
+
return this.cachedVersion;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.warn('⚠️ 无法读取版本信息,使用默认版本');
|
|
51
|
+
this.cachedVersion = this.parseVersion('0.0.1');
|
|
52
|
+
return this.cachedVersion;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 获取版本字符串
|
|
58
|
+
*/
|
|
59
|
+
getVersionString(): string {
|
|
60
|
+
return this.getCurrentVersion().version;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 解析版本字符串
|
|
65
|
+
*/
|
|
66
|
+
private parseVersion(versionString: string): VersionInfo {
|
|
67
|
+
const match = versionString.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
|
|
68
|
+
|
|
69
|
+
if (!match) {
|
|
70
|
+
throw new Error(`无效的版本格式: ${versionString}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const [, major, minor, patch, prerelease] = match;
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
version: versionString,
|
|
77
|
+
major: parseInt(major, 10),
|
|
78
|
+
minor: parseInt(minor, 10),
|
|
79
|
+
patch: parseInt(patch, 10),
|
|
80
|
+
prerelease
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 获取 package.json 路径
|
|
86
|
+
*/
|
|
87
|
+
private getPackageJsonPath(): string {
|
|
88
|
+
// 在构建后的环境中,package.json 在 ../package.json
|
|
89
|
+
// 在开发环境中,可能在不同的位置
|
|
90
|
+
const paths = [
|
|
91
|
+
join(__dirname, '../package.json'), // 构建后
|
|
92
|
+
join(__dirname, '../../package.json'), // 开发环境
|
|
93
|
+
join(process.cwd(), 'package.json') // 当前工作目录
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
for (const path of paths) {
|
|
97
|
+
try {
|
|
98
|
+
readFileSync(path, 'utf8');
|
|
99
|
+
return path;
|
|
100
|
+
} catch {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
throw new Error('找不到 package.json 文件');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 预测下一个版本
|
|
110
|
+
*/
|
|
111
|
+
getNextVersion(type: 'patch' | 'minor' | 'major'): string {
|
|
112
|
+
const current = this.getCurrentVersion();
|
|
113
|
+
|
|
114
|
+
switch (type) {
|
|
115
|
+
case 'patch':
|
|
116
|
+
return `${current.major}.${current.minor}.${current.patch + 1}`;
|
|
117
|
+
case 'minor':
|
|
118
|
+
return `${current.major}.${current.minor + 1}.0`;
|
|
119
|
+
case 'major':
|
|
120
|
+
return `${current.major + 1}.0.0`;
|
|
121
|
+
default:
|
|
122
|
+
throw new Error(`不支持的版本类型: ${type}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 获取版本变更建议(基于 git 历史分析)
|
|
128
|
+
*/
|
|
129
|
+
async getVersionSuggestion(): Promise<'patch' | 'minor' | 'major'> {
|
|
130
|
+
try {
|
|
131
|
+
// 这里可以添加 git 历史分析逻辑
|
|
132
|
+
// 比如分析提交信息中的关键词来推荐版本类型
|
|
133
|
+
const { exec } = await import('child_process');
|
|
134
|
+
const { promisify } = await import('util');
|
|
135
|
+
const execAsync = promisify(exec);
|
|
136
|
+
|
|
137
|
+
// 获取最近的提交信息
|
|
138
|
+
const { stdout } = await execAsync('git log --oneline -10');
|
|
139
|
+
const commits = stdout.toLowerCase();
|
|
140
|
+
|
|
141
|
+
// 简单的启发式规则
|
|
142
|
+
if (commits.includes('breaking') || commits.includes('major')) {
|
|
143
|
+
return 'major';
|
|
144
|
+
} else if (commits.includes('feat') || commits.includes('feature') || commits.includes('add')) {
|
|
145
|
+
return 'minor';
|
|
146
|
+
} else {
|
|
147
|
+
return 'patch';
|
|
148
|
+
}
|
|
149
|
+
} catch {
|
|
150
|
+
// 默认推荐 patch
|
|
151
|
+
return 'patch';
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* 清除缓存(测试用)
|
|
157
|
+
*/
|
|
158
|
+
clearCache(): void {
|
|
159
|
+
this.cachedVersion = undefined;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 便捷函数:获取当前版本字符串
|
|
165
|
+
*/
|
|
166
|
+
export function getCurrentVersion(): string {
|
|
167
|
+
return VersionManager.getInstance().getVersionString();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 便捷函数:获取版本信息
|
|
172
|
+
*/
|
|
173
|
+
export function getVersionInfo(): VersionInfo {
|
|
174
|
+
return VersionManager.getInstance().getCurrentVersion();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 便捷函数:获取下一个版本
|
|
179
|
+
*/
|
|
180
|
+
export function getNextVersion(type: 'patch' | 'minor' | 'major'): string {
|
|
181
|
+
return VersionManager.getInstance().getNextVersion(type);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 便捷函数:获取版本建议
|
|
186
|
+
*/
|
|
187
|
+
export async function getVersionSuggestion(): Promise<'patch' | 'minor' | 'major'> {
|
|
188
|
+
return VersionManager.getInstance().getVersionSuggestion();
|
|
189
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationMap": true,
|
|
14
|
+
"sourceMap": true,
|
|
15
|
+
"resolveJsonModule": true
|
|
16
|
+
},
|
|
17
|
+
"include": [
|
|
18
|
+
"src/**/*"
|
|
19
|
+
],
|
|
20
|
+
"exclude": [
|
|
21
|
+
"node_modules",
|
|
22
|
+
"dist",
|
|
23
|
+
"**/*.test.ts"
|
|
24
|
+
]
|
|
25
|
+
}
|