@harryisfish/gitt 1.5.0 → 1.6.2
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/clean.js +30 -38
- package/dist/commands/upgrade.js +265 -0
- package/dist/errors.js +27 -14
- package/dist/index.js +66 -99
- package/dist/utils/config.js +53 -2
- package/package.json +9 -2
- package/dist/commands/rebase.js +0 -137
- package/dist/commands/stash.js +0 -217
- package/dist/display.js +0 -159
- package/dist/prompts.js +0 -56
- package/dist/theme.js +0 -59
package/dist/utils/config.js
CHANGED
|
@@ -39,7 +39,50 @@ exports.writeConfigFile = writeConfigFile;
|
|
|
39
39
|
const fs = __importStar(require("fs"));
|
|
40
40
|
const path = __importStar(require("path"));
|
|
41
41
|
const simple_git_1 = require("simple-git");
|
|
42
|
+
const minimatch_1 = require("minimatch");
|
|
42
43
|
const CONFIG_FILE_NAME = '.gitt';
|
|
44
|
+
/**
|
|
45
|
+
* Validate configuration values.
|
|
46
|
+
*/
|
|
47
|
+
function validateConfig(config) {
|
|
48
|
+
const validated = {};
|
|
49
|
+
// Validate mainBranch
|
|
50
|
+
if (config.mainBranch !== undefined) {
|
|
51
|
+
if (typeof config.mainBranch !== 'string' || config.mainBranch.trim() === '') {
|
|
52
|
+
throw new Error('Invalid config: mainBranch must be a non-empty string');
|
|
53
|
+
}
|
|
54
|
+
validated.mainBranch = config.mainBranch.trim();
|
|
55
|
+
}
|
|
56
|
+
// Validate ignoreBranches
|
|
57
|
+
if (config.ignoreBranches !== undefined) {
|
|
58
|
+
if (!Array.isArray(config.ignoreBranches)) {
|
|
59
|
+
throw new Error('Invalid config: ignoreBranches must be an array');
|
|
60
|
+
}
|
|
61
|
+
const validPatterns = [];
|
|
62
|
+
for (const pattern of config.ignoreBranches) {
|
|
63
|
+
if (typeof pattern !== 'string' || pattern.trim() === '') {
|
|
64
|
+
throw new Error('Invalid config: ignoreBranches must contain non-empty strings');
|
|
65
|
+
}
|
|
66
|
+
// Test if it's a valid glob pattern by trying to use it
|
|
67
|
+
try {
|
|
68
|
+
(0, minimatch_1.minimatch)('test', pattern);
|
|
69
|
+
validPatterns.push(pattern);
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
throw new Error(`Invalid config: "${pattern}" is not a valid glob pattern`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
validated.ignoreBranches = validPatterns;
|
|
76
|
+
}
|
|
77
|
+
// Validate staleDays
|
|
78
|
+
if (config.staleDays !== undefined) {
|
|
79
|
+
if (typeof config.staleDays !== 'number' || config.staleDays < 1 || config.staleDays > 365 || !Number.isInteger(config.staleDays)) {
|
|
80
|
+
throw new Error('Invalid config: staleDays must be an integer between 1 and 365');
|
|
81
|
+
}
|
|
82
|
+
validated.staleDays = config.staleDays;
|
|
83
|
+
}
|
|
84
|
+
return validated;
|
|
85
|
+
}
|
|
43
86
|
/**
|
|
44
87
|
* Get the project root directory (where .git is located).
|
|
45
88
|
*/
|
|
@@ -64,9 +107,15 @@ async function readConfigFile() {
|
|
|
64
107
|
return {};
|
|
65
108
|
}
|
|
66
109
|
const content = fs.readFileSync(configPath, 'utf-8');
|
|
67
|
-
|
|
110
|
+
const parsedConfig = JSON.parse(content);
|
|
111
|
+
return validateConfig(parsedConfig);
|
|
68
112
|
}
|
|
69
113
|
catch (error) {
|
|
114
|
+
// If validation fails, throw the error instead of returning empty config
|
|
115
|
+
if (error instanceof Error && error.message.startsWith('Invalid config:')) {
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
// For other errors (file read, JSON parse), return empty config
|
|
70
119
|
return {};
|
|
71
120
|
}
|
|
72
121
|
}
|
|
@@ -74,10 +123,12 @@ async function readConfigFile() {
|
|
|
74
123
|
* Write to the .gitt configuration file.
|
|
75
124
|
*/
|
|
76
125
|
async function writeConfigFile(config) {
|
|
126
|
+
// Validate new config before writing
|
|
127
|
+
const validatedConfig = validateConfig(config);
|
|
77
128
|
const root = await getProjectRoot();
|
|
78
129
|
const configPath = path.join(root, CONFIG_FILE_NAME);
|
|
79
130
|
// Read existing config to merge
|
|
80
131
|
const currentConfig = await readConfigFile();
|
|
81
|
-
const newConfig = { ...currentConfig, ...
|
|
132
|
+
const newConfig = { ...currentConfig, ...validatedConfig };
|
|
82
133
|
fs.writeFileSync(configPath, JSON.stringify(newConfig, null, 2));
|
|
83
134
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harryisfish/gitt",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"description": "A command-line tool to help you manage Git repositories and remote repositories, such as keeping in sync, pushing, pulling, etc.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
"build": "tsc",
|
|
11
11
|
"start": "tsx src/index.ts",
|
|
12
12
|
"dev": "tsx watch src/index.ts",
|
|
13
|
+
"test": "vitest",
|
|
14
|
+
"test:ui": "vitest --ui",
|
|
15
|
+
"test:coverage": "vitest --coverage",
|
|
13
16
|
"prepare": "pnpm run build",
|
|
14
17
|
"dev:link": "pnpm build && pnpm link --global",
|
|
15
18
|
"dev:unlink": "pnpm unlink --global"
|
|
@@ -32,14 +35,18 @@
|
|
|
32
35
|
"homepage": "https://github.com/harryisfish/gitt#readme",
|
|
33
36
|
"devDependencies": {
|
|
34
37
|
"@types/node": "^20.11.24",
|
|
38
|
+
"@vitest/ui": "^4.0.14",
|
|
35
39
|
"ts-node": "^10.9.2",
|
|
36
40
|
"tsx": "^4.7.1",
|
|
37
|
-
"typescript": "^5.3.3"
|
|
41
|
+
"typescript": "^5.3.3",
|
|
42
|
+
"vitest": "^4.0.14"
|
|
38
43
|
},
|
|
39
44
|
"dependencies": {
|
|
40
45
|
"@inquirer/prompts": "^3.3.0",
|
|
41
46
|
"@types/minimatch": "^6.0.0",
|
|
42
47
|
"@types/update-notifier": "^6.0.8",
|
|
48
|
+
"chalk": "4",
|
|
49
|
+
"commander": "^14.0.2",
|
|
43
50
|
"listr2": "^8.0.0",
|
|
44
51
|
"minimatch": "^10.1.1",
|
|
45
52
|
"simple-git": "^3.22.0",
|
package/dist/commands/rebase.js
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.interactiveRebase = interactiveRebase;
|
|
4
|
-
const prompts_1 = require("@inquirer/prompts");
|
|
5
|
-
const simple_git_1 = require("simple-git");
|
|
6
|
-
const errors_1 = require("../errors");
|
|
7
|
-
const git = (0, simple_git_1.simpleGit)();
|
|
8
|
-
async function interactiveRebase() {
|
|
9
|
-
try {
|
|
10
|
-
// 获取当前分支最近的提交记录
|
|
11
|
-
const commits = await getRecentCommits();
|
|
12
|
-
// 选择要变基的提交范围
|
|
13
|
-
const targetCommit = await selectCommitRange(commits);
|
|
14
|
-
if (!targetCommit)
|
|
15
|
-
return;
|
|
16
|
-
// 选择变基操作类型
|
|
17
|
-
const action = await selectRebaseAction();
|
|
18
|
-
// 根据不同的操作类型执行相应的变基操作
|
|
19
|
-
switch (action) {
|
|
20
|
-
case 'squash':
|
|
21
|
-
await handleSquashRebase(targetCommit);
|
|
22
|
-
break;
|
|
23
|
-
case 'reword':
|
|
24
|
-
await handleRewordRebase(targetCommit);
|
|
25
|
-
break;
|
|
26
|
-
case 'drop':
|
|
27
|
-
await handleDropRebase(targetCommit);
|
|
28
|
-
break;
|
|
29
|
-
case 'reorder':
|
|
30
|
-
await handleReorderRebase(targetCommit);
|
|
31
|
-
break;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
if (error instanceof Error) {
|
|
36
|
-
throw new errors_1.GitError(`变基操作失败: ${error.message}`);
|
|
37
|
-
}
|
|
38
|
-
throw error;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
// 获取最近的提交记录
|
|
42
|
-
async function getRecentCommits(count = 10) {
|
|
43
|
-
const log = await git.log(['--max-count=' + count]);
|
|
44
|
-
return log.all.map(commit => ({
|
|
45
|
-
hash: commit.hash,
|
|
46
|
-
message: commit.message,
|
|
47
|
-
date: commit.date,
|
|
48
|
-
author: commit.author_name
|
|
49
|
-
}));
|
|
50
|
-
}
|
|
51
|
-
// 选择要变基的提交范围
|
|
52
|
-
async function selectCommitRange(commits) {
|
|
53
|
-
const choices = commits.map(commit => ({
|
|
54
|
-
name: `${commit.hash.substring(0, 7)} - ${commit.date.split('T')[0]} - ${commit.message}`,
|
|
55
|
-
value: commit.hash
|
|
56
|
-
}));
|
|
57
|
-
const targetCommit = await (0, prompts_1.select)({
|
|
58
|
-
message: '请选择要变基到的提交(此提交之后的所有提交都将被包含在变基操作中):',
|
|
59
|
-
choices: [
|
|
60
|
-
...choices,
|
|
61
|
-
{ name: '取消', value: null }
|
|
62
|
-
]
|
|
63
|
-
});
|
|
64
|
-
return targetCommit;
|
|
65
|
-
}
|
|
66
|
-
// 选择变基操作类型
|
|
67
|
-
async function selectRebaseAction() {
|
|
68
|
-
return await (0, prompts_1.select)({
|
|
69
|
-
message: '请选择要执行的变基操作:',
|
|
70
|
-
choices: [
|
|
71
|
-
{
|
|
72
|
-
name: '合并多个提交 (squash)',
|
|
73
|
-
value: 'squash',
|
|
74
|
-
description: '将多个提交合并为一个提交'
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
name: '修改提交信息 (reword)',
|
|
78
|
-
value: 'reword',
|
|
79
|
-
description: '修改某个提交的提交信息'
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
name: '删除提交 (drop)',
|
|
83
|
-
value: 'drop',
|
|
84
|
-
description: '删除某个提交'
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
name: '调整提交顺序 (reorder)',
|
|
88
|
-
value: 'reorder',
|
|
89
|
-
description: '改变提交的顺序'
|
|
90
|
-
}
|
|
91
|
-
]
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
// 处理压缩提交的变基操作
|
|
95
|
-
async function handleSquashRebase(targetCommit) {
|
|
96
|
-
const confirmed = await (0, prompts_1.confirm)({
|
|
97
|
-
message: '此操作将打开编辑器进行交互式变基,是否继续?',
|
|
98
|
-
default: true
|
|
99
|
-
});
|
|
100
|
-
if (!confirmed)
|
|
101
|
-
return;
|
|
102
|
-
// 执行交互式变基
|
|
103
|
-
await git.raw(['rebase', '-i', `${targetCommit}~1`]);
|
|
104
|
-
(0, errors_1.printSuccess)('变基操作已完成,请检查提交历史');
|
|
105
|
-
}
|
|
106
|
-
// 处理修改提交信息的变基操作
|
|
107
|
-
async function handleRewordRebase(targetCommit) {
|
|
108
|
-
const newMessage = await (0, prompts_1.input)({
|
|
109
|
-
message: '请输入新的提交信息:'
|
|
110
|
-
});
|
|
111
|
-
if (!newMessage)
|
|
112
|
-
return;
|
|
113
|
-
await git.raw(['rebase', '-i', `${targetCommit}~1`]);
|
|
114
|
-
(0, errors_1.printSuccess)('提交信息已更新,请检查提交历史');
|
|
115
|
-
}
|
|
116
|
-
// 处理删除提交的变基操作
|
|
117
|
-
async function handleDropRebase(targetCommit) {
|
|
118
|
-
const confirmed = await (0, prompts_1.confirm)({
|
|
119
|
-
message: '⚠️ 删除提交是一个危险操作,确定要继续吗?',
|
|
120
|
-
default: false
|
|
121
|
-
});
|
|
122
|
-
if (!confirmed)
|
|
123
|
-
return;
|
|
124
|
-
await git.raw(['rebase', '-i', `${targetCommit}~1`]);
|
|
125
|
-
(0, errors_1.printSuccess)('提交已删除,请检查提交历史');
|
|
126
|
-
}
|
|
127
|
-
// 处理调整提交顺序的变基操作
|
|
128
|
-
async function handleReorderRebase(targetCommit) {
|
|
129
|
-
const confirmed = await (0, prompts_1.confirm)({
|
|
130
|
-
message: '此操作将打开编辑器让您调整提交顺序,是否继续?',
|
|
131
|
-
default: true
|
|
132
|
-
});
|
|
133
|
-
if (!confirmed)
|
|
134
|
-
return;
|
|
135
|
-
await git.raw(['rebase', '-i', `${targetCommit}~1`]);
|
|
136
|
-
(0, errors_1.printSuccess)('提交顺序已调整,请检查提交历史');
|
|
137
|
-
}
|
package/dist/commands/stash.js
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.advancedStash = advancedStash;
|
|
4
|
-
const prompts_1 = require("@inquirer/prompts");
|
|
5
|
-
const simple_git_1 = require("simple-git");
|
|
6
|
-
const errors_1 = require("../errors");
|
|
7
|
-
const git = (0, simple_git_1.simpleGit)();
|
|
8
|
-
/**
|
|
9
|
-
* Advanced stash management functionality
|
|
10
|
-
* @description Provides interactive interface for managing git stashes
|
|
11
|
-
* @throws {GitError} When stash operation fails
|
|
12
|
-
*/
|
|
13
|
-
async function advancedStash() {
|
|
14
|
-
try {
|
|
15
|
-
const action = await (0, prompts_1.select)({
|
|
16
|
-
message: '请选择储藏操作:',
|
|
17
|
-
choices: [
|
|
18
|
-
{
|
|
19
|
-
name: '创建新储藏',
|
|
20
|
-
value: 'create',
|
|
21
|
-
description: '储藏当前的修改'
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
name: '储藏部分文件',
|
|
25
|
-
value: 'partial',
|
|
26
|
-
description: '选择性储藏部分文件的修改'
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
name: '管理储藏列表',
|
|
30
|
-
value: 'manage',
|
|
31
|
-
description: '查看、应用或删除已有的储藏'
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
name: '返回主菜单',
|
|
35
|
-
value: 'back'
|
|
36
|
-
}
|
|
37
|
-
]
|
|
38
|
-
});
|
|
39
|
-
switch (action) {
|
|
40
|
-
case 'create':
|
|
41
|
-
await createStash();
|
|
42
|
-
break;
|
|
43
|
-
case 'partial':
|
|
44
|
-
await createPartialStash();
|
|
45
|
-
break;
|
|
46
|
-
case 'manage':
|
|
47
|
-
await manageStashes();
|
|
48
|
-
break;
|
|
49
|
-
case 'back':
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
if (error instanceof Error) {
|
|
55
|
-
throw new errors_1.GitError(`储藏操作失败: ${error.message}`);
|
|
56
|
-
}
|
|
57
|
-
throw error;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Create a new stash with optional message and untracked files
|
|
62
|
-
*/
|
|
63
|
-
async function createStash() {
|
|
64
|
-
// 检查是否有可储藏的修改
|
|
65
|
-
const status = await git.status();
|
|
66
|
-
if (status.files.length === 0) {
|
|
67
|
-
console.log('没有可储藏的修改。');
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
// 询问是否包含未跟踪的文件
|
|
71
|
-
const includeUntracked = await (0, prompts_1.confirm)({
|
|
72
|
-
message: '是否包含未跟踪的文件?',
|
|
73
|
-
default: false
|
|
74
|
-
});
|
|
75
|
-
// 获取储藏信息
|
|
76
|
-
const message = await (0, prompts_1.input)({
|
|
77
|
-
message: '请输入储藏说明(可选):'
|
|
78
|
-
});
|
|
79
|
-
// 执行储藏操作
|
|
80
|
-
const args = ['push'];
|
|
81
|
-
if (includeUntracked)
|
|
82
|
-
args.push('-u');
|
|
83
|
-
if (message)
|
|
84
|
-
args.push('-m', message);
|
|
85
|
-
await git.stash(args);
|
|
86
|
-
(0, errors_1.printSuccess)('修改已成功储藏');
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Create a partial stash by selecting specific files
|
|
90
|
-
*/
|
|
91
|
-
async function createPartialStash() {
|
|
92
|
-
// 获取当前状态
|
|
93
|
-
const status = await git.status();
|
|
94
|
-
if (status.files.length === 0) {
|
|
95
|
-
console.log('没有可储藏的修改。');
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
// 列出所有修改的文件供选择
|
|
99
|
-
const fileChoices = status.files.map(file => ({
|
|
100
|
-
name: `${file.path} (${file.index}${file.working_dir})`,
|
|
101
|
-
value: file.path
|
|
102
|
-
}));
|
|
103
|
-
const selectedFiles = await (0, prompts_1.checkbox)({
|
|
104
|
-
message: '请选择要储藏的文件(空格选择,回车确认):',
|
|
105
|
-
choices: fileChoices
|
|
106
|
-
});
|
|
107
|
-
if (selectedFiles.length === 0) {
|
|
108
|
-
console.log('未选择任何文件,操作已取消。');
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
// 获取储藏信息
|
|
112
|
-
const message = await (0, prompts_1.input)({
|
|
113
|
-
message: '请输入储藏说明(可选):'
|
|
114
|
-
});
|
|
115
|
-
// 执行部分储藏
|
|
116
|
-
await git.raw(['stash', 'push', '-p', ...(message ? ['-m', message] : []), ...selectedFiles]);
|
|
117
|
-
(0, errors_1.printSuccess)('选中的文件已成功储藏');
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Manage existing stashes (list, apply, drop)
|
|
121
|
-
*/
|
|
122
|
-
async function manageStashes() {
|
|
123
|
-
// 获取所有储藏
|
|
124
|
-
const stashList = await getStashList();
|
|
125
|
-
if (stashList.length === 0) {
|
|
126
|
-
console.log('没有找到任何储藏。');
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
// 显示储藏列表
|
|
130
|
-
const choices = stashList.map(stash => ({
|
|
131
|
-
name: `stash@{${stash.index}}: ${stash.message} (${stash.date})`,
|
|
132
|
-
value: stash.index
|
|
133
|
-
}));
|
|
134
|
-
const selectedStash = await (0, prompts_1.select)({
|
|
135
|
-
message: '请选择要操作的储藏:',
|
|
136
|
-
choices: [
|
|
137
|
-
...choices,
|
|
138
|
-
{ name: '返回', value: -1 }
|
|
139
|
-
]
|
|
140
|
-
});
|
|
141
|
-
if (selectedStash === -1)
|
|
142
|
-
return;
|
|
143
|
-
// 选择操作
|
|
144
|
-
const operation = await (0, prompts_1.select)({
|
|
145
|
-
message: '请选择操作:',
|
|
146
|
-
choices: [
|
|
147
|
-
{
|
|
148
|
-
name: '应用储藏',
|
|
149
|
-
value: 'apply',
|
|
150
|
-
description: '应用选中的储藏(保留储藏记录)'
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
name: '弹出储藏',
|
|
154
|
-
value: 'pop',
|
|
155
|
-
description: '应用并删除选中的储藏'
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
name: '删除储藏',
|
|
159
|
-
value: 'drop',
|
|
160
|
-
description: '删除选中的储藏'
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
name: '查看储藏内容',
|
|
164
|
-
value: 'show',
|
|
165
|
-
description: '显示储藏的详细内容'
|
|
166
|
-
}
|
|
167
|
-
]
|
|
168
|
-
});
|
|
169
|
-
// 执行选中的操作
|
|
170
|
-
switch (operation) {
|
|
171
|
-
case 'apply':
|
|
172
|
-
await git.stash(['apply', `stash@{${selectedStash}}`]);
|
|
173
|
-
(0, errors_1.printSuccess)('储藏已应用');
|
|
174
|
-
break;
|
|
175
|
-
case 'pop':
|
|
176
|
-
await git.stash(['pop', `stash@{${selectedStash}}`]);
|
|
177
|
-
(0, errors_1.printSuccess)('储藏已弹出');
|
|
178
|
-
break;
|
|
179
|
-
case 'drop':
|
|
180
|
-
const confirmed = await (0, prompts_1.confirm)({
|
|
181
|
-
message: '确定要删除这个储藏吗?此操作不可撤销。',
|
|
182
|
-
default: false
|
|
183
|
-
});
|
|
184
|
-
if (confirmed) {
|
|
185
|
-
await git.stash(['drop', `stash@{${selectedStash}}`]);
|
|
186
|
-
(0, errors_1.printSuccess)('储藏已删除');
|
|
187
|
-
}
|
|
188
|
-
break;
|
|
189
|
-
case 'show':
|
|
190
|
-
const diff = await git.stash(['show', '-p', `stash@{${selectedStash}}`]);
|
|
191
|
-
console.log('\n储藏内容:\n', diff);
|
|
192
|
-
break;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Get list of all stashes
|
|
197
|
-
*/
|
|
198
|
-
async function getStashList() {
|
|
199
|
-
const result = await git.stash(['list']);
|
|
200
|
-
if (!result)
|
|
201
|
-
return [];
|
|
202
|
-
return result
|
|
203
|
-
.split('\n')
|
|
204
|
-
.filter(line => line.trim())
|
|
205
|
-
.map(line => {
|
|
206
|
-
const match = line.match(/^stash@{(\d+)}: (.*)/);
|
|
207
|
-
if (!match)
|
|
208
|
-
return null;
|
|
209
|
-
return {
|
|
210
|
-
index: parseInt(match[1]),
|
|
211
|
-
hash: '', // 可以通过额外命令获取完整 hash
|
|
212
|
-
message: match[2],
|
|
213
|
-
date: '' // 可以通过额外命令获取详细时间
|
|
214
|
-
};
|
|
215
|
-
})
|
|
216
|
-
.filter((entry) => entry !== null);
|
|
217
|
-
}
|
package/dist/display.js
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.display = void 0;
|
|
4
|
-
const readline_1 = require("readline");
|
|
5
|
-
// ANSI 转义序列
|
|
6
|
-
const CLEAR_SCREEN = '\x1b[2J';
|
|
7
|
-
const CLEAR_SCREEN_AND_SCROLLBACK = '\x1b[2J\x1b[3J';
|
|
8
|
-
const MOVE_TO_TOP = '\x1b[H';
|
|
9
|
-
const SAVE_CURSOR = '\x1b[s';
|
|
10
|
-
const RESTORE_CURSOR = '\x1b[u';
|
|
11
|
-
const HIDE_CURSOR = '\x1b[?25l';
|
|
12
|
-
const SHOW_CURSOR = '\x1b[?25h';
|
|
13
|
-
const ALTERNATE_SCREEN_BUFFER = '\x1b[?1049h';
|
|
14
|
-
const NORMAL_SCREEN_BUFFER = '\x1b[?1049l';
|
|
15
|
-
// 颜色和样式常量
|
|
16
|
-
const RESET = '\x1b[0m';
|
|
17
|
-
const BOLD = '\x1b[1m';
|
|
18
|
-
const DIM = '\x1b[2m';
|
|
19
|
-
const CYAN = '\x1b[36m';
|
|
20
|
-
const GREEN = '\x1b[32m';
|
|
21
|
-
const YELLOW = '\x1b[33m';
|
|
22
|
-
const BLUE = '\x1b[34m';
|
|
23
|
-
const MAGENTA = '\x1b[35m';
|
|
24
|
-
const RED = '\x1b[31m';
|
|
25
|
-
const BG_BLACK = '\x1b[40m';
|
|
26
|
-
// 状态栏样式
|
|
27
|
-
const STATUS_STYLES = {
|
|
28
|
-
branch: `${BOLD}${GREEN}`,
|
|
29
|
-
changes: `${YELLOW}`,
|
|
30
|
-
ahead: `${CYAN}`,
|
|
31
|
-
behind: `${MAGENTA}`,
|
|
32
|
-
separator: `${DIM}│${RESET}`,
|
|
33
|
-
label: `${DIM}`,
|
|
34
|
-
};
|
|
35
|
-
class DisplayManager {
|
|
36
|
-
constructor() {
|
|
37
|
-
this.statusSection = [];
|
|
38
|
-
this.menuSection = [];
|
|
39
|
-
this.dividerLine = '';
|
|
40
|
-
this.isLoading = true;
|
|
41
|
-
this.isDetailedView = false;
|
|
42
|
-
// 获取终端大小
|
|
43
|
-
this.terminalSize = { rows: process.stdout.rows, columns: process.stdout.columns };
|
|
44
|
-
// 创建分隔线
|
|
45
|
-
this.updateDividerLine();
|
|
46
|
-
// 计算上下区域的大小 - 状态区域只占一行
|
|
47
|
-
this.statusHeight = 1;
|
|
48
|
-
this.menuHeight = this.terminalSize.rows - this.statusHeight - 1;
|
|
49
|
-
// 监听终端大小变化
|
|
50
|
-
process.stdout.on('resize', () => {
|
|
51
|
-
this.terminalSize = { rows: process.stdout.rows, columns: process.stdout.columns };
|
|
52
|
-
this.updateDividerLine();
|
|
53
|
-
this.menuHeight = this.terminalSize.rows - this.statusHeight - 1;
|
|
54
|
-
if (!this.isDetailedView) {
|
|
55
|
-
this.redrawScreen();
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
updateDividerLine() {
|
|
60
|
-
this.dividerLine = '─'.repeat(this.terminalSize.columns);
|
|
61
|
-
}
|
|
62
|
-
// 清屏并初始化显示区域
|
|
63
|
-
initDisplay() {
|
|
64
|
-
// 切换到备用屏幕缓冲区,实现全屏模式
|
|
65
|
-
process.stdout.write(ALTERNATE_SCREEN_BUFFER);
|
|
66
|
-
process.stdout.write(CLEAR_SCREEN_AND_SCROLLBACK + MOVE_TO_TOP);
|
|
67
|
-
process.stdout.write(HIDE_CURSOR);
|
|
68
|
-
this.drawDivider();
|
|
69
|
-
this.showLoadingStatus();
|
|
70
|
-
// 确保程序退出时恢复终端状态
|
|
71
|
-
process.on('exit', () => {
|
|
72
|
-
process.stdout.write(SHOW_CURSOR);
|
|
73
|
-
process.stdout.write(NORMAL_SCREEN_BUFFER);
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
// 绘制分隔线
|
|
77
|
-
drawDivider() {
|
|
78
|
-
process.stdout.write(SAVE_CURSOR);
|
|
79
|
-
process.stdout.write(`\x1b[${this.statusHeight + 1}H${this.dividerLine}`);
|
|
80
|
-
process.stdout.write(RESTORE_CURSOR);
|
|
81
|
-
}
|
|
82
|
-
// 显示加载状态
|
|
83
|
-
showLoadingStatus() {
|
|
84
|
-
if (this.isLoading) {
|
|
85
|
-
process.stdout.write(MOVE_TO_TOP);
|
|
86
|
-
process.stdout.write('正在获取仓库信息...\n');
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
// 更新状态区域内容
|
|
90
|
-
updateStatus(lines) {
|
|
91
|
-
this.isLoading = false;
|
|
92
|
-
this.statusSection = lines;
|
|
93
|
-
this.refreshStatus();
|
|
94
|
-
}
|
|
95
|
-
// 更新简洁状态栏
|
|
96
|
-
updateCompactStatus(branchName, uncommittedChanges, ahead, behind) {
|
|
97
|
-
this.isLoading = false;
|
|
98
|
-
const statusLine = `🌿 ${branchName} | 📝 ${uncommittedChanges} | ⬆️ ${ahead} | ⬇️ ${behind}`;
|
|
99
|
-
this.statusSection = [statusLine];
|
|
100
|
-
this.refreshStatus();
|
|
101
|
-
}
|
|
102
|
-
// 显示详细状态信息
|
|
103
|
-
showDetailedStatus(lines) {
|
|
104
|
-
this.isDetailedView = true;
|
|
105
|
-
process.stdout.write(CLEAR_SCREEN + MOVE_TO_TOP);
|
|
106
|
-
lines.forEach(line => {
|
|
107
|
-
process.stdout.write(line + '\n');
|
|
108
|
-
});
|
|
109
|
-
process.stdout.write('\n按任意键返回主菜单...');
|
|
110
|
-
// 监听一次按键事件
|
|
111
|
-
process.stdin.setRawMode(true);
|
|
112
|
-
process.stdin.once('data', () => {
|
|
113
|
-
process.stdin.setRawMode(false);
|
|
114
|
-
this.isDetailedView = false;
|
|
115
|
-
this.redrawScreen();
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
// 刷新状态区域显示
|
|
119
|
-
refreshStatus() {
|
|
120
|
-
process.stdout.write(SAVE_CURSOR);
|
|
121
|
-
process.stdout.write(MOVE_TO_TOP);
|
|
122
|
-
// 清空状态区域
|
|
123
|
-
for (let i = 0; i < this.statusHeight; i++) {
|
|
124
|
-
(0, readline_1.clearLine)(process.stdout, 0);
|
|
125
|
-
process.stdout.write('\n');
|
|
126
|
-
}
|
|
127
|
-
// 重新显示状态信息
|
|
128
|
-
process.stdout.write(MOVE_TO_TOP);
|
|
129
|
-
this.statusSection.forEach(line => {
|
|
130
|
-
process.stdout.write(line + '\n');
|
|
131
|
-
});
|
|
132
|
-
process.stdout.write(RESTORE_CURSOR);
|
|
133
|
-
}
|
|
134
|
-
// 重绘整个屏幕
|
|
135
|
-
redrawScreen() {
|
|
136
|
-
process.stdout.write(CLEAR_SCREEN + MOVE_TO_TOP);
|
|
137
|
-
this.drawDivider();
|
|
138
|
-
this.refreshStatus();
|
|
139
|
-
this.prepareForMenu();
|
|
140
|
-
}
|
|
141
|
-
// 准备菜单区域
|
|
142
|
-
prepareForMenu() {
|
|
143
|
-
// 将光标移动到分隔线下方
|
|
144
|
-
process.stdout.write(`\x1b[${this.statusHeight + 2}H`);
|
|
145
|
-
// 清除菜单区域
|
|
146
|
-
for (let i = this.statusHeight + 2; i < this.terminalSize.rows; i++) {
|
|
147
|
-
process.stdout.write(`\x1b[${i}H`);
|
|
148
|
-
(0, readline_1.clearLine)(process.stdout, 0);
|
|
149
|
-
}
|
|
150
|
-
// 重新定位到菜单开始位置
|
|
151
|
-
process.stdout.write(`\x1b[${this.statusHeight + 2}H`);
|
|
152
|
-
}
|
|
153
|
-
// 显示错误信息
|
|
154
|
-
showError(message) {
|
|
155
|
-
const currentPos = process.stdout.rows;
|
|
156
|
-
process.stdout.write(`\x1b[${currentPos}H\x1b[31m${message}\x1b[0m\n`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
exports.display = new DisplayManager();
|
package/dist/prompts.js
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { select, confirm } from '@inquirer/prompts';
|
|
2
|
-
import { theme } from './theme';
|
|
3
|
-
// 菜单选项
|
|
4
|
-
const menuChoices = [
|
|
5
|
-
{
|
|
6
|
-
name: '清理远程已删除的分支',
|
|
7
|
-
value: 'clean',
|
|
8
|
-
description: '清理那些在远程仓库已经被删除的本地分支'
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
name: '退出',
|
|
12
|
-
value: 'exit',
|
|
13
|
-
description: '退出程序'
|
|
14
|
-
}
|
|
15
|
-
];
|
|
16
|
-
// 显示主菜单
|
|
17
|
-
export async function showMainMenu() {
|
|
18
|
-
try {
|
|
19
|
-
const options = {
|
|
20
|
-
message: '请选择要执行的操作:',
|
|
21
|
-
choices: menuChoices,
|
|
22
|
-
pageSize: 10, // 一次显示的选项数量
|
|
23
|
-
loop: true, // 循环滚动
|
|
24
|
-
theme, // 使用自定义主题
|
|
25
|
-
};
|
|
26
|
-
return await select(options);
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
return 'exit';
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
// 确认提示
|
|
33
|
-
export async function confirmAction(message) {
|
|
34
|
-
try {
|
|
35
|
-
const options = {
|
|
36
|
-
message,
|
|
37
|
-
default: true,
|
|
38
|
-
theme, // 使用自定义主题
|
|
39
|
-
};
|
|
40
|
-
return await confirm(options);
|
|
41
|
-
}
|
|
42
|
-
catch (error) {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
// 初始化 Git 仓库确认
|
|
47
|
-
export async function confirmInitRepo() {
|
|
48
|
-
return await confirmAction('当前目录不是 Git 仓库,是否要创建?');
|
|
49
|
-
}
|
|
50
|
-
// 删除分支确认
|
|
51
|
-
export async function confirmDeleteBranches(branches) {
|
|
52
|
-
if (branches.length === 0)
|
|
53
|
-
return false;
|
|
54
|
-
const branchList = branches.join('\n - ');
|
|
55
|
-
return await confirmAction(`是否删除以下分支?\n - ${branchList}`);
|
|
56
|
-
}
|