Skip to content

Commit 361493d

Browse files
Added branch detector, improved GitExecutor output, modular command routing, improve index, improve handleCommands, modify command list
1 parent 9f2225f commit 361493d

File tree

12 files changed

+288
-58
lines changed

12 files changed

+288
-58
lines changed

src/cli/index.ts

Lines changed: 47 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import chalk from 'chalk';
44
import figlet from 'figlet';
55
import { GitDetector } from '../core/GitDetector';
66
import { Logger } from '../utils/Logger';
7-
7+
import { COMMANDS_LIST } from '../config/commandsList';
8+
import { handleCommand } from '../core/HandleCommands';
9+
import { BranchDetector } from '../core/BranchDetector';
810

911
/**
1012
* plain-git CLI
@@ -22,81 +24,72 @@ function showBanner() {
2224
console.log(chalk.cyanBright(figlet.textSync('Plain-Git', { horizontalLayout: 'default' })));
2325
console.log(chalk.gray('✨ Operate Git in plain English.\n'));
2426

25-
Logger.info("🚀 Initializing plain-git environment check...\n");
27+
Logger.info('🚀 Initializing plain-git environment check...\n');
2628
}
2729

2830
/**
2931
* Displays the interactive menu options for the developer.
32+
* Dynamically build menu from COMMANDS_LIST
3033
*/
31-
async function showMenu(): Promise<string> {
32-
const choices = [
33-
{ name: '📦 Initialize a new Git repository', value: 'init' },
34-
{ name: '📂 Check repository status', value: 'status' },
35-
{ name: '🌿 Create a new branch', value: 'branch' },
36-
{ name: '📝 Commit changes', value: 'commit' },
37-
{ name: '🚀 Push changes to remote', value: 'push' },
38-
{ name: '⬇️ Pull latest changes', value: 'pull' },
39-
{ name: '❌ Exit', value: 'exit' },
40-
];
34+
async function showMenu(): Promise<void> {
35+
const currentBranch = BranchDetector.getCurrentBranch();
36+
37+
console.log();
38+
if (currentBranch) {
39+
console.log(chalk.cyanBright(`📍 Current branch: ${chalk.bold(currentBranch)}`));
40+
} else {
41+
console.log(chalk.gray('📍 Not inside a Git repository'));
42+
}
43+
console.log(chalk.gray('--------------------------------------------------\n'));
4144

42-
const { command } = await inquirer.prompt([
45+
const choices = COMMANDS_LIST.map((cmd) => ({
46+
name: `${cmd.name} ${cmd.command.includes('<') ? '(requires input)' : ''}`,
47+
value: cmd.handler, // use handler instead of direct git command
48+
short: cmd.category,
49+
}));
50+
51+
const { selected } = await inquirer.prompt([
4352
{
4453
type: 'list',
45-
name: 'command',
46-
message: 'What would you like to do?',
47-
choices,
54+
name: 'selected',
55+
message: 'Select a Git operation to perform:',
56+
pageSize: 20,
57+
choices: [...choices, new inquirer.Separator(), { name: '❌ Exit', value: 'exit' }],
4858
},
4959
]);
5060

51-
return command;
61+
if (selected === 'exit') {
62+
Logger.info('👋 Exiting plain-git.');
63+
process.exit(0);
64+
}
65+
66+
await handleCommand(selected);
67+
Logger.success('\n✅ Operation completed successfully!\n');
68+
69+
// await showMenu(); // loop back
5270
}
5371

5472
/**
55-
* Handles user-selected menu commands.
73+
* Local placeholder removed: using imported handleCommand from '../core/HandleCommands'.
5674
*/
57-
async function handleCommand(command: string) {
58-
switch (command) {
59-
case 'init':
60-
// await RepositoryManager.init();
61-
break;
62-
case 'status':
63-
// await RepositoryManager.status();
64-
break;
65-
case 'branch':
66-
// await RepositoryManager.createBranch();
67-
break;
68-
case 'commit':
69-
// await RepositoryManager.commitChanges();
70-
break;
71-
case 'push':
72-
// await RepositoryManager.pushChanges();
73-
break;
74-
case 'pull':
75-
// await RepositoryManager.pullChanges();
76-
break;
77-
default:
78-
Logger.info('👋 Goodbye!');
79-
process.exit(0);
80-
}
81-
}
8275

8376
/**
8477
* Main entry function
8578
*/
8679
(async function main() {
87-
console.clear();
88-
// Welcome banner
89-
showBanner();
80+
try {
81+
console.clear();
82+
// Welcome banner
83+
showBanner();
9084

91-
// Step 1: Ensure Git is available and check environment
92-
await GitDetector.checkEnvironment();
85+
// Step 1: Ensure Git is available and check environment
86+
await GitDetector.checkEnvironment();
9387

94-
Logger.success("\n🎯 Environment ready! You can start using plain-git commands.");
88+
Logger.success('\n🎯 Environment ready! You can start using plain-git commands.');
9589

96-
// Step 2: Interactive menu
97-
while (true) {
98-
const selected = await showMenu();
99-
await handleCommand(selected);
100-
console.log('\n');
90+
// Step 2: Launch category-based command menu
91+
await showMenu();
92+
} catch (error) {
93+
Logger.error(`❌ Something went wrong: ${(error as Error).message}`);
10194
}
10295
})();

src/config/commandsList.ts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/**
2+
* 📜 commandsList.ts
3+
* -------------------------------------------------------------
4+
* Central list of all plain-English Git actions,
5+
* their Git commands, and mapped handlers.
6+
* -------------------------------------------------------------
7+
*/
8+
9+
export type PlainGitCommand = {
10+
category: string;
11+
name: string;
12+
command: string;
13+
description: string;
14+
handler: string; // maps to specific Manager method
15+
};
16+
17+
export const COMMANDS_LIST: PlainGitCommand[] = [
18+
// 🏁 Repository Operations
19+
{
20+
category: "Repository",
21+
name: "📦 Initialize a new repository",
22+
command: "git init",
23+
description: "Create a new local Git repository in the current folder.",
24+
handler: "RepositoryManager.initRepo",
25+
},
26+
{
27+
category: "Repository",
28+
name: "🌐 Clone a repository from URL",
29+
command: "git clone <url>",
30+
description: "Clone an existing repository from a remote Git URL.",
31+
handler: "RepositoryManager.cloneRepo",
32+
},
33+
{
34+
category: "Repository",
35+
name: "📂 Check repository status",
36+
command: "git status",
37+
description: "Display the working tree status.",
38+
handler: "RepositoryManager.status",
39+
},
40+
{
41+
category: "Repository",
42+
name: "⚙️ Show Git configuration",
43+
command: "git config --list",
44+
description: "Show global and local Git config values.",
45+
handler: "RepositoryManager.showConfig",
46+
},
47+
48+
// 🌿 Branch Operations
49+
{
50+
category: "Branch",
51+
name: "🌱 Create a new branch",
52+
command: "git branch <branch-name>",
53+
description: "Create a new branch locally.",
54+
handler: "BranchManager.createBranch",
55+
},
56+
{
57+
category: "Branch",
58+
name: "🔄 Switch to another branch",
59+
command: "git checkout <branch-name>",
60+
description: "Switch to an existing branch.",
61+
handler: "BranchManager.switchBranch",
62+
},
63+
{
64+
category: "Branch",
65+
name: "🗑️ Delete a branch",
66+
command: "git branch -d <branch-name>",
67+
description: "Delete a local branch safely.",
68+
handler: "BranchManager.deleteBranch",
69+
},
70+
71+
// 📝 Commit Operations
72+
{
73+
category: "Commit",
74+
name: "🧩 Stage all changes",
75+
command: "git add .",
76+
description: "Add all modified files to staging.",
77+
handler: "CommitManager.stageAll",
78+
},
79+
{
80+
category: "Commit",
81+
name: "📝 Commit all staged changes",
82+
command: "git commit -m '<message>'",
83+
description: "Commit staged changes with a message.",
84+
handler: "CommitManager.commitChanges",
85+
},
86+
{
87+
category: "Commit",
88+
name: "📜 Show commit history",
89+
command: "git log --oneline",
90+
description: "Show a compact list of previous commits.",
91+
handler: "CommitManager.showLog",
92+
},
93+
94+
// 🚀 Remote Operations
95+
{
96+
category: "Remote",
97+
name: "🚀 Push changes to remote",
98+
command: "git push",
99+
description: "Push local commits to the default remote branch.",
100+
handler: "RemoteManager.pushChanges",
101+
},
102+
{
103+
category: "Remote",
104+
name: "⬇️ Pull latest changes",
105+
command: "git pull",
106+
description: "Pull changes from the remote branch into your local branch.",
107+
handler: "RemoteManager.pullChanges",
108+
},
109+
{
110+
category: "Remote",
111+
name: "🔍 Fetch updates from remote",
112+
command: "git fetch",
113+
description: "Fetch remote changes without merging them.",
114+
handler: "RemoteManager.fetchUpdates",
115+
},
116+
117+
// 🕓 History & Diff
118+
{
119+
category: "History",
120+
name: "🕓 View commit history (graph)",
121+
command: "git log --oneline --graph --decorate",
122+
description: "Show commits with visual branch structure.",
123+
handler: "HistoryManager.showHistoryGraph",
124+
},
125+
{
126+
category: "History",
127+
name: "🔍 Compare changes (diff)",
128+
command: "git diff",
129+
description: "Compare working directory with last commit.",
130+
handler: "HistoryManager.showDiff",
131+
},
132+
133+
// 🧹 Reset & Cleanup
134+
{
135+
category: "Reset",
136+
name: "↩️ Undo last commit (soft)",
137+
command: "git reset --soft HEAD~1",
138+
description: "Undo last commit but keep changes staged.",
139+
handler: "ResetManager.undoLastCommit",
140+
},
141+
{
142+
category: "Reset",
143+
name: "🧹 Clean untracked files",
144+
command: "git clean -fd",
145+
description: "Remove all untracked files and folders.",
146+
handler: "ResetManager.cleanUntracked",
147+
},
148+
];

src/core/BranchDetector.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// core/BranchDetector.ts
2+
import { execSync } from "child_process";
3+
4+
export class BranchDetector {
5+
static getCurrentBranch(): string | null {
6+
try {
7+
const branch = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8" }).trim();
8+
return branch || null;
9+
} catch {
10+
return null; // Not a git repo or detached HEAD
11+
}
12+
}
13+
}

src/core/GitExecutor.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,31 @@
11
import { exec } from 'child_process';
22
import { promisify } from 'util';
33
import { Logger } from '../utils/Logger';
4+
import chalk from 'chalk';
45

56
const execAsync = promisify(exec);
67

78
export class GitExecutor {
89
/**
910
* Run any Git command safely.
1011
*/
11-
static async run(command: string): Promise<string> {
12+
static async run(command: string): Promise<void> {
1213
try {
13-
const { stdout } = await execAsync(command);
14-
return stdout.trim();
14+
const { stdout, stderr } = await execAsync(command);
15+
16+
if (stdout.trim().length > 0) {
17+
console.log(chalk.greenBright(stdout)); // ✅ print actual output
18+
}
19+
20+
if (stderr.trim().length > 0) {
21+
console.log(chalk.yellowBright(stderr)); // ⚠️ show warnings/errors
22+
}
1523
} catch (err) {
16-
throw new Error(`Git command failed: ${command}`);
24+
if (err instanceof Error) {
25+
Logger.error(`Git command failed: ${err.message}`);
26+
} else {
27+
Logger.error('Git command failed with an unknown error.');
28+
}
1729
}
1830
}
1931

src/core/HandleCommands.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {RepositoryManager, BranchManager, CommitManager, RemoteManager, ResetManager, HistoryManager} from '../managers'
2+
import { Logger } from '../utils/Logger';
3+
4+
const managerMap: Record<string, any> = {
5+
RepositoryManager,
6+
BranchManager,
7+
CommitManager,
8+
RemoteManager,
9+
HistoryManager,
10+
ResetManager,
11+
};
12+
13+
export async function handleCommand(handlerPath: string) {
14+
try {
15+
const [managerName, methodName] = handlerPath.split('.');
16+
const manager = managerMap[managerName];
17+
18+
if (!manager || typeof manager[methodName] !== 'function') {
19+
throw new Error(`Invalid handler: ${handlerPath}`);
20+
}
21+
22+
await manager[methodName]();
23+
} catch (err) {
24+
Logger.error(`❌ Command failed: ${(err as Error).message}`);
25+
}
26+
}

src/managers/BranchManager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const BranchManager = {}

src/managers/CommitManager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const CommitManager = {}

src/managers/HistoryManager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const HistoryManager = {}

src/managers/RemoteManager.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const RemoteManager = {}

src/managers/RepositoryManager.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import inquirer from 'inquirer';
2+
import { GitExecutor } from '../core/GitExecutor';
3+
import { Logger } from '../utils/Logger';
4+
5+
export const RepositoryManager = {
6+
async initRepo() {
7+
Logger.info('🌀 Initializing new Git repository...');
8+
await GitExecutor.run('git init');
9+
},
10+
11+
async status() {
12+
Logger.info('📋 Checking repository status...');
13+
await GitExecutor.run('git status');
14+
},
15+
16+
17+
};

0 commit comments

Comments
 (0)