e-git-zain 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.
Files changed (3) hide show
  1. package/README.md +105 -0
  2. package/index.js +635 -0
  3. package/package.json +32 -0
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # e-git šŸš€
2
+
3
+ **The ultimate CLI tool to automate your GitHub workflow. Stop typing, start pushing.**
4
+
5
+ `e-git` is a powerful, interactive command-line interface designed to take the friction out of Git. Whether you're a beginner or a pro, `e-git` makes staging, committing, pushing, and even "time traveling" (undo/redo) easier than ever.
6
+
7
+ ---
8
+
9
+ ## 🌟 Key Features
10
+
11
+ - **šŸŽØ One-Command Push**: Stage all changes, write a commit message, and push to GitHub in one go.
12
+ - **šŸ›”ļø Smart GitIgnore Assistant**: Automatically detects missing `.gitignore` files and helps you hide sensitive data (like `.env`) or heavy folders (`node_modules`).
13
+ - **šŸ•°ļø Time Travel (Undo & Redo)**:
14
+ - `undo`: Instantly revert to your last clean push.
15
+ - `redo`: Jump forward if you changed your mind after an undo (with a 30-minute safety warning).
16
+ - **šŸ“œ Professional History Management**:
17
+ - `history`: Interactive menu to browse past pushes and view detailed colorized diffs.
18
+ - `list`: A quick table view of all your recorded pushes.
19
+ - **šŸ” Auth Shield**: Verifies your GitHub login status and helps you authenticate via GitHub CLI if needed.
20
+ - **šŸ“” Auto-Remote Setup**: Detects missing remotes and helps you link to GitHub interactively.
21
+
22
+ ---
23
+
24
+ ## šŸš€ Installation
25
+
26
+ Install `e-git` globally via NPM to use it in any project:
27
+
28
+ ```bash
29
+ npm install -g e-git
30
+ ```
31
+
32
+ ---
33
+
34
+ ## šŸ“– Usage Guide
35
+
36
+ ### 1. Pushing Code
37
+ Simply type `e-git` in your terminal.
38
+ ```bash
39
+ # Interactive mode (will ask for message)
40
+ e-git
41
+
42
+ # Quick push (one-shot)
43
+ e-git "Initial commit for my awesome app"
44
+ ```
45
+
46
+ ### 2. Undoing Mistakes (The "Reset" Button)
47
+ Made a mess of your code? Revert back to safety:
48
+ ```bash
49
+ e-git undo
50
+ ```
51
+ *Note: If you just pushed, it will step back to the push BEFORE the current one.*
52
+
53
+ ### 3. Redoing (The "Jump Forward")
54
+ Accidentally undid something you actually liked? Jump back forward:
55
+ ```bash
56
+ e-git redo
57
+ ```
58
+ *Note: If the code is older than 30 minutes, it will give you a special warning.*
59
+
60
+ ### 4. Viewing History
61
+ ```bash
62
+ # Interactive menu (Browse changes, view diffs, or restore versions)
63
+ e-git history
64
+
65
+ # Simple table list for a quick glance
66
+ e-git list
67
+ ```
68
+
69
+ ### 5. Maintenance & Credits
70
+ ```bash
71
+ # Clear all local push records
72
+ e-git clear
73
+
74
+ # See the creators
75
+ e-git credits
76
+ ```
77
+
78
+ ---
79
+
80
+ ## šŸ“… Monthly Updates
81
+ This project is actively maintained. I promise to release **new features and performance updates every month** to ensure `e-git` remains the best tool in your developer kit.
82
+
83
+ ---
84
+
85
+ ## ✨ Credits & Author
86
+
87
+ **e-git** was created with ā¤ļø by **Zain Ali** to make developers' lives easier.
88
+
89
+ - **Portfolio**: [zain-mughal.vercel.app](https://zain-mughal.vercel.app)
90
+ - **GitHub**: [@usernamezain](https://github.com/usernamezain)
91
+ - **Learning Course**: [m-learn.eu.cc](http://m-learn.eu.cc)
92
+ - **Email**: [devmughal8@gmail.com](mailto:devmughal8@gmail.com)
93
+
94
+ ---
95
+
96
+ ## šŸ”„ Support & Donations
97
+
98
+ If `e-git` saved you time and made your workflow easier, feel free to support the project!
99
+
100
+ - **Help & Donations**: Contact me directly if you'd like to contribute or help with the project.
101
+ - **WhatsApp / Contact**: `03124030056`
102
+
103
+ ---
104
+
105
+ Built by **Zain Ali** & Antigravity AI.
package/index.js ADDED
@@ -0,0 +1,635 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import inquirer from 'inquirer';
5
+ import simpleGit from 'simple-git';
6
+ import chalk from 'chalk';
7
+ import ora from 'ora';
8
+ import fs from 'fs/promises';
9
+ import path from 'path';
10
+ import { execSync, spawnSync } from 'child_process';
11
+
12
+ const git = simpleGit();
13
+ const program = new Command();
14
+
15
+ /**
16
+ * Returns the absolute path to the .git-easy-history.json file in the git root.
17
+ */
18
+ async function getHistoryPath() {
19
+ try {
20
+ const root = await git.revparse(['--show-toplevel']);
21
+ return path.join(root.trim(), '.git', 'git-easy-history.json');
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Checks if a command exists in the current environment.
29
+ */
30
+ function commandExists(cmd) {
31
+ try {
32
+ execSync(`command -v ${cmd} || where ${cmd}`, { stdio: 'ignore' });
33
+ return true;
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Verifies if the user is authenticated and has access to the remote repo.
41
+ */
42
+ async function ensureAuthenticated() {
43
+ const remotes = await git.getRemotes(true);
44
+ if (remotes.length === 0) return;
45
+
46
+ const origin = remotes.find(r => r.name === 'origin');
47
+ if (!origin) return;
48
+
49
+ const spinner = ora(chalk.blue('Verifying GitHub authentication...')).start();
50
+ try {
51
+ await git.listRemote(['--heads', 'origin']);
52
+ spinner.succeed(chalk.green('Authentication verified!'));
53
+ } catch (err) {
54
+ spinner.stop();
55
+ console.log(chalk.yellow('\nāš ļø Authentication failed or permission denied.'));
56
+
57
+ const { action } = await inquirer.prompt([
58
+ {
59
+ type: 'list',
60
+ name: 'action',
61
+ message: 'How would you like to proceed?',
62
+ choices: [
63
+ { name: 'Login using GitHub CLI (Recommended)', value: 'gh' },
64
+ { name: 'I will handle it manually (Paste a PAT/SSH)', value: 'manual' },
65
+ { name: 'Abort', value: 'abort' }
66
+ ]
67
+ }
68
+ ]);
69
+
70
+ if (action === 'abort') process.exit(1);
71
+
72
+ if (action === 'gh') {
73
+ if (commandExists('gh')) {
74
+ console.log(chalk.blue('\nLaunching GitHub CLI login...'));
75
+ spawnSync('gh', ['auth', 'login'], { stdio: 'inherit' });
76
+ return ensureAuthenticated();
77
+ } else {
78
+ console.log(chalk.red('\nāœ– GitHub CLI ("gh") is not installed.'));
79
+ console.log(chalk.cyan('Download it here: https://cli.github.com/\n'));
80
+ process.exit(1);
81
+ }
82
+ }
83
+
84
+ if (action === 'manual') {
85
+ console.log(chalk.cyan('\nTip: You can use a Personal Access Token (PAT) as your password.'));
86
+ console.log(chalk.cyan('Generate one here: https://github.com/settings/tokens\n'));
87
+ }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Ensures the repo has an origin remote. If not, asks the user to add one.
93
+ */
94
+ async function checkAndSetupRemote() {
95
+ const remotes = await git.getRemotes(true);
96
+ if (remotes.length === 0) {
97
+ console.log(chalk.yellow('\nℹ No remote found for this repository.'));
98
+ const { setupRemote } = await inquirer.prompt([
99
+ {
100
+ type: 'confirm',
101
+ name: 'setupRemote',
102
+ message: 'Would you like to add a GitHub remote now?',
103
+ default: true
104
+ }
105
+ ]);
106
+
107
+ if (setupRemote) {
108
+ const { remoteUrl } = await inquirer.prompt([
109
+ {
110
+ type: 'input',
111
+ name: 'remoteUrl',
112
+ message: 'Enter the GitHub repository URL (e.g., https://github.com/user/repo.git):',
113
+ validate: (input) => input.trim().length > 0 ? true : 'Please enter a valid Git URL.'
114
+ }
115
+ ]);
116
+
117
+ const spinner = ora(chalk.blue('Adding remote "origin"...')).start();
118
+ try {
119
+ await git.addRemote('origin', remoteUrl);
120
+ spinner.succeed(chalk.green('Remote "origin" added successfully!'));
121
+ } catch (err) {
122
+ spinner.fail(chalk.red('Failed to add remote.'));
123
+ console.error(chalk.red(`Error: ${err.message}`));
124
+ process.exit(1);
125
+ }
126
+ } else {
127
+ console.log(chalk.red('\nāœ– Error: A remote is required to push code.\n'));
128
+ process.exit(1);
129
+ }
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Logs a successful push event to a local JSON file in .git directory.
135
+ */
136
+ async function logPushEvent(message, branch, hash) {
137
+ try {
138
+ const historyPath = await getHistoryPath();
139
+ if (!historyPath) return;
140
+
141
+ const timestamp = new Date().toISOString();
142
+ const displayTimestamp = new Date().toLocaleString();
143
+ const newEntry = { timestamp, displayTimestamp, message, branch, hash };
144
+
145
+ let history = [];
146
+ try {
147
+ const data = await fs.readFile(historyPath, 'utf8');
148
+ history = JSON.parse(data);
149
+ } catch (e) {
150
+ // File doesn't exist
151
+ }
152
+
153
+ if (history.length > 0 && history[0].hash === hash) return;
154
+
155
+ history.unshift(newEntry);
156
+ await fs.writeFile(historyPath, JSON.stringify(history.slice(0, 50), null, 2));
157
+ } catch (err) {
158
+ // Silently fail
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Checks for a .gitignore file and helps the user create one if missing.
164
+ */
165
+ async function ensureGitIgnore() {
166
+ try {
167
+ const root = await git.revparse(['--show-toplevel']);
168
+ const gitIgnorePath = path.join(root.trim(), '.gitignore');
169
+
170
+ try {
171
+ await fs.access(gitIgnorePath);
172
+ return; // .gitignore already exists
173
+ } catch {
174
+ // .gitignore does not exist
175
+ console.log(chalk.yellow('\nšŸ›”ļø No .gitignore found in your project.'));
176
+ const { createIgnore } = await inquirer.prompt([
177
+ {
178
+ type: 'confirm',
179
+ name: 'createIgnore',
180
+ message: 'Would you like to create a .gitignore to keep your repo clean?',
181
+ default: true
182
+ }
183
+ ]);
184
+
185
+ if (createIgnore) {
186
+ const { patterns } = await inquirer.prompt([
187
+ {
188
+ type: 'checkbox',
189
+ name: 'patterns',
190
+ message: 'Select folders/files to ignore:',
191
+ choices: [
192
+ { name: 'node_modules (NPM dependencies)', value: 'node_modules/', checked: true },
193
+ { name: '.env (Sensitive secrets & keys)', value: '.env', checked: true },
194
+ { name: 'dist (Build output)', value: 'dist/' },
195
+ { name: 'build (Build output)', value: 'build/' },
196
+ { name: '.DS_Store (Mac junk files)', value: '.DS_Store' },
197
+ { name: 'npm-debug.log', value: 'npm-debug.log' },
198
+ { name: '.git-easy-history.json (Git-Easy local log)', value: '.git/git-easy-history.json' }
199
+ ]
200
+ }
201
+ ]);
202
+
203
+ if (patterns.length > 0) {
204
+ const content = patterns.join('\n') + '\n';
205
+ await fs.writeFile(gitIgnorePath, content);
206
+ console.log(chalk.green('āœ… .gitignore created successfully! Your secrets are safe. šŸ›”ļø\n'));
207
+ } else {
208
+ console.log(chalk.cyan('No patterns selected. Skipping .gitignore creation.\n'));
209
+ }
210
+ }
211
+ }
212
+ } catch (err) {
213
+ // Silently fail if not in a git repo or other issues
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Destructively restores the repository to a specific commit hash.
219
+ */
220
+ async function restoreToState(hash, autoConfirm = false, extraWarning = false) {
221
+ try {
222
+ if (!autoConfirm) {
223
+ console.log(chalk.red.bold('\nāš ļø WARNING: DESTRUCTIVE ACTION'));
224
+ console.log(chalk.red('This will delete all unpushed changes and reset your files to this specific version.'));
225
+
226
+ if (extraWarning) {
227
+ console.log(chalk.red.bold('CRITICAL: This version is more than 30 minutes old. Proceed with caution!'));
228
+ }
229
+
230
+ const { confirm } = await inquirer.prompt([
231
+ {
232
+ type: 'confirm',
233
+ name: 'confirm',
234
+ message: chalk.yellow('Are you absolutely sure you want to redo/undo to this state?'),
235
+ default: false
236
+ }
237
+ ]);
238
+
239
+ if (!confirm) {
240
+ console.log(chalk.cyan('\nRestore aborted. Your files are safe.\n'));
241
+ return false;
242
+ }
243
+ }
244
+
245
+ const spinner = ora(chalk.blue(`Restoring files to ${hash.substring(0, 7)}...`)).start();
246
+ await git.reset(['--hard', hash]);
247
+ spinner.succeed(chalk.green('Files restored successfully! Your workspace is back in time. šŸ•°ļø'));
248
+ return true;
249
+ } catch (err) {
250
+ console.error(chalk.red(`\nāœ– Restore failed: ${err.message}\n`));
251
+ return false;
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Displays the diff for a specific commit hash.
257
+ */
258
+ async function viewCommitDiff(hash) {
259
+ try {
260
+ console.log(chalk.cyan.bold(`\nšŸ“ Showing changes for push: ${chalk.white(hash.substring(0, 7))}\n`));
261
+ const diff = await git.show([hash, '--color=always', '--pretty=format:%B', '--compact-summary']);
262
+ console.log(diff);
263
+ console.log(chalk.gray('\n' + ''.padEnd(80, '-') + '\n'));
264
+ } catch (err) {
265
+ console.error(chalk.red(`\nāœ– Could not fetch diff: ${err.message}\n`));
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Displays a simple list of pushes without interactivity.
271
+ */
272
+ async function listPushes() {
273
+ try {
274
+ const isRepo = await git.checkIsRepo();
275
+ if (!isRepo) {
276
+ console.error(chalk.red('\nāœ– Error: Not a git repository.\n'));
277
+ return;
278
+ }
279
+
280
+ const historyPath = await getHistoryPath();
281
+ if (!historyPath) {
282
+ console.log(chalk.yellow('\nℹ No push history found for this repository.\n'));
283
+ return;
284
+ }
285
+
286
+ let history = [];
287
+ try {
288
+ const data = await fs.readFile(historyPath, 'utf8');
289
+ history = JSON.parse(data);
290
+ } catch (e) {
291
+ console.log(chalk.yellow('\nℹ No push history found for this repository.\n'));
292
+ return;
293
+ }
294
+
295
+ if (history.length === 0) {
296
+ console.log(chalk.yellow('\nℹ No push history found for this repository.\n'));
297
+ return;
298
+ }
299
+
300
+ console.log(chalk.cyan.bold('\nšŸš€ Git-Easy Push History (List View):\n'));
301
+ console.log(chalk.gray(''.padEnd(80, '-')));
302
+ console.log(
303
+ `${chalk.bold('Date & Time').padEnd(30)} | ${chalk.bold('Branch').padEnd(15)} | ${chalk.bold('Message')}`
304
+ );
305
+ console.log(chalk.gray(''.padEnd(80, '-')));
306
+
307
+ history.forEach(entry => {
308
+ const time = entry.displayTimestamp || entry.timestamp || 'Unknown';
309
+ console.log(
310
+ `${time.padEnd(28)} | ${chalk.green((entry.branch || 'main').padEnd(15))} | ${entry.message}`
311
+ );
312
+ });
313
+ console.log(chalk.gray(''.padEnd(80, '-')) + '\n');
314
+
315
+ } catch (err) {
316
+ console.log(chalk.yellow('\nℹ No push history found for this repository.\n'));
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Displays the push history in an interactive format.
322
+ */
323
+ async function displayHistory() {
324
+ try {
325
+ const isRepo = await git.checkIsRepo();
326
+ if (!isRepo) {
327
+ console.error(chalk.red('\nāœ– Error: Not a git repository.\n'));
328
+ return;
329
+ }
330
+
331
+ const historyPath = await getHistoryPath();
332
+ if (!historyPath) {
333
+ console.log(chalk.yellow('\nℹ No push history found for this repository.\n'));
334
+ return;
335
+ }
336
+
337
+ let history = [];
338
+ try {
339
+ const data = await fs.readFile(historyPath, 'utf8');
340
+ history = JSON.parse(data);
341
+ } catch (e) {
342
+ console.log(chalk.yellow('\nℹ No push history found for this repository.\n'));
343
+ return;
344
+ }
345
+
346
+ if (history.length === 0) {
347
+ console.log(chalk.yellow('\nℹ No push history found for this repository.\n'));
348
+ return;
349
+ }
350
+
351
+ console.log(chalk.cyan.bold('\nšŸš€ Git-Easy Push History:'));
352
+
353
+ const choices = history.map((entry) => ({
354
+ name: `${chalk.gray((entry.displayTimestamp || entry.timestamp).padEnd(25))} | ${chalk.green((entry.branch || 'main').padEnd(10))} | ${entry.message}`,
355
+ value: entry
356
+ }));
357
+
358
+ choices.push(new inquirer.Separator());
359
+ choices.push({ name: 'āŒ Exit', value: 'exit' });
360
+
361
+ const { selectedPush } = await inquirer.prompt([
362
+ {
363
+ type: 'list',
364
+ name: 'selectedPush',
365
+ message: 'Select a push to view details/restore:',
366
+ choices,
367
+ pageSize: 15
368
+ }
369
+ ]);
370
+
371
+ if (selectedPush === 'exit' || !selectedPush) return;
372
+
373
+ if (selectedPush.hash) {
374
+ await viewCommitDiff(selectedPush.hash);
375
+
376
+ const { action } = await inquirer.prompt([
377
+ {
378
+ type: 'list',
379
+ name: 'action',
380
+ message: 'What would like to do with this version?',
381
+ choices: [
382
+ { name: 'šŸ”™ Back to list', value: 'back' },
383
+ { name: 'šŸ•°ļø Restore My Files to this Version (Undo Mistakes)', value: 'restore' },
384
+ { name: 'āŒ Exit', value: 'exit' }
385
+ ]
386
+ }
387
+ ]);
388
+
389
+ if (action === 'back') return displayHistory();
390
+ if (action === 'restore') {
391
+ await restoreToState(selectedPush.hash);
392
+ }
393
+ } else {
394
+ console.log(chalk.yellow('\nℹ Detailed view only available for newer pushes.\n'));
395
+ }
396
+
397
+ } catch (err) {
398
+ console.error(chalk.red('\nāœ– An error occurred while browsing history:'));
399
+ console.error(chalk.gray(err.message));
400
+ }
401
+ }
402
+
403
+ /**
404
+ * Clears the push history from the repo.
405
+ */
406
+ async function clearHistory() {
407
+ try {
408
+ const historyPath = await getHistoryPath();
409
+ if (!historyPath) return;
410
+
411
+ const { confirm } = await inquirer.prompt([
412
+ {
413
+ type: 'confirm',
414
+ name: 'confirm',
415
+ message: chalk.red('Are you sure you want to clear your local push history? (This cannot be undone)'),
416
+ default: false
417
+ }
418
+ ]);
419
+
420
+ if (confirm) {
421
+ await fs.unlink(historyPath);
422
+ console.log(chalk.green('\nāœ… Push history cleared successfully!\n'));
423
+ }
424
+ } catch (err) {
425
+ console.log(chalk.yellow('\nℹ No history file found to clear.\n'));
426
+ }
427
+ }
428
+
429
+ program
430
+ .name('git-easy')
431
+ .description('šŸš€ Git-Easy: The ultimate CLI to automate your GitHub workflow.')
432
+ .version('2.0.0');
433
+
434
+ // Main Push Command (Default)
435
+ program
436
+ .argument('[message]', '⚔ Commits all changes and pushes them to GitHub. If no message is given, it will ask for one.')
437
+ .action(async (message) => {
438
+ try {
439
+ const isRepo = await git.checkIsRepo();
440
+ if (!isRepo) {
441
+ console.error(chalk.red('\nāœ– Error: Not a git repository.\n'));
442
+ process.exit(1);
443
+ }
444
+
445
+ await checkAndSetupRemote();
446
+ await ensureAuthenticated();
447
+ await ensureGitIgnore();
448
+
449
+ const status = await git.status();
450
+ if (status.files.length === 0) {
451
+ console.log(chalk.yellow('\nℹ No changes detected. Nothing to push.\n'));
452
+ process.exit(0);
453
+ }
454
+
455
+ let commitMessage = message;
456
+ if (!commitMessage) {
457
+ const answers = await inquirer.prompt([
458
+ {
459
+ type: 'input',
460
+ name: 'message',
461
+ message: 'Enter commit message:',
462
+ validate: (input) => input.trim() !== '' ? true : 'Commit message cannot be empty.'
463
+ }
464
+ ]);
465
+ commitMessage = answers.message;
466
+ }
467
+
468
+ const spinner = ora(chalk.blue('Processing your push...')).start();
469
+
470
+ spinner.text = chalk.blue('Staging files...');
471
+ await git.add('.');
472
+
473
+ spinner.text = chalk.blue('Committing changes...');
474
+ const commitResult = await git.commit(commitMessage);
475
+ const commitHash = commitResult.commit;
476
+
477
+ const currentBranch = status.current || 'main';
478
+ spinner.text = chalk.blue(`Pushing to remote (${currentBranch})...`);
479
+
480
+ try {
481
+ await git.push('origin', currentBranch);
482
+ spinner.succeed(chalk.green('Code pushed successfully! šŸš€'));
483
+
484
+ await logPushEvent(commitMessage, currentBranch, commitHash);
485
+
486
+ } catch (pushErr) {
487
+ spinner.fail(chalk.red('Failed to push to remote.'));
488
+ console.error(chalk.red(`\nError details: ${pushErr.message}`));
489
+ }
490
+
491
+ } catch (err) {
492
+ console.error(chalk.red(`\nāœ– An unexpected error occurred: ${err.message}\n`));
493
+ process.exit(1);
494
+ }
495
+ });
496
+
497
+ // History Command (Interactive)
498
+ program
499
+ .command('history')
500
+ .description('šŸ“œ Opens an interactive menu to browse past pushes, view detailed code changes, or RESTORE your project to any previous version.')
501
+ .action(displayHistory);
502
+
503
+ // List Command (Non-interactive)
504
+ program
505
+ .command('list')
506
+ .description('šŸ“‹ Provides a simple, non-interactive table view of all recorded pushes for a quick overview.')
507
+ .action(listPushes);
508
+
509
+ // Clear Command
510
+ program
511
+ .command('clear')
512
+ .description('🧹 Wipes your local Git-Easy history log. Use this if you want to start your push history tracking from scratch.')
513
+ .action(clearHistory);
514
+
515
+ // Undo Command
516
+ program
517
+ .command('undo')
518
+ .description('šŸ”™ The "Mistake Eraser". Instantly reverts all files in your project to the state of the last successful push. If you just pushed, it steps back to the push before that.')
519
+ .action(async () => {
520
+ try {
521
+ const isRepo = await git.checkIsRepo();
522
+ if (!isRepo) {
523
+ console.error(chalk.red('\nāœ– Error: Not a git repository.\n'));
524
+ process.exit(1);
525
+ }
526
+
527
+ const historyPath = await getHistoryPath();
528
+ if (!historyPath) {
529
+ console.log(chalk.yellow('\nℹ No push history found. Nothing to undo.\n'));
530
+ return;
531
+ }
532
+
533
+ let history = [];
534
+ try {
535
+ const data = await fs.readFile(historyPath, 'utf8');
536
+ history = JSON.parse(data);
537
+ } catch (e) {
538
+ console.log(chalk.yellow('\nℹ No push history found. Nothing to undo.\n'));
539
+ return;
540
+ }
541
+
542
+ const latestPush = history.find(entry => entry.hash);
543
+ if (!latestPush) {
544
+ console.log(chalk.yellow('\nℹ No tracked pushes found to undo.\n'));
545
+ return;
546
+ }
547
+
548
+ const currentHash = (await git.revparse(['HEAD'])).trim();
549
+ let targetPush = latestPush;
550
+
551
+ if (currentHash === latestPush.hash) {
552
+ const previousPush = history.find((entry) => entry.hash && entry.hash !== latestPush.hash);
553
+ if (previousPush) {
554
+ targetPush = previousPush;
555
+ }
556
+ }
557
+
558
+ console.log(chalk.cyan(`\nTargeting undo to push: "${chalk.white(targetPush.message)}" (${targetPush.displayTimestamp || targetPush.timestamp})`));
559
+ await restoreToState(targetPush.hash);
560
+
561
+ } catch (err) {
562
+ console.error(chalk.red(`\nāœ– Undo failed: ${err.message}\n`));
563
+ }
564
+ });
565
+
566
+ // Redo Command
567
+ program
568
+ .command('redo')
569
+ .description('ā­ļø The "Forward Jumper". If you ran an "undo" by accident, this command jumps your project forward to the newer state you were previously on.')
570
+ .action(async () => {
571
+ try {
572
+ const isRepo = await git.checkIsRepo();
573
+ if (!isRepo) {
574
+ console.error(chalk.red('\nāœ– Error: Not a git repository.\n'));
575
+ process.exit(1);
576
+ }
577
+
578
+ const historyPath = await getHistoryPath();
579
+ if (!historyPath) {
580
+ console.log(chalk.yellow('\nℹ No push history found. Nothing to redo.\n'));
581
+ return;
582
+ }
583
+
584
+ let history = [];
585
+ try {
586
+ const data = await fs.readFile(historyPath, 'utf8');
587
+ history = JSON.parse(data);
588
+ } catch (e) {
589
+ console.log(chalk.yellow('\nℹ No push history found. Nothing to redo.\n'));
590
+ return;
591
+ }
592
+
593
+ const currentHash = (await git.revparse(['HEAD'])).trim();
594
+ const currentIndex = history.findIndex(entry => entry.hash === currentHash);
595
+
596
+ if (currentIndex <= 0) {
597
+ console.log(chalk.yellow('\nℹ You are already at the latest redoable state.\n'));
598
+ return;
599
+ }
600
+
601
+ const targetPush = history[currentIndex - 1];
602
+ const pushTime = new Date(targetPush.timestamp).getTime();
603
+ const now = Date.now();
604
+ const minutesDiff = (now - pushTime) / (1000 * 60);
605
+
606
+ const extraWarning = minutesDiff > 30;
607
+
608
+ console.log(chalk.cyan(`\nTargeting redo to push: "${chalk.white(targetPush.message)}" (${targetPush.displayTimestamp || targetPush.timestamp})`));
609
+ await restoreToState(targetPush.hash, false, extraWarning);
610
+
611
+ } catch (err) {
612
+ console.error(chalk.red(`\nāœ– Redo failed: ${err.message}\n`));
613
+ }
614
+ });
615
+
616
+ // Credits Command
617
+ program
618
+ .command('credits')
619
+ .description('✨ View the creators of Git-Easy.')
620
+ .action(() => {
621
+ console.log(chalk.cyan.bold('\n✨ Git-Easy Credits ✨'));
622
+ console.log(chalk.white('-----------------------------------'));
623
+ console.log(`${chalk.yellow('Principal Creator:')} ${chalk.green.bold('Zain Ali')} (for the easy work!)`);
624
+ console.log(`${chalk.yellow('Developed by:')} ${chalk.blue('Antigravity AI')}`);
625
+ console.log(chalk.white('-----------------------------------\n'));
626
+ });
627
+
628
+ // Custom Help Text
629
+ program.addHelpText('after', `
630
+ ${chalk.yellow('Credits:')}
631
+ Created with ā¤ļø by ${chalk.green.bold('Zain Ali')} & Antigravity.
632
+ Use ${chalk.cyan('git-easy credits')} to see more!
633
+ `);
634
+
635
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "e-git-zain",
3
+ "version": "1.0.0",
4
+ "description": "šŸš€ The ultimate CLI to automate your GitHub workflow. Simplified push, undo, redo, and history management.",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "e-git": "index.js",
9
+ "git-easy": "index.js"
10
+ },
11
+ "keywords": [
12
+ "git",
13
+ "github",
14
+ "cli",
15
+ "automation",
16
+ "push",
17
+ "undo",
18
+ "redo",
19
+ "e-git",
20
+ "zain-ali"
21
+ ],
22
+ "author": "Zain Ali",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "chalk": "^5.3.0",
26
+ "commander": "^12.0.0",
27
+ "inquirer": "^9.2.16",
28
+ "ora": "^8.0.1",
29
+ "path": "^0.12.7",
30
+ "simple-git": "^3.22.0"
31
+ }
32
+ }