grf-cli 1.0.0
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/LICENSE +21 -0
- package/README.md +503 -0
- package/README.zh-CN.md +503 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +74 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/add.d.ts +7 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +92 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/clean.d.ts +7 -0
- package/dist/commands/clean.d.ts.map +1 -0
- package/dist/commands/clean.js +171 -0
- package/dist/commands/clean.js.map +1 -0
- package/dist/commands/config.d.ts +7 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +170 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/list.d.ts +7 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +149 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/load.d.ts +8 -0
- package/dist/commands/load.d.ts.map +1 -0
- package/dist/commands/load.js +239 -0
- package/dist/commands/load.js.map +1 -0
- package/dist/commands/unload.d.ts +7 -0
- package/dist/commands/unload.d.ts.map +1 -0
- package/dist/commands/unload.js +738 -0
- package/dist/commands/unload.js.map +1 -0
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +471 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/use.d.ts +8 -0
- package/dist/commands/use.d.ts.map +1 -0
- package/dist/commands/use.js +224 -0
- package/dist/commands/use.js.map +1 -0
- package/dist/core/config.d.ts +55 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +179 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/filesystem.d.ts +74 -0
- package/dist/core/filesystem.d.ts.map +1 -0
- package/dist/core/filesystem.js +300 -0
- package/dist/core/filesystem.js.map +1 -0
- package/dist/core/git.d.ts +75 -0
- package/dist/core/git.d.ts.map +1 -0
- package/dist/core/git.js +169 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/loading.d.ts +85 -0
- package/dist/core/loading.d.ts.map +1 -0
- package/dist/core/loading.js +224 -0
- package/dist/core/loading.js.map +1 -0
- package/dist/core/repository.d.ts +120 -0
- package/dist/core/repository.d.ts.map +1 -0
- package/dist/core/repository.js +374 -0
- package/dist/core/repository.js.map +1 -0
- package/dist/core/sync.d.ts +72 -0
- package/dist/core/sync.d.ts.map +1 -0
- package/dist/core/sync.js +226 -0
- package/dist/core/sync.js.map +1 -0
- package/dist/types/index.d.ts +135 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +63 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* unload 命令
|
|
4
|
+
* 移除当前项目中已加载的参考代码(use 命令的逆操作)
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.unloadCommand = void 0;
|
|
44
|
+
const commander_1 = require("commander");
|
|
45
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
46
|
+
const ora_1 = __importDefault(require("ora"));
|
|
47
|
+
const path_1 = __importDefault(require("path"));
|
|
48
|
+
const readline = __importStar(require("readline"));
|
|
49
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
50
|
+
const filesystem = __importStar(require("../core/filesystem.js"));
|
|
51
|
+
const loading = __importStar(require("../core/loading.js"));
|
|
52
|
+
const index_js_1 = require("../types/index.js");
|
|
53
|
+
/** .gitreference 目录名 */
|
|
54
|
+
const GITREFERENCE_DIR = ".gitreference";
|
|
55
|
+
/**
|
|
56
|
+
* 从 loading.json 获取所有已加载仓库的目标路径
|
|
57
|
+
* @param projectRoot 项目根目录
|
|
58
|
+
* @returns 由 gitreference 管理的路径列表
|
|
59
|
+
*/
|
|
60
|
+
async function getGitreferenceManagedPaths(projectRoot) {
|
|
61
|
+
const entries = await loading.getEntries(projectRoot);
|
|
62
|
+
return entries.map((entry) => entry.targetPath);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 填充字符串到指定宽度
|
|
66
|
+
* @param str 原始字符串
|
|
67
|
+
* @param width 目标宽度
|
|
68
|
+
* @returns 填充后的字符串
|
|
69
|
+
*/
|
|
70
|
+
function padEnd(str, width) {
|
|
71
|
+
if (str.length >= width)
|
|
72
|
+
return str;
|
|
73
|
+
return str + " ".repeat(width - str.length);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 确认提示
|
|
77
|
+
* @param message 提示消息
|
|
78
|
+
* @returns 用户是否确认
|
|
79
|
+
*/
|
|
80
|
+
async function confirm(message) {
|
|
81
|
+
const rl = readline.createInterface({
|
|
82
|
+
input: process.stdin,
|
|
83
|
+
output: process.stdout,
|
|
84
|
+
});
|
|
85
|
+
return new Promise((resolve) => {
|
|
86
|
+
rl.question(`${message} (y/N) `, (answer) => {
|
|
87
|
+
rl.close();
|
|
88
|
+
resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 从 loading.json 获取所有已加载的仓库条目
|
|
94
|
+
* @param projectRoot 项目根目录
|
|
95
|
+
* @returns 加载条目列表
|
|
96
|
+
*/
|
|
97
|
+
async function getLoadedEntries(projectRoot) {
|
|
98
|
+
return await loading.getEntries(projectRoot);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 递归扫描 .gitreference 目录,获取空目录列表
|
|
102
|
+
* @param baseDir .gitreference 目录的绝对路径
|
|
103
|
+
* @param currentPath 当前扫描的相对路径
|
|
104
|
+
* @param loadedPaths 已加载的路径列表(用于排除)
|
|
105
|
+
* @returns 空目录列表
|
|
106
|
+
*/
|
|
107
|
+
async function scanEmptyDirs(baseDir, currentPath = "", loadedPaths = new Set()) {
|
|
108
|
+
const emptyDirs = [];
|
|
109
|
+
const fullCurrentPath = path_1.default.join(baseDir, currentPath);
|
|
110
|
+
try {
|
|
111
|
+
const entries = await filesystem.readDir(fullCurrentPath);
|
|
112
|
+
// 如果目录为空,标记为空目录
|
|
113
|
+
if (entries.length === 0 && currentPath) {
|
|
114
|
+
emptyDirs.push({
|
|
115
|
+
fullPath: currentPath,
|
|
116
|
+
absolutePath: fullCurrentPath,
|
|
117
|
+
});
|
|
118
|
+
return emptyDirs;
|
|
119
|
+
}
|
|
120
|
+
// 检查当前目录是否包含非目录文件
|
|
121
|
+
let hasFiles = false;
|
|
122
|
+
const subdirs = [];
|
|
123
|
+
for (const entry of entries) {
|
|
124
|
+
const entryPath = path_1.default.join(fullCurrentPath, entry);
|
|
125
|
+
try {
|
|
126
|
+
const isDir = await filesystem.isDirectory(entryPath);
|
|
127
|
+
if (isDir) {
|
|
128
|
+
subdirs.push(entry);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
hasFiles = true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// 忽略无法访问的条目
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// 如果当前目录没有文件,继续递归扫描子目录
|
|
139
|
+
if (!hasFiles) {
|
|
140
|
+
for (const subdir of subdirs) {
|
|
141
|
+
const subPath = currentPath ? path_1.default.join(currentPath, subdir) : subdir;
|
|
142
|
+
const subEmptyDirs = await scanEmptyDirs(baseDir, subPath, loadedPaths);
|
|
143
|
+
emptyDirs.push(...subEmptyDirs);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// 目录不存在或无法访问
|
|
149
|
+
}
|
|
150
|
+
return emptyDirs;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 匹配仓库条目
|
|
154
|
+
* @param entries 加载条目列表
|
|
155
|
+
* @param name 要匹配的名称(可以是 repoName、targetPath 或部分路径)
|
|
156
|
+
* @returns 匹配的条目列表
|
|
157
|
+
*/
|
|
158
|
+
function matchEntries(entries, name) {
|
|
159
|
+
const normalizedName = name.replace(/\\/g, "/");
|
|
160
|
+
return entries.filter((entry) => {
|
|
161
|
+
const repoName = entry.repoName.replace(/\\/g, "/");
|
|
162
|
+
const targetPath = entry.targetPath.replace(/\\/g, "/");
|
|
163
|
+
// 完整仓库名匹配: github.com/facebook/react
|
|
164
|
+
if (repoName === normalizedName) {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
// 完整目标路径匹配
|
|
168
|
+
if (targetPath === normalizedName) {
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
// 仓库短名匹配: react -> 匹配所有名为 react 的仓库
|
|
172
|
+
const shortName = path_1.default.basename(repoName);
|
|
173
|
+
if (shortName === normalizedName) {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
// 部分路径匹配: facebook/react -> 匹配 */facebook/react
|
|
177
|
+
if (repoName.endsWith("/" + normalizedName)) {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
// 目标路径部分匹配
|
|
181
|
+
if (targetPath.endsWith("/" + normalizedName)) {
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* 递归删除空的父目录
|
|
189
|
+
* @param dirPath 起始目录路径
|
|
190
|
+
* @param stopAt 停止删除的目录(不会删除此目录)
|
|
191
|
+
* @param verbose 是否输出详细信息
|
|
192
|
+
*/
|
|
193
|
+
async function removeEmptyParents(dirPath, stopAt, verbose = false) {
|
|
194
|
+
let currentDir = path_1.default.dirname(dirPath);
|
|
195
|
+
while (currentDir !== stopAt && currentDir.startsWith(stopAt)) {
|
|
196
|
+
try {
|
|
197
|
+
const entries = await filesystem.readDir(currentDir);
|
|
198
|
+
if (entries.length === 0) {
|
|
199
|
+
if (verbose) {
|
|
200
|
+
console.log(chalk_1.default.gray(` 清理空目录: ${path_1.default.relative(stopAt, currentDir)}`));
|
|
201
|
+
}
|
|
202
|
+
await filesystem.removeDir(currentDir);
|
|
203
|
+
currentDir = path_1.default.dirname(currentDir);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* 清理所有空目录
|
|
216
|
+
* @param gitrefDir .gitreference 目录的绝对路径
|
|
217
|
+
* @param emptyDirs 空目录列表
|
|
218
|
+
* @param verbose 是否输出详细信息
|
|
219
|
+
* @returns 清理的目录数量
|
|
220
|
+
*/
|
|
221
|
+
async function cleanEmptyDirectories(gitrefDir, emptyDirs, verbose = false) {
|
|
222
|
+
let cleanedCount = 0;
|
|
223
|
+
// 按路径深度排序,先删除最深的目录
|
|
224
|
+
const sortedDirs = [...emptyDirs].sort((a, b) => {
|
|
225
|
+
const depthA = a.fullPath.split(path_1.default.sep).length;
|
|
226
|
+
const depthB = b.fullPath.split(path_1.default.sep).length;
|
|
227
|
+
return depthB - depthA;
|
|
228
|
+
});
|
|
229
|
+
for (const dir of sortedDirs) {
|
|
230
|
+
try {
|
|
231
|
+
// 检查目录是否仍然存在且为空
|
|
232
|
+
if (await filesystem.exists(dir.absolutePath)) {
|
|
233
|
+
const entries = await filesystem.readDir(dir.absolutePath);
|
|
234
|
+
if (entries.length === 0) {
|
|
235
|
+
if (verbose) {
|
|
236
|
+
console.log(chalk_1.default.gray(` 删除空目录: ${dir.fullPath.replace(/\\/g, "/")}`));
|
|
237
|
+
}
|
|
238
|
+
await filesystem.removeDir(dir.absolutePath);
|
|
239
|
+
cleanedCount++;
|
|
240
|
+
// 递归清理空的父目录
|
|
241
|
+
await removeEmptyParents(dir.absolutePath, gitrefDir, verbose);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// 忽略删除失败的目录
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return cleanedCount;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* 交互式选择仓库条目
|
|
253
|
+
* @param matches 匹配的条目列表
|
|
254
|
+
* @param name 用户输入的名称
|
|
255
|
+
* @returns 用户选择的条目,如果取消则返回 null
|
|
256
|
+
*/
|
|
257
|
+
async function selectEntry(matches, name) {
|
|
258
|
+
console.log(chalk_1.default.yellow(`找到 ${matches.length} 个匹配 '${name}' 的参考代码:`));
|
|
259
|
+
console.log();
|
|
260
|
+
try {
|
|
261
|
+
const selected = await (0, prompts_1.select)({
|
|
262
|
+
message: "请选择要删除的参考代码:",
|
|
263
|
+
choices: [
|
|
264
|
+
...matches.map((match) => ({
|
|
265
|
+
name: `${match.repoName} -> ${match.targetPath}`,
|
|
266
|
+
value: match,
|
|
267
|
+
})),
|
|
268
|
+
{
|
|
269
|
+
name: chalk_1.default.gray("取消"),
|
|
270
|
+
value: null,
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
});
|
|
274
|
+
return selected;
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
// 用户按 Ctrl+C 取消
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* 详细模式下删除目录
|
|
283
|
+
* @param dirPath 要删除的目录路径
|
|
284
|
+
* @param displayPath 显示用的路径
|
|
285
|
+
* @param verbose 是否输出详细信息
|
|
286
|
+
*/
|
|
287
|
+
async function removeDirVerbose(dirPath, displayPath, verbose) {
|
|
288
|
+
if (verbose) {
|
|
289
|
+
console.log(chalk_1.default.gray(` 删除目录: ${displayPath}`));
|
|
290
|
+
}
|
|
291
|
+
await filesystem.removeDir(dirPath);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* 清理 .gitignore 中的参考代码路径条目
|
|
295
|
+
* @param targetPath 被删除的目标路径(相对于工作目录)
|
|
296
|
+
* @param gitreferenceDirExists .gitreference 目录是否仍然存在
|
|
297
|
+
* @param verbose 是否输出详细信息
|
|
298
|
+
*/
|
|
299
|
+
async function cleanupGitignore(targetPath, gitreferenceDirExists, verbose = false) {
|
|
300
|
+
const cwd = process.cwd();
|
|
301
|
+
// 如果删除的是 .gitreference 目录下的内容
|
|
302
|
+
if (targetPath.startsWith(".gitreference") ||
|
|
303
|
+
targetPath.startsWith(GITREFERENCE_DIR)) {
|
|
304
|
+
// 只有当 .gitreference 目录不存在或为空时,才移除 .gitignore 中的 .gitreference/ 条目
|
|
305
|
+
if (!gitreferenceDirExists) {
|
|
306
|
+
const removed = await filesystem.removeFromGitignore(cwd, ".gitreference/");
|
|
307
|
+
if (removed && verbose) {
|
|
308
|
+
console.log(chalk_1.default.gray(" 已从 .gitignore 中移除 .gitreference/"));
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
// 如果是自定义路径,直接移除对应的 .gitignore 条目
|
|
314
|
+
const gitignoreEntry = targetPath.endsWith("/")
|
|
315
|
+
? targetPath
|
|
316
|
+
: targetPath + "/";
|
|
317
|
+
const removed = await filesystem.removeFromGitignore(cwd, gitignoreEntry);
|
|
318
|
+
if (removed && verbose) {
|
|
319
|
+
console.log(chalk_1.default.gray(` 已从 .gitignore 中移除 ${gitignoreEntry}`));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
exports.unloadCommand = new commander_1.Command("unload")
|
|
324
|
+
.description("Remove reference code from current project")
|
|
325
|
+
.argument("[name]", "Repository name to remove")
|
|
326
|
+
.option("-a, --all", "Remove all reference code")
|
|
327
|
+
.option("-f, --force", "Skip confirmation prompt")
|
|
328
|
+
.option("--dry-run", "Show what would be deleted without actually deleting")
|
|
329
|
+
.option("-l, --list", "List all loaded reference code")
|
|
330
|
+
.option("--keep-empty", "Keep empty .gitreference/ directory after removal")
|
|
331
|
+
.option("--clean-empty", "Clean empty directory structures in .gitreference/")
|
|
332
|
+
.option("-v, --verbose", "Show detailed deletion progress")
|
|
333
|
+
.action(async (name, options) => {
|
|
334
|
+
const startTime = Date.now();
|
|
335
|
+
try {
|
|
336
|
+
const cwd = process.cwd();
|
|
337
|
+
const gitrefDir = path_1.default.join(cwd, GITREFERENCE_DIR);
|
|
338
|
+
// 获取所有已加载的条目
|
|
339
|
+
const loadedEntries = await getLoadedEntries(cwd);
|
|
340
|
+
// 检查 .gitreference 目录是否存在(用于空目录扫描)
|
|
341
|
+
const gitrefDirExists = await filesystem.exists(gitrefDir);
|
|
342
|
+
// 扫描空目录(仅当 .gitreference 目录存在时)
|
|
343
|
+
const loadedPaths = new Set(loadedEntries.map((e) => e.targetPath));
|
|
344
|
+
const emptyDirs = gitrefDirExists
|
|
345
|
+
? await scanEmptyDirs(gitrefDir, "", loadedPaths)
|
|
346
|
+
: [];
|
|
347
|
+
// 情况 0: --clean-empty 选项,清理空目录
|
|
348
|
+
if (options.cleanEmpty) {
|
|
349
|
+
if (emptyDirs.length === 0) {
|
|
350
|
+
console.log(chalk_1.default.green("没有需要清理的空目录。"));
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
console.log(`发现 ${chalk_1.default.bold(emptyDirs.length)} 个空目录:`);
|
|
354
|
+
for (const dir of emptyDirs) {
|
|
355
|
+
console.log(` - ${dir.fullPath.replace(/\\/g, "/")}`);
|
|
356
|
+
}
|
|
357
|
+
console.log();
|
|
358
|
+
// dry-run 模式
|
|
359
|
+
if (options.dryRun) {
|
|
360
|
+
console.log(chalk_1.default.yellow("(试运行模式,未执行实际删除)"));
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
// 确认删除
|
|
364
|
+
if (!options.force) {
|
|
365
|
+
const confirmed = await confirm("确定要清理这些空目录吗?");
|
|
366
|
+
if (!confirmed) {
|
|
367
|
+
console.log(chalk_1.default.yellow("操作已取消。"));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// 执行清理
|
|
372
|
+
const spinner = (0, ora_1.default)("正在清理空目录...").start();
|
|
373
|
+
try {
|
|
374
|
+
const cleanedCount = await cleanEmptyDirectories(gitrefDir, emptyDirs, options.verbose);
|
|
375
|
+
// 检查 .gitreference 目录是否为空
|
|
376
|
+
if (!options.keepEmpty) {
|
|
377
|
+
try {
|
|
378
|
+
const remaining = await filesystem.readDir(gitrefDir);
|
|
379
|
+
if (remaining.length === 0) {
|
|
380
|
+
if (options.verbose) {
|
|
381
|
+
console.log(chalk_1.default.gray(` 删除空的 .gitreference 目录`));
|
|
382
|
+
}
|
|
383
|
+
await filesystem.removeDir(gitrefDir);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
// 忽略错误
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
const elapsed = Date.now() - startTime;
|
|
391
|
+
spinner.succeed(chalk_1.default.green(`已清理 ${cleanedCount} 个空目录!`));
|
|
392
|
+
if (options.verbose) {
|
|
393
|
+
console.log(chalk_1.default.gray(` 耗时: ${elapsed}ms`));
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
spinner.fail(chalk_1.default.red("清理失败"));
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
// 情况 1: --list 选项,列出所有已加载的参考代码
|
|
403
|
+
if (options.list) {
|
|
404
|
+
if (loadedEntries.length === 0 && emptyDirs.length === 0) {
|
|
405
|
+
console.log(chalk_1.default.yellow("当前项目中没有已加载的参考代码。"));
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (loadedEntries.length > 0) {
|
|
409
|
+
console.log(chalk_1.default.bold(`📦 当前项目中已加载的参考代码 (${loadedEntries.length} 个)`));
|
|
410
|
+
console.log();
|
|
411
|
+
// 表头
|
|
412
|
+
const COL_NAME = 35;
|
|
413
|
+
const COL_PATH = 30;
|
|
414
|
+
const COL_COMMIT = 10;
|
|
415
|
+
console.log(chalk_1.default.gray(" " +
|
|
416
|
+
padEnd("REPO", COL_NAME) +
|
|
417
|
+
padEnd("PATH", COL_PATH) +
|
|
418
|
+
padEnd("COMMIT", COL_COMMIT)));
|
|
419
|
+
// 条目列表
|
|
420
|
+
for (const entry of loadedEntries) {
|
|
421
|
+
const repoName = entry.repoName.replace(/\\/g, "/");
|
|
422
|
+
const targetPath = entry.targetPath.replace(/\\/g, "/");
|
|
423
|
+
const commitShort = entry.commitId
|
|
424
|
+
? entry.commitId.substring(0, 7)
|
|
425
|
+
: "-";
|
|
426
|
+
console.log(" " +
|
|
427
|
+
padEnd(repoName, COL_NAME) +
|
|
428
|
+
padEnd(targetPath, COL_PATH) +
|
|
429
|
+
padEnd(commitShort, COL_COMMIT));
|
|
430
|
+
}
|
|
431
|
+
console.log();
|
|
432
|
+
// 详细模式下显示更多信息
|
|
433
|
+
if (options.verbose) {
|
|
434
|
+
console.log(chalk_1.default.gray("详细信息:"));
|
|
435
|
+
for (const entry of loadedEntries) {
|
|
436
|
+
console.log(chalk_1.default.gray(` - ${entry.repoName}`));
|
|
437
|
+
console.log(chalk_1.default.gray(` ID: ${entry.id}`));
|
|
438
|
+
console.log(chalk_1.default.gray(` URL: ${entry.repoUrl}`));
|
|
439
|
+
console.log(chalk_1.default.gray(` 路径: ${entry.targetPath}`));
|
|
440
|
+
console.log(chalk_1.default.gray(` Commit: ${entry.commitId}`));
|
|
441
|
+
if (entry.branch) {
|
|
442
|
+
console.log(chalk_1.default.gray(` 分支: ${entry.branch}`));
|
|
443
|
+
}
|
|
444
|
+
if (entry.subdir) {
|
|
445
|
+
console.log(chalk_1.default.gray(` 子目录: ${entry.subdir}`));
|
|
446
|
+
}
|
|
447
|
+
console.log(chalk_1.default.gray(` 加载时间: ${entry.loadedAt}`));
|
|
448
|
+
if (entry.updatedAt) {
|
|
449
|
+
console.log(chalk_1.default.gray(` 更新时间: ${entry.updatedAt}`));
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
console.log();
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
console.log(chalk_1.default.yellow("当前项目中没有已加载的参考代码。"));
|
|
457
|
+
console.log();
|
|
458
|
+
}
|
|
459
|
+
// 显示空目录提示
|
|
460
|
+
if (emptyDirs.length > 0) {
|
|
461
|
+
console.log(chalk_1.default.yellow(`⚠️ 发现 ${emptyDirs.length} 个空目录结构`));
|
|
462
|
+
if (options.verbose) {
|
|
463
|
+
for (const dir of emptyDirs) {
|
|
464
|
+
console.log(chalk_1.default.gray(` - ${dir.fullPath.replace(/\\/g, "/")}`));
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
console.log(chalk_1.default.gray(` 使用 'grf unload --clean-empty' 清理空目录`));
|
|
468
|
+
console.log();
|
|
469
|
+
}
|
|
470
|
+
if (loadedEntries.length > 0) {
|
|
471
|
+
console.log(chalk_1.default.gray(`💡 使用 'grf unload <name>' 移除指定的参考代码`));
|
|
472
|
+
}
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
// 情况 2: --all 选项,删除所有参考代码
|
|
476
|
+
if (options.all) {
|
|
477
|
+
// 如果没有已加载的条目
|
|
478
|
+
if (loadedEntries.length === 0) {
|
|
479
|
+
console.log(chalk_1.default.yellow("没有找到需要删除的参考代码。"));
|
|
480
|
+
console.log();
|
|
481
|
+
console.log(chalk_1.default.gray("💡 提示: loading.json 中没有记录任何已加载的参考代码。"));
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const pathsToDelete = [];
|
|
485
|
+
for (const entry of loadedEntries) {
|
|
486
|
+
const absolutePath = path_1.default.resolve(cwd, entry.targetPath);
|
|
487
|
+
const exists = await filesystem.exists(absolutePath);
|
|
488
|
+
pathsToDelete.push({
|
|
489
|
+
entry,
|
|
490
|
+
absolutePath,
|
|
491
|
+
exists,
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
// 显示将要删除的内容
|
|
495
|
+
console.log(`将要删除 ${chalk_1.default.bold(loadedEntries.length)} 个参考代码:`);
|
|
496
|
+
for (const pathInfo of pathsToDelete) {
|
|
497
|
+
const status = pathInfo.exists ? "" : chalk_1.default.gray(" (路径不存在)");
|
|
498
|
+
console.log(` - ${pathInfo.entry.repoName} -> ${pathInfo.entry.targetPath}${status}`);
|
|
499
|
+
}
|
|
500
|
+
console.log();
|
|
501
|
+
// dry-run 模式
|
|
502
|
+
if (options.dryRun) {
|
|
503
|
+
console.log(chalk_1.default.yellow("(试运行模式,未执行实际删除)"));
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
// 确认删除
|
|
507
|
+
if (!options.force) {
|
|
508
|
+
const confirmed = await confirm("确定要删除吗?");
|
|
509
|
+
if (!confirmed) {
|
|
510
|
+
console.log(chalk_1.default.yellow("操作已取消。"));
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
// 执行删除
|
|
515
|
+
const spinner = (0, ora_1.default)("正在删除参考代码...").start();
|
|
516
|
+
try {
|
|
517
|
+
let deletedCount = 0;
|
|
518
|
+
for (const pathInfo of pathsToDelete) {
|
|
519
|
+
if (options.verbose) {
|
|
520
|
+
spinner.stop();
|
|
521
|
+
console.log(chalk_1.default.gray(` 删除: ${pathInfo.entry.targetPath}`));
|
|
522
|
+
spinner.start();
|
|
523
|
+
}
|
|
524
|
+
// 删除实际文件/目录(如果存在)
|
|
525
|
+
if (pathInfo.exists) {
|
|
526
|
+
await filesystem.removeDir(pathInfo.absolutePath);
|
|
527
|
+
}
|
|
528
|
+
// 清理 .gitignore 中对应的条目
|
|
529
|
+
const gitignoreEntry = pathInfo.entry.targetPath.endsWith("/")
|
|
530
|
+
? pathInfo.entry.targetPath
|
|
531
|
+
: pathInfo.entry.targetPath + "/";
|
|
532
|
+
await filesystem.removeFromGitignore(cwd, gitignoreEntry);
|
|
533
|
+
deletedCount++;
|
|
534
|
+
}
|
|
535
|
+
// 清空 loading.json
|
|
536
|
+
await loading.clearAllEntries(cwd);
|
|
537
|
+
// 检查 .gitreference 目录是否为空
|
|
538
|
+
if (!options.keepEmpty && gitrefDirExists) {
|
|
539
|
+
try {
|
|
540
|
+
const remaining = await filesystem.readDir(gitrefDir);
|
|
541
|
+
// 只剩下 loading.json 或为空时删除整个目录
|
|
542
|
+
if (remaining.length === 0 ||
|
|
543
|
+
(remaining.length === 1 && remaining[0] === "loading.json")) {
|
|
544
|
+
if (options.verbose) {
|
|
545
|
+
spinner.stop();
|
|
546
|
+
console.log(chalk_1.default.gray(` 删除 .gitreference 目录`));
|
|
547
|
+
spinner.start();
|
|
548
|
+
}
|
|
549
|
+
await filesystem.removeDir(gitrefDir);
|
|
550
|
+
// 移除 .gitignore 中的 .gitreference/ 条目
|
|
551
|
+
await filesystem.removeFromGitignore(cwd, ".gitreference/");
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
catch {
|
|
555
|
+
// 忽略错误
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
const elapsed = Date.now() - startTime;
|
|
559
|
+
spinner.succeed(chalk_1.default.green(`已删除 ${deletedCount} 个参考代码!`));
|
|
560
|
+
if (options.verbose) {
|
|
561
|
+
console.log(chalk_1.default.gray(` 耗时: ${elapsed}ms`));
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
spinner.fail(chalk_1.default.red("删除失败"));
|
|
566
|
+
throw error;
|
|
567
|
+
}
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
// 情况 3: 指定仓库名称,删除指定的参考代码
|
|
571
|
+
if (name) {
|
|
572
|
+
// 匹配条目
|
|
573
|
+
const matches = matchEntries(loadedEntries, name);
|
|
574
|
+
if (matches.length === 0) {
|
|
575
|
+
console.error(chalk_1.default.red(`${chalk_1.default.bold("✗")} 未找到匹配的参考代码: ${name}`));
|
|
576
|
+
console.log();
|
|
577
|
+
console.log(`使用 '${chalk_1.default.cyan("grf unload --list")}' 查看所有已加载的参考代码。`);
|
|
578
|
+
process.exit(1);
|
|
579
|
+
}
|
|
580
|
+
let targetEntry;
|
|
581
|
+
if (matches.length > 1) {
|
|
582
|
+
// 如果使用了 --force 选项,仍然报错要求精确指定
|
|
583
|
+
if (options.force) {
|
|
584
|
+
console.error(chalk_1.default.red(`${chalk_1.default.bold("✗")} 找到多个匹配的参考代码:`));
|
|
585
|
+
console.log();
|
|
586
|
+
for (const match of matches) {
|
|
587
|
+
console.log(` - ${match.repoName} -> ${match.targetPath}`);
|
|
588
|
+
}
|
|
589
|
+
console.log();
|
|
590
|
+
console.log(`请使用完整路径精确指定要删除的参考代码。`);
|
|
591
|
+
process.exit(1);
|
|
592
|
+
}
|
|
593
|
+
// 交互式选择
|
|
594
|
+
const selected = await selectEntry(matches, name);
|
|
595
|
+
if (!selected) {
|
|
596
|
+
console.log(chalk_1.default.yellow("操作已取消。"));
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
targetEntry = selected;
|
|
600
|
+
console.log();
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
targetEntry = matches[0];
|
|
604
|
+
}
|
|
605
|
+
const displayName = targetEntry.repoName.replace(/\\/g, "/");
|
|
606
|
+
const displayPath = targetEntry.targetPath.replace(/\\/g, "/");
|
|
607
|
+
const absolutePath = path_1.default.resolve(cwd, targetEntry.targetPath);
|
|
608
|
+
const pathExists = await filesystem.exists(absolutePath);
|
|
609
|
+
// 显示将要删除的内容
|
|
610
|
+
console.log(`将要删除: ${chalk_1.default.cyan(displayName)}`);
|
|
611
|
+
console.log(` 目标路径: ${chalk_1.default.gray(displayPath)}`);
|
|
612
|
+
if (!pathExists) {
|
|
613
|
+
console.log(chalk_1.default.yellow(` (注意: 目标路径不存在,将仅从 loading.json 中移除记录)`));
|
|
614
|
+
}
|
|
615
|
+
if (options.verbose) {
|
|
616
|
+
console.log(` 绝对路径: ${chalk_1.default.gray(absolutePath)}`);
|
|
617
|
+
console.log(` Commit: ${chalk_1.default.gray(targetEntry.commitId)}`);
|
|
618
|
+
if (targetEntry.branch) {
|
|
619
|
+
console.log(` 分支: ${chalk_1.default.gray(targetEntry.branch)}`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
console.log();
|
|
623
|
+
// dry-run 模式
|
|
624
|
+
if (options.dryRun) {
|
|
625
|
+
console.log(chalk_1.default.yellow("(试运行模式,未执行实际删除)"));
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
// 确认删除
|
|
629
|
+
if (!options.force) {
|
|
630
|
+
const confirmed = await confirm(`确定要删除 '${displayName}' 吗?`);
|
|
631
|
+
if (!confirmed) {
|
|
632
|
+
console.log(chalk_1.default.yellow("操作已取消。"));
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
// 执行删除
|
|
637
|
+
const spinner = (0, ora_1.default)("正在删除参考代码...").start();
|
|
638
|
+
try {
|
|
639
|
+
// 删除实际文件/目录(如果存在)
|
|
640
|
+
if (pathExists) {
|
|
641
|
+
if (options.verbose) {
|
|
642
|
+
spinner.stop();
|
|
643
|
+
console.log(chalk_1.default.gray(` 删除目录: ${displayPath}`));
|
|
644
|
+
spinner.start();
|
|
645
|
+
}
|
|
646
|
+
await filesystem.removeDir(absolutePath);
|
|
647
|
+
// 递归删除空的父目录(仅当在 .gitreference 目录下时)
|
|
648
|
+
if (targetEntry.targetPath.startsWith(".gitreference/") ||
|
|
649
|
+
targetEntry.targetPath.startsWith(GITREFERENCE_DIR + "/")) {
|
|
650
|
+
if (options.verbose) {
|
|
651
|
+
spinner.stop();
|
|
652
|
+
}
|
|
653
|
+
await removeEmptyParents(absolutePath, gitrefDir, options.verbose);
|
|
654
|
+
if (options.verbose) {
|
|
655
|
+
spinner.start();
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
// 从 loading.json 移除条目
|
|
660
|
+
await loading.removeEntry(targetEntry.id, cwd);
|
|
661
|
+
// 清理 .gitignore 中对应的条目
|
|
662
|
+
const gitignoreEntry = targetEntry.targetPath.endsWith("/")
|
|
663
|
+
? targetEntry.targetPath
|
|
664
|
+
: targetEntry.targetPath + "/";
|
|
665
|
+
const gitignoreRemoved = await filesystem.removeFromGitignore(cwd, gitignoreEntry);
|
|
666
|
+
if (gitignoreRemoved && options.verbose) {
|
|
667
|
+
spinner.stop();
|
|
668
|
+
console.log(chalk_1.default.gray(` 已从 .gitignore 中移除 ${gitignoreEntry}`));
|
|
669
|
+
spinner.start();
|
|
670
|
+
}
|
|
671
|
+
// 检查 .gitreference 目录是否为空
|
|
672
|
+
if (!options.keepEmpty && gitrefDirExists) {
|
|
673
|
+
try {
|
|
674
|
+
const remaining = await filesystem.readDir(gitrefDir);
|
|
675
|
+
// 只剩下 loading.json 或为空时删除整个目录
|
|
676
|
+
if (remaining.length === 0 ||
|
|
677
|
+
(remaining.length === 1 && remaining[0] === "loading.json")) {
|
|
678
|
+
// 检查 loading.json 是否还有其他条目
|
|
679
|
+
const remainingEntries = await loading.getEntries(cwd);
|
|
680
|
+
if (remainingEntries.length === 0) {
|
|
681
|
+
if (options.verbose) {
|
|
682
|
+
spinner.stop();
|
|
683
|
+
console.log(chalk_1.default.gray(` 删除空的 .gitreference 目录`));
|
|
684
|
+
spinner.start();
|
|
685
|
+
}
|
|
686
|
+
await filesystem.removeDir(gitrefDir);
|
|
687
|
+
// 移除 .gitignore 中的 .gitreference/ 条目
|
|
688
|
+
await filesystem.removeFromGitignore(cwd, ".gitreference/");
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
catch {
|
|
693
|
+
// 忽略错误
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
const elapsed = Date.now() - startTime;
|
|
697
|
+
spinner.succeed(chalk_1.default.green("参考代码已移除!"));
|
|
698
|
+
console.log();
|
|
699
|
+
console.log(` ${chalk_1.default.gray("仓库:")} ${displayName}`);
|
|
700
|
+
console.log(` ${chalk_1.default.gray("路径:")} ${displayPath}`);
|
|
701
|
+
if (options.verbose) {
|
|
702
|
+
console.log(chalk_1.default.gray(` 耗时: ${elapsed}ms`));
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
catch (error) {
|
|
706
|
+
spinner.fail(chalk_1.default.red("删除失败"));
|
|
707
|
+
throw error;
|
|
708
|
+
}
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
// 情况 4: 没有指定名称也没有 --all 或 --list,显示使用说明
|
|
712
|
+
console.log(chalk_1.default.yellow("未指定要移除的参考代码。"));
|
|
713
|
+
console.log();
|
|
714
|
+
console.log("用法:");
|
|
715
|
+
console.log(` ${chalk_1.default.cyan("grf unload <name>")} 移除指定的参考代码`);
|
|
716
|
+
console.log(` ${chalk_1.default.cyan("grf unload --all")} 移除所有参考代码`);
|
|
717
|
+
console.log(` ${chalk_1.default.cyan("grf unload --list")} 列出所有已加载的参考代码`);
|
|
718
|
+
console.log(` ${chalk_1.default.cyan("grf unload --clean-empty")} 清理空目录结构`);
|
|
719
|
+
console.log();
|
|
720
|
+
console.log(`使用 '${chalk_1.default.cyan("grf unload --list")}' 查看所有已加载的参考代码。`);
|
|
721
|
+
}
|
|
722
|
+
catch (error) {
|
|
723
|
+
if (error instanceof index_js_1.GrfError) {
|
|
724
|
+
console.error(chalk_1.default.red(`${chalk_1.default.bold("✗")} ${error.message}`));
|
|
725
|
+
if (error.code === index_js_1.ErrorCode.FS_PERMISSION_DENIED) {
|
|
726
|
+
console.error(chalk_1.default.gray(` 权限被拒绝,请检查文件权限。`));
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
else if (error instanceof Error) {
|
|
730
|
+
console.error(chalk_1.default.red(`${chalk_1.default.bold("✗")} ${error.message}`));
|
|
731
|
+
}
|
|
732
|
+
else {
|
|
733
|
+
console.error(chalk_1.default.red(`${chalk_1.default.bold("✗")} 发生未知错误`));
|
|
734
|
+
}
|
|
735
|
+
process.exit(1);
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
//# sourceMappingURL=unload.js.map
|