tools-cc 1.0.0 → 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/commands/source.d.ts +1 -0
- package/dist/commands/source.js +29 -0
- package/dist/commands/use.d.ts +1 -0
- package/dist/commands/use.js +38 -0
- package/dist/core/source.d.ts +6 -0
- package/dist/core/source.js +78 -0
- package/dist/index.js +18 -1
- package/package.json +1 -1
- package/readme.md +41 -5
- package/src/commands/source.ts +34 -1
- package/src/commands/use.ts +190 -147
- package/src/core/source.ts +184 -100
- package/src/index.ts +22 -3
|
@@ -2,3 +2,4 @@ export declare function handleSourceAdd(name: string, pathOrUrl: string): Promis
|
|
|
2
2
|
export declare function handleSourceList(): Promise<void>;
|
|
3
3
|
export declare function handleSourceRemove(name: string): Promise<void>;
|
|
4
4
|
export declare function handleSourceUpdate(name?: string): Promise<void>;
|
|
5
|
+
export declare function handleSourceScan(): Promise<void>;
|
package/dist/commands/source.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.handleSourceAdd = handleSourceAdd;
|
|
|
7
7
|
exports.handleSourceList = handleSourceList;
|
|
8
8
|
exports.handleSourceRemove = handleSourceRemove;
|
|
9
9
|
exports.handleSourceUpdate = handleSourceUpdate;
|
|
10
|
+
exports.handleSourceScan = handleSourceScan;
|
|
10
11
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
12
|
const source_1 = require("../core/source");
|
|
12
13
|
const path_1 = require("../utils/path");
|
|
@@ -70,3 +71,31 @@ async function handleSourceUpdate(name) {
|
|
|
70
71
|
console.log(chalk_1.default.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
71
72
|
}
|
|
72
73
|
}
|
|
74
|
+
async function handleSourceScan() {
|
|
75
|
+
try {
|
|
76
|
+
console.log(chalk_1.default.bold('Scanning sources directory...'));
|
|
77
|
+
const result = await (0, source_1.scanSources)(path_1.GLOBAL_CONFIG_DIR);
|
|
78
|
+
if (result.added.length > 0) {
|
|
79
|
+
console.log(chalk_1.default.green(`\n✓ Added ${result.added.length} source(s):`));
|
|
80
|
+
for (const name of result.added) {
|
|
81
|
+
console.log(chalk_1.default.gray(` + ${name}`));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (result.updated.length > 0) {
|
|
85
|
+
console.log(chalk_1.default.yellow(`\n⚡ Updated ${result.updated.length} source(s):`));
|
|
86
|
+
for (const name of result.updated) {
|
|
87
|
+
console.log(chalk_1.default.gray(` ~ ${name}`));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (result.skipped.length > 0) {
|
|
91
|
+
console.log(chalk_1.default.gray(`\n→ Skipped ${result.skipped.length} existing source(s)`));
|
|
92
|
+
}
|
|
93
|
+
if (result.added.length === 0 && result.updated.length === 0 && result.skipped.length === 0) {
|
|
94
|
+
console.log(chalk_1.default.gray('No sources found in sources directory.'));
|
|
95
|
+
}
|
|
96
|
+
console.log(chalk_1.default.green(`\n✓ Scan complete`));
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.log(chalk_1.default.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
100
|
+
}
|
|
101
|
+
}
|
package/dist/commands/use.d.ts
CHANGED
|
@@ -4,3 +4,4 @@ export declare function handleUse(sourceNames: string[], options: {
|
|
|
4
4
|
export declare function handleList(): Promise<void>;
|
|
5
5
|
export declare function handleRemove(sourceName: string): Promise<void>;
|
|
6
6
|
export declare function handleStatus(): Promise<void>;
|
|
7
|
+
export declare function handleProjectUpdate(sourceNames?: string[]): Promise<void>;
|
package/dist/commands/use.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.handleUse = handleUse;
|
|
|
7
7
|
exports.handleList = handleList;
|
|
8
8
|
exports.handleRemove = handleRemove;
|
|
9
9
|
exports.handleStatus = handleStatus;
|
|
10
|
+
exports.handleProjectUpdate = handleProjectUpdate;
|
|
10
11
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
12
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
12
13
|
const project_1 = require("../core/project");
|
|
@@ -131,3 +132,40 @@ async function handleStatus() {
|
|
|
131
132
|
}
|
|
132
133
|
console.log();
|
|
133
134
|
}
|
|
135
|
+
async function handleProjectUpdate(sourceNames) {
|
|
136
|
+
const projectDir = process.cwd();
|
|
137
|
+
const configFile = path_2.default.join(projectDir, 'tools-cc.json');
|
|
138
|
+
// 检查项目是否已初始化
|
|
139
|
+
if (!(await fs_extra_1.default.pathExists(configFile))) {
|
|
140
|
+
console.log(chalk_1.default.yellow('Project not initialized. Run `tools-cc use <source>` first.'));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const config = await fs_extra_1.default.readJson(configFile);
|
|
144
|
+
let sourcesToUpdate = sourceNames && sourceNames.length > 0
|
|
145
|
+
? sourceNames
|
|
146
|
+
: config.sources || [];
|
|
147
|
+
if (sourcesToUpdate.length === 0) {
|
|
148
|
+
console.log(chalk_1.default.gray('No sources to update.'));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// 验证指定的源是否存在于项目配置中
|
|
152
|
+
if (sourceNames && sourceNames.length > 0) {
|
|
153
|
+
const invalidSources = sourceNames.filter((s) => !config.sources.includes(s));
|
|
154
|
+
if (invalidSources.length > 0) {
|
|
155
|
+
console.log(chalk_1.default.yellow(`Sources not in project: ${invalidSources.join(', ')}`));
|
|
156
|
+
}
|
|
157
|
+
sourcesToUpdate = sourcesToUpdate.filter((s) => config.sources.includes(s));
|
|
158
|
+
}
|
|
159
|
+
// 更新每个配置源
|
|
160
|
+
for (const sourceName of sourcesToUpdate) {
|
|
161
|
+
try {
|
|
162
|
+
const sourcePath = await (0, source_1.getSourcePath)(sourceName, path_1.GLOBAL_CONFIG_DIR);
|
|
163
|
+
await (0, project_1.useSource)(sourceName, sourcePath, projectDir);
|
|
164
|
+
console.log(chalk_1.default.green(`✓ Updated source: ${sourceName}`));
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
console.log(chalk_1.default.red(`✗ Failed to update ${sourceName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
console.log(chalk_1.default.green(`\n✓ Project update complete`));
|
|
171
|
+
}
|
package/dist/core/source.d.ts
CHANGED
|
@@ -4,3 +4,9 @@ export declare function listSources(configDir: string): Promise<Record<string, S
|
|
|
4
4
|
export declare function removeSource(name: string, configDir: string): Promise<void>;
|
|
5
5
|
export declare function updateSource(name: string, configDir: string): Promise<void>;
|
|
6
6
|
export declare function getSourcePath(name: string, configDir: string): Promise<string>;
|
|
7
|
+
export interface ScanResult {
|
|
8
|
+
added: string[];
|
|
9
|
+
updated: string[];
|
|
10
|
+
skipped: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function scanSources(configDir: string): Promise<ScanResult>;
|
package/dist/core/source.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.listSources = listSources;
|
|
|
8
8
|
exports.removeSource = removeSource;
|
|
9
9
|
exports.updateSource = updateSource;
|
|
10
10
|
exports.getSourcePath = getSourcePath;
|
|
11
|
+
exports.scanSources = scanSources;
|
|
11
12
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
13
|
const path_1 = __importDefault(require("path"));
|
|
13
14
|
const child_process_1 = require("child_process");
|
|
@@ -84,3 +85,80 @@ async function getSourcePath(name, configDir) {
|
|
|
84
85
|
}
|
|
85
86
|
return path_1.default.join(config.sourcesDir, name);
|
|
86
87
|
}
|
|
88
|
+
async function scanSources(configDir) {
|
|
89
|
+
const config = await (0, config_1.loadGlobalConfig)(configDir);
|
|
90
|
+
const result = { added: [], updated: [], skipped: [] };
|
|
91
|
+
// 确保 sourcesDir 存在
|
|
92
|
+
if (!(await fs_extra_1.default.pathExists(config.sourcesDir))) {
|
|
93
|
+
await fs_extra_1.default.ensureDir(config.sourcesDir);
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
// 获取 sourcesDir 下的所有文件夹
|
|
97
|
+
const entries = await fs_extra_1.default.readdir(config.sourcesDir, { withFileTypes: true });
|
|
98
|
+
const directories = entries
|
|
99
|
+
.filter(entry => entry.isDirectory())
|
|
100
|
+
.map(entry => entry.name);
|
|
101
|
+
// 检查每个 git source 的克隆目录是否还存在
|
|
102
|
+
for (const [name, source] of Object.entries(config.sources)) {
|
|
103
|
+
if (source.type === 'git') {
|
|
104
|
+
const cloneDir = path_1.default.join(config.sourcesDir, name);
|
|
105
|
+
if (!(await fs_extra_1.default.pathExists(cloneDir))) {
|
|
106
|
+
// 克隆目录不存在,从配置中移除
|
|
107
|
+
delete config.sources[name];
|
|
108
|
+
console.log(`Removed missing git source: ${name}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// 扫描目录并更新配置
|
|
113
|
+
for (const dirName of directories) {
|
|
114
|
+
const dirPath = path_1.default.join(config.sourcesDir, dirName);
|
|
115
|
+
const existingSource = config.sources[dirName];
|
|
116
|
+
// 检查是否是 git 仓库
|
|
117
|
+
const isGitRepo = await fs_extra_1.default.pathExists(path_1.default.join(dirPath, '.git'));
|
|
118
|
+
if (existingSource) {
|
|
119
|
+
// 已存在配置
|
|
120
|
+
if (existingSource.type === 'git' && isGitRepo) {
|
|
121
|
+
result.skipped.push(dirName);
|
|
122
|
+
}
|
|
123
|
+
else if (existingSource.type === 'local' && existingSource.path === dirPath) {
|
|
124
|
+
result.skipped.push(dirName);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// 配置不一致,更新为当前状态
|
|
128
|
+
if (isGitRepo) {
|
|
129
|
+
// 尝试获取远程 URL
|
|
130
|
+
try {
|
|
131
|
+
const remoteUrl = (0, child_process_1.execSync)(`git -C "${dirPath}" config --get remote.origin.url`, { encoding: 'utf-8' }).trim();
|
|
132
|
+
config.sources[dirName] = { type: 'git', url: remoteUrl };
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
config.sources[dirName] = { type: 'local', path: dirPath };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
config.sources[dirName] = { type: 'local', path: dirPath };
|
|
140
|
+
}
|
|
141
|
+
result.updated.push(dirName);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// 新发现的目录,添加到配置
|
|
146
|
+
if (isGitRepo) {
|
|
147
|
+
// 尝试获取远程 URL
|
|
148
|
+
try {
|
|
149
|
+
const remoteUrl = (0, child_process_1.execSync)(`git -C "${dirPath}" config --get remote.origin.url`, { encoding: 'utf-8' }).trim();
|
|
150
|
+
config.sources[dirName] = { type: 'git', url: remoteUrl };
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
config.sources[dirName] = { type: 'local', path: dirPath };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
config.sources[dirName] = { type: 'local', path: dirPath };
|
|
158
|
+
}
|
|
159
|
+
result.added.push(dirName);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
await (0, config_1.saveGlobalConfig)(config, configDir);
|
|
163
|
+
return result;
|
|
164
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -42,10 +42,17 @@ sourceCmd
|
|
|
42
42
|
sourceCmd
|
|
43
43
|
.command('update [name]')
|
|
44
44
|
.alias('up')
|
|
45
|
-
.
|
|
45
|
+
.alias('upgrade')
|
|
46
|
+
.description('Update source(s) with git pull')
|
|
46
47
|
.action(async (name) => {
|
|
47
48
|
await (0, source_1.handleSourceUpdate)(name);
|
|
48
49
|
});
|
|
50
|
+
sourceCmd
|
|
51
|
+
.command('scan')
|
|
52
|
+
.description('Scan sources directory and update configuration')
|
|
53
|
+
.action(async () => {
|
|
54
|
+
await (0, source_1.handleSourceScan)();
|
|
55
|
+
});
|
|
49
56
|
// Config subcommands (full command version)
|
|
50
57
|
const configCmd = program
|
|
51
58
|
.command('config')
|
|
@@ -94,6 +101,12 @@ program
|
|
|
94
101
|
.action(async () => {
|
|
95
102
|
await (0, use_1.handleStatus)();
|
|
96
103
|
});
|
|
104
|
+
program
|
|
105
|
+
.command('update [sources...]')
|
|
106
|
+
.description('Update source(s) in current project')
|
|
107
|
+
.action(async (sources) => {
|
|
108
|
+
await (0, use_1.handleProjectUpdate)(sources);
|
|
109
|
+
});
|
|
97
110
|
// Help command
|
|
98
111
|
program
|
|
99
112
|
.command('help')
|
|
@@ -129,8 +142,12 @@ program
|
|
|
129
142
|
break;
|
|
130
143
|
case 'update':
|
|
131
144
|
case 'up':
|
|
145
|
+
case 'upgrade':
|
|
132
146
|
await (0, source_1.handleSourceUpdate)(args[0]);
|
|
133
147
|
break;
|
|
148
|
+
case 'scan':
|
|
149
|
+
await (0, source_1.handleSourceScan)();
|
|
150
|
+
break;
|
|
134
151
|
default:
|
|
135
152
|
console.log(`Unknown source command: ${cmd}`);
|
|
136
153
|
}
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -26,6 +26,9 @@ tools-cc -c set sourcesDir D:/skills-hub-sources
|
|
|
26
26
|
tools-cc -s add my-skills https://github.com/user/my-skills.git
|
|
27
27
|
tools-cc -s add local-skills D:/path/to/local-skills
|
|
28
28
|
|
|
29
|
+
# 或者扫描源目录自动发现并添加配置
|
|
30
|
+
tools-cc -s scan
|
|
31
|
+
|
|
29
32
|
# 3. 查看已添加的配置源
|
|
30
33
|
tools-cc -s list
|
|
31
34
|
|
|
@@ -39,32 +42,65 @@ tools-cc status
|
|
|
39
42
|
# 6. 查看已启用的配置源
|
|
40
43
|
tools-cc list
|
|
41
44
|
|
|
42
|
-
# 7.
|
|
45
|
+
# 7. 更新配置源(从源目录同步最新内容到项目)
|
|
46
|
+
tools-cc update my-skills
|
|
47
|
+
tools-cc update # 更新全部
|
|
48
|
+
|
|
49
|
+
# 8. 移除配置源
|
|
43
50
|
tools-cc rm my-skills
|
|
44
51
|
```
|
|
45
52
|
|
|
53
|
+
## 工作流说明
|
|
54
|
+
|
|
55
|
+
### 全局配置源管理 vs 项目配置
|
|
56
|
+
|
|
57
|
+
| 命令 | 作用域 | 说明 |
|
|
58
|
+
|------|--------|------|
|
|
59
|
+
| `tools-cc -s add/remove/list` | 全局 | 管理全局配置源 |
|
|
60
|
+
| `tools-cc -s update/upgrade` | 全局 | git pull 更新源代码 |
|
|
61
|
+
| `tools-cc -s scan` | 全局 | 扫描目录发现新源 |
|
|
62
|
+
| `tools-cc use/rm` | 项目 | 在项目中启用/禁用源 |
|
|
63
|
+
| `tools-cc update` | 项目 | 同步源内容到项目 |
|
|
64
|
+
|
|
65
|
+
### 典型工作流
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# 场景1: 配置源有更新,需要同步到项目
|
|
69
|
+
tools-cc -s upgrade my-skills # 1. git pull 更新源代码
|
|
70
|
+
cd my-project
|
|
71
|
+
tools-cc update my-skills # 2. 同步到项目 .toolscc 目录
|
|
72
|
+
|
|
73
|
+
# 场景2: 批量更新所有源
|
|
74
|
+
tools-cc -s upgrade # 1. 更新所有 git 源
|
|
75
|
+
cd my-project
|
|
76
|
+
tools-cc update # 2. 同步所有源到项目
|
|
77
|
+
```
|
|
78
|
+
|
|
46
79
|
## 命令列表
|
|
47
80
|
|
|
48
81
|
### Source 管理
|
|
49
82
|
|
|
50
83
|
```bash
|
|
51
84
|
# 快捷方式 (-s)
|
|
52
|
-
tools-cc -s add <name> <path-or-url> #
|
|
85
|
+
tools-cc -s add <name> <path-or-url> # 添加配置源(Git URL 或本地路径)
|
|
53
86
|
tools-cc -s list # 列出所有配置源 (缩写: -s ls)
|
|
54
87
|
tools-cc -s remove <name> # 移除配置源 (缩写: -s rm)
|
|
55
|
-
tools-cc -s update [name] #
|
|
88
|
+
tools-cc -s update [name] # git pull 更新配置源代码 (缩写: -s up, -s upgrade)
|
|
89
|
+
tools-cc -s scan # 扫描 sourcesDir 目录,自动发现并添加配置源
|
|
56
90
|
|
|
57
91
|
# 完整命令 (sources)
|
|
58
92
|
tools-cc sources add <name> <path-or-url> # 添加配置源
|
|
59
93
|
tools-cc sources list # 列出所有配置源 (缩写: sources ls)
|
|
60
94
|
tools-cc sources remove <name> # 移除配置源 (缩写: sources rm)
|
|
61
|
-
tools-cc sources update [name] #
|
|
95
|
+
tools-cc sources update [name] # git pull 更新配置源代码 (缩写: sources up, sources upgrade)
|
|
96
|
+
tools-cc sources scan # 扫描 sourcesDir 目录,自动发现并添加配置源
|
|
62
97
|
```
|
|
63
98
|
|
|
64
99
|
### 项目配置
|
|
65
100
|
|
|
66
101
|
```bash
|
|
67
|
-
tools-cc use [sources...] [-p tools...] #
|
|
102
|
+
tools-cc use [sources...] [-p tools...] # 启用配置源并创建符号链接
|
|
103
|
+
tools-cc update [sources...] # 同步配置源内容到项目 .toolscc 目录
|
|
68
104
|
tools-cc list # 列出已启用的配置源
|
|
69
105
|
tools-cc rm <source> # 禁用配置源
|
|
70
106
|
tools-cc status # 查看项目状态
|
package/src/commands/source.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { addSource, listSources, removeSource, updateSource } from '../core/source';
|
|
2
|
+
import { addSource, listSources, removeSource, updateSource, scanSources } from '../core/source';
|
|
3
3
|
import { GLOBAL_CONFIG_DIR } from '../utils/path';
|
|
4
4
|
|
|
5
5
|
export async function handleSourceAdd(name: string, pathOrUrl: string): Promise<void> {
|
|
@@ -61,3 +61,36 @@ export async function handleSourceUpdate(name?: string): Promise<void> {
|
|
|
61
61
|
console.log(chalk.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
export async function handleSourceScan(): Promise<void> {
|
|
66
|
+
try {
|
|
67
|
+
console.log(chalk.bold('Scanning sources directory...'));
|
|
68
|
+
const result = await scanSources(GLOBAL_CONFIG_DIR);
|
|
69
|
+
|
|
70
|
+
if (result.added.length > 0) {
|
|
71
|
+
console.log(chalk.green(`\n✓ Added ${result.added.length} source(s):`));
|
|
72
|
+
for (const name of result.added) {
|
|
73
|
+
console.log(chalk.gray(` + ${name}`));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (result.updated.length > 0) {
|
|
78
|
+
console.log(chalk.yellow(`\n⚡ Updated ${result.updated.length} source(s):`));
|
|
79
|
+
for (const name of result.updated) {
|
|
80
|
+
console.log(chalk.gray(` ~ ${name}`));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (result.skipped.length > 0) {
|
|
85
|
+
console.log(chalk.gray(`\n→ Skipped ${result.skipped.length} existing source(s)`));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (result.added.length === 0 && result.updated.length === 0 && result.skipped.length === 0) {
|
|
89
|
+
console.log(chalk.gray('No sources found in sources directory.'));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(chalk.green(`\n✓ Scan complete`));
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.log(chalk.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
95
|
+
}
|
|
96
|
+
}
|
package/src/commands/use.ts
CHANGED
|
@@ -1,147 +1,190 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
|
-
import { useSource, unuseSource, listUsedSources, initProject } from '../core/project';
|
|
4
|
-
import { getSourcePath, listSources } from '../core/source';
|
|
5
|
-
import { createSymlink, isSymlink } from '../core/symlink';
|
|
6
|
-
import { GLOBAL_CONFIG_DIR, getToolsccDir } from '../utils/path';
|
|
7
|
-
import fs from 'fs-extra';
|
|
8
|
-
import path from 'path';
|
|
9
|
-
|
|
10
|
-
const SUPPORTED_TOOLS: Record<string, string> = {
|
|
11
|
-
iflow: '.iflow',
|
|
12
|
-
claude: '.claude',
|
|
13
|
-
codebuddy: '.codebuddy',
|
|
14
|
-
opencode: '.opencode'
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export async function handleUse(
|
|
18
|
-
sourceNames: string[],
|
|
19
|
-
options: { projects?: string[] }
|
|
20
|
-
): Promise<void> {
|
|
21
|
-
const projectDir = process.cwd();
|
|
22
|
-
|
|
23
|
-
// 如果没有指定 source,进入交互模式
|
|
24
|
-
if (sourceNames.length === 0) {
|
|
25
|
-
const sources = await listSources(GLOBAL_CONFIG_DIR);
|
|
26
|
-
const sourceList = Object.keys(sources);
|
|
27
|
-
|
|
28
|
-
if (sourceList.length === 0) {
|
|
29
|
-
console.log(chalk.yellow('No sources configured. Use `tools-cc -s add` to add one.'));
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const answers = await inquirer.prompt([
|
|
34
|
-
{
|
|
35
|
-
type: 'checkbox',
|
|
36
|
-
name: 'selectedSources',
|
|
37
|
-
message: 'Select sources to use:',
|
|
38
|
-
choices: sourceList
|
|
39
|
-
}
|
|
40
|
-
]);
|
|
41
|
-
|
|
42
|
-
sourceNames = answers.selectedSources;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (sourceNames.length === 0) {
|
|
46
|
-
console.log(chalk.gray('No sources selected.'));
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 初始化项目
|
|
51
|
-
await initProject(projectDir);
|
|
52
|
-
|
|
53
|
-
// 启用每个配置源
|
|
54
|
-
for (const sourceName of sourceNames) {
|
|
55
|
-
try {
|
|
56
|
-
const sourcePath = await getSourcePath(sourceName, GLOBAL_CONFIG_DIR);
|
|
57
|
-
await useSource(sourceName, sourcePath, projectDir);
|
|
58
|
-
console.log(chalk.green(`✓ Using source: ${sourceName}`));
|
|
59
|
-
} catch (error) {
|
|
60
|
-
console.log(chalk.red(`✗ Failed to use ${sourceName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// 创建符号链接
|
|
65
|
-
const tools = options.projects || Object.keys(SUPPORTED_TOOLS);
|
|
66
|
-
const toolsccDir = getToolsccDir(projectDir);
|
|
67
|
-
|
|
68
|
-
for (const tool of tools) {
|
|
69
|
-
const linkName = SUPPORTED_TOOLS[tool];
|
|
70
|
-
if (!linkName) {
|
|
71
|
-
console.log(chalk.yellow(`Unknown tool: ${tool}`));
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const linkPath = path.join(projectDir, linkName);
|
|
76
|
-
|
|
77
|
-
try {
|
|
78
|
-
await createSymlink(toolsccDir, linkPath, true);
|
|
79
|
-
console.log(chalk.green(`✓ Linked: ${linkName} -> .toolscc`));
|
|
80
|
-
} catch (error) {
|
|
81
|
-
console.log(chalk.red(`✗ Failed to link ${linkName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// 更新项目配置
|
|
86
|
-
const configFile = path.join(projectDir, 'tools-cc.json');
|
|
87
|
-
const config = await fs.readJson(configFile);
|
|
88
|
-
const existingLinks = config.links || [];
|
|
89
|
-
config.links = [...new Set([...existingLinks, ...tools])];
|
|
90
|
-
await fs.writeJson(configFile, config, { spaces: 2 });
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export async function handleList(): Promise<void> {
|
|
94
|
-
const projectDir = process.cwd();
|
|
95
|
-
const sources = await listUsedSources(projectDir);
|
|
96
|
-
|
|
97
|
-
if (sources.length === 0) {
|
|
98
|
-
console.log(chalk.gray('No sources in use. Run `tools-cc use <source-name>` to add one.'));
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
console.log(chalk.bold('Sources in use:'));
|
|
103
|
-
for (const source of sources) {
|
|
104
|
-
console.log(` ${chalk.cyan(source)}`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export async function handleRemove(sourceName: string): Promise<void> {
|
|
109
|
-
const projectDir = process.cwd();
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
await unuseSource(sourceName, projectDir);
|
|
113
|
-
console.log(chalk.green(`✓ Removed source: ${sourceName}`));
|
|
114
|
-
} catch (error) {
|
|
115
|
-
console.log(chalk.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export async function handleStatus(): Promise<void> {
|
|
120
|
-
const projectDir = process.cwd();
|
|
121
|
-
const sources = await listUsedSources(projectDir);
|
|
122
|
-
|
|
123
|
-
console.log(chalk.bold('\nProject Status:'));
|
|
124
|
-
console.log(chalk.gray(` Directory: ${projectDir}`));
|
|
125
|
-
|
|
126
|
-
// 检查 .toolscc
|
|
127
|
-
const toolsccDir = getToolsccDir(projectDir);
|
|
128
|
-
console.log(` .toolscc: ${await fs.pathExists(toolsccDir) ? chalk.green('exists') : chalk.red('not found')}`);
|
|
129
|
-
|
|
130
|
-
// 检查 sources
|
|
131
|
-
console.log(` Sources: ${sources.length > 0 ? sources.map(s => chalk.cyan(s)).join(', ') : chalk.gray('none')}`);
|
|
132
|
-
|
|
133
|
-
// 检查 links
|
|
134
|
-
const configFile = path.join(projectDir, 'tools-cc.json');
|
|
135
|
-
if (await fs.pathExists(configFile)) {
|
|
136
|
-
const config = await fs.readJson(configFile);
|
|
137
|
-
console.log(` Links:`);
|
|
138
|
-
for (const tool of config.links || []) {
|
|
139
|
-
const linkName = SUPPORTED_TOOLS[tool];
|
|
140
|
-
if (!linkName) continue;
|
|
141
|
-
const linkPath = path.join(projectDir, linkName);
|
|
142
|
-
const isLink = await isSymlink(linkPath);
|
|
143
|
-
console.log(` ${tool}: ${isLink ? chalk.green('linked') : chalk.red('not linked')}`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
console.log();
|
|
147
|
-
}
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { useSource, unuseSource, listUsedSources, initProject } from '../core/project';
|
|
4
|
+
import { getSourcePath, listSources } from '../core/source';
|
|
5
|
+
import { createSymlink, isSymlink } from '../core/symlink';
|
|
6
|
+
import { GLOBAL_CONFIG_DIR, getToolsccDir } from '../utils/path';
|
|
7
|
+
import fs from 'fs-extra';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
|
|
10
|
+
const SUPPORTED_TOOLS: Record<string, string> = {
|
|
11
|
+
iflow: '.iflow',
|
|
12
|
+
claude: '.claude',
|
|
13
|
+
codebuddy: '.codebuddy',
|
|
14
|
+
opencode: '.opencode'
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export async function handleUse(
|
|
18
|
+
sourceNames: string[],
|
|
19
|
+
options: { projects?: string[] }
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
const projectDir = process.cwd();
|
|
22
|
+
|
|
23
|
+
// 如果没有指定 source,进入交互模式
|
|
24
|
+
if (sourceNames.length === 0) {
|
|
25
|
+
const sources = await listSources(GLOBAL_CONFIG_DIR);
|
|
26
|
+
const sourceList = Object.keys(sources);
|
|
27
|
+
|
|
28
|
+
if (sourceList.length === 0) {
|
|
29
|
+
console.log(chalk.yellow('No sources configured. Use `tools-cc -s add` to add one.'));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const answers = await inquirer.prompt([
|
|
34
|
+
{
|
|
35
|
+
type: 'checkbox',
|
|
36
|
+
name: 'selectedSources',
|
|
37
|
+
message: 'Select sources to use:',
|
|
38
|
+
choices: sourceList
|
|
39
|
+
}
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
sourceNames = answers.selectedSources;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (sourceNames.length === 0) {
|
|
46
|
+
console.log(chalk.gray('No sources selected.'));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 初始化项目
|
|
51
|
+
await initProject(projectDir);
|
|
52
|
+
|
|
53
|
+
// 启用每个配置源
|
|
54
|
+
for (const sourceName of sourceNames) {
|
|
55
|
+
try {
|
|
56
|
+
const sourcePath = await getSourcePath(sourceName, GLOBAL_CONFIG_DIR);
|
|
57
|
+
await useSource(sourceName, sourcePath, projectDir);
|
|
58
|
+
console.log(chalk.green(`✓ Using source: ${sourceName}`));
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.log(chalk.red(`✗ Failed to use ${sourceName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 创建符号链接
|
|
65
|
+
const tools = options.projects || Object.keys(SUPPORTED_TOOLS);
|
|
66
|
+
const toolsccDir = getToolsccDir(projectDir);
|
|
67
|
+
|
|
68
|
+
for (const tool of tools) {
|
|
69
|
+
const linkName = SUPPORTED_TOOLS[tool];
|
|
70
|
+
if (!linkName) {
|
|
71
|
+
console.log(chalk.yellow(`Unknown tool: ${tool}`));
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const linkPath = path.join(projectDir, linkName);
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
await createSymlink(toolsccDir, linkPath, true);
|
|
79
|
+
console.log(chalk.green(`✓ Linked: ${linkName} -> .toolscc`));
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.log(chalk.red(`✗ Failed to link ${linkName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 更新项目配置
|
|
86
|
+
const configFile = path.join(projectDir, 'tools-cc.json');
|
|
87
|
+
const config = await fs.readJson(configFile);
|
|
88
|
+
const existingLinks = config.links || [];
|
|
89
|
+
config.links = [...new Set([...existingLinks, ...tools])];
|
|
90
|
+
await fs.writeJson(configFile, config, { spaces: 2 });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function handleList(): Promise<void> {
|
|
94
|
+
const projectDir = process.cwd();
|
|
95
|
+
const sources = await listUsedSources(projectDir);
|
|
96
|
+
|
|
97
|
+
if (sources.length === 0) {
|
|
98
|
+
console.log(chalk.gray('No sources in use. Run `tools-cc use <source-name>` to add one.'));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log(chalk.bold('Sources in use:'));
|
|
103
|
+
for (const source of sources) {
|
|
104
|
+
console.log(` ${chalk.cyan(source)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function handleRemove(sourceName: string): Promise<void> {
|
|
109
|
+
const projectDir = process.cwd();
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
await unuseSource(sourceName, projectDir);
|
|
113
|
+
console.log(chalk.green(`✓ Removed source: ${sourceName}`));
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.log(chalk.red(`✗ ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function handleStatus(): Promise<void> {
|
|
120
|
+
const projectDir = process.cwd();
|
|
121
|
+
const sources = await listUsedSources(projectDir);
|
|
122
|
+
|
|
123
|
+
console.log(chalk.bold('\nProject Status:'));
|
|
124
|
+
console.log(chalk.gray(` Directory: ${projectDir}`));
|
|
125
|
+
|
|
126
|
+
// 检查 .toolscc
|
|
127
|
+
const toolsccDir = getToolsccDir(projectDir);
|
|
128
|
+
console.log(` .toolscc: ${await fs.pathExists(toolsccDir) ? chalk.green('exists') : chalk.red('not found')}`);
|
|
129
|
+
|
|
130
|
+
// 检查 sources
|
|
131
|
+
console.log(` Sources: ${sources.length > 0 ? sources.map(s => chalk.cyan(s)).join(', ') : chalk.gray('none')}`);
|
|
132
|
+
|
|
133
|
+
// 检查 links
|
|
134
|
+
const configFile = path.join(projectDir, 'tools-cc.json');
|
|
135
|
+
if (await fs.pathExists(configFile)) {
|
|
136
|
+
const config = await fs.readJson(configFile);
|
|
137
|
+
console.log(` Links:`);
|
|
138
|
+
for (const tool of config.links || []) {
|
|
139
|
+
const linkName = SUPPORTED_TOOLS[tool];
|
|
140
|
+
if (!linkName) continue;
|
|
141
|
+
const linkPath = path.join(projectDir, linkName);
|
|
142
|
+
const isLink = await isSymlink(linkPath);
|
|
143
|
+
console.log(` ${tool}: ${isLink ? chalk.green('linked') : chalk.red('not linked')}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
console.log();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function handleProjectUpdate(sourceNames?: string[]): Promise<void> {
|
|
150
|
+
const projectDir = process.cwd();
|
|
151
|
+
const configFile = path.join(projectDir, 'tools-cc.json');
|
|
152
|
+
|
|
153
|
+
// 检查项目是否已初始化
|
|
154
|
+
if (!(await fs.pathExists(configFile))) {
|
|
155
|
+
console.log(chalk.yellow('Project not initialized. Run `tools-cc use <source>` first.'));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const config = await fs.readJson(configFile);
|
|
160
|
+
let sourcesToUpdate = sourceNames && sourceNames.length > 0
|
|
161
|
+
? sourceNames
|
|
162
|
+
: config.sources || [];
|
|
163
|
+
|
|
164
|
+
if (sourcesToUpdate.length === 0) {
|
|
165
|
+
console.log(chalk.gray('No sources to update.'));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 验证指定的源是否存在于项目配置中
|
|
170
|
+
if (sourceNames && sourceNames.length > 0) {
|
|
171
|
+
const invalidSources = sourceNames.filter((s: string) => !config.sources.includes(s));
|
|
172
|
+
if (invalidSources.length > 0) {
|
|
173
|
+
console.log(chalk.yellow(`Sources not in project: ${invalidSources.join(', ')}`));
|
|
174
|
+
}
|
|
175
|
+
sourcesToUpdate = sourcesToUpdate.filter((s: string) => config.sources.includes(s));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 更新每个配置源
|
|
179
|
+
for (const sourceName of sourcesToUpdate) {
|
|
180
|
+
try {
|
|
181
|
+
const sourcePath = await getSourcePath(sourceName, GLOBAL_CONFIG_DIR);
|
|
182
|
+
await useSource(sourceName, sourcePath, projectDir);
|
|
183
|
+
console.log(chalk.green(`✓ Updated source: ${sourceName}`));
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.log(chalk.red(`✗ Failed to update ${sourceName}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log(chalk.green(`\n✓ Project update complete`));
|
|
190
|
+
}
|
package/src/core/source.ts
CHANGED
|
@@ -1,100 +1,184 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { execSync } from 'child_process';
|
|
4
|
-
import { loadGlobalConfig, saveGlobalConfig } from './config';
|
|
5
|
-
import { SourceConfig } from '../types';
|
|
6
|
-
|
|
7
|
-
export async function addSource(
|
|
8
|
-
name: string,
|
|
9
|
-
sourcePath: string,
|
|
10
|
-
configDir: string
|
|
11
|
-
): Promise<SourceConfig> {
|
|
12
|
-
const config = await loadGlobalConfig(configDir);
|
|
13
|
-
|
|
14
|
-
// 判断是 git url 还是本地路径
|
|
15
|
-
const isGit = sourcePath.startsWith('http') || sourcePath.startsWith('git@');
|
|
16
|
-
|
|
17
|
-
let sourceConfig: SourceConfig;
|
|
18
|
-
|
|
19
|
-
if (isGit) {
|
|
20
|
-
// Clone git repo
|
|
21
|
-
const cloneDir = path.join(config.sourcesDir, name);
|
|
22
|
-
console.log(`Cloning ${sourcePath} to ${cloneDir}...`);
|
|
23
|
-
|
|
24
|
-
await fs.ensureDir(config.sourcesDir);
|
|
25
|
-
execSync(`git clone ${sourcePath} "${cloneDir}"`, { stdio: 'inherit' });
|
|
26
|
-
|
|
27
|
-
sourceConfig = { type: 'git', url: sourcePath };
|
|
28
|
-
} else {
|
|
29
|
-
// 本地路径
|
|
30
|
-
const absolutePath = path.resolve(sourcePath);
|
|
31
|
-
if (!(await fs.pathExists(absolutePath))) {
|
|
32
|
-
throw new Error(`Path does not exist: ${absolutePath}`);
|
|
33
|
-
}
|
|
34
|
-
sourceConfig = { type: 'local', path: absolutePath };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
config.sources[name] = sourceConfig;
|
|
38
|
-
await saveGlobalConfig(config, configDir);
|
|
39
|
-
|
|
40
|
-
return sourceConfig;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function listSources(configDir: string): Promise<Record<string, SourceConfig>> {
|
|
44
|
-
const config = await loadGlobalConfig(configDir);
|
|
45
|
-
return config.sources;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export async function removeSource(name: string, configDir: string): Promise<void> {
|
|
49
|
-
const config = await loadGlobalConfig(configDir);
|
|
50
|
-
|
|
51
|
-
if (!config.sources[name]) {
|
|
52
|
-
throw new Error(`Source not found: ${name}`);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const source = config.sources[name];
|
|
56
|
-
|
|
57
|
-
// 如果是 git 类型,清理克隆目录
|
|
58
|
-
if (source.type === 'git') {
|
|
59
|
-
const cloneDir = path.join(config.sourcesDir, name);
|
|
60
|
-
if (await fs.pathExists(cloneDir)) {
|
|
61
|
-
console.log(`Removing cloned directory: ${cloneDir}`);
|
|
62
|
-
await fs.remove(cloneDir);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
delete config.sources[name];
|
|
67
|
-
await saveGlobalConfig(config, configDir);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export async function updateSource(name: string, configDir: string): Promise<void> {
|
|
71
|
-
const config = await loadGlobalConfig(configDir);
|
|
72
|
-
const source = config.sources[name];
|
|
73
|
-
|
|
74
|
-
if (!source) {
|
|
75
|
-
throw new Error(`Source not found: ${name}`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (source.type === 'git') {
|
|
79
|
-
const cloneDir = path.join(config.sourcesDir, name);
|
|
80
|
-
console.log(`Updating ${name}...`);
|
|
81
|
-
execSync(`git -C "${cloneDir}" pull`, { stdio: 'inherit' });
|
|
82
|
-
} else {
|
|
83
|
-
console.log(`Source ${name} is local, no update needed.`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export async function getSourcePath(name: string, configDir: string): Promise<string> {
|
|
88
|
-
const config = await loadGlobalConfig(configDir);
|
|
89
|
-
const source = config.sources[name];
|
|
90
|
-
|
|
91
|
-
if (!source) {
|
|
92
|
-
throw new Error(`Source not found: ${name}`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (source.type === 'local') {
|
|
96
|
-
return source.path!;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return path.join(config.sourcesDir, name);
|
|
100
|
-
}
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import { loadGlobalConfig, saveGlobalConfig } from './config';
|
|
5
|
+
import { SourceConfig } from '../types';
|
|
6
|
+
|
|
7
|
+
export async function addSource(
|
|
8
|
+
name: string,
|
|
9
|
+
sourcePath: string,
|
|
10
|
+
configDir: string
|
|
11
|
+
): Promise<SourceConfig> {
|
|
12
|
+
const config = await loadGlobalConfig(configDir);
|
|
13
|
+
|
|
14
|
+
// 判断是 git url 还是本地路径
|
|
15
|
+
const isGit = sourcePath.startsWith('http') || sourcePath.startsWith('git@');
|
|
16
|
+
|
|
17
|
+
let sourceConfig: SourceConfig;
|
|
18
|
+
|
|
19
|
+
if (isGit) {
|
|
20
|
+
// Clone git repo
|
|
21
|
+
const cloneDir = path.join(config.sourcesDir, name);
|
|
22
|
+
console.log(`Cloning ${sourcePath} to ${cloneDir}...`);
|
|
23
|
+
|
|
24
|
+
await fs.ensureDir(config.sourcesDir);
|
|
25
|
+
execSync(`git clone ${sourcePath} "${cloneDir}"`, { stdio: 'inherit' });
|
|
26
|
+
|
|
27
|
+
sourceConfig = { type: 'git', url: sourcePath };
|
|
28
|
+
} else {
|
|
29
|
+
// 本地路径
|
|
30
|
+
const absolutePath = path.resolve(sourcePath);
|
|
31
|
+
if (!(await fs.pathExists(absolutePath))) {
|
|
32
|
+
throw new Error(`Path does not exist: ${absolutePath}`);
|
|
33
|
+
}
|
|
34
|
+
sourceConfig = { type: 'local', path: absolutePath };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
config.sources[name] = sourceConfig;
|
|
38
|
+
await saveGlobalConfig(config, configDir);
|
|
39
|
+
|
|
40
|
+
return sourceConfig;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function listSources(configDir: string): Promise<Record<string, SourceConfig>> {
|
|
44
|
+
const config = await loadGlobalConfig(configDir);
|
|
45
|
+
return config.sources;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function removeSource(name: string, configDir: string): Promise<void> {
|
|
49
|
+
const config = await loadGlobalConfig(configDir);
|
|
50
|
+
|
|
51
|
+
if (!config.sources[name]) {
|
|
52
|
+
throw new Error(`Source not found: ${name}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const source = config.sources[name];
|
|
56
|
+
|
|
57
|
+
// 如果是 git 类型,清理克隆目录
|
|
58
|
+
if (source.type === 'git') {
|
|
59
|
+
const cloneDir = path.join(config.sourcesDir, name);
|
|
60
|
+
if (await fs.pathExists(cloneDir)) {
|
|
61
|
+
console.log(`Removing cloned directory: ${cloneDir}`);
|
|
62
|
+
await fs.remove(cloneDir);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
delete config.sources[name];
|
|
67
|
+
await saveGlobalConfig(config, configDir);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function updateSource(name: string, configDir: string): Promise<void> {
|
|
71
|
+
const config = await loadGlobalConfig(configDir);
|
|
72
|
+
const source = config.sources[name];
|
|
73
|
+
|
|
74
|
+
if (!source) {
|
|
75
|
+
throw new Error(`Source not found: ${name}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (source.type === 'git') {
|
|
79
|
+
const cloneDir = path.join(config.sourcesDir, name);
|
|
80
|
+
console.log(`Updating ${name}...`);
|
|
81
|
+
execSync(`git -C "${cloneDir}" pull`, { stdio: 'inherit' });
|
|
82
|
+
} else {
|
|
83
|
+
console.log(`Source ${name} is local, no update needed.`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function getSourcePath(name: string, configDir: string): Promise<string> {
|
|
88
|
+
const config = await loadGlobalConfig(configDir);
|
|
89
|
+
const source = config.sources[name];
|
|
90
|
+
|
|
91
|
+
if (!source) {
|
|
92
|
+
throw new Error(`Source not found: ${name}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (source.type === 'local') {
|
|
96
|
+
return source.path!;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return path.join(config.sourcesDir, name);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ScanResult {
|
|
103
|
+
added: string[];
|
|
104
|
+
updated: string[];
|
|
105
|
+
skipped: string[];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function scanSources(configDir: string): Promise<ScanResult> {
|
|
109
|
+
const config = await loadGlobalConfig(configDir);
|
|
110
|
+
const result: ScanResult = { added: [], updated: [], skipped: [] };
|
|
111
|
+
|
|
112
|
+
// 确保 sourcesDir 存在
|
|
113
|
+
if (!(await fs.pathExists(config.sourcesDir))) {
|
|
114
|
+
await fs.ensureDir(config.sourcesDir);
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 获取 sourcesDir 下的所有文件夹
|
|
119
|
+
const entries = await fs.readdir(config.sourcesDir, { withFileTypes: true });
|
|
120
|
+
const directories = entries
|
|
121
|
+
.filter(entry => entry.isDirectory())
|
|
122
|
+
.map(entry => entry.name);
|
|
123
|
+
|
|
124
|
+
// 检查每个 git source 的克隆目录是否还存在
|
|
125
|
+
for (const [name, source] of Object.entries(config.sources)) {
|
|
126
|
+
if (source.type === 'git') {
|
|
127
|
+
const cloneDir = path.join(config.sourcesDir, name);
|
|
128
|
+
if (!(await fs.pathExists(cloneDir))) {
|
|
129
|
+
// 克隆目录不存在,从配置中移除
|
|
130
|
+
delete config.sources[name];
|
|
131
|
+
console.log(`Removed missing git source: ${name}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 扫描目录并更新配置
|
|
137
|
+
for (const dirName of directories) {
|
|
138
|
+
const dirPath = path.join(config.sourcesDir, dirName);
|
|
139
|
+
const existingSource = config.sources[dirName];
|
|
140
|
+
|
|
141
|
+
// 检查是否是 git 仓库
|
|
142
|
+
const isGitRepo = await fs.pathExists(path.join(dirPath, '.git'));
|
|
143
|
+
|
|
144
|
+
if (existingSource) {
|
|
145
|
+
// 已存在配置
|
|
146
|
+
if (existingSource.type === 'git' && isGitRepo) {
|
|
147
|
+
result.skipped.push(dirName);
|
|
148
|
+
} else if (existingSource.type === 'local' && existingSource.path === dirPath) {
|
|
149
|
+
result.skipped.push(dirName);
|
|
150
|
+
} else {
|
|
151
|
+
// 配置不一致,更新为当前状态
|
|
152
|
+
if (isGitRepo) {
|
|
153
|
+
// 尝试获取远程 URL
|
|
154
|
+
try {
|
|
155
|
+
const remoteUrl = execSync(`git -C "${dirPath}" config --get remote.origin.url`, { encoding: 'utf-8' }).trim();
|
|
156
|
+
config.sources[dirName] = { type: 'git', url: remoteUrl };
|
|
157
|
+
} catch {
|
|
158
|
+
config.sources[dirName] = { type: 'local', path: dirPath };
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
config.sources[dirName] = { type: 'local', path: dirPath };
|
|
162
|
+
}
|
|
163
|
+
result.updated.push(dirName);
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
// 新发现的目录,添加到配置
|
|
167
|
+
if (isGitRepo) {
|
|
168
|
+
// 尝试获取远程 URL
|
|
169
|
+
try {
|
|
170
|
+
const remoteUrl = execSync(`git -C "${dirPath}" config --get remote.origin.url`, { encoding: 'utf-8' }).trim();
|
|
171
|
+
config.sources[dirName] = { type: 'git', url: remoteUrl };
|
|
172
|
+
} catch {
|
|
173
|
+
config.sources[dirName] = { type: 'local', path: dirPath };
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
config.sources[dirName] = { type: 'local', path: dirPath };
|
|
177
|
+
}
|
|
178
|
+
result.added.push(dirName);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
await saveGlobalConfig(config, configDir);
|
|
183
|
+
return result;
|
|
184
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import { handleConfigSet, handleConfigGet, handleConfigList } from './commands/config';
|
|
5
|
-
import { handleSourceAdd, handleSourceList, handleSourceRemove, handleSourceUpdate } from './commands/source';
|
|
6
|
-
import { handleUse, handleList, handleRemove, handleStatus } from './commands/use';
|
|
5
|
+
import { handleSourceAdd, handleSourceList, handleSourceRemove, handleSourceUpdate, handleSourceScan } from './commands/source';
|
|
6
|
+
import { handleUse, handleList, handleRemove, handleStatus, handleProjectUpdate } from './commands/use';
|
|
7
7
|
import { showHelp } from './commands/help';
|
|
8
8
|
import { GLOBAL_CONFIG_DIR } from './utils/path';
|
|
9
9
|
|
|
@@ -50,11 +50,19 @@ sourceCmd
|
|
|
50
50
|
sourceCmd
|
|
51
51
|
.command('update [name]')
|
|
52
52
|
.alias('up')
|
|
53
|
-
.
|
|
53
|
+
.alias('upgrade')
|
|
54
|
+
.description('Update source(s) with git pull')
|
|
54
55
|
.action(async (name?: string) => {
|
|
55
56
|
await handleSourceUpdate(name);
|
|
56
57
|
});
|
|
57
58
|
|
|
59
|
+
sourceCmd
|
|
60
|
+
.command('scan')
|
|
61
|
+
.description('Scan sources directory and update configuration')
|
|
62
|
+
.action(async () => {
|
|
63
|
+
await handleSourceScan();
|
|
64
|
+
});
|
|
65
|
+
|
|
58
66
|
// Config subcommands (full command version)
|
|
59
67
|
const configCmd = program
|
|
60
68
|
.command('config')
|
|
@@ -111,6 +119,13 @@ program
|
|
|
111
119
|
await handleStatus();
|
|
112
120
|
});
|
|
113
121
|
|
|
122
|
+
program
|
|
123
|
+
.command('update [sources...]')
|
|
124
|
+
.description('Update source(s) in current project')
|
|
125
|
+
.action(async (sources: string[]) => {
|
|
126
|
+
await handleProjectUpdate(sources);
|
|
127
|
+
});
|
|
128
|
+
|
|
114
129
|
// Help command
|
|
115
130
|
program
|
|
116
131
|
.command('help')
|
|
@@ -147,8 +162,12 @@ program
|
|
|
147
162
|
break;
|
|
148
163
|
case 'update':
|
|
149
164
|
case 'up':
|
|
165
|
+
case 'upgrade':
|
|
150
166
|
await handleSourceUpdate(args[0]);
|
|
151
167
|
break;
|
|
168
|
+
case 'scan':
|
|
169
|
+
await handleSourceScan();
|
|
170
|
+
break;
|
|
152
171
|
default:
|
|
153
172
|
console.log(`Unknown source command: ${cmd}`);
|
|
154
173
|
}
|