@harryisfish/gitt 1.6.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.
@@ -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) => {
@@ -135,11 +109,11 @@ async function cleanDeletedBranches(options = {}) {
135
109
  else {
136
110
  // Auto mode: Filter out unmerged branches
137
111
  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
112
  state.deletedBranches = state.deletedBranches.filter(b => b.isMerged);
113
+ if (state.deletedBranches.length === 0 && unmerged.length > 0) {
114
+ (0, errors_1.printSuccess)(`No merged branches to clean up (${unmerged.length} unmerged branch${unmerged.length > 1 ? 'es' : ''} skipped, use -i to review)`);
115
+ return;
116
+ }
143
117
  }
144
118
  if (state.deletedBranches.length === 0) {
145
119
  (0, errors_1.printSuccess)('No branches selected for deletion');
@@ -150,6 +124,18 @@ async function cleanDeletedBranches(options = {}) {
150
124
  state.deletedBranches.forEach(b => console.log(` - ${b.name} (${b.reason})`));
151
125
  return;
152
126
  }
127
+ // Switch to main branch if current branch will be deleted
128
+ const willDeleteCurrent = state.deletedBranches.some(b => b.name === state.currentBranch);
129
+ if (willDeleteCurrent) {
130
+ try {
131
+ await git.checkout(state.mainBranch);
132
+ await git.pull();
133
+ console.log(`Switched to ${state.mainBranch} and synced with remote`);
134
+ }
135
+ catch (e) {
136
+ 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'}`);
137
+ }
138
+ }
153
139
  // Phase 3: Execution
154
140
  const deleteTasks = new listr2_1.Listr([
155
141
  {
@@ -169,6 +155,11 @@ async function cleanDeletedBranches(options = {}) {
169
155
  (0, errors_1.printSuccess)('Branch cleanup completed');
170
156
  }
171
157
  catch (error) {
172
- throw new errors_1.GitError(error instanceof Error ? error.message : 'Unknown error occurred while cleaning branches');
158
+ if (error instanceof errors_1.GitError || error instanceof errors_1.UserCancelError) {
159
+ // Re-throw our custom errors as-is
160
+ throw error;
161
+ }
162
+ // Wrap other errors and preserve the original error
163
+ throw new errors_1.GitError(error instanceof Error ? error.message : 'Unknown error occurred while cleaning branches', error instanceof Error ? error : undefined);
173
164
  }
174
165
  }
@@ -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
- // 处理 Ctrl+C 和其他终止信号
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
- // 检查当前目录是否是 Git 仓库
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 eval to prevent TypeScript from transpiling dynamic import to require()
74
- const { default: updateNotifier } = await eval('import("update-notifier")');
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.0",
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": {
@@ -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
- }
@@ -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
- };