@mindbase/node-tools 1.1.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/bin/clear.js +74 -0
- package/bin/git-log.js +189 -0
- package/package.json +42 -0
- package/src/clear/cleaner.js +68 -0
- package/src/clear/config.js +130 -0
- package/src/clear/scanner.js +75 -0
- package/src/clear/ui.js +353 -0
- package/src/common/ui/ansi.js +49 -0
- package/src/common/ui/pagination.js +63 -0
- package/src/common/ui/screen.js +56 -0
- package/src/common/ui/single-select.js +80 -0
- package/src/common/ui/utils.js +80 -0
- package/src/git-log/ui.js +309 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 NoahLiu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/bin/clear.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { Command } = require("commander");
|
|
3
|
+
const { resolve } = require("path");
|
|
4
|
+
const { scan } = require("../src/clear/scanner.js");
|
|
5
|
+
const { clean } = require("../src/clear/cleaner.js");
|
|
6
|
+
const { selectItems, confirmDelete, showResult } = require("../src/clear/ui.js");
|
|
7
|
+
const { getConfig, resetConfig, showConfig, editConfig } = require("../src/clear/config.js");
|
|
8
|
+
|
|
9
|
+
const program = new Command();
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.name("light-up-clear")
|
|
13
|
+
.description("交互式清理 node_modules 和锁文件的 CLI 工具")
|
|
14
|
+
.version("1.0.0");
|
|
15
|
+
|
|
16
|
+
// 默认命令 - 执行清理
|
|
17
|
+
program
|
|
18
|
+
.argument("[path]", "要清理的目录路径", ".")
|
|
19
|
+
.action(async (path) => {
|
|
20
|
+
const targetPath = resolve(path);
|
|
21
|
+
console.log(`扫描目录: ${targetPath}\n`);
|
|
22
|
+
|
|
23
|
+
const config = getConfig();
|
|
24
|
+
const items = scan(targetPath, config);
|
|
25
|
+
|
|
26
|
+
if (items.length === 0) {
|
|
27
|
+
console.log("未找到可清理的内容");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 显示多选界面
|
|
32
|
+
const selected = await selectItems(items);
|
|
33
|
+
|
|
34
|
+
if (selected.length === 0) {
|
|
35
|
+
console.log("未选择任何内容");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 确认删除
|
|
40
|
+
const confirmed = await confirmDelete(selected);
|
|
41
|
+
if (!confirmed) return;
|
|
42
|
+
|
|
43
|
+
// 执行删除
|
|
44
|
+
console.log("\n执行删除中...");
|
|
45
|
+
const stats = clean(selected);
|
|
46
|
+
await showResult(stats);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// config 子命令
|
|
50
|
+
program
|
|
51
|
+
.command("config")
|
|
52
|
+
.description("配置管理")
|
|
53
|
+
.option("--show", "显示当前配置")
|
|
54
|
+
.option("--reset", "重置为默认配置")
|
|
55
|
+
.action((options) => {
|
|
56
|
+
if (options.show) {
|
|
57
|
+
showConfig();
|
|
58
|
+
} else if (options.reset) {
|
|
59
|
+
resetConfig();
|
|
60
|
+
console.log("配置已重置为默认值");
|
|
61
|
+
} else {
|
|
62
|
+
showConfig();
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// editConfig 子命令
|
|
67
|
+
program
|
|
68
|
+
.command("editConfig")
|
|
69
|
+
.description("打开编辑器编辑配置文件")
|
|
70
|
+
.action(() => {
|
|
71
|
+
editConfig();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
program.parse();
|
package/bin/git-log.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { Command } = require("commander");
|
|
3
|
+
const { resolve } = require("path");
|
|
4
|
+
const { exec } = require("child_process");
|
|
5
|
+
const { promisify } = require("util");
|
|
6
|
+
const fs = require("fs").promises;
|
|
7
|
+
const path = require("path");
|
|
8
|
+
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
const { inputFilters, displayLogs, formatDateForGit } = require("../src/git-log/ui.js");
|
|
11
|
+
|
|
12
|
+
// 验证和转义用户输入
|
|
13
|
+
function sanitizeInput (input) {
|
|
14
|
+
if (typeof input !== 'string' || !input) {
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
17
|
+
return input.replace(/[;&|`$(){}[\]\\'"<>*?]/g, '');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 检查 Git 安装
|
|
21
|
+
async function checkGitInstallation () {
|
|
22
|
+
try {
|
|
23
|
+
const { stdout } = await execAsync('git --version');
|
|
24
|
+
return true;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 获取 Git 日志
|
|
31
|
+
async function getGitLogs (dir, repoName, filters = {}) {
|
|
32
|
+
const safeAuthor = sanitizeInput(filters.author);
|
|
33
|
+
const safeSince = formatDateForGit(sanitizeInput(filters.since));
|
|
34
|
+
const safeUntil = formatDateForGit(sanitizeInput(filters.until));
|
|
35
|
+
|
|
36
|
+
let command = 'git log --all --pretty=format:"%H|%an|%ae|%ad|%s" --date=iso';
|
|
37
|
+
|
|
38
|
+
if (safeAuthor) {
|
|
39
|
+
command += ` --author="${safeAuthor}"`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (safeSince) {
|
|
43
|
+
command += ` --since="${safeSince}"`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (safeUntil) {
|
|
47
|
+
command += ` --until="${safeUntil}"`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const { stdout } = await execAsync(command, {
|
|
52
|
+
cwd: dir,
|
|
53
|
+
maxBuffer: 1024 * 1024 * 10
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const logs = stdout.split('\n')
|
|
57
|
+
.filter(line => line.trim() !== '')
|
|
58
|
+
.map(line => {
|
|
59
|
+
const logRegex = /^([0-9a-f]{40})\|([^|]+)\|([^|]+)\|([^|]+)\|(.+)$/;
|
|
60
|
+
const match = line.match(logRegex);
|
|
61
|
+
if (match) {
|
|
62
|
+
return {
|
|
63
|
+
hash: match[1],
|
|
64
|
+
authorName: match[2],
|
|
65
|
+
authorEmail: match[3],
|
|
66
|
+
date: match[4],
|
|
67
|
+
subject: match[5],
|
|
68
|
+
repo: repoName
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
})
|
|
73
|
+
.filter(log => log !== null);
|
|
74
|
+
|
|
75
|
+
return logs;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 扫描目录查找 Git 仓库
|
|
82
|
+
async function scanDirectoriesForGitRepos (rootDir) {
|
|
83
|
+
const repos = [];
|
|
84
|
+
try {
|
|
85
|
+
// 先检查当前目录是否是 Git 仓库
|
|
86
|
+
try {
|
|
87
|
+
await fs.access(path.join(rootDir, '.git'));
|
|
88
|
+
repos.push({
|
|
89
|
+
name: path.basename(rootDir),
|
|
90
|
+
path: rootDir
|
|
91
|
+
});
|
|
92
|
+
} catch {
|
|
93
|
+
// 当前目录不是 Git 仓库,扫描子目录
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 扫描子目录
|
|
97
|
+
const entries = await fs.readdir(rootDir, { withFileTypes: true });
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
if (entry.isDirectory() && entry.name !== 'node_modules') {
|
|
100
|
+
const fullPath = path.join(rootDir, entry.name);
|
|
101
|
+
try {
|
|
102
|
+
await fs.access(path.join(fullPath, '.git'));
|
|
103
|
+
repos.push({
|
|
104
|
+
name: entry.name,
|
|
105
|
+
path: fullPath
|
|
106
|
+
});
|
|
107
|
+
} catch {
|
|
108
|
+
// 不是 Git 仓库
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error('扫描目录时出错:', error);
|
|
114
|
+
}
|
|
115
|
+
return repos;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 主程序
|
|
119
|
+
const program = new Command();
|
|
120
|
+
|
|
121
|
+
program
|
|
122
|
+
.name("light-up-git-log")
|
|
123
|
+
.description("交互式 Git 日志查看工具")
|
|
124
|
+
.version("1.0.0")
|
|
125
|
+
.argument("[path]", "要扫描的目录路径", ".")
|
|
126
|
+
.action(async (targetPath) => {
|
|
127
|
+
const rootDir = resolve(targetPath);
|
|
128
|
+
|
|
129
|
+
console.log(`[扫描目录]: ${rootDir}`);
|
|
130
|
+
|
|
131
|
+
// 检查 Git
|
|
132
|
+
const hasGit = await checkGitInstallation();
|
|
133
|
+
if (!hasGit) {
|
|
134
|
+
console.log("\n[X] 请先安装 Git: https://git-scm.com/downloads");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 扫描仓库
|
|
139
|
+
const repos = await scanDirectoriesForGitRepos(rootDir);
|
|
140
|
+
|
|
141
|
+
if (repos.length === 0) {
|
|
142
|
+
console.log("\n[X] 未找到 Git 仓库");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
console.log(`[OK] 找到 ${repos.length} 个 Git 仓库\n`);
|
|
147
|
+
|
|
148
|
+
// 循环筛选和显示
|
|
149
|
+
async function showViewer () {
|
|
150
|
+
// 输入过滤条件(传入 repos 对象)
|
|
151
|
+
const filters = await inputFilters(repos);
|
|
152
|
+
|
|
153
|
+
if (!filters) {
|
|
154
|
+
console.log("已取消");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 获取选中仓库的日志
|
|
159
|
+
console.log("\n正在获取日志...");
|
|
160
|
+
const allLogs = [];
|
|
161
|
+
|
|
162
|
+
// 只获取选中的仓库
|
|
163
|
+
const selectedRepos = repos.filter(r => filters.selectedRepos.includes(r.path));
|
|
164
|
+
|
|
165
|
+
for (const repo of selectedRepos) {
|
|
166
|
+
const logs = await getGitLogs(repo.path, repo.name, filters);
|
|
167
|
+
allLogs.push(...logs);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (allLogs.length === 0) {
|
|
171
|
+
console.log("\n[X] 没有找到符合条件的日志");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 按日期倒序排列
|
|
176
|
+
allLogs.sort((a, b) => new Date(b.date) - new Date(a.date));
|
|
177
|
+
|
|
178
|
+
console.log(`[OK] 获取到 ${allLogs.length} 条日志\n`);
|
|
179
|
+
|
|
180
|
+
// 显示日志,传入重新筛选的回调
|
|
181
|
+
await displayLogs(allLogs, showViewer);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
await showViewer();
|
|
185
|
+
|
|
186
|
+
console.log("\n[OK] 完成");
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mindbase/node-tools",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Node.js 开发工具集合:清理 node_modules、查看 Git 日志",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"nodeclear": "./bin/clear.js",
|
|
8
|
+
"gitlog": "./bin/git-log.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"src"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node bin/clear.js"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"clean",
|
|
19
|
+
"node_modules",
|
|
20
|
+
"cli",
|
|
21
|
+
"lock-file"
|
|
22
|
+
],
|
|
23
|
+
"author": "",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"chalk": "^4.1.2",
|
|
27
|
+
"cli-table3": "^0.6.3",
|
|
28
|
+
"commander": "^11.0.0",
|
|
29
|
+
"editor": "^1.0.0",
|
|
30
|
+
"prompts": "^2.4.2",
|
|
31
|
+
"treeify": "^1.1.0"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=16.0.0"
|
|
35
|
+
},
|
|
36
|
+
"volta": {
|
|
37
|
+
"node": "20.20.0"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const { spawnSync } = require("child_process");
|
|
2
|
+
const { platform } = require("process");
|
|
3
|
+
|
|
4
|
+
const isWin = platform === "win32";
|
|
5
|
+
let rmAvailable = null;
|
|
6
|
+
|
|
7
|
+
// 检测 rm 命令是否可用
|
|
8
|
+
function checkRmAvailable() {
|
|
9
|
+
if (rmAvailable !== null) return rmAvailable;
|
|
10
|
+
const result = spawnSync("rm", ["--version"], { shell: true });
|
|
11
|
+
rmAvailable = result.status === 0;
|
|
12
|
+
return rmAvailable;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// 执行删除命令
|
|
16
|
+
function removeDir(path) {
|
|
17
|
+
if (checkRmAvailable()) {
|
|
18
|
+
const result = spawnSync("rm", ["-rf", path], { shell: true, stdio: "inherit" });
|
|
19
|
+
return result.status === 0;
|
|
20
|
+
} else if (isWin) {
|
|
21
|
+
const result = spawnSync("rmdir", ["/S", "/Q", path], { shell: true, stdio: "inherit" });
|
|
22
|
+
return result.status === 0;
|
|
23
|
+
} else {
|
|
24
|
+
const result = spawnSync("rm", ["-rf", path], { shell: true, stdio: "inherit" });
|
|
25
|
+
return result.status === 0;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function removeFile(path) {
|
|
30
|
+
if (checkRmAvailable()) {
|
|
31
|
+
const result = spawnSync("rm", [path], { shell: true, stdio: "inherit" });
|
|
32
|
+
return result.status === 0;
|
|
33
|
+
} else if (isWin) {
|
|
34
|
+
const result = spawnSync("del", [path], { shell: true, stdio: "inherit" });
|
|
35
|
+
return result.status === 0;
|
|
36
|
+
} else {
|
|
37
|
+
const result = spawnSync("rm", [path], { shell: true, stdio: "inherit" });
|
|
38
|
+
return result.status === 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 批量删除选中的项目
|
|
44
|
+
* @param {array} items - 要删除的项目数组
|
|
45
|
+
* @returns {object} 删除结果统计
|
|
46
|
+
*/
|
|
47
|
+
function clean(items) {
|
|
48
|
+
const stats = {
|
|
49
|
+
success: 0,
|
|
50
|
+
failed: 0,
|
|
51
|
+
details: []
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
for (const item of items) {
|
|
55
|
+
const success = item.type === "dir" ? removeDir(item.path) : removeFile(item.path);
|
|
56
|
+
if (success) {
|
|
57
|
+
stats.success++;
|
|
58
|
+
stats.details.push({ ...item, status: "success" });
|
|
59
|
+
} else {
|
|
60
|
+
stats.failed++;
|
|
61
|
+
stats.details.push({ ...item, status: "failed" });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return stats;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { clean };
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const { existsSync, readFileSync, writeFileSync, mkdirSync } = require("fs");
|
|
2
|
+
const { join, dirname } = require("path");
|
|
3
|
+
const { homedir } = require("os");
|
|
4
|
+
|
|
5
|
+
// 获取配置文件路径
|
|
6
|
+
function getConfigPath() {
|
|
7
|
+
const appData = process.env.APPDATA || join(homedir(), ".config");
|
|
8
|
+
return join(appData, "light-up-clear", "config.json");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// 默认配置
|
|
12
|
+
const DEFAULT_CONFIG = {
|
|
13
|
+
targets: {
|
|
14
|
+
directories: ["node_modules", "pnpm"],
|
|
15
|
+
files: ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"]
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// 确保配置目录存在
|
|
20
|
+
function ensureConfigDir(configPath) {
|
|
21
|
+
const dir = dirname(configPath);
|
|
22
|
+
if (!existsSync(dir)) {
|
|
23
|
+
mkdirSync(dir, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 读取配置
|
|
28
|
+
function getConfig() {
|
|
29
|
+
const configPath = getConfigPath();
|
|
30
|
+
|
|
31
|
+
if (!existsSync(configPath)) {
|
|
32
|
+
// 首次运行,创建默认配置
|
|
33
|
+
ensureConfigDir(configPath);
|
|
34
|
+
writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2), "utf-8");
|
|
35
|
+
return DEFAULT_CONFIG;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const content = readFileSync(configPath, "utf-8");
|
|
40
|
+
return JSON.parse(content);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error("配置文件读取失败,使用默认配置");
|
|
43
|
+
return DEFAULT_CONFIG;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 写入配置
|
|
48
|
+
function setConfig(config) {
|
|
49
|
+
const configPath = getConfigPath();
|
|
50
|
+
ensureConfigDir(configPath);
|
|
51
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 重置为默认配置
|
|
55
|
+
function resetConfig() {
|
|
56
|
+
setConfig(DEFAULT_CONFIG);
|
|
57
|
+
return DEFAULT_CONFIG;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 显示配置
|
|
61
|
+
function showConfig() {
|
|
62
|
+
const config = getConfig();
|
|
63
|
+
console.log("当前配置:");
|
|
64
|
+
console.log(JSON.stringify(config, null, 2));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 编辑配置
|
|
68
|
+
function editConfig() {
|
|
69
|
+
const edit = require('editor');
|
|
70
|
+
const { spawn } = require('child_process');
|
|
71
|
+
const { platform } = require('process');
|
|
72
|
+
const configPath = getConfigPath();
|
|
73
|
+
|
|
74
|
+
// 确保配置文件存在
|
|
75
|
+
if (!existsSync(configPath)) {
|
|
76
|
+
ensureConfigDir(configPath);
|
|
77
|
+
writeFileSync(configPath, JSON.stringify(DEFAULT_CONFIG, null, 2), "utf-8");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log(`打开配置文件: ${configPath}`);
|
|
81
|
+
|
|
82
|
+
// 优先使用 code(VS Code)
|
|
83
|
+
const editorCmd = process.env.EDITOR || (platform === 'win32' ? 'code' : 'vim');
|
|
84
|
+
|
|
85
|
+
if (editorCmd === 'code') {
|
|
86
|
+
// Windows/macOS/Linux 使用 VS Code
|
|
87
|
+
const spawnOptions = platform === 'win32'
|
|
88
|
+
? { shell: true, stdio: 'inherit' } // Windows 需要 shell
|
|
89
|
+
: { stdio: 'inherit' };
|
|
90
|
+
|
|
91
|
+
spawn('code', [configPath, '--wait'], spawnOptions)
|
|
92
|
+
.on('exit', (code) => {
|
|
93
|
+
if (code === 0) {
|
|
94
|
+
console.log('\n配置已保存');
|
|
95
|
+
} else {
|
|
96
|
+
console.log(`\n编辑器退出,代码: ${code}`);
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
.on('error', () => {
|
|
100
|
+
// code 命令不可用,回退到 editor 包
|
|
101
|
+
console.log('VS Code 不可用,使用默认编辑器...');
|
|
102
|
+
edit(configPath, function (code, sig) {
|
|
103
|
+
if (code === 0) {
|
|
104
|
+
console.log('\n配置已保存');
|
|
105
|
+
} else {
|
|
106
|
+
console.log(`\n编辑器退出,代码: ${code}`);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
} else {
|
|
111
|
+
// 使用环境变量指定的编辑器
|
|
112
|
+
edit(configPath, function (code, sig) {
|
|
113
|
+
if (code === 0) {
|
|
114
|
+
console.log('\n配置已保存');
|
|
115
|
+
} else {
|
|
116
|
+
console.log(`\n编辑器退出,代码: ${code}`);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = {
|
|
123
|
+
getConfig,
|
|
124
|
+
setConfig,
|
|
125
|
+
resetConfig,
|
|
126
|
+
showConfig,
|
|
127
|
+
editConfig,
|
|
128
|
+
getConfigPath,
|
|
129
|
+
DEFAULT_CONFIG
|
|
130
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const { readdirSync, statSync } = require("fs");
|
|
2
|
+
const { join, relative } = require("path");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 递归扫描目录,查找匹配的目标
|
|
6
|
+
* @param {string} basePath - 基础路径
|
|
7
|
+
* @param {string} currentPath - 当前扫描路径
|
|
8
|
+
* @param {object} config - 配置对象
|
|
9
|
+
* @param {array} results - 结果数组
|
|
10
|
+
*/
|
|
11
|
+
function scanDirectory(basePath, currentPath, config, results) {
|
|
12
|
+
const { targets } = config;
|
|
13
|
+
const targetDirs = targets.directories || [];
|
|
14
|
+
const targetFiles = targets.files || [];
|
|
15
|
+
|
|
16
|
+
let entries;
|
|
17
|
+
try {
|
|
18
|
+
entries = readdirSync(currentPath);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
// 无权限访问或目录不存在,跳过
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const fullPath = join(currentPath, entry);
|
|
26
|
+
let stat;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
stat = statSync(fullPath);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (stat.isDirectory()) {
|
|
35
|
+
// 检查是否是目标目录
|
|
36
|
+
if (targetDirs.includes(entry)) {
|
|
37
|
+
const relativePath = relative(basePath, fullPath);
|
|
38
|
+
results.push({
|
|
39
|
+
path: fullPath,
|
|
40
|
+
relativePath,
|
|
41
|
+
name: entry,
|
|
42
|
+
type: "dir"
|
|
43
|
+
});
|
|
44
|
+
} else {
|
|
45
|
+
// 递归扫描子目录
|
|
46
|
+
scanDirectory(basePath, fullPath, config, results);
|
|
47
|
+
}
|
|
48
|
+
} else if (stat.isFile()) {
|
|
49
|
+
// 检查是否是目标文件
|
|
50
|
+
if (targetFiles.includes(entry)) {
|
|
51
|
+
const relativePath = relative(basePath, fullPath);
|
|
52
|
+
results.push({
|
|
53
|
+
path: fullPath,
|
|
54
|
+
relativePath,
|
|
55
|
+
name: entry,
|
|
56
|
+
type: "file"
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 扫描指定目录
|
|
65
|
+
* @param {string} scanPath - 要扫描的目录路径
|
|
66
|
+
* @param {object} config - 配置对象
|
|
67
|
+
* @returns {array} 扫描结果数组
|
|
68
|
+
*/
|
|
69
|
+
function scan(scanPath, config) {
|
|
70
|
+
const results = [];
|
|
71
|
+
scanDirectory(scanPath, scanPath, config, results);
|
|
72
|
+
return results;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { scan };
|