@snapcommit/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +162 -0
- package/dist/ai/anthropic-client.js +92 -0
- package/dist/ai/commit-generator.js +200 -0
- package/dist/ai/gemini-client.js +201 -0
- package/dist/ai/git-interpreter.js +209 -0
- package/dist/ai/smart-solver.js +260 -0
- package/dist/auth/supabase-client.js +288 -0
- package/dist/commands/activate.js +108 -0
- package/dist/commands/commit.js +255 -0
- package/dist/commands/conflict.js +233 -0
- package/dist/commands/doctor.js +113 -0
- package/dist/commands/git-advanced.js +311 -0
- package/dist/commands/github-auth.js +193 -0
- package/dist/commands/login.js +11 -0
- package/dist/commands/natural.js +305 -0
- package/dist/commands/onboard.js +111 -0
- package/dist/commands/quick.js +173 -0
- package/dist/commands/setup.js +163 -0
- package/dist/commands/stats.js +128 -0
- package/dist/commands/uninstall.js +131 -0
- package/dist/db/database.js +99 -0
- package/dist/index.js +144 -0
- package/dist/lib/auth.js +171 -0
- package/dist/lib/github.js +280 -0
- package/dist/lib/multi-repo.js +276 -0
- package/dist/lib/supabase.js +153 -0
- package/dist/license/manager.js +203 -0
- package/dist/repl/index.js +185 -0
- package/dist/repl/interpreter.js +524 -0
- package/dist/utils/analytics.js +36 -0
- package/dist/utils/auth-storage.js +65 -0
- package/dist/utils/dopamine.js +211 -0
- package/dist/utils/errors.js +56 -0
- package/dist/utils/git.js +105 -0
- package/dist/utils/heatmap.js +265 -0
- package/dist/utils/rate-limit.js +68 -0
- package/dist/utils/retry.js +46 -0
- package/dist/utils/ui.js +189 -0
- package/dist/utils/version.js +81 -0
- package/package.json +69 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Conflict resolution wizard
|
|
4
|
+
* Helps users resolve merge conflicts with AI assistance
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.conflictCommand = conflictCommand;
|
|
11
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
+
const child_process_1 = require("child_process");
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
|
+
const readline_1 = __importDefault(require("readline"));
|
|
15
|
+
const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
|
|
16
|
+
const analytics_1 = require("../utils/analytics");
|
|
17
|
+
const anthropic = new sdk_1.default({
|
|
18
|
+
apiKey: process.env.ANTHROPIC_API_KEY || '',
|
|
19
|
+
});
|
|
20
|
+
function askQuestion(query) {
|
|
21
|
+
const rl = readline_1.default.createInterface({
|
|
22
|
+
input: process.stdin,
|
|
23
|
+
output: process.stdout,
|
|
24
|
+
});
|
|
25
|
+
return new Promise(resolve => rl.question(query, ans => {
|
|
26
|
+
rl.close();
|
|
27
|
+
resolve(ans);
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
async function conflictCommand() {
|
|
31
|
+
console.log(chalk_1.default.blue.bold('\nš§ Conflict Resolution Wizard\n'));
|
|
32
|
+
// Check for conflicts
|
|
33
|
+
let conflictFiles;
|
|
34
|
+
try {
|
|
35
|
+
const output = (0, child_process_1.execSync)('git diff --name-only --diff-filter=U', { encoding: 'utf-8' });
|
|
36
|
+
conflictFiles = output.trim().split('\n').filter(Boolean);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
conflictFiles = [];
|
|
40
|
+
}
|
|
41
|
+
if (conflictFiles.length === 0) {
|
|
42
|
+
console.log(chalk_1.default.green('ā
No conflicts detected!\n'));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
console.log(chalk_1.default.yellow(`Found ${conflictFiles.length} file(s) with conflicts:\n`));
|
|
46
|
+
conflictFiles.forEach((file, i) => {
|
|
47
|
+
console.log(chalk_1.default.white(` ${i + 1}. ${file}`));
|
|
48
|
+
});
|
|
49
|
+
console.log();
|
|
50
|
+
// Offer AI assistance
|
|
51
|
+
console.log(chalk_1.default.blue('š¤ I can help you resolve these conflicts with AI.\n'));
|
|
52
|
+
const choice = await askQuestion(chalk_1.default.yellow('Choose an option:\n') +
|
|
53
|
+
chalk_1.default.gray(' 1. AI-assisted resolution (recommended)\n') +
|
|
54
|
+
chalk_1.default.gray(' 2. Manual resolution (open in editor)\n') +
|
|
55
|
+
chalk_1.default.gray(' 3. Abort merge\n') +
|
|
56
|
+
chalk_1.default.yellow('Your choice (1/2/3): '));
|
|
57
|
+
if (choice === '3') {
|
|
58
|
+
console.log(chalk_1.default.yellow('\nā ļø Aborting merge...\n'));
|
|
59
|
+
(0, child_process_1.execSync)('git merge --abort');
|
|
60
|
+
console.log(chalk_1.default.green('ā
Merge aborted. You\'re back to a clean state.\n'));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (choice === '2') {
|
|
64
|
+
console.log(chalk_1.default.blue('\nš Opening files for manual resolution...\n'));
|
|
65
|
+
console.log(chalk_1.default.gray('After resolving conflicts:'));
|
|
66
|
+
console.log(chalk_1.default.cyan(' git add <file>'));
|
|
67
|
+
console.log(chalk_1.default.cyan(' git commit'));
|
|
68
|
+
console.log();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// AI-assisted resolution
|
|
72
|
+
for (const filePath of conflictFiles) {
|
|
73
|
+
await resolveConflictWithAI(filePath);
|
|
74
|
+
}
|
|
75
|
+
// Final commit
|
|
76
|
+
console.log(chalk_1.default.blue('\nā
All conflicts resolved!\n'));
|
|
77
|
+
const shouldCommit = await askQuestion(chalk_1.default.yellow('Commit the merge? (Y/n): '));
|
|
78
|
+
if (shouldCommit.toLowerCase() !== 'n') {
|
|
79
|
+
try {
|
|
80
|
+
(0, child_process_1.execSync)('git add -A');
|
|
81
|
+
(0, child_process_1.execSync)('git commit --no-edit');
|
|
82
|
+
console.log(chalk_1.default.green('\nā
Merge committed successfully!\n'));
|
|
83
|
+
(0, analytics_1.trackEvent)({ event: 'conflict_resolved', data: { filesCount: conflictFiles.length } });
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.log(chalk_1.default.red(`\nā Commit failed: ${error.message}\n`));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function resolveConflictWithAI(filePath) {
|
|
91
|
+
console.log(chalk_1.default.blue(`\nš Analyzing ${chalk_1.default.white.bold(filePath)}...\n`));
|
|
92
|
+
// Read file content
|
|
93
|
+
const content = fs_1.default.readFileSync(filePath, 'utf-8');
|
|
94
|
+
// Extract conflict markers
|
|
95
|
+
const conflicts = extractConflicts(content);
|
|
96
|
+
console.log(chalk_1.default.yellow(`Found ${conflicts.length} conflict(s) in this file.\n`));
|
|
97
|
+
for (let i = 0; i < conflicts.length; i++) {
|
|
98
|
+
const conflict = conflicts[i];
|
|
99
|
+
console.log(chalk_1.default.white.bold(`Conflict ${i + 1}/${conflicts.length}:`));
|
|
100
|
+
console.log(chalk_1.default.gray('ā'.repeat(50)));
|
|
101
|
+
console.log(chalk_1.default.green('<<<<<<< YOURS (current branch)'));
|
|
102
|
+
console.log(chalk_1.default.white(conflict.ours));
|
|
103
|
+
console.log(chalk_1.default.yellow('======='));
|
|
104
|
+
console.log(chalk_1.default.white(conflict.theirs));
|
|
105
|
+
console.log(chalk_1.default.blue('>>>>>>> THEIRS (incoming branch)'));
|
|
106
|
+
console.log(chalk_1.default.gray('ā'.repeat(50)));
|
|
107
|
+
console.log();
|
|
108
|
+
// Get AI suggestion
|
|
109
|
+
console.log(chalk_1.default.blue('š¤ Getting AI suggestion...\n'));
|
|
110
|
+
const suggestion = await getAISuggestion(conflict, filePath);
|
|
111
|
+
console.log(chalk_1.default.green('AI Suggestion:'));
|
|
112
|
+
console.log(chalk_1.default.white(suggestion.resolution));
|
|
113
|
+
console.log();
|
|
114
|
+
console.log(chalk_1.default.gray('Reasoning: ' + suggestion.explanation));
|
|
115
|
+
console.log();
|
|
116
|
+
const choice = await askQuestion(chalk_1.default.yellow('Choose:\n') +
|
|
117
|
+
chalk_1.default.gray(' 1. Use AI suggestion\n') +
|
|
118
|
+
chalk_1.default.gray(' 2. Keep yours\n') +
|
|
119
|
+
chalk_1.default.gray(' 3. Keep theirs\n') +
|
|
120
|
+
chalk_1.default.gray(' 4. Skip (manual edit)\n') +
|
|
121
|
+
chalk_1.default.yellow('Your choice (1/2/3/4): '));
|
|
122
|
+
let resolution;
|
|
123
|
+
if (choice === '1') {
|
|
124
|
+
resolution = suggestion.resolution;
|
|
125
|
+
}
|
|
126
|
+
else if (choice === '2') {
|
|
127
|
+
resolution = conflict.ours;
|
|
128
|
+
}
|
|
129
|
+
else if (choice === '3') {
|
|
130
|
+
resolution = conflict.theirs;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
console.log(chalk_1.default.yellow('Skipped. Resolve manually.\n'));
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
// Replace conflict in content
|
|
137
|
+
const conflictBlock = `<<<<<<< HEAD\n${conflict.ours}\n=======\n${conflict.theirs}\n>>>>>>> ${conflict.branch || 'MERGE_HEAD'}`;
|
|
138
|
+
const newContent = content.replace(conflictBlock, resolution);
|
|
139
|
+
fs_1.default.writeFileSync(filePath, newContent);
|
|
140
|
+
console.log(chalk_1.default.green('ā
Resolved!\n'));
|
|
141
|
+
}
|
|
142
|
+
// Stage the file
|
|
143
|
+
(0, child_process_1.execSync)(`git add "${filePath}"`);
|
|
144
|
+
console.log(chalk_1.default.green(`ā
Staged ${filePath}\n`));
|
|
145
|
+
}
|
|
146
|
+
function extractConflicts(content) {
|
|
147
|
+
const conflicts = [];
|
|
148
|
+
const lines = content.split('\n');
|
|
149
|
+
let inConflict = false;
|
|
150
|
+
let ours = [];
|
|
151
|
+
let theirs = [];
|
|
152
|
+
let inTheirs = false;
|
|
153
|
+
let branch;
|
|
154
|
+
for (const line of lines) {
|
|
155
|
+
if (line.startsWith('<<<<<<<')) {
|
|
156
|
+
inConflict = true;
|
|
157
|
+
ours = [];
|
|
158
|
+
theirs = [];
|
|
159
|
+
inTheirs = false;
|
|
160
|
+
}
|
|
161
|
+
else if (line.startsWith('=======')) {
|
|
162
|
+
inTheirs = true;
|
|
163
|
+
}
|
|
164
|
+
else if (line.startsWith('>>>>>>>')) {
|
|
165
|
+
inConflict = false;
|
|
166
|
+
branch = line.substring(8).trim();
|
|
167
|
+
conflicts.push({
|
|
168
|
+
ours: ours.join('\n'),
|
|
169
|
+
theirs: theirs.join('\n'),
|
|
170
|
+
branch,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else if (inConflict) {
|
|
174
|
+
if (inTheirs) {
|
|
175
|
+
theirs.push(line);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
ours.push(line);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return conflicts;
|
|
183
|
+
}
|
|
184
|
+
async function getAISuggestion(conflict, filePath) {
|
|
185
|
+
try {
|
|
186
|
+
const response = await anthropic.messages.create({
|
|
187
|
+
model: 'claude-sonnet-4-20250514',
|
|
188
|
+
max_tokens: 1000,
|
|
189
|
+
messages: [
|
|
190
|
+
{
|
|
191
|
+
role: 'user',
|
|
192
|
+
content: `You are an expert software engineer resolving a Git merge conflict.
|
|
193
|
+
|
|
194
|
+
File: ${filePath}
|
|
195
|
+
|
|
196
|
+
CURRENT BRANCH (ours):
|
|
197
|
+
\`\`\`
|
|
198
|
+
${conflict.ours}
|
|
199
|
+
\`\`\`
|
|
200
|
+
|
|
201
|
+
INCOMING BRANCH (theirs):
|
|
202
|
+
\`\`\`
|
|
203
|
+
${conflict.theirs}
|
|
204
|
+
\`\`\`
|
|
205
|
+
|
|
206
|
+
Analyze both versions and suggest the best resolution. Consider:
|
|
207
|
+
1. Are they compatible? Can both changes be kept?
|
|
208
|
+
2. Which version is more correct/better?
|
|
209
|
+
3. Are there bugs or issues in either version?
|
|
210
|
+
|
|
211
|
+
Respond with JSON (no markdown, just JSON):
|
|
212
|
+
{
|
|
213
|
+
"resolution": "the merged code",
|
|
214
|
+
"explanation": "why this is the best resolution"
|
|
215
|
+
}`,
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
});
|
|
219
|
+
const text = response.content[0];
|
|
220
|
+
if (text.type !== 'text') {
|
|
221
|
+
throw new Error('Unexpected response');
|
|
222
|
+
}
|
|
223
|
+
const result = JSON.parse(text.text);
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
// Fallback: suggest keeping both
|
|
228
|
+
return {
|
|
229
|
+
resolution: `${conflict.ours}\n${conflict.theirs}`,
|
|
230
|
+
explanation: 'AI suggestion failed. Keeping both versions (you may need to clean up).',
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.doctorCommand = doctorCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
/**
|
|
13
|
+
* Doctor command - checks if everything is set up correctly
|
|
14
|
+
*/
|
|
15
|
+
function doctorCommand() {
|
|
16
|
+
console.log(chalk_1.default.blue.bold('\nš SnapCommit Health Check\n'));
|
|
17
|
+
let allGood = true;
|
|
18
|
+
// Check 1: Git installed
|
|
19
|
+
console.log(chalk_1.default.white('Checking Git...'));
|
|
20
|
+
try {
|
|
21
|
+
const gitVersion = (0, child_process_1.execSync)('git --version', { encoding: 'utf-8' }).trim();
|
|
22
|
+
console.log(chalk_1.default.green(` ā ${gitVersion}`));
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
console.log(chalk_1.default.red(' ā Git not found'));
|
|
26
|
+
console.log(chalk_1.default.gray(' Install: https://git-scm.com'));
|
|
27
|
+
allGood = false;
|
|
28
|
+
}
|
|
29
|
+
// Check 2: Node.js version
|
|
30
|
+
console.log(chalk_1.default.white('\nChecking Node.js...'));
|
|
31
|
+
const nodeVersion = process.version;
|
|
32
|
+
const majorVersion = parseInt(nodeVersion.slice(1).split('.')[0]);
|
|
33
|
+
if (majorVersion >= 18) {
|
|
34
|
+
console.log(chalk_1.default.green(` ā ${nodeVersion} (good)`));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
console.log(chalk_1.default.yellow(` ā ${nodeVersion} (recommend v18+)`));
|
|
38
|
+
}
|
|
39
|
+
// Check 3: API Keys
|
|
40
|
+
console.log(chalk_1.default.white('\nChecking API Keys...'));
|
|
41
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
42
|
+
if (anthropicKey && anthropicKey.startsWith('sk-ant')) {
|
|
43
|
+
console.log(chalk_1.default.green(' ā ANTHROPIC_API_KEY set'));
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.log(chalk_1.default.red(' ā ANTHROPIC_API_KEY missing or invalid'));
|
|
47
|
+
console.log(chalk_1.default.gray(' Get key: https://console.anthropic.com'));
|
|
48
|
+
allGood = false;
|
|
49
|
+
}
|
|
50
|
+
const geminiKey = process.env.GOOGLE_AI_API_KEY;
|
|
51
|
+
if (geminiKey && geminiKey.startsWith('AIza')) {
|
|
52
|
+
console.log(chalk_1.default.green(' ā GOOGLE_AI_API_KEY set'));
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.log(chalk_1.default.yellow(' ā GOOGLE_AI_API_KEY missing'));
|
|
56
|
+
console.log(chalk_1.default.gray(' Optional for now, needed for autocomplete'));
|
|
57
|
+
}
|
|
58
|
+
// Check 4: Shell integration
|
|
59
|
+
console.log(chalk_1.default.white('\nChecking Shell Integration...'));
|
|
60
|
+
const shell = process.env.SHELL || '';
|
|
61
|
+
const homeDir = os_1.default.homedir();
|
|
62
|
+
let shellConfigPath = '';
|
|
63
|
+
if (shell.includes('zsh')) {
|
|
64
|
+
shellConfigPath = path_1.default.join(homeDir, '.zshrc');
|
|
65
|
+
}
|
|
66
|
+
else if (shell.includes('bash')) {
|
|
67
|
+
shellConfigPath = path_1.default.join(homeDir, '.bashrc');
|
|
68
|
+
}
|
|
69
|
+
if (shellConfigPath && fs_1.default.existsSync(shellConfigPath)) {
|
|
70
|
+
const content = fs_1.default.readFileSync(shellConfigPath, 'utf-8');
|
|
71
|
+
if (content.includes('SnapCommit Integration')) {
|
|
72
|
+
console.log(chalk_1.default.green(' ā Shell integration installed'));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.log(chalk_1.default.yellow(' ā Shell integration not installed'));
|
|
76
|
+
console.log(chalk_1.default.gray(' Run: snapcommit setup'));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
console.log(chalk_1.default.gray(' - Shell config not found'));
|
|
81
|
+
}
|
|
82
|
+
// Check 5: Database
|
|
83
|
+
console.log(chalk_1.default.white('\nChecking Database...'));
|
|
84
|
+
const dbPath = path_1.default.join(homeDir, '.builderos', 'builderos.db');
|
|
85
|
+
if (fs_1.default.existsSync(dbPath)) {
|
|
86
|
+
const stats = fs_1.default.statSync(dbPath);
|
|
87
|
+
const sizeKB = (stats.size / 1024).toFixed(2);
|
|
88
|
+
console.log(chalk_1.default.green(` ā Database exists (${sizeKB} KB)`));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
console.log(chalk_1.default.gray(' - Database will be created on first use'));
|
|
92
|
+
}
|
|
93
|
+
// Check 6: Git repo (current directory)
|
|
94
|
+
console.log(chalk_1.default.white('\nChecking Current Directory...'));
|
|
95
|
+
try {
|
|
96
|
+
(0, child_process_1.execSync)('git rev-parse --git-dir', { stdio: 'ignore' });
|
|
97
|
+
const branch = (0, child_process_1.execSync)('git branch --show-current', { encoding: 'utf-8' }).trim();
|
|
98
|
+
console.log(chalk_1.default.green(` ā Git repository (${branch})`));
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
console.log(chalk_1.default.gray(' - Not in a git repository'));
|
|
102
|
+
}
|
|
103
|
+
// Summary
|
|
104
|
+
console.log();
|
|
105
|
+
if (allGood) {
|
|
106
|
+
console.log(chalk_1.default.green.bold('ā
All systems go! SnapCommit is ready.\n'));
|
|
107
|
+
console.log(chalk_1.default.gray('Try: ') + chalk_1.default.cyan('snapcommit quick') + chalk_1.default.gray(' to make your first commit'));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
console.log(chalk_1.default.yellow.bold('ā ļø Some issues found. Fix them to get started.\n'));
|
|
111
|
+
}
|
|
112
|
+
console.log();
|
|
113
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.stashChanges = stashChanges;
|
|
7
|
+
exports.listStash = listStash;
|
|
8
|
+
exports.applyStash = applyStash;
|
|
9
|
+
exports.popStash = popStash;
|
|
10
|
+
exports.dropStash = dropStash;
|
|
11
|
+
exports.rebaseOnto = rebaseOnto;
|
|
12
|
+
exports.interactiveRebase = interactiveRebase;
|
|
13
|
+
exports.cherryPick = cherryPick;
|
|
14
|
+
exports.amendCommit = amendCommit;
|
|
15
|
+
exports.resetToCommit = resetToCommit;
|
|
16
|
+
exports.createBranch = createBranch;
|
|
17
|
+
exports.deleteBranch = deleteBranch;
|
|
18
|
+
exports.switchBranch = switchBranch;
|
|
19
|
+
exports.mergeBranch = mergeBranch;
|
|
20
|
+
exports.showLog = showLog;
|
|
21
|
+
exports.showDiff = showDiff;
|
|
22
|
+
const child_process_1 = require("child_process");
|
|
23
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
24
|
+
const readline_1 = __importDefault(require("readline"));
|
|
25
|
+
/**
|
|
26
|
+
* Stash current changes
|
|
27
|
+
*/
|
|
28
|
+
async function stashChanges(message) {
|
|
29
|
+
try {
|
|
30
|
+
console.log(chalk_1.default.gray('\nš¾ Stashing changes...\n'));
|
|
31
|
+
if (message) {
|
|
32
|
+
(0, child_process_1.execSync)(`git stash push -m "${message}"`, { stdio: 'inherit' });
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
(0, child_process_1.execSync)('git stash', { stdio: 'inherit' });
|
|
36
|
+
}
|
|
37
|
+
console.log(chalk_1.default.green('\nā
Changes stashed!\n'));
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.log(chalk_1.default.red(`\nā Failed to stash: ${error.message}\n`));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* List stashed changes
|
|
45
|
+
*/
|
|
46
|
+
function listStash() {
|
|
47
|
+
try {
|
|
48
|
+
console.log(chalk_1.default.bold('\nš¦ Stashed Changes:\n'));
|
|
49
|
+
(0, child_process_1.execSync)('git stash list', { stdio: 'inherit' });
|
|
50
|
+
console.log('');
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.log(chalk_1.default.red(`\nā Failed to list stash: ${error.message}\n`));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Apply stashed changes
|
|
58
|
+
*/
|
|
59
|
+
async function applyStash(stashIndex) {
|
|
60
|
+
try {
|
|
61
|
+
console.log(chalk_1.default.gray('\nš Applying stashed changes...\n'));
|
|
62
|
+
if (stashIndex !== undefined) {
|
|
63
|
+
(0, child_process_1.execSync)(`git stash apply stash@{${stashIndex}}`, { stdio: 'inherit' });
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
(0, child_process_1.execSync)('git stash apply', { stdio: 'inherit' });
|
|
67
|
+
}
|
|
68
|
+
console.log(chalk_1.default.green('\nā
Stash applied!\n'));
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.log(chalk_1.default.red(`\nā Failed to apply stash: ${error.message}\n`));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Pop stashed changes (apply and remove)
|
|
76
|
+
*/
|
|
77
|
+
async function popStash() {
|
|
78
|
+
try {
|
|
79
|
+
console.log(chalk_1.default.gray('\nš Popping stashed changes...\n'));
|
|
80
|
+
(0, child_process_1.execSync)('git stash pop', { stdio: 'inherit' });
|
|
81
|
+
console.log(chalk_1.default.green('\nā
Stash popped!\n'));
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
console.log(chalk_1.default.red(`\nā Failed to pop stash: ${error.message}\n`));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Drop stashed changes
|
|
89
|
+
*/
|
|
90
|
+
async function dropStash(stashIndex) {
|
|
91
|
+
try {
|
|
92
|
+
const confirmed = await confirmAction('This will permanently delete the stashed changes. Continue?');
|
|
93
|
+
if (!confirmed) {
|
|
94
|
+
console.log(chalk_1.default.gray('\nš« Cancelled\n'));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
console.log(chalk_1.default.gray('\nšļø Dropping stash...\n'));
|
|
98
|
+
if (stashIndex !== undefined) {
|
|
99
|
+
(0, child_process_1.execSync)(`git stash drop stash@{${stashIndex}}`, { stdio: 'inherit' });
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
(0, child_process_1.execSync)('git stash drop', { stdio: 'inherit' });
|
|
103
|
+
}
|
|
104
|
+
console.log(chalk_1.default.green('\nā
Stash dropped!\n'));
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.log(chalk_1.default.red(`\nā Failed to drop stash: ${error.message}\n`));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Rebase current branch onto another
|
|
112
|
+
*/
|
|
113
|
+
async function rebaseOnto(targetBranch) {
|
|
114
|
+
try {
|
|
115
|
+
const confirmed = await confirmAction(`This will rebase your current branch onto ${targetBranch}. This rewrites history. Continue?`);
|
|
116
|
+
if (!confirmed) {
|
|
117
|
+
console.log(chalk_1.default.gray('\nš« Cancelled\n'));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
console.log(chalk_1.default.gray(`\nš Rebasing onto ${targetBranch}...\n`));
|
|
121
|
+
(0, child_process_1.execSync)(`git rebase ${targetBranch}`, { stdio: 'inherit' });
|
|
122
|
+
console.log(chalk_1.default.green('\nā
Rebase complete!\n'));
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.log(chalk_1.default.red(`\nā Rebase failed. You may need to resolve conflicts.\n`));
|
|
126
|
+
console.log(chalk_1.default.yellow('Run "git rebase --abort" to cancel, or resolve conflicts and "git rebase --continue"\n'));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Interactive rebase
|
|
131
|
+
*/
|
|
132
|
+
async function interactiveRebase(commits = 5) {
|
|
133
|
+
try {
|
|
134
|
+
const confirmed = await confirmAction(`This will start an interactive rebase for the last ${commits} commits. Continue?`);
|
|
135
|
+
if (!confirmed) {
|
|
136
|
+
console.log(chalk_1.default.gray('\nš« Cancelled\n'));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
console.log(chalk_1.default.gray(`\nš Starting interactive rebase...\n`));
|
|
140
|
+
(0, child_process_1.execSync)(`git rebase -i HEAD~${commits}`, { stdio: 'inherit' });
|
|
141
|
+
console.log(chalk_1.default.green('\nā
Interactive rebase complete!\n'));
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
console.log(chalk_1.default.red(`\nā Interactive rebase failed or cancelled.\n`));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Cherry-pick a commit
|
|
149
|
+
*/
|
|
150
|
+
async function cherryPick(commitHash) {
|
|
151
|
+
try {
|
|
152
|
+
console.log(chalk_1.default.gray(`\nš Cherry-picking commit ${commitHash}...\n`));
|
|
153
|
+
(0, child_process_1.execSync)(`git cherry-pick ${commitHash}`, { stdio: 'inherit' });
|
|
154
|
+
console.log(chalk_1.default.green('\nā
Cherry-pick complete!\n'));
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
console.log(chalk_1.default.red(`\nā Cherry-pick failed. You may need to resolve conflicts.\n`));
|
|
158
|
+
console.log(chalk_1.default.yellow('Run "git cherry-pick --abort" to cancel, or resolve conflicts and "git cherry-pick --continue"\n'));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Amend last commit
|
|
163
|
+
*/
|
|
164
|
+
async function amendCommit(message) {
|
|
165
|
+
try {
|
|
166
|
+
console.log(chalk_1.default.gray('\nāļø Amending last commit...\n'));
|
|
167
|
+
if (message) {
|
|
168
|
+
(0, child_process_1.execSync)(`git commit --amend -m "${message}"`, { stdio: 'inherit' });
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
(0, child_process_1.execSync)('git commit --amend --no-edit', { stdio: 'inherit' });
|
|
172
|
+
}
|
|
173
|
+
console.log(chalk_1.default.green('\nā
Commit amended!\n'));
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
console.log(chalk_1.default.red(`\nā Failed to amend commit: ${error.message}\n`));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Reset to a specific commit
|
|
181
|
+
*/
|
|
182
|
+
async function resetToCommit(commitHash, mode = 'mixed') {
|
|
183
|
+
try {
|
|
184
|
+
const warnings = {
|
|
185
|
+
soft: 'This will undo commits but keep all changes staged.',
|
|
186
|
+
mixed: 'This will undo commits and unstage changes, but keep them in working directory.',
|
|
187
|
+
hard: 'ā ļø This will PERMANENTLY delete all changes! Cannot be undone!',
|
|
188
|
+
};
|
|
189
|
+
const confirmed = await confirmAction(`${warnings[mode]} Continue?`);
|
|
190
|
+
if (!confirmed) {
|
|
191
|
+
console.log(chalk_1.default.gray('\nš« Cancelled\n'));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
console.log(chalk_1.default.gray(`\nš Resetting to ${commitHash} (--${mode})...\n`));
|
|
195
|
+
(0, child_process_1.execSync)(`git reset --${mode} ${commitHash}`, { stdio: 'inherit' });
|
|
196
|
+
console.log(chalk_1.default.green('\nā
Reset complete!\n'));
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
console.log(chalk_1.default.red(`\nā Failed to reset: ${error.message}\n`));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Create and checkout a new branch
|
|
204
|
+
*/
|
|
205
|
+
async function createBranch(branchName, fromBranch) {
|
|
206
|
+
try {
|
|
207
|
+
console.log(chalk_1.default.gray(`\nšæ Creating branch ${branchName}...\n`));
|
|
208
|
+
if (fromBranch) {
|
|
209
|
+
(0, child_process_1.execSync)(`git checkout -b ${branchName} ${fromBranch}`, { stdio: 'inherit' });
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
(0, child_process_1.execSync)(`git checkout -b ${branchName}`, { stdio: 'inherit' });
|
|
213
|
+
}
|
|
214
|
+
console.log(chalk_1.default.green(`\nā
Created and switched to ${branchName}!\n`));
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
console.log(chalk_1.default.red(`\nā Failed to create branch: ${error.message}\n`));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Delete a branch
|
|
222
|
+
*/
|
|
223
|
+
async function deleteBranch(branchName, force = false) {
|
|
224
|
+
try {
|
|
225
|
+
const confirmed = await confirmAction(`This will delete the branch "${branchName}". Continue?`);
|
|
226
|
+
if (!confirmed) {
|
|
227
|
+
console.log(chalk_1.default.gray('\nš« Cancelled\n'));
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
console.log(chalk_1.default.gray(`\nšļø Deleting branch ${branchName}...\n`));
|
|
231
|
+
const flag = force ? '-D' : '-d';
|
|
232
|
+
(0, child_process_1.execSync)(`git branch ${flag} ${branchName}`, { stdio: 'inherit' });
|
|
233
|
+
console.log(chalk_1.default.green(`\nā
Branch ${branchName} deleted!\n`));
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
console.log(chalk_1.default.red(`\nā Failed to delete branch: ${error.message}\n`));
|
|
237
|
+
if (!force) {
|
|
238
|
+
console.log(chalk_1.default.yellow('š” Use force delete if branch has unmerged changes\n'));
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Switch to a branch
|
|
244
|
+
*/
|
|
245
|
+
async function switchBranch(branchName) {
|
|
246
|
+
try {
|
|
247
|
+
console.log(chalk_1.default.gray(`\nš Switching to ${branchName}...\n`));
|
|
248
|
+
(0, child_process_1.execSync)(`git checkout ${branchName}`, { stdio: 'inherit' });
|
|
249
|
+
console.log(chalk_1.default.green(`\nā
Switched to ${branchName}!\n`));
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
console.log(chalk_1.default.red(`\nā Failed to switch branch: ${error.message}\n`));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Merge a branch
|
|
257
|
+
*/
|
|
258
|
+
async function mergeBranch(branchName, noFastForward = false) {
|
|
259
|
+
try {
|
|
260
|
+
console.log(chalk_1.default.gray(`\nš Merging ${branchName}...\n`));
|
|
261
|
+
const flag = noFastForward ? '--no-ff' : '';
|
|
262
|
+
(0, child_process_1.execSync)(`git merge ${flag} ${branchName}`, { stdio: 'inherit' });
|
|
263
|
+
console.log(chalk_1.default.green(`\nā
Merged ${branchName}!\n`));
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
console.log(chalk_1.default.red(`\nā Merge failed. You may need to resolve conflicts.\n`));
|
|
267
|
+
console.log(chalk_1.default.yellow('Run "git merge --abort" to cancel, or resolve conflicts and commit\n'));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Show commit log
|
|
272
|
+
*/
|
|
273
|
+
function showLog(limit = 10) {
|
|
274
|
+
try {
|
|
275
|
+
console.log(chalk_1.default.bold(`\nš Last ${limit} Commits:\n`));
|
|
276
|
+
(0, child_process_1.execSync)(`git log --oneline --graph --decorate -${limit}`, { stdio: 'inherit' });
|
|
277
|
+
console.log('');
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
console.log(chalk_1.default.red(`\nā Failed to show log: ${error.message}\n`));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Show diff
|
|
285
|
+
*/
|
|
286
|
+
function showDiff(cached = false) {
|
|
287
|
+
try {
|
|
288
|
+
const flag = cached ? '--cached' : '';
|
|
289
|
+
console.log(chalk_1.default.bold('\nš Changes:\n'));
|
|
290
|
+
(0, child_process_1.execSync)(`git diff ${flag}`, { stdio: 'inherit' });
|
|
291
|
+
console.log('');
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
console.log(chalk_1.default.red(`\nā Failed to show diff: ${error.message}\n`));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Helper: Confirm action
|
|
299
|
+
*/
|
|
300
|
+
async function confirmAction(message) {
|
|
301
|
+
return new Promise((resolve) => {
|
|
302
|
+
const rl = readline_1.default.createInterface({
|
|
303
|
+
input: process.stdin,
|
|
304
|
+
output: process.stdout,
|
|
305
|
+
});
|
|
306
|
+
rl.question(chalk_1.default.yellow(`\nā ļø ${message} (y/N): `), (answer) => {
|
|
307
|
+
rl.close();
|
|
308
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
}
|