@harryisfish/gitt 1.6.0 → 1.6.3
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 +31 -35
- package/dist/commands/upgrade.js +265 -0
- package/dist/errors.js +18 -5
- package/dist/index.js +16 -7
- package/package.json +2 -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/commands/clean.js
CHANGED
|
@@ -17,6 +17,7 @@ async function cleanDeletedBranches(options = {}) {
|
|
|
17
17
|
try {
|
|
18
18
|
const state = {
|
|
19
19
|
mainBranch: '',
|
|
20
|
+
currentBranch: '',
|
|
20
21
|
deletedBranches: []
|
|
21
22
|
};
|
|
22
23
|
// Phase 1: Discovery
|
|
@@ -26,38 +27,11 @@ async function cleanDeletedBranches(options = {}) {
|
|
|
26
27
|
task: async (ctx) => {
|
|
27
28
|
const mainBranch = await (0, git_1.getMainBranch)();
|
|
28
29
|
ctx.mainBranch = mainBranch;
|
|
30
|
+
const branchInfo = await git.branchLocal();
|
|
31
|
+
ctx.currentBranch = branchInfo.current;
|
|
29
32
|
await git.fetch(['origin', mainBranch]);
|
|
30
33
|
}
|
|
31
34
|
},
|
|
32
|
-
{
|
|
33
|
-
title: 'Switch to main branch',
|
|
34
|
-
task: async (ctx) => {
|
|
35
|
-
const currentBranch = (await git.branchLocal()).current;
|
|
36
|
-
const willDeleteCurrent = state.deletedBranches.some(b => b.name === currentBranch);
|
|
37
|
-
try {
|
|
38
|
-
await git.checkout(ctx.mainBranch);
|
|
39
|
-
}
|
|
40
|
-
catch (e) {
|
|
41
|
-
// If current branch will be deleted, we must switch - fail the operation
|
|
42
|
-
if (willDeleteCurrent) {
|
|
43
|
-
throw new errors_1.GitError(`Cannot switch to ${ctx.mainBranch}. Current branch "${currentBranch}" will be deleted but checkout failed.`);
|
|
44
|
-
}
|
|
45
|
-
// Otherwise, warn but continue (we can delete other branches)
|
|
46
|
-
console.warn(`Warning: Could not switch to ${ctx.mainBranch}, but continuing as current branch is not being deleted.`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
title: 'Sync main with remote',
|
|
52
|
-
task: async () => {
|
|
53
|
-
try {
|
|
54
|
-
await git.pull();
|
|
55
|
-
}
|
|
56
|
-
catch (e) {
|
|
57
|
-
// Ignore pull errors (e.g. if not on branch)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
35
|
{
|
|
62
36
|
title: 'Analyze branches',
|
|
63
37
|
task: async (ctx) => {
|
|
@@ -84,9 +58,14 @@ async function cleanDeletedBranches(options = {}) {
|
|
|
84
58
|
}
|
|
85
59
|
else {
|
|
86
60
|
// Default mode: check "gone" branches
|
|
61
|
+
// Note: label may contain newlines/whitespace when branch names are long
|
|
87
62
|
const goneBranches = branchSummary.all.filter(branch => {
|
|
88
63
|
const branchInfo = branchSummary.branches[branch];
|
|
89
|
-
|
|
64
|
+
if (!branchInfo.label)
|
|
65
|
+
return false;
|
|
66
|
+
// Normalize whitespace and check for "gone" status
|
|
67
|
+
const normalizedLabel = branchInfo.label.replace(/\s+/g, ' ');
|
|
68
|
+
return normalizedLabel.includes(': gone]');
|
|
90
69
|
});
|
|
91
70
|
candidates = goneBranches.map(b => ({ name: b, reason: 'Remote deleted' }));
|
|
92
71
|
}
|
|
@@ -135,11 +114,11 @@ async function cleanDeletedBranches(options = {}) {
|
|
|
135
114
|
else {
|
|
136
115
|
// Auto mode: Filter out unmerged branches
|
|
137
116
|
const unmerged = state.deletedBranches.filter(b => !b.isMerged);
|
|
138
|
-
if (unmerged.length > 0) {
|
|
139
|
-
console.log('\nSkipping unmerged branches (use -i to force delete):');
|
|
140
|
-
unmerged.forEach(b => console.log(` - ${b.name} (${b.reason})`));
|
|
141
|
-
}
|
|
142
117
|
state.deletedBranches = state.deletedBranches.filter(b => b.isMerged);
|
|
118
|
+
if (state.deletedBranches.length === 0 && unmerged.length > 0) {
|
|
119
|
+
(0, errors_1.printSuccess)(`No merged branches to clean up (${unmerged.length} unmerged branch${unmerged.length > 1 ? 'es' : ''} skipped, use -i to review)`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
143
122
|
}
|
|
144
123
|
if (state.deletedBranches.length === 0) {
|
|
145
124
|
(0, errors_1.printSuccess)('No branches selected for deletion');
|
|
@@ -150,6 +129,18 @@ async function cleanDeletedBranches(options = {}) {
|
|
|
150
129
|
state.deletedBranches.forEach(b => console.log(` - ${b.name} (${b.reason})`));
|
|
151
130
|
return;
|
|
152
131
|
}
|
|
132
|
+
// Switch to main branch if current branch will be deleted
|
|
133
|
+
const willDeleteCurrent = state.deletedBranches.some(b => b.name === state.currentBranch);
|
|
134
|
+
if (willDeleteCurrent) {
|
|
135
|
+
try {
|
|
136
|
+
await git.checkout(state.mainBranch);
|
|
137
|
+
await git.pull();
|
|
138
|
+
console.log(`Switched to ${state.mainBranch} and synced with remote`);
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
throw new errors_1.GitError(`Cannot switch to ${state.mainBranch}. Current branch "${state.currentBranch}" will be deleted but checkout failed: ${e instanceof Error ? e.message : 'Unknown error'}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
153
144
|
// Phase 3: Execution
|
|
154
145
|
const deleteTasks = new listr2_1.Listr([
|
|
155
146
|
{
|
|
@@ -169,6 +160,11 @@ async function cleanDeletedBranches(options = {}) {
|
|
|
169
160
|
(0, errors_1.printSuccess)('Branch cleanup completed');
|
|
170
161
|
}
|
|
171
162
|
catch (error) {
|
|
172
|
-
|
|
163
|
+
if (error instanceof errors_1.GitError || error instanceof errors_1.UserCancelError) {
|
|
164
|
+
// Re-throw our custom errors as-is
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
// Wrap other errors and preserve the original error
|
|
168
|
+
throw new errors_1.GitError(error instanceof Error ? error.message : 'Unknown error occurred while cleaning branches', error instanceof Error ? error : undefined);
|
|
173
169
|
}
|
|
174
170
|
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.upgradeCommand = upgradeCommand;
|
|
40
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
41
|
+
const child_process_1 = require("child_process");
|
|
42
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
43
|
+
const errors_1 = require("../errors");
|
|
44
|
+
const packageJson = require('../../package.json');
|
|
45
|
+
/**
|
|
46
|
+
* Detect which package manager is being used
|
|
47
|
+
*/
|
|
48
|
+
function detectPackageManager() {
|
|
49
|
+
try {
|
|
50
|
+
// Check if installed globally with pnpm
|
|
51
|
+
try {
|
|
52
|
+
const result = (0, child_process_1.spawnSync)('pnpm', ['list', '-g', '--depth=0'], {
|
|
53
|
+
encoding: 'utf8',
|
|
54
|
+
stdio: 'pipe'
|
|
55
|
+
});
|
|
56
|
+
if (result.status === 0 && result.stdout.includes(packageJson.name)) {
|
|
57
|
+
return 'pnpm';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
// Not installed with pnpm or pnpm not available
|
|
62
|
+
}
|
|
63
|
+
// Check if installed globally with yarn
|
|
64
|
+
try {
|
|
65
|
+
const result = (0, child_process_1.spawnSync)('yarn', ['global', 'list'], {
|
|
66
|
+
encoding: 'utf8',
|
|
67
|
+
stdio: 'pipe'
|
|
68
|
+
});
|
|
69
|
+
if (result.status === 0 && result.stdout.includes(packageJson.name)) {
|
|
70
|
+
return 'yarn';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
// Not installed with yarn or yarn not available
|
|
75
|
+
}
|
|
76
|
+
// Check if installed globally with npm
|
|
77
|
+
try {
|
|
78
|
+
const result = (0, child_process_1.spawnSync)('npm', ['list', '-g', '--depth=0'], {
|
|
79
|
+
encoding: 'utf8',
|
|
80
|
+
stdio: 'pipe'
|
|
81
|
+
});
|
|
82
|
+
if (result.status === 0 && result.stdout.includes(packageJson.name)) {
|
|
83
|
+
return 'npm';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
// Not installed with npm
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Fetch latest release information from GitHub
|
|
97
|
+
*/
|
|
98
|
+
async function fetchLatestRelease() {
|
|
99
|
+
try {
|
|
100
|
+
const response = await fetch('https://api.github.com/repos/harryisfish/gitt/releases/latest', {
|
|
101
|
+
headers: {
|
|
102
|
+
'Accept': 'application/vnd.github.v3+json',
|
|
103
|
+
'User-Agent': 'gitt-cli'
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const data = await response.json();
|
|
110
|
+
return {
|
|
111
|
+
version: data.tag_name.replace(/^v/, ''), // Remove 'v' prefix if present
|
|
112
|
+
changelog: data.body || 'No changelog available',
|
|
113
|
+
publishedAt: data.published_at
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Format changelog for display
|
|
122
|
+
*/
|
|
123
|
+
function formatChangelog(changelog) {
|
|
124
|
+
// Split by lines and format
|
|
125
|
+
const lines = changelog.split('\n');
|
|
126
|
+
const formatted = [];
|
|
127
|
+
for (const line of lines) {
|
|
128
|
+
if (line.trim() === '')
|
|
129
|
+
continue;
|
|
130
|
+
// Format headers
|
|
131
|
+
if (line.startsWith('## ')) {
|
|
132
|
+
formatted.push(chalk_1.default.bold.cyan(line));
|
|
133
|
+
}
|
|
134
|
+
else if (line.startsWith('### ')) {
|
|
135
|
+
formatted.push(chalk_1.default.bold.yellow(line));
|
|
136
|
+
}
|
|
137
|
+
else if (line.startsWith('- ') || line.startsWith('* ')) {
|
|
138
|
+
formatted.push(chalk_1.default.gray(' ' + line));
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
formatted.push(line);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return formatted.join('\n');
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Check for updates using update-notifier
|
|
148
|
+
*/
|
|
149
|
+
async function checkForUpdate() {
|
|
150
|
+
try {
|
|
151
|
+
const updateNotifier = await Promise.resolve().then(() => __importStar(require('update-notifier'))).then(m => m.default);
|
|
152
|
+
const notifier = updateNotifier({
|
|
153
|
+
pkg: packageJson,
|
|
154
|
+
updateCheckInterval: 0 // Force check
|
|
155
|
+
});
|
|
156
|
+
if (notifier.update) {
|
|
157
|
+
return {
|
|
158
|
+
current: notifier.update.current,
|
|
159
|
+
latest: notifier.update.latest,
|
|
160
|
+
type: notifier.update.type
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Execute upgrade command
|
|
171
|
+
*/
|
|
172
|
+
function executeUpgrade(packageManager, packageName) {
|
|
173
|
+
console.log(chalk_1.default.cyan('\nUpgrading...'));
|
|
174
|
+
try {
|
|
175
|
+
let command;
|
|
176
|
+
let args;
|
|
177
|
+
switch (packageManager) {
|
|
178
|
+
case 'pnpm':
|
|
179
|
+
command = 'pnpm';
|
|
180
|
+
args = ['add', '-g', `${packageName}@latest`];
|
|
181
|
+
break;
|
|
182
|
+
case 'yarn':
|
|
183
|
+
command = 'yarn';
|
|
184
|
+
args = ['global', 'add', `${packageName}@latest`];
|
|
185
|
+
break;
|
|
186
|
+
case 'npm':
|
|
187
|
+
default:
|
|
188
|
+
command = 'npm';
|
|
189
|
+
args = ['install', '-g', `${packageName}@latest`];
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
console.log(chalk_1.default.gray(`Running: ${command} ${args.join(' ')}\n`));
|
|
193
|
+
const result = (0, child_process_1.spawnSync)(command, args, {
|
|
194
|
+
stdio: 'inherit',
|
|
195
|
+
encoding: 'utf8'
|
|
196
|
+
});
|
|
197
|
+
if (result.status !== 0) {
|
|
198
|
+
throw new Error(`Command exited with status ${result.status}`);
|
|
199
|
+
}
|
|
200
|
+
console.log('');
|
|
201
|
+
(0, errors_1.printSuccess)('Upgrade completed successfully!');
|
|
202
|
+
console.log(chalk_1.default.gray('Run "gitt --version" to verify the new version.\n'));
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
throw new errors_1.GitError('Upgrade failed. Please try upgrading manually:\n' +
|
|
206
|
+
` ${packageManager} install -g ${packageName}@latest`, error instanceof Error ? error : undefined);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Main upgrade command handler
|
|
211
|
+
*/
|
|
212
|
+
async function upgradeCommand() {
|
|
213
|
+
try {
|
|
214
|
+
console.log(chalk_1.default.cyan('🔍 Checking for updates...\n'));
|
|
215
|
+
// Check for updates
|
|
216
|
+
const updateInfo = await checkForUpdate();
|
|
217
|
+
if (!updateInfo) {
|
|
218
|
+
(0, errors_1.printSuccess)('You are already on the latest version! 🎉');
|
|
219
|
+
console.log(chalk_1.default.gray(`Current version: ${packageJson.version}\n`));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const { current, latest, type } = updateInfo;
|
|
223
|
+
// Display version comparison
|
|
224
|
+
console.log(chalk_1.default.bold('Version Information:'));
|
|
225
|
+
console.log(` Current: ${chalk_1.default.yellow(current)}`);
|
|
226
|
+
console.log(` Latest: ${chalk_1.default.green(latest)}`);
|
|
227
|
+
console.log(` Type: ${chalk_1.default.gray(type)}\n`);
|
|
228
|
+
// Fetch and display changelog
|
|
229
|
+
console.log(chalk_1.default.bold('📝 Fetching release notes...\n'));
|
|
230
|
+
const releaseInfo = await fetchLatestRelease();
|
|
231
|
+
if (releaseInfo && releaseInfo.changelog) {
|
|
232
|
+
console.log(chalk_1.default.bold('What\'s New:\n'));
|
|
233
|
+
console.log(formatChangelog(releaseInfo.changelog));
|
|
234
|
+
console.log('');
|
|
235
|
+
}
|
|
236
|
+
// Detect package manager
|
|
237
|
+
const packageManager = detectPackageManager();
|
|
238
|
+
if (!packageManager) {
|
|
239
|
+
console.log(chalk_1.default.yellow('⚠ Could not detect package manager.'));
|
|
240
|
+
console.log(chalk_1.default.gray('Please upgrade manually using one of these commands:\n'));
|
|
241
|
+
console.log(chalk_1.default.gray(` npm install -g ${packageJson.name}@latest`));
|
|
242
|
+
console.log(chalk_1.default.gray(` pnpm add -g ${packageJson.name}@latest`));
|
|
243
|
+
console.log(chalk_1.default.gray(` yarn global add ${packageJson.name}@latest\n`));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
console.log(chalk_1.default.gray(`Package manager detected: ${packageManager}\n`));
|
|
247
|
+
// Ask for confirmation
|
|
248
|
+
const shouldUpgrade = await (0, prompts_1.confirm)({
|
|
249
|
+
message: `Would you like to upgrade to v${latest}?`,
|
|
250
|
+
default: true
|
|
251
|
+
});
|
|
252
|
+
if (!shouldUpgrade) {
|
|
253
|
+
console.log(chalk_1.default.gray('\nUpgrade cancelled.\n'));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
// Execute upgrade
|
|
257
|
+
executeUpgrade(packageManager, packageJson.name);
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
if (error instanceof errors_1.GitError) {
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
throw new errors_1.GitError('Failed to check for updates. Please check your internet connection.', error instanceof Error ? error : undefined);
|
|
264
|
+
}
|
|
265
|
+
}
|
package/dist/errors.js
CHANGED
|
@@ -8,11 +8,16 @@ exports.handleError = handleError;
|
|
|
8
8
|
exports.printSuccess = printSuccess;
|
|
9
9
|
exports.printError = printError;
|
|
10
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
-
//
|
|
11
|
+
// Custom error types
|
|
12
12
|
class GitError extends Error {
|
|
13
|
-
constructor(message) {
|
|
13
|
+
constructor(message, cause) {
|
|
14
14
|
super(message);
|
|
15
15
|
this.name = 'GitError';
|
|
16
|
+
this.cause = cause;
|
|
17
|
+
// Preserve stack trace from the cause if available
|
|
18
|
+
if (cause === null || cause === void 0 ? void 0 : cause.stack) {
|
|
19
|
+
this.stack = `${this.stack}\nCaused by: ${cause.stack}`;
|
|
20
|
+
}
|
|
16
21
|
}
|
|
17
22
|
}
|
|
18
23
|
exports.GitError = GitError;
|
|
@@ -23,7 +28,7 @@ class UserCancelError extends Error {
|
|
|
23
28
|
}
|
|
24
29
|
}
|
|
25
30
|
exports.UserCancelError = UserCancelError;
|
|
26
|
-
//
|
|
31
|
+
// Unified error handling function
|
|
27
32
|
function handleError(error) {
|
|
28
33
|
if (error instanceof UserCancelError) {
|
|
29
34
|
console.log(error.message);
|
|
@@ -31,20 +36,28 @@ function handleError(error) {
|
|
|
31
36
|
}
|
|
32
37
|
if (error instanceof GitError) {
|
|
33
38
|
console.error(chalk_1.default.red('Error:'), error.message);
|
|
39
|
+
// In development mode, show full stack trace
|
|
40
|
+
if (process.env.DEBUG || process.env.VERBOSE) {
|
|
41
|
+
console.error(chalk_1.default.gray(error.stack));
|
|
42
|
+
}
|
|
34
43
|
process.exit(1);
|
|
35
44
|
}
|
|
36
45
|
if (error instanceof Error) {
|
|
37
46
|
console.error(chalk_1.default.red('Program error:'), error.message);
|
|
47
|
+
// In development mode, show full stack trace
|
|
48
|
+
if (process.env.DEBUG || process.env.VERBOSE) {
|
|
49
|
+
console.error(chalk_1.default.gray(error.stack));
|
|
50
|
+
}
|
|
38
51
|
process.exit(1);
|
|
39
52
|
}
|
|
40
53
|
console.error(chalk_1.default.red('Unknown error occurred'));
|
|
41
54
|
process.exit(1);
|
|
42
55
|
}
|
|
43
|
-
//
|
|
56
|
+
// Success message handler
|
|
44
57
|
function printSuccess(message) {
|
|
45
58
|
console.log(chalk_1.default.green('✓'), message);
|
|
46
59
|
}
|
|
47
|
-
//
|
|
60
|
+
// Error message handler
|
|
48
61
|
function printError(message) {
|
|
49
62
|
console.error(chalk_1.default.red(message));
|
|
50
63
|
}
|
package/dist/index.js
CHANGED
|
@@ -40,25 +40,25 @@ const errors_1 = require("./errors");
|
|
|
40
40
|
const clean_1 = require("./commands/clean");
|
|
41
41
|
const git = (0, simple_git_1.simpleGit)();
|
|
42
42
|
const packageJson = require('../package.json');
|
|
43
|
-
//
|
|
43
|
+
// Handle Ctrl+C and other termination signals
|
|
44
44
|
process.on('SIGINT', () => {
|
|
45
45
|
throw new errors_1.UserCancelError('\nOperation cancelled');
|
|
46
46
|
});
|
|
47
47
|
process.on('SIGTERM', () => {
|
|
48
48
|
throw new errors_1.UserCancelError('\nProgram terminated');
|
|
49
49
|
});
|
|
50
|
-
//
|
|
50
|
+
// Check if current directory is a Git repository
|
|
51
51
|
async function checkGitRepo() {
|
|
52
52
|
const isRepo = await git.checkIsRepo();
|
|
53
53
|
if (!isRepo) {
|
|
54
54
|
throw new errors_1.GitError('Current directory is not a Git repository');
|
|
55
55
|
}
|
|
56
|
-
//
|
|
56
|
+
// Check if remote repository is configured
|
|
57
57
|
const remotes = await git.getRemotes();
|
|
58
58
|
if (remotes.length === 0) {
|
|
59
59
|
throw new errors_1.GitError('Current Git repository has no remote configured');
|
|
60
60
|
}
|
|
61
|
-
//
|
|
61
|
+
// Check if remote repository is accessible
|
|
62
62
|
try {
|
|
63
63
|
await git.fetch(['--dry-run']);
|
|
64
64
|
}
|
|
@@ -70,8 +70,8 @@ async function main() {
|
|
|
70
70
|
try {
|
|
71
71
|
// Check for updates before parsing commands
|
|
72
72
|
try {
|
|
73
|
-
// Use
|
|
74
|
-
const
|
|
73
|
+
// Use dynamic import to load update-notifier
|
|
74
|
+
const updateNotifier = await Promise.resolve().then(() => __importStar(require('update-notifier'))).then(m => m.default);
|
|
75
75
|
updateNotifier({ pkg: packageJson }).notify();
|
|
76
76
|
}
|
|
77
77
|
catch (e) {
|
|
@@ -114,6 +114,14 @@ async function main() {
|
|
|
114
114
|
await checkGitRepo();
|
|
115
115
|
await Promise.resolve().then(() => __importStar(require('./commands/config'))).then(m => m.configIgnoreBranch(pattern));
|
|
116
116
|
});
|
|
117
|
+
// upgrade command
|
|
118
|
+
program
|
|
119
|
+
.command('upgrade')
|
|
120
|
+
.description('Check for updates and upgrade to the latest version')
|
|
121
|
+
.action(async () => {
|
|
122
|
+
// No need to check Git repo for upgrade command
|
|
123
|
+
await Promise.resolve().then(() => __importStar(require('./commands/upgrade'))).then(m => m.upgradeCommand());
|
|
124
|
+
});
|
|
117
125
|
// Add examples to help
|
|
118
126
|
program.addHelpText('after', `
|
|
119
127
|
Examples:
|
|
@@ -124,6 +132,7 @@ Examples:
|
|
|
124
132
|
$ gitt --stale 30 # Find branches inactive for 30+ days
|
|
125
133
|
$ gitt ignore "temp/*" # Ignore branches matching "temp/*"
|
|
126
134
|
$ gitt set-main master # Set main branch to 'master'
|
|
135
|
+
$ gitt upgrade # Check for updates and upgrade
|
|
127
136
|
`);
|
|
128
137
|
await program.parseAsync(process.argv);
|
|
129
138
|
}
|
|
@@ -131,5 +140,5 @@ Examples:
|
|
|
131
140
|
(0, errors_1.handleError)(error);
|
|
132
141
|
}
|
|
133
142
|
}
|
|
134
|
-
//
|
|
143
|
+
// Start the program
|
|
135
144
|
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harryisfish/gitt",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.3",
|
|
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": {
|
|
@@ -61,4 +61,4 @@
|
|
|
61
61
|
"README.md",
|
|
62
62
|
"LICENSE"
|
|
63
63
|
]
|
|
64
|
-
}
|
|
64
|
+
}
|
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
|
-
}
|
package/dist/theme.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
// 自定义颜色
|
|
3
|
-
const colors = {
|
|
4
|
-
primary: chalk.cyan,
|
|
5
|
-
success: chalk.green,
|
|
6
|
-
warning: chalk.yellow,
|
|
7
|
-
error: chalk.red,
|
|
8
|
-
muted: chalk.gray,
|
|
9
|
-
highlight: chalk.blue,
|
|
10
|
-
info: chalk.white,
|
|
11
|
-
};
|
|
12
|
-
// 自定义图标
|
|
13
|
-
const icons = {
|
|
14
|
-
pointer: '❯',
|
|
15
|
-
done: '✔',
|
|
16
|
-
error: '✖',
|
|
17
|
-
waiting: '○',
|
|
18
|
-
};
|
|
19
|
-
// 定义主题
|
|
20
|
-
export const theme = {
|
|
21
|
-
prefix: {
|
|
22
|
-
idle: colors.primary('?'),
|
|
23
|
-
done: colors.success(icons.done),
|
|
24
|
-
},
|
|
25
|
-
spinner: {
|
|
26
|
-
interval: 100,
|
|
27
|
-
frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
|
|
28
|
-
},
|
|
29
|
-
icon: {
|
|
30
|
-
cursor: colors.primary(icons.pointer),
|
|
31
|
-
},
|
|
32
|
-
style: {
|
|
33
|
-
// 答案样式
|
|
34
|
-
answer: (text) => colors.success(text),
|
|
35
|
-
// 问题样式
|
|
36
|
-
message: (text, status) => {
|
|
37
|
-
switch (status) {
|
|
38
|
-
case 'loading':
|
|
39
|
-
return colors.muted(text);
|
|
40
|
-
case 'done':
|
|
41
|
-
return colors.success(text);
|
|
42
|
-
default:
|
|
43
|
-
return colors.primary(text);
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
// 错误信息样式
|
|
47
|
-
error: (text) => colors.error(icons.error + ' ' + text),
|
|
48
|
-
// 帮助信息样式
|
|
49
|
-
help: (text) => colors.muted(text),
|
|
50
|
-
// 高亮样式
|
|
51
|
-
highlight: (text) => colors.highlight(text),
|
|
52
|
-
// 选项描述样式
|
|
53
|
-
description: (text) => colors.muted(`\n ${text}`),
|
|
54
|
-
// 禁用选项样式
|
|
55
|
-
disabled: (text) => colors.muted(`${icons.waiting} ${text}`),
|
|
56
|
-
},
|
|
57
|
-
// 帮助提示显示模式:'always' | 'never' | 'auto'
|
|
58
|
-
helpMode: 'auto',
|
|
59
|
-
};
|