@mindbase/node-tools 1.1.0 → 1.3.7
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/README.md +170 -0
- package/bin/clear.js +9 -18
- package/bin/publish.js +236 -0
- package/package.json +8 -4
- package/src/clear/ui.js +183 -204
- package/src/common/ui/screen.js +26 -2
- package/src/common/ui/utils.js +31 -0
- package/src/git-log/ui.js +3 -10
- package/src/publish/core/builder.js +61 -0
- package/src/publish/core/dependency.js +137 -0
- package/src/publish/core/detector.js +76 -0
- package/src/publish/core/scanner.js +91 -0
- package/src/publish/core/version.js +80 -0
- package/src/publish/publisher.js +157 -0
- package/src/publish/registry/adapters/base.js +51 -0
- package/src/publish/registry/adapters/codeup.js +97 -0
- package/src/publish/registry/adapters/npm.js +120 -0
- package/src/publish/registry/global-config.js +275 -0
- package/src/publish/registry/project-config.js +172 -0
- package/src/publish/registry/registry-manager.js +118 -0
- package/src/publish/ui.js +464 -0
package/src/clear/ui.js
CHANGED
|
@@ -3,14 +3,12 @@
|
|
|
3
3
|
* 使用 prompts + chalk + cli-table3 + treeify 实现
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const prompts = require('prompts');
|
|
7
6
|
const chalk = require('chalk');
|
|
8
|
-
const Table = require('cli-table3');
|
|
9
7
|
const treeify = require('treeify');
|
|
10
|
-
const {
|
|
11
|
-
const { ScreenRenderer } = require('../common/ui/screen.js');
|
|
8
|
+
const { withScreenSession } = require('../common/ui/screen.js');
|
|
12
9
|
const { PagedDisplay } = require('../common/ui/pagination.js');
|
|
13
|
-
const { waitForAnyKey } = require('../common/ui/utils.js');
|
|
10
|
+
const { waitForAnyKey, waitForConfirm, waitForNavigation } = require('../common/ui/utils.js');
|
|
11
|
+
const { clean } = require('./cleaner.js');
|
|
14
12
|
const readline = require('readline');
|
|
15
13
|
|
|
16
14
|
/**
|
|
@@ -24,60 +22,12 @@ function formatItemName(item) {
|
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
/**
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
function waitForNavigation() {
|
|
30
|
-
return new Promise((resolve) => {
|
|
31
|
-
readline.emitKeypressEvents(process.stdin);
|
|
32
|
-
|
|
33
|
-
if (process.stdin.isPaused()) {
|
|
34
|
-
process.stdin.resume();
|
|
35
|
-
process.stdin.setRawMode(true);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const handler = (_str, key) => {
|
|
39
|
-
if (key.name === 'n' || key.name === 'down' || key.name === 'pagedown') {
|
|
40
|
-
cleanup();
|
|
41
|
-
resolve('next');
|
|
42
|
-
} else if (key.name === 'p' || key.name === 'up' || key.name === 'pageup') {
|
|
43
|
-
cleanup();
|
|
44
|
-
resolve('prev');
|
|
45
|
-
} else if (key.name === 'return') {
|
|
46
|
-
cleanup();
|
|
47
|
-
resolve('confirm');
|
|
48
|
-
} else if ((key.name === 'c' && key.ctrl) || key.name === 'escape') {
|
|
49
|
-
cleanup();
|
|
50
|
-
resolve('exit');
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const cleanup = () => {
|
|
55
|
-
process.stdin.removeListener('keypress', handler);
|
|
56
|
-
process.stdin.setRawMode(false);
|
|
57
|
-
process.stdin.pause();
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
process.stdin.on('keypress', handler);
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* 显示多选菜单
|
|
25
|
+
* 内部:显示多选菜单(接收 renderer)
|
|
26
|
+
* @param {Object} renderer - 屏幕渲染器
|
|
66
27
|
* @param {Array} items - 可选项数组
|
|
67
28
|
* @returns {Promise<Array>} 选中的项
|
|
68
29
|
*/
|
|
69
|
-
async function
|
|
70
|
-
if (items.length === 0) {
|
|
71
|
-
console.log(chalk.yellow('没有找到可清理的内容'));
|
|
72
|
-
return [];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// 进入备用屏幕
|
|
76
|
-
enterAlternateScreen();
|
|
77
|
-
|
|
78
|
-
// 创建渲染器
|
|
79
|
-
const renderer = new ScreenRenderer();
|
|
80
|
-
|
|
30
|
+
async function selectItemsInternal(renderer, items) {
|
|
81
31
|
// 分页显示
|
|
82
32
|
const usePagination = items.length > 20;
|
|
83
33
|
const pageSize = 15;
|
|
@@ -92,80 +42,83 @@ async function selectItems(items) {
|
|
|
92
42
|
// 选中状态追踪(默认全选)
|
|
93
43
|
const selectedIndices = new Set(items.map((_, idx) => idx));
|
|
94
44
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
);
|
|
102
|
-
|
|
103
|
-
const hint = chalk.dim(
|
|
104
|
-
'↑↓ 移动 | 空格 选择 | Enter 确认' +
|
|
105
|
-
(usePagination ? ' | PageUp/PageDown/n/p 翻页' : '') +
|
|
106
|
-
' | CTRL_C 退出\n'
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
// 构建选项列表
|
|
110
|
-
const choices = currentPageItems.map((item) => {
|
|
111
|
-
const globalIndex = items.indexOf(item);
|
|
112
|
-
return {
|
|
113
|
-
title: formatItemName(item),
|
|
114
|
-
value: globalIndex,
|
|
115
|
-
selected: selectedIndices.has(globalIndex),
|
|
116
|
-
};
|
|
117
|
-
});
|
|
45
|
+
// 主循环
|
|
46
|
+
while (true) {
|
|
47
|
+
// 渲染当前页面
|
|
48
|
+
const header = chalk.bold.white(
|
|
49
|
+
`找到 ${items.length} 个可清理的项目 (已选: ${selectedIndices.size})\n`
|
|
50
|
+
);
|
|
118
51
|
|
|
119
|
-
|
|
120
|
-
|
|
52
|
+
const hint = chalk.dim(
|
|
53
|
+
'↑↓ 移动 | 空格 选择 | Enter 确认' +
|
|
54
|
+
(usePagination ? ' | PageUp/PageDown/n/p 翻页' : '') +
|
|
55
|
+
' | CTRL_C 退出\n'
|
|
56
|
+
);
|
|
121
57
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
58
|
+
// 构建选项列表
|
|
59
|
+
const choices = currentPageItems.map((item) => {
|
|
60
|
+
const globalIndex = items.indexOf(item);
|
|
61
|
+
return {
|
|
62
|
+
title: formatItemName(item),
|
|
63
|
+
value: globalIndex,
|
|
64
|
+
selected: selectedIndices.has(globalIndex),
|
|
65
|
+
};
|
|
66
|
+
});
|
|
127
67
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
68
|
+
// 使用 readline 实现自定义多选
|
|
69
|
+
const result = await customMultiselect(choices, selectedIndices, header + hint);
|
|
70
|
+
|
|
71
|
+
// 处理退出
|
|
72
|
+
if (result === 'exit') {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 更新选中状态
|
|
77
|
+
result.newSelections.forEach((idx) => selectedIndices.add(idx));
|
|
78
|
+
result.deselections.forEach((idx) => selectedIndices.delete(idx));
|
|
79
|
+
|
|
80
|
+
// 分页处理
|
|
81
|
+
if (usePagination) {
|
|
82
|
+
const navigation = await waitForNavigation();
|
|
83
|
+
|
|
84
|
+
if (navigation === 'next') {
|
|
85
|
+
pagedDisplay.nextPage();
|
|
86
|
+
currentPageItems = pagedDisplay.getCurrentPage().items;
|
|
87
|
+
renderer.clear();
|
|
88
|
+
continue;
|
|
89
|
+
} else if (navigation === 'prev') {
|
|
90
|
+
pagedDisplay.prevPage();
|
|
91
|
+
currentPageItems = pagedDisplay.getCurrentPage().items;
|
|
92
|
+
renderer.clear();
|
|
93
|
+
continue;
|
|
94
|
+
} else if (navigation === 'exit') {
|
|
95
|
+
return [];
|
|
96
|
+
} else if (navigation === 'confirm') {
|
|
153
97
|
break;
|
|
154
98
|
}
|
|
99
|
+
} else {
|
|
100
|
+
break;
|
|
155
101
|
}
|
|
102
|
+
}
|
|
156
103
|
|
|
157
|
-
|
|
158
|
-
|
|
104
|
+
// 返回选中项目
|
|
105
|
+
return Array.from(selectedIndices).map((idx) => items[idx]);
|
|
106
|
+
}
|
|
159
107
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
108
|
+
/**
|
|
109
|
+
* 显示多选菜单
|
|
110
|
+
* @param {Array} items - 可选项数组
|
|
111
|
+
* @returns {Promise<Array>} 选中的项
|
|
112
|
+
*/
|
|
113
|
+
async function selectItems(items) {
|
|
114
|
+
if (items.length === 0) {
|
|
115
|
+
console.log(chalk.yellow('没有找到可清理的内容'));
|
|
116
|
+
return [];
|
|
168
117
|
}
|
|
118
|
+
|
|
119
|
+
return withScreenSession(async (renderer) => {
|
|
120
|
+
return await selectItemsInternal(renderer, items);
|
|
121
|
+
});
|
|
169
122
|
}
|
|
170
123
|
|
|
171
124
|
/**
|
|
@@ -235,119 +188,145 @@ function customMultiselect(choices, currentSelected, titleText = '') {
|
|
|
235
188
|
}
|
|
236
189
|
|
|
237
190
|
/**
|
|
238
|
-
*
|
|
191
|
+
* 内部:显示删除预览(接收 renderer)
|
|
192
|
+
* @param {Object} renderer - 屏幕渲染器
|
|
239
193
|
* @param {Array} items - 选中的项
|
|
240
194
|
* @returns {Promise<boolean>} 是否确认删除
|
|
241
195
|
*/
|
|
242
|
-
async function
|
|
196
|
+
async function confirmDeleteInternal(renderer, items) {
|
|
243
197
|
if (items.length === 0) {
|
|
244
198
|
return false;
|
|
245
199
|
}
|
|
246
200
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
try {
|
|
251
|
-
// 创建表格
|
|
252
|
-
const table = new Table({
|
|
253
|
-
head: [chalk.bold('类型'), chalk.bold('路径')],
|
|
254
|
-
colWidths: [10, 70],
|
|
255
|
-
wordWrap: true,
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
// 填充数据
|
|
259
|
-
items.forEach((item) => {
|
|
260
|
-
const icon =
|
|
261
|
-
item.type === 'dir' ? chalk.cyan('📁 目录') : chalk.white('📄 文件');
|
|
262
|
-
table.push([icon, item.relativePath]);
|
|
263
|
-
});
|
|
201
|
+
// 构建简单列表
|
|
202
|
+
let content = chalk.bold.red(`\n即将删除以下 ${items.length} 项:\n\n`);
|
|
264
203
|
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
const
|
|
204
|
+
items.forEach((item) => {
|
|
205
|
+
const icon = item.type === 'dir' ? '📁' : '📄';
|
|
206
|
+
const coloredPath =
|
|
207
|
+
item.type === 'dir' ? chalk.cyan(item.relativePath) : chalk.white(item.relativePath);
|
|
208
|
+
content += ` ${icon} ${coloredPath}\n`;
|
|
209
|
+
});
|
|
268
210
|
|
|
269
|
-
|
|
211
|
+
// 操作提示
|
|
212
|
+
content += '\n' + chalk.dim('按 y/Enter 确认,n/Esc 取消...');
|
|
270
213
|
|
|
271
|
-
|
|
272
|
-
const response = await prompts({
|
|
273
|
-
type: 'confirm',
|
|
274
|
-
name: 'confirm',
|
|
275
|
-
message: chalk.yellow('确认删除?'),
|
|
276
|
-
initial: false,
|
|
277
|
-
});
|
|
214
|
+
renderer.render(content);
|
|
278
215
|
|
|
279
|
-
|
|
216
|
+
// 等待确认
|
|
217
|
+
return await waitForConfirm();
|
|
218
|
+
}
|
|
280
219
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
220
|
+
/**
|
|
221
|
+
* 显示删除预览
|
|
222
|
+
* @param {Array} items - 选中的项
|
|
223
|
+
* @returns {Promise<boolean>} 是否确认删除
|
|
224
|
+
*/
|
|
225
|
+
async function confirmDelete(items) {
|
|
226
|
+
if (items.length === 0) {
|
|
227
|
+
return false;
|
|
288
228
|
}
|
|
229
|
+
|
|
230
|
+
return withScreenSession(async (renderer) => {
|
|
231
|
+
return await confirmDeleteInternal(renderer, items);
|
|
232
|
+
});
|
|
289
233
|
}
|
|
290
234
|
|
|
291
235
|
/**
|
|
292
|
-
*
|
|
236
|
+
* 内部:显示删除结果(接收 renderer)
|
|
237
|
+
* @param {Object} renderer - 屏幕渲染器
|
|
293
238
|
* @param {Object} stats - 删除统计
|
|
294
239
|
*/
|
|
295
|
-
async function
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
240
|
+
async function showResultInternal(renderer, stats) {
|
|
241
|
+
const total = stats.success + stats.failed;
|
|
242
|
+
let content =
|
|
243
|
+
chalk.bold.white('\n删除完成\n\n') +
|
|
244
|
+
` 总处理数: ${chalk.bold(total)}\n` +
|
|
245
|
+
` ${chalk.green('成功')}: ${chalk.bold.green(stats.success)}\n` +
|
|
246
|
+
` ${chalk.red('失败')}: ${chalk.bold.red(stats.failed)}\n`;
|
|
247
|
+
|
|
248
|
+
// 失败详情
|
|
249
|
+
if (stats.failed > 0) {
|
|
250
|
+
const failedItems = stats.details.filter((d) => d.status === 'failed');
|
|
251
|
+
|
|
252
|
+
content += '\n' + chalk.bold.red('失败详情:\n');
|
|
253
|
+
|
|
254
|
+
// 使用 treeify 构建目录树
|
|
255
|
+
const tree = {};
|
|
256
|
+
failedItems.forEach((item) => {
|
|
257
|
+
const parts = item.relativePath.split(/[/\\]/);
|
|
258
|
+
let current = tree;
|
|
259
|
+
|
|
260
|
+
parts.forEach((part, idx) => {
|
|
261
|
+
if (!current[part]) {
|
|
262
|
+
current[part] = idx === parts.length - 1 ? null : {};
|
|
263
|
+
}
|
|
264
|
+
if (current[part] !== null) {
|
|
265
|
+
current = current[part];
|
|
266
|
+
}
|
|
267
|
+
});
|
|
303
268
|
});
|
|
304
269
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
[chalk.green('成功'), chalk.bold.green(stats.success)],
|
|
308
|
-
[chalk.red('失败'), chalk.bold.red(stats.failed)]
|
|
309
|
-
);
|
|
270
|
+
content += '\n' + chalk.red(treeify.asTree(tree, true)) + '\n';
|
|
271
|
+
}
|
|
310
272
|
|
|
311
|
-
|
|
273
|
+
// 操作提示
|
|
274
|
+
content += '\n' + chalk.dim('按任意键继续...');
|
|
312
275
|
|
|
313
|
-
|
|
314
|
-
if (stats.failed > 0) {
|
|
315
|
-
const failedItems = stats.details.filter((d) => d.status === 'failed');
|
|
276
|
+
renderer.render(content);
|
|
316
277
|
|
|
317
|
-
|
|
278
|
+
// 等待按键
|
|
279
|
+
await waitForAnyKey();
|
|
280
|
+
}
|
|
318
281
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
282
|
+
/**
|
|
283
|
+
* 显示删除结果
|
|
284
|
+
* @param {Object} stats - 删除统计
|
|
285
|
+
*/
|
|
286
|
+
async function showResult(stats) {
|
|
287
|
+
await withScreenSession(async (renderer) => {
|
|
288
|
+
await showResultInternal(renderer, stats);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
324
291
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
292
|
+
/**
|
|
293
|
+
* 运行清理流程(全程备用屏幕)
|
|
294
|
+
* @param {string} targetPath - 目标路径
|
|
295
|
+
* @param {Array} items - 扫描到的项目
|
|
296
|
+
* @returns {Promise<Object|null>} 删除统计,取消时返回 null
|
|
297
|
+
*/
|
|
298
|
+
async function runCleanFlow(targetPath, items) {
|
|
299
|
+
return withScreenSession(async (renderer) => {
|
|
300
|
+
// 显示扫描信息
|
|
301
|
+
let content = chalk.bold.white(`扫描目录: ${targetPath}\n\n`);
|
|
302
|
+
content += `找到 ${chalk.bold(items.length)} 个可清理的项目\n\n`;
|
|
303
|
+
content += chalk.dim('按任意键继续...');
|
|
304
|
+
renderer.render(content);
|
|
305
|
+
await waitForAnyKey();
|
|
334
306
|
|
|
335
|
-
|
|
307
|
+
// 选择项目
|
|
308
|
+
const selected = await selectItemsInternal(renderer, items);
|
|
309
|
+
if (selected.length === 0) {
|
|
310
|
+
renderer.render(chalk.yellow('\n未选择任何内容\n按任意键退出...'));
|
|
311
|
+
await waitForAnyKey();
|
|
312
|
+
return null;
|
|
336
313
|
}
|
|
337
314
|
|
|
338
|
-
//
|
|
339
|
-
|
|
315
|
+
// 确认删除
|
|
316
|
+
const confirmed = await confirmDeleteInternal(renderer, selected);
|
|
317
|
+
if (!confirmed) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
340
320
|
|
|
341
|
-
|
|
321
|
+
// 执行删除
|
|
322
|
+
renderer.render(chalk.bold('\n执行删除中...'));
|
|
323
|
+
const stats = clean(selected);
|
|
342
324
|
|
|
343
|
-
//
|
|
344
|
-
await
|
|
325
|
+
// 显示结果
|
|
326
|
+
await showResultInternal(renderer, stats);
|
|
345
327
|
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
exitAlternateScreen();
|
|
349
|
-
throw error;
|
|
350
|
-
}
|
|
328
|
+
return stats;
|
|
329
|
+
});
|
|
351
330
|
}
|
|
352
331
|
|
|
353
|
-
module.exports = { selectItems, confirmDelete, showResult };
|
|
332
|
+
module.exports = { selectItems, confirmDelete, showResult, runCleanFlow };
|
package/src/common/ui/screen.js
CHANGED
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
* 负责屏幕的清屏、重绘等操作
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
CLEAR_SCREEN,
|
|
8
|
+
MOVE_CURSOR_HOME,
|
|
9
|
+
SAVE_CURSOR,
|
|
10
|
+
RESTORE_CURSOR,
|
|
11
|
+
enterAlternateScreen,
|
|
12
|
+
exitAlternateScreen
|
|
13
|
+
} = require('./ansi.js');
|
|
7
14
|
|
|
8
15
|
class ScreenRenderer {
|
|
9
16
|
constructor() {
|
|
@@ -53,4 +60,21 @@ class ScreenRenderer {
|
|
|
53
60
|
}
|
|
54
61
|
}
|
|
55
62
|
|
|
56
|
-
|
|
63
|
+
/**
|
|
64
|
+
* 屏幕会话工厂函数
|
|
65
|
+
* 统一管理备用屏幕的生命周期
|
|
66
|
+
* @param {Function} fn - 在屏幕会话中执行的异步函数,接收 renderer 参数
|
|
67
|
+
* @returns {Promise<any>} fn 的返回值
|
|
68
|
+
*/
|
|
69
|
+
async function withScreenSession(fn) {
|
|
70
|
+
enterAlternateScreen();
|
|
71
|
+
const renderer = new ScreenRenderer();
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
return await fn(renderer);
|
|
75
|
+
} finally {
|
|
76
|
+
exitAlternateScreen();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { ScreenRenderer, withScreenSession };
|
package/src/common/ui/utils.js
CHANGED
|
@@ -74,7 +74,38 @@ function waitForNavigation() {
|
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* 等待确认按键
|
|
79
|
+
* @returns {Promise<boolean>} true=确认, false=取消
|
|
80
|
+
*/
|
|
81
|
+
function waitForConfirm() {
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
readline.emitKeypressEvents(process.stdin);
|
|
84
|
+
process.stdin.resume();
|
|
85
|
+
process.stdin.setRawMode(true);
|
|
86
|
+
|
|
87
|
+
const handler = (str, key) => {
|
|
88
|
+
if (key.name === 'return' || str.toLowerCase() === 'y') {
|
|
89
|
+
cleanup();
|
|
90
|
+
resolve(true);
|
|
91
|
+
} else if (key.name === 'escape' || str.toLowerCase() === 'n') {
|
|
92
|
+
cleanup();
|
|
93
|
+
resolve(false);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const cleanup = () => {
|
|
98
|
+
process.stdin.removeListener('keypress', handler);
|
|
99
|
+
process.stdin.setRawMode(false);
|
|
100
|
+
process.stdin.pause();
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
process.stdin.on('keypress', handler);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
77
107
|
module.exports = {
|
|
78
108
|
waitForAnyKey,
|
|
79
109
|
waitForNavigation,
|
|
110
|
+
waitForConfirm,
|
|
80
111
|
};
|
package/src/git-log/ui.js
CHANGED
|
@@ -12,8 +12,7 @@ const readline = require('readline');
|
|
|
12
12
|
|
|
13
13
|
// Common UI 模块
|
|
14
14
|
const { singleSelect } = require('../common/ui/single-select.js');
|
|
15
|
-
const {
|
|
16
|
-
const { ScreenRenderer } = require('../common/ui/screen.js');
|
|
15
|
+
const { withScreenSession } = require('../common/ui/screen.js');
|
|
17
16
|
const { PagedDisplay } = require('../common/ui/pagination.js');
|
|
18
17
|
|
|
19
18
|
// 配置文件路径
|
|
@@ -266,10 +265,7 @@ async function displayLogs (logs, onRefilter) {
|
|
|
266
265
|
return;
|
|
267
266
|
}
|
|
268
267
|
|
|
269
|
-
|
|
270
|
-
const renderer = new ScreenRenderer();
|
|
271
|
-
|
|
272
|
-
try {
|
|
268
|
+
await withScreenSession(async (renderer) => {
|
|
273
269
|
// 创建分页显示(动态计算每页条数)
|
|
274
270
|
const pageSize = calculateDynamicPageSize();
|
|
275
271
|
const pagedDisplay = new PagedDisplay(logs, pageSize);
|
|
@@ -290,16 +286,13 @@ async function displayLogs (logs, onRefilter) {
|
|
|
290
286
|
} else if (action === 'prev') {
|
|
291
287
|
pagedDisplay.prevPage();
|
|
292
288
|
} else if (action === 'refilter') {
|
|
293
|
-
exitAlternateScreen();
|
|
294
289
|
onRefilter();
|
|
295
290
|
return;
|
|
296
291
|
} else if (action === 'exit') {
|
|
297
292
|
break;
|
|
298
293
|
}
|
|
299
294
|
}
|
|
300
|
-
}
|
|
301
|
-
exitAlternateScreen();
|
|
302
|
-
}
|
|
295
|
+
});
|
|
303
296
|
}
|
|
304
297
|
|
|
305
298
|
module.exports = {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 构建执行模块
|
|
3
|
+
* 检测和执行构建脚本
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { execaCommand } = require('execa');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 检测构建脚本
|
|
10
|
+
* @param {Object} scripts - package.json 中的 scripts 对象
|
|
11
|
+
* @returns {string|null} 找到的构建脚本名,未找到返回 null
|
|
12
|
+
*/
|
|
13
|
+
function detectBuildScripts(scripts) {
|
|
14
|
+
// 按优先级检测构建脚本
|
|
15
|
+
const buildScriptNames = [
|
|
16
|
+
'build',
|
|
17
|
+
'compile',
|
|
18
|
+
'prebuild',
|
|
19
|
+
'prepublishOnly'
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
for (const scriptName of buildScriptNames) {
|
|
23
|
+
if (scripts && scripts[scriptName]) {
|
|
24
|
+
return scriptName;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 执行构建脚本
|
|
33
|
+
* @param {string} pkgPath - 包路径
|
|
34
|
+
* @param {string} scriptName - 脚本名称
|
|
35
|
+
* @returns {Promise<Object>} 执行结果
|
|
36
|
+
*/
|
|
37
|
+
async function runBuild(pkgPath, scriptName) {
|
|
38
|
+
try {
|
|
39
|
+
const result = await execaCommand(`npm run ${scriptName}`, {
|
|
40
|
+
cwd: pkgPath,
|
|
41
|
+
stdout: 'inherit',
|
|
42
|
+
stderr: 'inherit'
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
success: true,
|
|
47
|
+
exitCode: result.exitCode
|
|
48
|
+
};
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
error: error.message,
|
|
53
|
+
exitCode: error.exitCode
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
detectBuildScripts,
|
|
60
|
+
runBuild
|
|
61
|
+
};
|