UNPKG

clitutor

Version:

Interactive CLI learning tool for beginners

1,306 lines (1,189 loc) â€ĸ 40.8 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 Your Time Machine!", content: async () => { clearScreen(); console.log( boxen(green("Chapter 4: Git Basics - Your Personal Time Machine"), { padding: 1, borderStyle: "double", borderColor: "green", }) ); await printWithTypingEffect( "\nYou've learned to navigate files and use commands. Now let's learn the most powerful tool in a developer's toolkit: " + bold("Git") + "!" ); await sleep(500); await printWithTypingEffect( "\nImagine having a " + bold("time machine") + " for your code... ⏰" ); await sleep(500); await printWithTypingEffect( "\nThat's exactly what Git is! It lets you:" ); await sleep(1000); console.log( boxen( "🕐 " + bold("Save snapshots") + " of your work at any moment\n" + "âĒ " + bold("Travel back") + " to any previous version\n" + "🔍 " + bold("See exactly") + " what changed and when\n" + "💾 " + bold("Never lose") + " your work again\n" + "🤝 " + bold("Share code") + " with teammates (later chapters!)", { padding: 1, margin: { top: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("Real-Life Scenarios Git Solves:\n\n") + "😱 " + '"I broke everything! How do I go back?"\n' + "🤔 " + '"What did I change yesterday?"\n' + "😰 " + '"I deleted the wrong file!"\n' + "📝 " + '"When did this bug appear?"\n\n' + dim("Git has your back in all these situations!"), { padding: 1, margin: { top: 1 }, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( "đŸ’ģ " + bold("Terminal says:") + "\n\n" + "\"Git might seem complex at first, but think of me as\n" + "your personal time machine operator! 🎩\n\n" + "I'll help you:\n" + "â€ĸ Save your work at important moments\n" + "â€ĸ Travel to any point in your project's history\n" + "â€ĸ Never lose a single line of code\n\n" + "Ready to master time travel? Let's go!\"", { padding: 1, margin: { top: 1 }, borderColor: "cyan", borderStyle: "round", } ) ); await sleep(1000); await printWithTypingEffect( "\nLet's start by understanding how Git thinks about your work..." ); }, }, { id: "git_stages", title: "Understanding Git's Three Stages", content: async () => { clearScreen(); console.log( boxen(green("The Three Stages of Git 🎭"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nBefore we use Git, let's understand how it organizes your work." ); await sleep(700); await printWithTypingEffect( "\n\nThink of Git like a " + bold("theater production") + "... 🎭" ); await sleep(1000); console.log( boxen( bold("The Three Stages:\n\n") + "1ī¸âƒŖ " + yellow("Working Directory") + " = Backstage\n" + " Where you write and edit your files\n\n" + "2ī¸âƒŖ " + cyan("Staging Area") + " = The Stage\n" + " Where you prepare files for the snapshot\n\n" + "3ī¸âƒŖ " + green("Repository") + " = Recorded Show\n" + " Where Git permanently stores snapshots\n\n" + dim("Files move: Backstage → Stage → Recorded"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); // Visual flow diagram console.log( boxen( bold("Visual Flow:\n\n") + " " + yellow("Working") + " " + cyan("Staging") + " " + green("Repository") + "\n" + " " + yellow("Directory") + " " + cyan("Area") + " " + green("(History)") + "\n" + " 📝 " + magenta("git add") + " đŸŽŦ " + magenta("git commit") + " 📚\n" + " (edit) → (prepare) → (save)\n\n" + dim("You control what moves to each stage!"), { padding: 1, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Theater Analogy:\n\n") + yellow("Backstage:") + " Actors practicing lines (editing files)\n" + cyan("On Stage:") + " Actors ready to perform (staged files)\n" + green("Recorded:") + " The final show on video (committed)\n\n" + "Just like a director, you decide:\n" + "â€ĸ Who goes on stage (" + cyan("git add") + ")\n" + "â€ĸ When to record (" + cyan("git commit") + ")\n\n" + dim("Not ready? Keep practicing backstage!"), { padding: 1, margin: { top: 1 }, borderColor: "magenta", } ) ); await sleep(1000); await printWithTypingEffect( "\nđŸŽ¯ Key insight: You have " + bold("complete control") + " over what gets saved and when!" ); }, }, { id: "git_init_status", title: "Starting Your Time Machine", content: async () => { clearScreen(); console.log( boxen(green("git init & git status 🚀"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nLet's start your time machine with " + cyan("git init") + "!" ); await sleep(700); console.log( boxen( bold("Creating a Repository:\n\n") + cyan("git init") + " → Initialize Git in current folder\n\n" + "What happens:\n" + "â€ĸ Creates a hidden " + dim(".git") + " folder\n" + "â€ĸ This folder IS your time machine\n" + "â€ĸ Contains all history and Git data\n\n" + dim("Think: 'Git, initialize my time machine here!'"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "cyan", } ) ); await sleep(1000); console.log( boxen( bold("Example:\n\n") + "$ " + cyan("git init") + "\n" + green("Initialized empty Git repository in /Users/alex/my-project/.git/") + "\n\n" + "Translation: " + dim('"Time machine activated! 🎉"'), { padding: 1, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Checking Status - Your Git Conversation:\n\n") + cyan("git status") + " → Ask Git 'What's happening?'\n\n" + "Git responds with:\n" + "â€ĸ Current branch\n" + "â€ĸ What's staged (on stage)\n" + "â€ĸ What's modified (backstage)\n" + "â€ĸ What's new (untracked)\n\n" + dim("Use this constantly - it's your GPS!"), { padding: 1, margin: { top: 1 }, borderColor: "cyan", } ) ); await sleep(1000); console.log( boxen( bold("Reading git status:\n\n") + "$ " + cyan("git status") + "\n\n" + red("Changes not staged:") + "\n" + " " + red("modified: README.md") + "\n\n" + green("Untracked files:") + "\n" + " " + red("script.js") + "\n\n" + "Git is telling you:\n" + "â€ĸ README.md was edited (backstage)\n" + "â€ĸ script.js is new (Git doesn't know it yet)\n\n" + dim("Red = needs attention, Green = good to go!"), { padding: 1, margin: { top: 1 }, borderColor: "gray", } ) ); await sleep(1000); await printWithTypingEffect( "\n💡 Pro tip: Run " + cyan("git status") + " after every command to see what changed!" ); }, }, { id: "git_add", title: "Staging Your Changes", content: async () => { clearScreen(); console.log( boxen(green("git add - Preparing the Stage đŸŽŦ"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nTime to move files from backstage to the stage with " + cyan("git add") + "!" ); await sleep(700); console.log( boxen( bold("Different Ways to Stage:\n\n") + cyan("git add file.txt") + " → Stage specific file\n" + cyan("git add .") + " → Stage everything in current folder\n" + cyan("git add *.js") + " → Stage all JavaScript files\n" + cyan("git add -A") + " → Stage ALL changes everywhere\n\n" + dim("Remember: Staging is like selecting actors for the show!"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "cyan", } ) ); await sleep(1000); // Visual staging demonstration console.log( boxen( bold("Visual Example:\n\n") + "BEFORE " + cyan("git add") + ":\n" + yellow("Working Directory") + " " + cyan("Staging Area") + "\n" + " 📝 index.html\n" + " 📝 style.css (empty)\n" + " 📝 script.js\n\n" + "AFTER " + cyan("git add index.html style.css") + ":\n" + yellow("Working Directory") + " " + cyan("Staging Area") + "\n" + " 📝 script.js 📝 index.html\n" + " 📝 style.css\n\n" + dim("Only staged files will be in the snapshot!"), { padding: 1, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Common Patterns:\n\n") + "â€ĸ " + cyan("git add .") + " → 'Stage everything here'\n" + " Use when: All changes are related\n\n" + "â€ĸ " + cyan("git add file1 file2") + " → 'Stage specific files'\n" + " Use when: Only some changes are ready\n\n" + "â€ĸ " + cyan("git add -p") + " → 'Let me choose line by line'\n" + " Use when: Advanced (Chapter 6!)\n\n" + dim("Start with 'git add .' for simplicity!"), { padding: 1, margin: { top: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( "đŸ’ģ " + bold("Terminal says:") + "\n\n" + "\"Think of 'git add' as your quality control!\n\n" + "You don't have to stage everything - just the\n" + "files that are ready for prime time.\n\n" + "Made more changes after staging? No problem!\n" + "You'll need to 'git add' again to include the\n" + "latest edits. I only stage what you tell me to!\"", { padding: 1, margin: { top: 1 }, borderColor: "cyan", borderStyle: "round", } ) ); }, }, { id: "git_commit", title: "Taking Snapshots in Time", content: async () => { clearScreen(); console.log( boxen(green("git commit - Saving Your Moment 📸"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nNow for the magic moment - saving a snapshot with " + cyan("git commit") + "!" ); await sleep(700); console.log( boxen( bold("What is a Commit?\n\n") + "A commit is like a " + bold("photograph") + " of your project:\n" + "â€ĸ Captures all staged files at this moment\n" + "â€ĸ Has a unique ID (hash)\n" + "â€ĸ Includes your message (caption)\n" + "â€ĸ Records who and when\n\n" + dim("Think: A photo in your project's album! 📸"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("The -m Flag for Messages:\n\n") + cyan('git commit -m "Your message here"') + "\n\n" + "The " + yellow("-m") + " flag means 'message'\n" + "â€ĸ Describes what changed\n" + "â€ĸ Helps future you understand\n" + "â€ĸ Like a photo caption\n\n" + "Without -m: Opens a text editor (scary!)\n" + "With -m: Quick inline message (easy!)", { padding: 1, borderColor: "cyan", } ) ); await sleep(1000); console.log( boxen( bold("Good Commit Messages:\n\n") + green("✓") + " " + cyan('git commit -m "Add login button to homepage"') + "\n" + green("✓") + " " + cyan('git commit -m "Fix crash when user submits empty form"') + "\n" + green("✓") + " " + cyan('git commit -m "Update README with installation steps"') + "\n\n" + bold("Bad Commit Messages:\n\n") + red("✗") + " " + dim('git commit -m "Fixed stuff"') + "\n" + red("✗") + " " + dim('git commit -m "asdfasdf"') + "\n" + red("✗") + " " + dim('git commit -m "!!!"') + "\n\n" + "Rule: " + bold("What") + " changed and " + bold("why") + " (if not obvious)", { padding: 1, margin: { top: 1 }, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Complete Example:\n\n") + "$ " + cyan("git add .") + "\n" + "$ " + cyan('git commit -m "Add navigation menu with dropdown"') + "\n\n" + yellow("[main 7a3f9c2]") + " Add navigation menu with dropdown\n" + " 3 files changed, 45 insertions(+)\n\n" + "Git responds with:\n" + "â€ĸ Branch name: " + yellow("[main]") + "\n" + "â€ĸ Commit ID: " + yellow("7a3f9c2") + " (unique identifier)\n" + "â€ĸ Your message\n" + "â€ĸ What changed\n\n" + dim("Success! Your snapshot is saved forever! 🎉"), { padding: 1, borderColor: "green", } ) ); await sleep(1000); await printWithTypingEffect( "\nđŸŽ¯ Remember: Commit " + bold("often") + " with " + bold("clear messages") + ". Your future self will thank you!" ); }, }, { id: "git_log", title: "Viewing Your Time Machine's History", content: async () => { clearScreen(); console.log( boxen(green("git log - Your Project's Timeline 📜"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nLet's explore your project's history with " + cyan("git log") + "!" ); await sleep(700); console.log( boxen( bold("Basic git log:\n\n") + "$ " + cyan("git log") + "\n\n" + yellow("commit 7a3f9c2e8d4b6a5c9f8e7d6c5b4a3") + "\n" + "Author: Alex Smith <alex@example.com>\n" + "Date: Mon Jan 15 14:30:00 2024 -0500\n\n" + " Add navigation menu with dropdown\n\n" + yellow("commit 5b2c8a9d7e6f5c4b3a2") + "\n" + "Author: Alex Smith <alex@example.com>\n" + "Date: Mon Jan 15 10:15:00 2024 -0500\n\n" + " Initial commit with homepage\n\n" + dim("Press 'q' to quit viewing logs"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Prettier Log Formats:\n\n") + cyan("git log --oneline") + " → Compact view\n" + yellow("7a3f9c2") + " Add navigation menu with dropdown\n" + yellow("5b2c8a9") + " Initial commit with homepage\n\n" + cyan("git log --oneline --graph") + " → With visual branch lines\n" + cyan("git log -3") + " → Show only last 3 commits\n\n" + dim("Much easier to scan! 👀"), { padding: 1, borderColor: "cyan", } ) ); await sleep(1000); console.log( boxen( bold("Understanding Commit Info:\n\n") + yellow("commit 7a3f9c2...") + " → Unique ID (SHA)\n" + "â€ĸ Like a fingerprint\n" + "â€ĸ Usually use first 7 characters\n\n" + "Author → Who made the change\n" + "Date → When it happened\n" + "Message → What changed\n\n" + dim("It's your project's complete story!"), { padding: 1, margin: { top: 1 }, borderColor: "yellow", } ) ); await sleep(1000); await printWithTypingEffect( "\n💡 Tip: Use " + cyan("git log --oneline") + " for a quick history overview!" ); }, }, { id: "git_diff", title: "Seeing What Changed", content: async () => { clearScreen(); console.log( boxen(green("git diff - Your Change Detective 🔍"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nBefore committing, let's see exactly what changed with " + cyan("git diff") + "!" ); await sleep(700); console.log( boxen( bold("What git diff Shows:\n\n") + cyan("git diff") + " → Changes NOT staged yet\n" + cyan("git diff --staged") + " → Changes already staged\n" + cyan("git diff HEAD") + " → All changes since last commit\n\n" + dim("Think: 'What's different from before?'"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "cyan", } ) ); await sleep(1000); console.log( boxen( bold("Reading diff Output:\n\n") + "diff --git a/index.html b/index.html\n" + "index 9c5e2a8..3f7b9d1 100644\n" + "--- a/index.html\n" + "+++ b/index.html\n" + "@@ -10,7 +10,7 @@\n" + " <h1>Welcome</h1>\n" + red("- <p>Hello World</p>") + "\n" + green("+ <p>Hello Git Users!</p>") + "\n" + " <footer>2024</footer>\n\n" + red("- Red lines") + " = Removed\n" + green("+ Green lines") + " = Added\n" + dim("Gray lines") + " = Context (unchanged)", { padding: 1, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Common Workflow:\n\n") + "1. Make changes to files\n" + "2. " + cyan("git diff") + " → See what you changed\n" + "3. " + cyan("git add .") + " → Stage changes\n" + "4. " + cyan("git diff --staged") + " → Verify staged changes\n" + "5. " + cyan("git commit -m \"...\"") + " → Save snapshot\n\n" + dim("Always check diff before committing!"), { padding: 1, margin: { top: 1 }, borderColor: "yellow", } ) ); await sleep(1000); await printWithTypingEffect( "\nđŸŽ¯ Pro tip: Small diffs = easier to understand = better commits!" ); }, }, { id: "undoing", title: "Undoing Mistakes Safely", content: async () => { clearScreen(); console.log( boxen(green("Undoing Changes - Your Safety Net đŸĻē"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nMade a mistake? No worries! Git is very forgiving..." ); await sleep(700); console.log( boxen( bold("Common Undo Scenarios:\n\n") + "1ī¸âƒŖ " + bold("Discard changes") + " to a file (not staged)\n" + "2ī¸âƒŖ " + bold("Unstage") + " a file (remove from staging)\n" + "3ī¸âƒŖ " + bold("Undo last commit") + " (advanced - Chapter 6!)\n\n" + dim("Let's learn the safe, easy ones!"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("Discard Changes (Careful!):\n\n") + cyan("git checkout -- file.txt") + "\n\n" + "What it does:\n" + "â€ĸ Reverts file to last committed version\n" + "â€ĸ " + red("DELETES all uncommitted changes") + "\n" + "â€ĸ No undo for this!\n\n" + "When to use:\n" + "â€ĸ You broke something while editing\n" + "â€ĸ Want to start fresh\n\n" + dim("âš ī¸ Make sure you really want to lose changes!"), { padding: 1, borderColor: "red", } ) ); await sleep(1000); console.log( boxen( bold("Unstaging Files (Safe!):\n\n") + cyan("git reset file.txt") + "\n" + "or\n" + cyan("git reset") + " (unstage everything)\n\n" + "What it does:\n" + "â€ĸ Removes from staging area\n" + "â€ĸ Keeps your changes in working directory\n" + "â€ĸ Totally safe!\n\n" + "When to use:\n" + "â€ĸ Accidentally staged wrong file\n" + "â€ĸ Want to commit files separately", { padding: 1, margin: { top: 1 }, borderColor: "green", } ) ); await sleep(1000); console.log( boxen( "đŸ’ģ " + bold("Terminal says:") + "\n\n" + "\"Everyone makes mistakes - that's why I have undo!\n\n" + "Remember:\n" + "â€ĸ " + cyan("git reset") + " = Safe (just unstages)\n" + "â€ĸ " + cyan("git checkout --") + " = Dangerous (loses work)\n\n" + "When in doubt, make a commit first! You can\n" + "always change your mind later. Better safe\n" + "than sorry! 🛟\"", { padding: 1, margin: { top: 1 }, borderColor: "cyan", borderStyle: "round", } ) ); await sleep(1000); await printWithTypingEffect( "\nđŸŽ¯ Golden rule: When unsure, " + bold("commit first") + ", worry later!" ); }, }, { id: "summary", title: "Your Git Toolkit", content: async () => { clearScreen(); console.log( boxen(green("Git Basics Command Reference 📚"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nHere's your complete Git basics toolkit:" ); await sleep(700); console.log( boxen( bold("🚀 Starting Up\n") + cyan("git init") + " → Start time machine in folder\n" + cyan("git status") + " → What's happening?\n\n" + bold("📸 Making Snapshots\n") + cyan("git add .") + " → Stage all changes\n" + cyan("git add file") + " → Stage specific file\n" + cyan('git commit -m "msg"') + " → Save snapshot\n\n" + bold("🔍 Exploring\n") + cyan("git log --oneline") + " → View history\n" + cyan("git diff") + " → See unstaged changes\n" + cyan("git diff --staged") + " → See staged changes\n\n" + bold("â†Šī¸ Undoing\n") + cyan("git reset file") + " → Unstage file\n" + cyan("git checkout -- file") + " → Discard changes", { padding: 1, margin: { top: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("Complete Workflow Example:\n\n") + dim("# Start a new project\n") + "$ " + cyan("mkdir my-app") + "\n" + "$ " + cyan("cd my-app") + "\n" + "$ " + cyan("git init") + "\n\n" + dim("# Create and save first snapshot\n") + "$ " + cyan("touch index.html") + "\n" + "$ " + cyan("git add index.html") + "\n" + "$ " + cyan('git commit -m "Initial commit"') + "\n\n" + dim("# Make changes and save again\n") + "$ " + dim("# edit index.html") + "\n" + "$ " + cyan("git diff") + "\n" + "$ " + cyan("git add .") + "\n" + "$ " + cyan('git commit -m "Add homepage content"') + "\n\n" + dim("You're using Git! 🎉"), { padding: 1, margin: { top: 1 }, borderColor: "gray", } ) ); await sleep(1000); await printWithTypingEffect( "\nđŸŽ¯ Next steps: Practice the add-commit cycle until it feels natural!" ); }, }, ]; // Exercises for Chapter 4 const exercises = [ { id: "repository_check", type: "multiple_choice", title: "🔍 Repository Recognition", scenario: "You " + cyan("cd") + " into a project folder and want to check if it's already a Git repository.\n" + "Which command tells you if Git is initialized here?", choices: [ cyan("git init") + " - to initialize Git", cyan("git status") + " - to check Git status", cyan("git check") + " - to check for Git", cyan("ls -la") + " - to see hidden files", ], answer: 1, explanation: cyan("git status") + " will either show you the repository status OR tell you 'fatal: not a git repository'. It's the quickest way to check! You could also use " + cyan("ls -la") + " to look for a .git folder, but git status is more direct.", wrongAnswerHints: { 0: "This would create a NEW repository - not check for existing one!", 2: "There's no 'git check' command - try something else!", 3: "This could work to see .git folder, but there's a Git command for this!", }, }, { id: "status_reading", type: "multiple_choice", title: "📊 Understanding Status", scenario: "You run " + cyan("git status") + " and see:\n\n" + red("Changes not staged for commit:\n") + red(" modified: app.js\n\n") + green("Untracked files:\n") + red(" test.js\n\n"), question: "What does this status tell you?", choices: [ "Both files are ready to commit", "app.js was edited, test.js is brand new", "Both files are in the staging area", "You need to run git init first", ], answer: 1, explanation: "The status shows: app.js is " + red("modified") + " (Git knows it, but changes aren't staged), and test.js is " + red("untracked") + " (Git has never seen this file before). Neither is staged yet!", wrongAnswerHints: { 0: "Red text means NOT ready - they need to be staged first!", 2: "If they were staged, you'd see 'Changes to be committed' in green!", 3: "Git status is working, so Git is already initialized!", }, }, { id: "staging_practice", type: "multiple_choice", title: "đŸŽŦ Staging Strategy", scenario: "You've edited 3 files:\n" + "â€ĸ " + yellow("index.html") + " (ready to commit)\n" + "â€ĸ " + yellow("style.css") + " (ready to commit)\n" + "â€ĸ " + yellow("experiment.js") + " (still testing, not ready)\n", question: "Which command stages ONLY the ready files?", choices: [ cyan("git add .") + " - stage everything", cyan("git add index.html style.css") + " - stage specific files", cyan("git add --ready") + " - stage ready files", cyan("git commit index.html style.css") + " - commit directly", ], answer: 1, explanation: cyan("git add index.html style.css") + " stages exactly the files you specify. Using " + cyan("git add .") + " would include experiment.js which isn't ready. Always be selective with staging!", wrongAnswerHints: { 0: "This would stage ALL files, including the experiment!", 2: "There's no --ready flag - you need to specify files!", 3: "You must stage files before committing them!", }, }, { id: "commit_message", type: "multiple_choice", title: "đŸ’Ŧ Commit Message Quality", question: "You fixed a bug where the login button wasn't working on mobile devices.\n\n" + "Which is the BEST commit message?", choices: [ cyan('git commit -m "Fixed stuff"'), cyan('git commit -m "Fix mobile login button not responding to taps"'), cyan('git commit -m "Update"'), cyan('git commit -m "Mobile fix - button - login - bug"'), ], answer: 1, explanation: "Good commit messages explain WHAT changed and WHY (if not obvious). 'Fix mobile login button not responding to taps' clearly describes both the problem and what was fixed. Future you will appreciate this clarity!", wrongAnswerHints: { 0: "Too vague - what stuff? What was broken?", 2: "Update what? This tells us nothing!", 3: "Keywords are hard to read - write a sentence!", }, }, { id: "complete_workflow", type: "type_command", title: "đŸŽ¯ Complete Git Workflow", scenario: "Let's practice the complete Git workflow! You've just created a new file called " + yellow("README.md") + " and want to:\n" + "1. Stage the file\n" + "2. Commit with message 'Add project README'\n\n" + "Type both commands separated by ' && '", question: "Type the complete command sequence:", answer: 'git add README.md && git commit -m "Add project README"', alternates: [ "git add README.md && git commit -m 'Add project README'", 'git add . && git commit -m "Add project README"', "git add . && git commit -m 'Add project README'", ], hints: [ "First command: git add (what file?)", "Second command: git commit -m (what message?)", "Remember to use && between commands and quotes around the message", ], explanation: "Perfect! You've completed the fundamental Git workflow: " + cyan("add") + " (stage) → " + cyan("commit") + " (snapshot). This two-step process gives you control over exactly what goes into each commit. Keep practicing this pattern!", showBuilder: true, }, ]; // Exercise runner functions 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 workflow builder for the last exercise if (exercise.showBuilder) { console.log("\n" + bold("Let's visualize the Git workflow:")); await sleep(1000); console.log( boxen( green("GIT WORKFLOW BUILDER 🔧") + "\n\n" + dim("─────────────────────────────────") + "\n\n" + yellow("Working") + " " + cyan("Staging") + " " + green("Repository") + "\n" + " 📝 → đŸŽŦ → 📚\n\n" + "Step 1: Stage the file\n" + "→ " + cyan("git add README.md") + "\n\n" + " " + dim("📝") + " " + cyan("đŸŽŦ") + " 📚\n" + " README.md\n\n" + "Step 2: Commit with message\n" + "→ " + cyan('git commit -m "Add project README"') + "\n\n" + " " + dim("📝") + " " + dim("đŸŽŦ") + " " + green("📚") + "\n" + " README.md", { padding: 1, margin: { top: 1, bottom: 1 }, borderStyle: "bold", borderColor: "yellow", title: "🔧 Interactive Builder", titleAlignment: "center", } ) ); await sleep(2000); await printWithTypingEffect("\nNow combine these commands with &&!"); 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! Git commands take 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("Time to Practice Git! 🚀") + "\n\n" + "Let's test your time machine skills with " + bold("5 exercises") + "\n\n" + dim("Remember: git status is your best friend!"), { padding: 1, borderStyle: "double", borderColor: "green", } ) ); await sleep(2000); // Quick command review clearScreen(); console.log( boxen(green("đŸŽ¯ Command Cheat Sheet"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nYour essential Git commands:" ); await sleep(700); console.log( boxen( bold("The Git Workflow:\n\n") + "1ī¸âƒŖ " + cyan("git init") + " → Start time machine\n" + "2ī¸âƒŖ " + cyan("git status") + " → Check what's happening\n" + "3ī¸âƒŖ " + cyan("git add") + " → Stage changes\n" + "4ī¸âƒŖ " + cyan("git commit -m") + " → Save snapshot\n" + "5ī¸âƒŖ " + cyan("git log") + " → View history\n\n" + "And remember:\n" + "â€ĸ " + cyan("git diff") + " → See what changed\n" + "â€ĸ " + cyan("git reset") + " → Unstage files\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 test your Git skills?", 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 exercise?", 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 Git time traveler!"; } else if (percentage >= 80) { message = "🌟 Excellent! Your Git basics are solid!"; } else if (percentage >= 60) { message = "👍 Good work! Keep practicing those Git commands!"; } else { message = "đŸ’Ē Nice effort! Git takes practice - you're getting there!"; } console.log( boxen( green("🎉 Chapter 4 Complete! 🎉") + "\n\n" + bold(`Your Score: ${score}/${exercises.length} (${percentage}%)`) + "\n\n" + message + "\n\n" + "Git skills unlocked:\n" + "✅ Initialize repositories with git init\n" + "✅ Check status and understand Git's states\n" + "✅ Stage files with git add\n" + "✅ Commit snapshots with clear messages\n" + "✅ View history with git log\n" + "✅ See changes with git diff\n" + "✅ Undo mistakes safely\n\n" + yellow("đŸŽ¯ Daily Challenge:") + "\n" + "Start using Git for a personal project!\n" + "Commit at least once a day with good messages.\n\n" + dim("Ready for Chapter 5: Git Collaboration? Coming soon..."), { 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 };