@snapcommit/cli 3.3.0 → 3.4.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.
@@ -190,7 +190,8 @@ async function executeCommitWithAI(intent) {
190
190
  const fileSelection = await new Promise((resolve) => {
191
191
  rlFileSelect.question(chalk_1.default.cyan('Select files: ') +
192
192
  chalk_1.default.gray('(Enter=all, 1-3=specific, 1,3,5=multiple): '), (ans) => {
193
- rlFileSelect.close();
193
+ // Don't close - just resolve and let it clean up
194
+ setImmediate(() => rlFileSelect.close());
194
195
  resolve(ans.trim());
195
196
  });
196
197
  });
@@ -253,7 +254,8 @@ async function executeCommitWithAI(intent) {
253
254
  });
254
255
  const response = await new Promise((resolve) => {
255
256
  rlCommit.question(chalk_1.default.gray('→ Press Enter to commit, or edit message: '), (ans) => {
256
- rlCommit.close();
257
+ // Don't close - just resolve and let it clean up
258
+ setImmediate(() => rlCommit.close());
257
259
  resolve(ans.trim());
258
260
  });
259
261
  });
@@ -290,16 +292,90 @@ async function executeCommitWithAI(intent) {
290
292
  // Push if requested
291
293
  const shouldPush = intent.shouldPush || intent.gitCommands?.some((cmd) => cmd.includes('push'));
292
294
  if (shouldPush) {
295
+ // STEP 1: Pull first to avoid conflicts (like Cursor does!)
296
+ try {
297
+ const currentBranch = (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
298
+ const hasRemote = (0, child_process_1.execSync)(`git ls-remote --heads origin ${currentBranch}`, { encoding: 'utf-8' }).trim();
299
+ if (hasRemote) {
300
+ console.log(chalk_1.default.blue('⚡ Syncing with remote...'));
301
+ try {
302
+ (0, child_process_1.execSync)('git pull --rebase', { encoding: 'utf-8', stdio: 'pipe' });
303
+ console.log(chalk_1.default.green('✓ Synced'));
304
+ }
305
+ catch (pullError) {
306
+ // Handle rebase conflicts
307
+ if (pullError.message.includes('conflict')) {
308
+ console.log(chalk_1.default.yellow('\n⚠️ Merge conflicts detected - resolving...'));
309
+ // Try to auto-resolve conflicts
310
+ const resolved = await tryAdvancedConflictResolution();
311
+ if (resolved) {
312
+ console.log(chalk_1.default.green('✓ Conflicts resolved'));
313
+ }
314
+ else {
315
+ console.log(chalk_1.default.red('\n❌ Cannot auto-resolve conflicts'));
316
+ console.log(chalk_1.default.white('\nConflicted files:'));
317
+ const conflicts = (0, child_process_1.execSync)('git diff --name-only --diff-filter=U', { encoding: 'utf-8' });
318
+ conflicts.split('\n').filter(f => f.trim()).forEach(file => {
319
+ console.log(chalk_1.default.red(` ✗ ${file}`));
320
+ });
321
+ console.log(chalk_1.default.cyan('\nTo resolve manually:'));
322
+ console.log(chalk_1.default.gray(' 1. Fix conflicts in the files above'));
323
+ console.log(chalk_1.default.gray(' 2. Run: ') + chalk_1.default.cyan('git add <files>'));
324
+ console.log(chalk_1.default.gray(' 3. Run: ') + chalk_1.default.cyan('git rebase --continue'));
325
+ console.log(chalk_1.default.gray(' 4. Then try: ') + chalk_1.default.cyan('snap') + chalk_1.default.gray(' and ') + chalk_1.default.cyan('push changes\n'));
326
+ return;
327
+ }
328
+ }
329
+ else {
330
+ // Other pull errors
331
+ console.log(chalk_1.default.yellow(`⚠️ Sync issue: ${pullError.message}`));
332
+ }
333
+ }
334
+ }
335
+ }
336
+ catch {
337
+ // No remote or other issues - continue with push
338
+ }
339
+ // STEP 2: Now push
293
340
  try {
294
341
  (0, child_process_1.execSync)('git push', { encoding: 'utf-8', stdio: 'pipe' });
295
342
  console.log(chalk_1.default.green('✓ Pushed\n'));
296
343
  }
297
- catch (error) {
298
- if (error.message.includes('no configured push destination')) {
344
+ catch (pushError) {
345
+ const errorMsg = pushError.message.toLowerCase();
346
+ if (errorMsg.includes('no configured push destination')) {
299
347
  console.log(chalk_1.default.gray('✓ Committed locally (no remote)\n'));
300
348
  }
349
+ else if (errorMsg.includes('rejected') && errorMsg.includes('non-fast-forward')) {
350
+ // Need force push - but be safe!
351
+ console.log(chalk_1.default.yellow('\n⚠️ Remote has changes that would be overwritten'));
352
+ console.log(chalk_1.default.white('This requires force push, which can be dangerous.\n'));
353
+ const rlForce = await Promise.resolve().then(() => __importStar(require('readline')));
354
+ const rlForcePush = rlForce.createInterface({
355
+ input: process.stdin,
356
+ output: process.stdout,
357
+ });
358
+ const confirmForce = await new Promise((resolve) => {
359
+ rlForcePush.question(chalk_1.default.cyan('Force push? ') + chalk_1.default.gray('(yes/no): '), (ans) => {
360
+ setImmediate(() => rlForcePush.close());
361
+ resolve(ans.trim().toLowerCase());
362
+ });
363
+ });
364
+ if (confirmForce === 'yes') {
365
+ try {
366
+ (0, child_process_1.execSync)('git push --force-with-lease', { encoding: 'utf-8', stdio: 'pipe' });
367
+ console.log(chalk_1.default.green('✓ Force pushed (safely)\n'));
368
+ }
369
+ catch (forceError) {
370
+ console.log(chalk_1.default.red(`\n❌ Force push failed: ${forceError.message}\n`));
371
+ }
372
+ }
373
+ else {
374
+ console.log(chalk_1.default.gray('\n✗ Push cancelled\n'));
375
+ }
376
+ }
301
377
  else {
302
- console.log(chalk_1.default.yellow(`⚠️ Push failed: ${error.message}\n`));
378
+ console.log(chalk_1.default.yellow(`\n⚠️ Push failed: ${pushError.message}\n`));
303
379
  }
304
380
  }
305
381
  }
@@ -307,6 +383,46 @@ async function executeCommitWithAI(intent) {
307
383
  console.log();
308
384
  }
309
385
  }
386
+ /**
387
+ * Advanced conflict resolution - try multiple strategies
388
+ */
389
+ async function tryAdvancedConflictResolution() {
390
+ try {
391
+ // Strategy 1: Try accepting ours (our changes)
392
+ try {
393
+ (0, child_process_1.execSync)('git checkout --ours .', { encoding: 'utf-8', stdio: 'pipe' });
394
+ (0, child_process_1.execSync)('git add .', { encoding: 'utf-8', stdio: 'pipe' });
395
+ (0, child_process_1.execSync)('git rebase --continue', { encoding: 'utf-8', stdio: 'pipe' });
396
+ return true;
397
+ }
398
+ catch {
399
+ // Strategy 1 failed
400
+ }
401
+ // Strategy 2: Try accepting theirs (remote changes)
402
+ try {
403
+ (0, child_process_1.execSync)('git rebase --abort', { encoding: 'utf-8', stdio: 'pipe' });
404
+ (0, child_process_1.execSync)('git pull --rebase', { encoding: 'utf-8', stdio: 'pipe' });
405
+ (0, child_process_1.execSync)('git checkout --theirs .', { encoding: 'utf-8', stdio: 'pipe' });
406
+ (0, child_process_1.execSync)('git add .', { encoding: 'utf-8', stdio: 'pipe' });
407
+ (0, child_process_1.execSync)('git rebase --continue', { encoding: 'utf-8', stdio: 'pipe' });
408
+ return true;
409
+ }
410
+ catch {
411
+ // Strategy 2 failed
412
+ }
413
+ // Strategy 3: Abort and let user handle
414
+ try {
415
+ (0, child_process_1.execSync)('git rebase --abort', { encoding: 'utf-8', stdio: 'pipe' });
416
+ }
417
+ catch {
418
+ // Already aborted
419
+ }
420
+ return false;
421
+ }
422
+ catch {
423
+ return false;
424
+ }
425
+ }
310
426
  /**
311
427
  * Execute Git commands - Cursor-style (clean, fast, auto-fix)
312
428
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snapcommit/cli",
3
- "version": "3.3.0",
3
+ "version": "3.4.0",
4
4
  "description": "Instant AI commits. Beautiful progress tracking. Never write commit messages again.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {