UNPKG

clitutor

Version:

Interactive CLI learning tool for beginners

1,382 lines (1,262 loc) β€’ 43.4 kB
const inquirer = require("inquirer"); const chalk = require("chalk"); const boxen = require("boxen"); const ora = require("ora"); const { clearScreen, sleep, printWithTypingEffect, validateCommand, showHint, } = require("../utils/helpers"); const green = chalk.hex("#00FF00"); const dim = chalk.dim; const bold = chalk.bold; const yellow = chalk.yellow; const cyan = chalk.cyan; const magenta = chalk.magenta; const blue = chalk.blue; const red = chalk.red; const lessons = [ { id: "intro", title: "Welcome to Git Mastery!", content: async () => { clearScreen(); console.log( boxen(green("Chapter 6: Git Mastery - Power User Workflows"), { padding: 1, borderStyle: "double", borderColor: "green", }) ); await printWithTypingEffect( "\nYou've learned the basics and collaboration. Now let's become a " + bold("Git ninja") + "! πŸ₯·" ); await sleep(500); await printWithTypingEffect( "\nThese advanced techniques will make you " + bold("faster") + ", " + bold("smarter") + ", and " + bold("more confident") + "." ); await sleep(500); await printWithTypingEffect( "\nThink of this as learning " + bold("secret shortcuts") + " and " + bold("hidden powers") + "!" ); await sleep(1000); console.log( boxen( bold("What Makes a Git Master?\n\n") + "🎯 " + bold("Efficiency") + " - Do more with less typing\n" + "πŸ”§ " + bold("Precision") + " - Surgical control over commits\n" + "πŸ› οΈ " + bold("Problem Solving") + " - Fix any Git situation\n" + "⚑ " + bold("Speed") + " - Work faster than ever\n" + "🦺 " + bold("Safety") + " - Never lose work again\n" + "πŸ€– " + bold("Automation") + " - Let Git work for you", { padding: 1, margin: { top: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("Real Scenarios You'll Master:\n\n") + "😱 " + '"I need to switch branches but have uncommitted work!"\n' + "🎯 " + '"I want to commit only SOME of my changes"\n' + "πŸ“ " + '"My commit history is messy - need to clean it up"\n' + "πŸ” " + '"When did this bug first appear?"\n' + "πŸ’Ύ " + '"I accidentally deleted a commit!"\n' + "πŸš€ " + '"I type the same long commands repeatedly"\n\n' + dim("After this chapter, these will be easy!"), { padding: 1, margin: { top: 1 }, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( "πŸ’» " + bold("Terminal says:") + "\n\n" + "\"Ready to unlock Git's hidden powers? πŸ”“\n\n" + "These aren't just party tricks - they're tools that\n" + "professional developers use every day!\n\n" + "Think of it like this:\n" + "β€’ Chapters 4-5 = Learning to drive\n" + "β€’ Chapter 6 = Racing techniques! 🏎️\n\n" + "Let's turn you into a Git power user!\"", { padding: 1, margin: { top: 1 }, borderColor: "cyan", borderStyle: "round", } ) ); await sleep(1000); await printWithTypingEffect( "\nFirst up: The magic of " + cyan("git stash") + " - your quick save button!" ); }, }, { id: "git_stash", title: "Git Stash: Your Quick Save", content: async () => { clearScreen(); console.log( boxen(green("git stash - The Emergency Button πŸ†˜"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nEver been in the middle of work when something urgent comes up?" ); await sleep(700); await printWithTypingEffect( "\n" + cyan("git stash") + " is like hitting " + bold("pause") + " on your work! ⏸️" ); await sleep(1000); console.log( boxen( bold("The Stash Scenario:\n\n") + "You're coding a new feature when suddenly:\n" + red("🚨 'URGENT: Fix the login bug NOW!'") + "\n\n" + "But you're not ready to commit...\n" + "β€’ Code is half-done\n" + "β€’ Tests aren't written\n" + "β€’ Don't want to lose work\n\n" + green("Solution: Stash it!") + " πŸ“¦", { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("Basic Stash Commands:\n\n") + cyan("git stash") + " β†’ Save current changes\n" + cyan("git stash pop") + " β†’ Restore and remove stash\n" + cyan("git stash list") + " β†’ See all stashes\n" + cyan("git stash apply") + " β†’ Restore but keep stash\n\n" + dim("Think: Save game β†’ Do something else β†’ Load game"), { padding: 1, borderColor: "cyan", } ) ); await sleep(1000); console.log( boxen( bold("Stash with Messages:\n\n") + cyan('git stash save "Working on navbar animation"') + "\n\n" + "Now " + cyan("git stash list") + " shows:\n" + yellow("stash@{0}:") + " On main: Working on navbar animation\n" + yellow("stash@{1}:") + " WIP on main: Previous work\n\n" + dim("Much easier to find later!"), { padding: 1, margin: { top: 1 }, borderColor: "gray", } ) ); await sleep(1000); // Visual stash workflow console.log( boxen( bold("Visual Stash Workflow:\n\n") + "Working Directory (messy)\n" + " πŸ“ Half-done feature\n" + " ↓ " + cyan("git stash") + "\n" + "Working Directory (clean!)\n" + " ✨ Ready for urgent fix\n" + " ↓ " + dim("fix bug, commit") + "\n" + "Bug Fixed!\n" + " ↓ " + cyan("git stash pop") + "\n" + "Working Directory (messy again)\n" + " πŸ“ Feature work restored!\n\n" + dim("Like a magical undo/redo for uncommitted work!"), { padding: 1, margin: { top: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("Pro Stash Tips:\n\n") + "β€’ " + cyan("git stash -u") + " β†’ Include untracked files\n" + "β€’ " + cyan("git stash branch feature") + " β†’ Create branch from stash\n" + "β€’ " + cyan("git stash drop stash@{1}") + " β†’ Delete specific stash\n" + "β€’ " + cyan("git stash clear") + " β†’ Delete all stashes\n\n" + yellow("⚠️ Stashes are local") + " - not shared with push!", { padding: 1, borderColor: "green", } ) ); await sleep(1000); await printWithTypingEffect( "\n🎯 Use stash for quick context switches - it's a lifesaver!" ); }, }, { id: "interactive_staging", title: "Interactive Staging: Surgical Precision", content: async () => { clearScreen(); console.log( boxen(green("git add -p - Choose Your Changes 🎯"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nSometimes you want to commit " + bold("only some") + " of your changes..." ); await sleep(700); await printWithTypingEffect( "\n" + cyan("git add -p") + " lets you pick and choose line by line!" ); await sleep(1000); console.log( boxen( bold("Why Selective Staging?\n\n") + "You've been working and made:\n" + "β€’ βœ… Bug fix (ready to commit)\n" + "β€’ 🚧 New feature (not ready)\n" + "β€’ 🧹 Code cleanup (ready)\n" + "β€’ πŸ”§ Debug prints (don't commit!)\n\n" + "Problem: They're all in the same file! 😱\n" + "Solution: " + cyan("git add -p") + " (p = patch)", { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("Interactive Mode:\n\n") + "$ " + cyan("git add -p") + "\n\n" + "diff --git a/app.js b/app.js\n" + "@@ -10,6 +10,8 @@\n" + " function login() {\n" + red("- // TODO: Fix this bug") + "\n" + green("+ validateInput(); // Fixed!") + "\n" + green("+ console.log('DEBUG: login called');") + "\n" + " performLogin();\n" + " }\n\n" + yellow("Stage this hunk [y,n,q,a,d,s,e,?]? ") + "\n\n" + dim("Git asks about each change!"), { padding: 1, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Interactive Options:\n\n") + yellow("y") + " = Yes, stage this change\n" + yellow("n") + " = No, skip this change\n" + yellow("s") + " = Split into smaller chunks\n" + yellow("e") + " = Edit the change manually\n" + yellow("q") + " = Quit (done selecting)\n" + yellow("?") + " = Show all options\n\n" + dim("You're in control of every line!"), { padding: 1, margin: { top: 1 }, borderColor: "cyan", } ) ); await sleep(1000); console.log( boxen( bold("Practical Example:\n\n") + "Change 1: Bug fix β†’ " + green("y") + " (stage it)\n" + "Change 2: Debug print β†’ " + red("n") + " (skip it)\n" + "Change 3: Cleanup β†’ " + green("y") + " (stage it)\n\n" + "Result: Perfect commit with only ready changes!\n\n" + cyan("git commit -m") + ' "Fix login validation and cleanup"\n\n' + dim("Debug prints still in working directory"), { padding: 1, borderColor: "green", } ) ); await sleep(1000); await printWithTypingEffect( "\nπŸ’‘ Pro tip: " + cyan("git add -p") + " leads to cleaner, more focused commits!" ); }, }, { id: "git_rebase", title: "Git Rebase: Rewriting History", content: async () => { clearScreen(); console.log( boxen(green("git rebase - History's Edit Button ✏️"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nYour commit history tells a story. " + cyan("git rebase") + " lets you edit that story!" ); await sleep(700); await printWithTypingEffect( "\n⚠️ With great power comes great responsibility..." ); await sleep(1000); console.log( boxen( bold("What is Rebase?\n\n") + "Imagine your commits are like a rough draft:\n" + "β€’ Typos in messages\n" + "β€’ Too many small commits\n" + "β€’ Wrong order\n" + "β€’ 'Oops' commits\n\n" + cyan("rebase") + " = Rewrite before publishing!\n\n" + dim("Like editing a document before sending"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("Interactive Rebase:\n\n") + cyan("git rebase -i HEAD~3") + " β†’ Edit last 3 commits\n\n" + "Opens editor with:\n" + yellow("pick 7a3f9c2") + " Add login feature\n" + yellow("pick 5b4d8e1") + " Fix typo\n" + yellow("pick 9c2f3a4") + " Oops, forgot file\n\n" + "Change to:\n" + yellow("pick 7a3f9c2") + " Add login feature\n" + yellow("squash 5b4d8e1") + " Fix typo\n" + yellow("squash 9c2f3a4") + " Oops, forgot file\n\n" + dim("Squash = Combine commits!"), { padding: 1, borderColor: "cyan", } ) ); await sleep(1000); // Visual rebase console.log( boxen( bold("Visual: Before and After Rebase\n\n") + "BEFORE (messy):\n" + "● Add login\n" + "● Fix typo\n" + "● Forgot file\n" + "● Another typo\n" + "● Finally working!\n\n" + "AFTER (clean):\n" + "● Add complete login feature\n\n" + dim("5 commits β†’ 1 clean commit! ✨"), { padding: 1, margin: { top: 1 }, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Rebase Options:\n\n") + yellow("pick") + " = Keep commit as-is\n" + yellow("reword") + " = Change commit message\n" + yellow("squash") + " = Combine with previous\n" + yellow("drop") + " = Delete commit\n" + yellow("edit") + " = Stop to make changes\n\n" + red("⚠️ Golden Rule:") + "\n" + "NEVER rebase commits that are already pushed\n" + "and shared with others!\n\n" + dim("Rebase local work only!"), { padding: 1, borderColor: "red", } ) ); await sleep(1000); console.log( boxen( "πŸ’» " + bold("Terminal says:") + "\n\n" + "\"Rebase is like time travel - use it wisely! ⏰\n\n" + "Think of it this way:\n" + "β€’ Your local commits = Your diary\n" + "β€’ Pushed commits = Published book\n\n" + "You can edit your diary all you want,\n" + "but don't change published history!\n\n" + "When in doubt, use merge instead.\"", { padding: 1, margin: { top: 1 }, borderColor: "cyan", borderStyle: "round", } ) ); }, }, { id: "git_bisect", title: "Git Bisect: Bug Detective", content: async () => { clearScreen(); console.log( boxen(green("git bisect - Find Bugs Like Sherlock πŸ”"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nA bug appeared... but when? " + cyan("git bisect") + " finds the exact commit!" ); await sleep(700); await printWithTypingEffect( "\nIt's like a " + bold("binary search") + " through your history." ); await sleep(1000); console.log( boxen( bold("The Mystery:\n\n") + "β€’ Last week: Everything worked βœ…\n" + "β€’ Today: Something's broken ❌\n" + "β€’ In between: 50 commits! 😱\n\n" + "Checking each commit = Hours\n" + "Using " + cyan("bisect") + " = Minutes!\n\n" + dim("It's like playing '20 questions' with Git"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("How Bisect Works:\n\n") + "1. " + cyan("git bisect start") + " β†’ Begin investigation\n" + "2. " + cyan("git bisect bad") + " β†’ Current commit is broken\n" + "3. " + cyan("git bisect good v1.0") + " β†’ v1.0 was working\n\n" + "Git picks middle commit:\n" + dim("'Is commit abc123 good or bad?'") + "\n\n" + "4. Test it...\n" + "5. " + cyan("git bisect good") + " or " + cyan("bad") + "\n" + "6. Repeat until found!\n\n" + dim("Narrows down like a detective!"), { padding: 1, borderColor: "cyan", } ) ); await sleep(1000); // Visual bisect process console.log( boxen( bold("Visual: Bisect in Action\n\n") + "good ●───●───●───●───●───●───●───● bad\n" + " ↑ ↑\n" + " v1.0 HEAD\n\n" + "Step 1: Test middle\n" + "good ●───●───●───" + yellow("●") + "───●───●───●───● bad\n" + " ↑\n" + " " + green("good!") + "\n\n" + "Step 2: Test between middle and bad\n" + "good ●───●───●───●───●───" + yellow("●") + "───●───● bad\n" + " ↑\n" + " " + red("bad!") + "\n\n" + "Found it! Bug introduced here! 🎯", { padding: 1, margin: { top: 1 }, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Bisect Result:\n\n") + yellow("7a3f9c2 is the first bad commit") + "\n" + "commit 7a3f9c2e8d4b6a5c9f8e7d6c5b4a3\n" + "Author: Dev <dev@example.com>\n" + "Date: Tue Oct 15 14:30:00 2024\n\n" + " Optimize database queries\n\n" + dim("Now you know exactly what broke it!") + "\n\n" + cyan("git bisect reset") + " β†’ Return to original commit", { padding: 1, borderColor: "green", } ) ); await sleep(1000); await printWithTypingEffect( "\n🎯 Bisect turns hours of debugging into minutes!" ); }, }, { id: "git_aliases", title: "Git Aliases: Your Custom Shortcuts", content: async () => { clearScreen(); console.log( boxen(green("Git Aliases - Type Less, Do More ⚑"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nTired of typing long commands? Create your own shortcuts!" ); await sleep(700); await printWithTypingEffect( "\nAliases are like " + bold("keyboard macros") + " for Git." ); await sleep(1000); console.log( boxen( bold("Creating Aliases:\n\n") + cyan("git config --global alias.co checkout") + "\n" + "Now: " + green("git co") + " = " + dim("git checkout") + "\n\n" + cyan("git config --global alias.br branch") + "\n" + "Now: " + green("git br") + " = " + dim("git branch") + "\n\n" + cyan("git config --global alias.st status") + "\n" + "Now: " + green("git st") + " = " + dim("git status") + "\n\n" + dim("Save keystrokes on common commands!"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "cyan", } ) ); await sleep(1000); console.log( boxen( bold("Useful Aliases Collection:\n\n") + green("# Quick commands\n") + "co = checkout\n" + "br = branch\n" + "ci = commit\n" + "st = status\n\n" + green("# See pretty log\n") + "lg = log --oneline --graph --decorate\n\n" + green("# Show last commit\n") + "last = log -1 HEAD\n\n" + green("# Undo last commit (keep changes)\n") + "undo = reset HEAD~1 --mixed", { padding: 1, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Complex Alias Example:\n\n") + cyan('git config --global alias.ac "!git add -A && git commit"') + "\n\n" + "Now " + green("git ac -m") + ' "message" does:\n' + "1. Stages all changes\n" + "2. Creates commit\n\n" + "One command instead of two! πŸš€\n\n" + dim('The "!" means run shell commands'), { padding: 1, margin: { top: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("Managing Aliases:\n\n") + cyan("git config --global --list") + "\n" + "β†’ See all your aliases\n\n" + cyan("git config --global --unset alias.co") + "\n" + "β†’ Remove an alias\n\n" + "Aliases live in " + yellow("~/.gitconfig") + "\n" + "You can edit directly:\n\n" + "[alias]\n" + " co = checkout\n" + " unstage = reset HEAD --", { padding: 1, borderColor: "green", } ) ); await sleep(1000); await printWithTypingEffect( "\nπŸ’‘ Create aliases for YOUR most-used commands!" ); }, }, { id: "git_reflog", title: "Git Reflog: The Ultimate Safety Net", content: async () => { clearScreen(); console.log( boxen(green("git reflog - Your Git Time Machine πŸ•"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nThink you lost commits? " + cyan("git reflog") + " remembers EVERYTHING!" ); await sleep(700); await printWithTypingEffect( "\nIt's Git's " + bold("secret diary") + " of all your actions." ); await sleep(1000); console.log( boxen( bold("What is Reflog?\n\n") + "Git secretly tracks every move:\n" + "β€’ Every checkout\n" + "β€’ Every commit\n" + "β€’ Every reset\n" + "β€’ Every rebase\n\n" + "Even 'lost' commits aren't really lost!\n\n" + dim("It's like Git's 'Recently Deleted' folder"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("Viewing Reflog:\n\n") + "$ " + cyan("git reflog") + "\n\n" + yellow("7a3f9c2") + " HEAD@{0}: commit: Add new feature\n" + yellow("5b4d8e1") + " HEAD@{1}: checkout: moving from fix to main\n" + yellow("9c2f3a4") + " HEAD@{2}: commit: Fix critical bug\n" + yellow("3d5e7f8") + " HEAD@{3}: reset: moving to HEAD~1\n" + yellow("abc1234") + " HEAD@{4}: commit: " + red("Lost commit!") + "\n\n" + dim("Every action with timestamp!"), { padding: 1, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Disaster Recovery:\n\n") + red("😱 'I accidentally reset --hard!'") + "\n\n" + "1. " + cyan("git reflog") + " β†’ Find lost commit\n" + "2. See " + yellow("abc1234") + " HEAD@{4}: commit: Important work\n" + "3. " + cyan("git checkout abc1234") + " β†’ Recover it!\n" + "4. " + cyan("git branch recovered-work") + " β†’ Save it\n\n" + green("Crisis averted! πŸŽ‰") + "\n\n" + dim("Reflog entries expire after 90 days"), { padding: 1, margin: { top: 1 }, borderColor: "green", } ) ); await sleep(1000); console.log( boxen( "πŸ’» " + bold("Terminal says:") + "\n\n" + "\"Reflog is my memory bank! 🧠\n\n" + "I never truly forget anything. Even when you\n" + "think you've destroyed commits, I remember.\n\n" + "Fun fact: 'reflog' = 'reference log'\n\n" + "It's saved countless developers from disasters.\n" + "Now it's your safety net too!\n\n" + "Remember: In Git, it's really hard to lose work!\"", { padding: 1, margin: { top: 1 }, borderColor: "cyan", borderStyle: "round", } ) ); await sleep(1000); await printWithTypingEffect( "\n🦺 With reflog, you can experiment fearlessly!" ); }, }, { id: "summary", title: "Your Git Mastery Toolkit", content: async () => { clearScreen(); console.log( boxen(green("Git Power User Reference πŸš€"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nCongratulations! You now have Git superpowers!" ); await sleep(700); console.log( boxen( bold("πŸ†˜ Emergency Tools\n") + cyan("git stash") + " β†’ Quick save uncommitted work\n" + cyan("git stash pop") + " β†’ Restore stashed work\n" + cyan("git reflog") + " β†’ See all Git history\n\n" + bold("🎯 Precision Tools\n") + cyan("git add -p") + " β†’ Stage specific lines\n" + cyan("git rebase -i") + " β†’ Edit commit history\n" + cyan("git cherry-pick") + " β†’ Copy specific commits\n\n" + bold("πŸ” Investigation Tools\n") + cyan("git bisect") + " β†’ Find bug introduction\n" + cyan("git blame") + " β†’ See who changed what\n" + cyan("git log -S") + " β†’ Search commit contents\n\n" + bold("⚑ Efficiency Tools\n") + cyan("git alias") + " β†’ Create shortcuts\n" + cyan("git hooks") + " β†’ Automate workflows", { padding: 1, margin: { top: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("Master's Workflow Example:\n\n") + dim("# Working on feature, urgent bug comes in\n") + "$ " + cyan("git stash save") + ' "Feature work"\n' + "$ " + cyan("git checkout -b hotfix") + "\n" + "$ " + dim("# Fix bug") + "\n" + "$ " + cyan("git add -p") + dim(" # Stage only bug fix") + "\n" + "$ " + cyan("git commit -m") + ' "Fix critical bug"\n' + "$ " + cyan("git checkout main") + "\n" + "$ " + cyan("git merge hotfix") + "\n" + "$ " + cyan("git stash pop") + "\n\n" + dim("Smooth as silk! 🎭"), { padding: 1, margin: { top: 1 }, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Your Git Journey:\n\n") + "Chapter 4: " + green("βœ“") + " Basic Git\n" + "Chapter 5: " + green("βœ“") + " Collaboration\n" + "Chapter 6: " + green("βœ“") + " Mastery\n\n" + "You've learned:\n" + "β€’ 25+ Git commands\n" + "β€’ Emergency recovery\n" + "β€’ History manipulation\n" + "β€’ Workflow optimization\n" + "β€’ Professional techniques\n\n" + yellow("You're now a Git Power User! πŸ†"), { padding: 1, borderColor: "green", } ) ); await sleep(1000); await printWithTypingEffect( "\n🎯 Final tip: The best Git users never stop learning. Keep exploring!" ); }, }, ]; // Exercises for Chapter 6 const exercises = [ { id: "stash_scenario", type: "multiple_choice", title: "πŸ†˜ Stash Emergency", scenario: "You're coding a new feature when your boss says: 'Drop everything! Production is broken!'\n\n" + "Your working directory has uncommitted changes you don't want to lose.", question: "What's the BEST sequence of commands?", choices: [ cyan("git commit -m 'WIP'") + " then fix the bug", cyan("git stash") + " β†’ fix bug β†’ " + cyan("git stash pop"), cyan("git reset --hard") + " then fix the bug", "Copy files elsewhere, then " + cyan("git checkout ."), ], answer: 1, explanation: cyan("git stash") + " is perfect for this! It saves your work without committing, lets you fix the bug with a clean working directory, then " + cyan("git stash pop") + " brings your work back. Much cleaner than a 'WIP' commit!", wrongAnswerHints: { 0: "This creates a messy commit history - stash is cleaner!", 2: "This LOSES your work! Never use --hard with uncommitted changes!", 3: "This works but is manual and error-prone - Git has a better way!", }, }, { id: "interactive_staging", type: "multiple_choice", title: "🎯 Selective Staging", scenario: "Your file has:\n" + "β€’ Bug fix (ready to commit)\n" + "β€’ New feature (not ready)\n" + "β€’ Debug console.log statements\n\n" + "All changes are in the same file!", question: "Which command lets you choose specific lines to stage?", choices: [ cyan("git add file.js") + " - stages everything", cyan("git add -p file.js") + " - interactive mode", cyan("git add --selective file.js"), cyan("git commit -p file.js"), ], answer: 1, explanation: cyan("git add -p") + " (patch mode) lets you review each change and decide whether to stage it. You can even split changes into smaller chunks or edit them! Perfect for creating clean, focused commits.", wrongAnswerHints: { 0: "This stages ALL changes - not selective!", 2: "There's no --selective flag, but close idea!", 3: "Commit doesn't have a -p flag - you need to stage first!", }, }, { id: "rebase_understanding", type: "multiple_choice", title: "✏️ History Cleanup", scenario: "Your last 3 commits are:\n" + "1. 'Add login feature'\n" + "2. 'Oops, fix typo'\n" + "3. 'Forgot to add login.css'\n\n" + "You want to combine them into one clean commit before pushing.", question: "What's the correct approach?", choices: [ cyan("git merge --squash HEAD~3"), cyan("git reset --soft HEAD~3") + " then recommit", cyan("git rebase -i HEAD~3") + " then squash", cyan("git commit --amend") + " three times", ], answer: 2, explanation: cyan("git rebase -i HEAD~3") + " opens interactive rebase where you can mark commits to 'squash' together. This combines them while preserving the changes. It's the safest way to clean up local history!", wrongAnswerHints: { 0: "Merge --squash is for branches, not rewriting history!", 1: "This works but loses individual commit info - rebase is better!", 3: "--amend only modifies the last commit, not multiple!", }, }, { id: "bisect_process", type: "multiple_choice", title: "πŸ” Bug Hunt", scenario: "A bug appeared somewhere in the last 100 commits. You know:\n" + "β€’ Current version is broken\n" + "β€’ Version 2.0 (100 commits ago) worked fine", question: "After starting " + cyan("git bisect") + ", what do you do FIRST?", choices: [ "Test each of the 100 commits manually", cyan("git bisect bad") + " then " + cyan("git bisect good v2.0"), cyan("git bisect run npm test"), cyan("git log") + " to read all commit messages", ], answer: 1, explanation: "First mark the current state as " + cyan("bad") + " and the known good version with " + cyan("good v2.0") + ". Git will then checkout a commit in the middle for you to test. After ~7 tests (logβ‚‚ of 100), you'll find the exact commit that introduced the bug!", wrongAnswerHints: { 0: "That would take hours! Bisect does this intelligently!", 2: "Automated bisect comes after marking good/bad points!", 3: "Reading won't find the bug - you need to test!", }, }, { id: "recovery_mission", type: "type_command", title: "🦺 Disaster Recovery", scenario: "Oh no! You accidentally ran " + cyan("git reset --hard HEAD~3") + " and lost 3 commits!\n\n" + "But wait... Git never truly forgets. You need to:\n" + "1. Find the lost commits\n" + "2. Recover the most recent one\n\n" + "What command shows you ALL recent Git operations, including 'lost' commits?", question: "Type the command to see your Git history safety net:", answer: "git reflog", alternates: [ "git reflog show", "git reflog HEAD", ], hints: [ "It's a special log that tracks references...", "Think 'reference log' shortened...", "git ref... what?", ], explanation: "Perfect! " + cyan("git reflog") + " shows every change to HEAD, including 'lost' commits. You'd see your commits before the reset, grab the SHA, and checkout or cherry-pick to recover them. Nothing is truly lost in Git!", showBuilder: true, }, ]; // Exercise runner functions (similar to previous chapters) async function runExercise(exercise, index, total) { clearScreen(); const progress = `[${index + 1}/${total}]`; console.log( boxen(green(progress + " " + exercise.title), { padding: 1, borderStyle: "round", borderColor: "green", }) ); // Show recovery workflow for the last exercise if (exercise.showBuilder) { console.log("\n" + bold("Let's visualize the recovery process:")); await sleep(1000); console.log( boxen( green("DISASTER RECOVERY 🦺") + "\n\n" + dim("─────────────────────────────────") + "\n\n" + bold("Before reset --hard:") + "\n" + green("●") + " Latest work (HEAD)\n" + green("●") + " Important feature\n" + green("●") + " Critical bugfix\n" + "● Older commits...\n\n" + bold("After reset --hard HEAD~3:") + "\n" + red("βœ—") + " Latest work (gone?)\n" + red("βœ—") + " Important feature (gone?)\n" + red("βœ—") + " Critical bugfix (gone?)\n" + green("●") + " Older commits... (HEAD now)\n\n" + dim("─────────────────────────────────") + "\n\n" + bold("Using " + cyan("git reflog") + ":") + "\n" + "Shows: abc123 HEAD@{1}: Latest work\n\n" + bold("Recovery: ") + cyan("git checkout abc123") + "\n" + "Your commits are back! πŸŽ‰", { padding: 1, margin: { top: 1, bottom: 1 }, borderStyle: "bold", borderColor: "yellow", title: "🦺 Interactive Builder", titleAlignment: "center", } ) ); await sleep(2000); await printWithTypingEffect("\nWhat command reveals this safety net?"); await sleep(1000); } if (exercise.scenario) { console.log("\n" + bold("Scenario:")); await printWithTypingEffect(exercise.scenario); await sleep(1000); } if (exercise.hint) { console.log("\n" + yellow("πŸ’‘ Hint: ") + dim(exercise.hint)); await sleep(1000); } if (exercise.type === "multiple_choice") { return await runMultipleChoice(exercise); } else if (exercise.type === "type_command") { return await runTypeCommand(exercise); } } async function runMultipleChoice(exercise) { if (exercise.command) { console.log("\n" + exercise.command); } console.log("\n" + exercise.question); const { choice } = await inquirer.prompt([ { type: "list", name: "choice", message: green(">"), prefix: "", choices: exercise.choices, }, ]); const choiceIndex = exercise.choices.indexOf(choice); const isCorrect = choiceIndex === exercise.answer; if (isCorrect) { const spinner = ora({ text: "Checking...", color: "green", }).start(); await sleep(800); spinner.succeed(green("Correct! ✨")); } else { console.log(chalk.red("\nNot quite right.")); if (exercise.wrongAnswerHints && exercise.wrongAnswerHints[choiceIndex]) { console.log(yellow("\nπŸ’‘ Hint: ") + dim(exercise.wrongAnswerHints[choiceIndex])); } } if (exercise.explanation) { console.log(dim("\n" + exercise.explanation)); } await sleep(2000); return isCorrect; } async function runTypeCommand(exercise) { console.log("\n" + exercise.question); let attempts = 0; let correct = false; while (!correct && attempts < 3) { const { answer } = await inquirer.prompt([ { type: "input", name: "answer", message: green(">"), prefix: "", validate: (input) => input.trim().length > 0 || "Please type a command", }, ]); const normalizedAnswer = answer.trim(); const isCorrect = normalizedAnswer === exercise.answer || (exercise.alternates && exercise.alternates.includes(normalizedAnswer)); if (isCorrect) { correct = true; const spinner = ora({ text: "Checking...", color: "green", }).start(); await sleep(800); spinner.succeed(green("Perfect! ✨")); if (exercise.explanation) { console.log(dim("\n" + exercise.explanation)); } } else { attempts++; if (attempts < 3) { console.log(chalk.red("Not quite. Try again!")); if (exercise.hints && exercise.hints[attempts - 1]) { showHint(exercise.hints[attempts - 1]); } } } } if (!correct) { console.log( chalk.yellow(`\nThe correct answer was: ${green(exercise.answer)}`) ); console.log(dim("Don't worry! Advanced Git takes practice. πŸ’ͺ")); } await sleep(2000); return correct; } async function start() { clearScreen(); // Run through lessons for (const lesson of lessons) { await lesson.content(); console.log(); // Add spacing before the prompt const { ready } = await inquirer.prompt([ { type: "confirm", name: "ready", message: "Ready to continue?", default: true, }, ]); if (!ready) { return; } } // Practice exercises intro clearScreen(); console.log( boxen( green("Advanced Git Practice! πŸ₯·") + "\n\n" + "Time to test your power user skills with " + bold("5 exercises") + "\n\n" + dim("Remember: With great power comes great responsibility!"), { padding: 1, borderStyle: "double", borderColor: "green", } ) ); await sleep(2000); // Quick command review clearScreen(); console.log( boxen(green("🎯 Power User Cheat Sheet"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nYour advanced Git arsenal:" ); await sleep(700); console.log( boxen( bold("Emergency Commands:\n\n") + "πŸ†˜ " + cyan("git stash") + " β†’ Quick save\n" + "🦺 " + cyan("git reflog") + " β†’ Recovery mode\n\n" + bold("Precision Commands:\n\n") + "🎯 " + cyan("git add -p") + " β†’ Surgical staging\n" + "✏️ " + cyan("git rebase -i") + " β†’ History editing\n\n" + bold("Investigation Commands:\n\n") + "πŸ” " + cyan("git bisect") + " β†’ Bug hunting\n" + "πŸ“Š " + cyan("git log --graph") + " β†’ Visualize history\n\n" + bold("Remember:\n") + "β€’ Stash before switching context\n" + "β€’ Rebase local commits only\n" + "β€’ Reflog is your safety net\n" + "β€’ Aliases save time\n\n" + dim("You've got this! πŸš€"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1500); const { ready } = await inquirer.prompt([ { type: "confirm", name: "ready", message: "Ready to prove your Git mastery?", default: true, }, ]); if (!ready) { return; } // Run exercises let score = 0; const results = []; for (let i = 0; i < exercises.length; i++) { const result = await runExercise(exercises[i], i, exercises.length); results.push({ exercise: exercises[i].title, passed: result, }); if (result) score++; if (i < exercises.length - 1) { const { ready } = await inquirer.prompt([ { type: "confirm", name: "ready", message: "Ready for the next challenge?", default: true, }, ]); if (!ready) break; } } // Chapter completion clearScreen(); const percentage = Math.round((score / exercises.length) * 100); let message = ""; if (percentage === 100) { message = "πŸ† PERFECT! You're a true Git Master!"; } else if (percentage >= 80) { message = "🌟 Excellent! Your Git skills are professional level!"; } else if (percentage >= 60) { message = "πŸ‘ Good work! Keep practicing these advanced techniques!"; } else { message = "πŸ’ͺ Nice effort! Advanced Git takes time to master!"; } console.log( boxen( green("πŸŽ‰ Chapter 6 Complete! πŸŽ‰") + "\n\n" + bold(`Your Score: ${score}/${exercises.length} (${percentage}%)`) + "\n\n" + message + "\n\n" + "Advanced skills mastered:\n" + "βœ… Quick save with git stash\n" + "βœ… Precise staging with add -p\n" + "βœ… History editing with rebase\n" + "βœ… Bug hunting with bisect\n" + "βœ… Custom shortcuts with aliases\n" + "βœ… Disaster recovery with reflog\n" + "βœ… Workflow automation\n\n" + yellow("πŸ† Git Mastery Achievement Unlocked!") + "\n\n" + "You've completed the Git trilogy:\n" + green("βœ“") + " Chapter 4: Time Machine Basics\n" + green("βœ“") + " Chapter 5: Team Collaboration\n" + green("βœ“") + " Chapter 6: Power User Mastery\n\n" + bold("What's Next?") + "\n" + "β€’ Create your own Git aliases\n" + "β€’ Set up Git hooks for your projects\n" + "β€’ Contribute to open source\n" + "β€’ Share your knowledge with others\n\n" + dim("Congratulations, Git Master! πŸ₯·"), { padding: 1, borderStyle: "double", borderColor: "green", margin: 1, } ) ); // Show exercise summary console.log("\n" + bold("Exercise Summary:")); results.forEach((result) => { const icon = result.passed ? green("βœ“") : chalk.red("βœ—"); console.log(`${icon} ${result.exercise}`); }); await inquirer.prompt([ { type: "input", name: "continue", message: "\nPress Enter to return to the main menu...", prefix: "", }, ]); } module.exports = { start };