UNPKG

clitutor

Version:

Interactive CLI learning tool for beginners

1,311 lines (1,197 loc) โ€ข 40.5 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: "Git Goes Social!", content: async () => { clearScreen(); console.log( boxen(green("Chapter 5: Git Collaboration - Working as a Team"), { padding: 1, borderStyle: "double", borderColor: "green", }) ); await printWithTypingEffect( "\nYou've mastered personal Git. Now let's learn to " + bold("collaborate") + " with others!" ); await sleep(500); await printWithTypingEffect( "\nImagine Git as " + bold("Google Docs for code") + "... ๐Ÿ“" ); await sleep(500); await printWithTypingEffect( "\nMultiple people can work on the same project without stepping on each other's toes!" ); await sleep(1000); console.log( boxen( bold("What You'll Learn:\n\n") + "๐ŸŒ " + bold("Clone") + " projects from the internet\n" + "๐ŸŒณ " + bold("Create branches") + " for safe experiments\n" + "๐Ÿค " + bold("Merge") + " your work with others\n" + "โš”๏ธ " + bold("Resolve conflicts") + " when changes clash\n" + "๐Ÿ“ค " + bold("Share") + " your code with the world\n" + "๐Ÿ”„ " + bold("Pull requests") + " for code reviews", { padding: 1, margin: { top: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("From Solo to Team:\n\n") + "Chapter 4: " + dim("You โ†” Your Computer\n") + "Chapter 5: " + green("You โ†” GitHub/GitLab โ†” Team\n\n") + "Git becomes a " + bold("communication tool") + "!\n" + "โ€ข Share your work\n" + "โ€ข Get others' updates\n" + "โ€ข Work on the same files safely\n\n" + dim("It's like multiplayer mode for coding! ๐ŸŽฎ"), { padding: 1, margin: { top: 1 }, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( "๐Ÿ’ป " + bold("Terminal says:") + "\n\n" + "\"Ready to join the global coding community? ๐ŸŒ\n\n" + "Git collaboration might seem complex, but it's just\n" + "sharing your time machine with friends!\n\n" + "Think of it like this:\n" + "โ€ข Your local Git = Your personal diary\n" + "โ€ข GitHub/GitLab = Shared library\n" + "โ€ข Collaboration = Borrowing and contributing books\n\n" + "Let's learn to play nicely with others!\"", { padding: 1, margin: { top: 1 }, borderColor: "cyan", borderStyle: "round", } ) ); await sleep(1000); await printWithTypingEffect( "\nFirst, let's understand where code lives on the internet..." ); }, }, { id: "remotes_clone", title: "Cloning: Downloading Projects", content: async () => { clearScreen(); console.log( boxen(green("git clone - Copying Projects ๐Ÿ“ฅ"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nBefore we share code, let's learn to " + bold("download") + " existing projects!" ); await sleep(700); console.log( boxen( bold("What is GitHub/GitLab/Bitbucket?\n\n") + "Think of them as " + bold("libraries for code") + ":\n" + "โ€ข Store Git repositories online\n" + "โ€ข Share with the world (or just your team)\n" + "โ€ข Browse others' code\n" + "โ€ข Collaborate on projects\n\n" + dim("GitHub is like Instagram for developers! ๐Ÿ“ธ"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("Cloning a Repository:\n\n") + cyan("git clone") + " " + yellow("https://github.com/user/project.git") + "\n\n" + "What happens:\n" + "1. Downloads entire project history\n" + "2. Sets up connection to origin\n" + "3. Checks out default branch\n" + "4. Ready to work!\n\n" + dim("It's like downloading a complete time machine!"), { padding: 1, borderColor: "cyan", } ) ); await sleep(1000); // Visual representation console.log( boxen( bold("Visual: Clone Process\n\n") + " " + blue("GitHub") + " " + green("Your Computer") + "\n" + " โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” " + cyan("clone") + " โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\n" + " โ”‚ Project โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’ โ”‚ Project โ”‚\n" + " โ”‚ History โ”‚ โ”‚ History โ”‚\n" + " โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜\n" + " (origin) (local)\n\n" + dim("You get the entire history, not just current files!"), { padding: 1, margin: { top: 1 }, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("After Cloning:\n\n") + "$ " + cyan("cd project") + dim(" # Enter the folder\n") + "$ " + cyan("git remote -v") + dim(" # See connection\n\n") + "origin https://github.com/user/project.git (fetch)\n" + "origin https://github.com/user/project.git (push)\n\n" + yellow("origin") + " = Nickname for the online repository\n" + dim("(You can have multiple remotes!)"), { padding: 1, borderColor: "yellow", } ) ); await sleep(1000); await printWithTypingEffect( "\n๐Ÿ’ก Remember: Clone " + bold("once") + ", then pull updates (we'll learn that soon!)" ); }, }, { id: "branches", title: "Branches: Parallel Universes", content: async () => { clearScreen(); console.log( boxen(green("Branches - Your Experiment Playground ๐ŸŒณ"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nBranches are Git's " + bold("killer feature") + " for collaboration!" ); await sleep(700); await printWithTypingEffect( "\n\nThink of branches as " + bold("parallel universes") + " for your code... ๐ŸŒŒ" ); await sleep(1000); console.log( boxen( bold("What are Branches?\n\n") + "Imagine writing a book:\n" + "โ€ข " + green("main") + " branch = Published book\n" + "โ€ข " + yellow("feature") + " branch = Draft chapter\n" + "โ€ข " + yellow("experiment") + " branch = Crazy idea\n\n" + "You can:\n" + "โ€ข Work on drafts without affecting the book\n" + "โ€ข Try multiple versions\n" + "โ€ข Merge good ideas back\n" + "โ€ข Abandon bad ideas\n\n" + dim("Branches = Safe experimentation!"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("Branch Commands:\n\n") + cyan("git branch") + " โ†’ List all branches\n" + cyan("git branch feature") + " โ†’ Create 'feature' branch\n" + cyan("git checkout feature") + " โ†’ Switch to 'feature'\n" + cyan("git checkout -b feature") + " โ†’ Create AND switch\n\n" + "The " + yellow("*") + " shows current branch:\n" + "$ " + cyan("git branch") + "\n" + yellow("* main") + "\n" + " feature\n" + " bugfix", { padding: 1, borderColor: "cyan", } ) ); await sleep(1000); // Visual branch diagram console.log( boxen( bold("Visual: Branch Timeline\n\n") + "main โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—\n" + " \\\n" + "feature โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—\n" + " \\\n" + "bugfix โ—โ”€โ”€โ”€โ—\n\n" + "Each โ— is a commit\n" + "Branches " + bold("diverge") + " from main\n" + "Work happens " + bold("independently") + "\n\n" + dim("Like alternate timelines in movies! ๐ŸŽฌ"), { padding: 1, margin: { top: 1 }, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Branch Naming Conventions:\n\n") + green("Good names:") + "\n" + "โ€ข feature/add-login\n" + "โ€ข bugfix/fix-crash\n" + "โ€ข feature/dark-mode\n" + "โ€ข hotfix/security-patch\n\n" + red("Bad names:") + "\n" + "โ€ข stuff\n" + "โ€ข asdf\n" + "โ€ข my-branch\n" + "โ€ข test123\n\n" + dim("Be descriptive - future you will thank you!"), { padding: 1, borderColor: "green", } ) ); await sleep(1000); await printWithTypingEffect( "\n๐ŸŽฏ Rule: One branch = One feature/fix. Keep it focused!" ); }, }, { id: "merging", title: "Merging: Combining Work", content: async () => { clearScreen(); console.log( boxen(green("git merge - Bringing Changes Together ๐Ÿค"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nAfter working on a branch, you'll want to " + bold("merge") + " changes back!" ); await sleep(700); console.log( boxen( bold("What is Merging?\n\n") + "Remember our book analogy:\n" + "โ€ข You finished your draft chapter\n" + "โ€ข Time to add it to the main book\n" + "โ€ข " + cyan("merge") + " combines the changes\n\n" + "Git is smart:\n" + "โ€ข Automatically combines changes\n" + "โ€ข Preserves everyone's work\n" + "โ€ข Creates a merge commit\n\n" + dim("Like weaving threads together! ๐Ÿงต"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("How to Merge:\n\n") + "1. Switch to target branch:\n" + " $ " + cyan("git checkout main") + "\n\n" + "2. Merge feature branch:\n" + " $ " + cyan("git merge feature/add-login") + "\n\n" + "3. Git combines the changes:\n" + " " + green("Updating 3a4f5b6..7c8d9e0") + "\n" + " " + green("Fast-forward") + "\n" + " index.html | 25 " + green("+++") + red("---") + "\n" + " 1 file changed, 20 insertions(+), 5 deletions(-)", { padding: 1, borderColor: "cyan", } ) ); await sleep(1000); // Visual merge console.log( boxen( bold("Visual: Before and After Merge\n\n") + "BEFORE:\n" + "main โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—\n" + " \\\n" + "feature โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—\n\n" + "AFTER " + cyan("git merge feature") + ":\n" + "main โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ—\n" + " \\ /\n" + "feature โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—\n\n" + dim("The branches reunite! ๐ŸŽŠ"), { padding: 1, margin: { top: 1 }, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( "๐Ÿ’ป " + bold("Terminal says:") + "\n\n" + "\"Merging is like a zipper! ๐Ÿค\n\n" + "I take changes from both branches and zip them\n" + "together intelligently.\n\n" + "Most of the time, I can figure it out myself.\n" + "But sometimes... conflicts happen!\n\n" + "Don't worry - we'll handle those next!\"", { padding: 1, margin: { top: 1 }, borderColor: "cyan", borderStyle: "round", } ) ); }, }, { id: "conflicts", title: "Merge Conflicts: When Changes Clash", content: async () => { clearScreen(); console.log( boxen(green("Merge Conflicts - Don't Panic! โš”๏ธ"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nSometimes two people change the " + bold("same lines") + "... Git needs your help!" ); await sleep(700); console.log( boxen( bold("Why Conflicts Happen:\n\n") + "Person A changes line 10 to: " + green('"Hello World!"') + "\n" + "Person B changes line 10 to: " + green('"Hi Everyone!"') + "\n\n" + "Git: " + dim('"Which one do you want? ๐Ÿคท"') + "\n\n" + "Conflicts are " + bold("normal") + "!\n" + "โ€ข Not errors\n" + "โ€ข Not anyone's fault\n" + "โ€ข Just need human decision\n\n" + dim("Think: Two chefs seasoning the same dish!"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("What Conflicts Look Like:\n\n") + "<<<<<<< HEAD\n" + red("Hello World!") + dim(" (current branch)\n") + "=======\n" + green("Hi Everyone!") + dim(" (incoming branch)\n") + ">>>>>>> feature/new-greeting\n\n" + "Git shows:\n" + "โ€ข " + red("Your version") + " (HEAD)\n" + "โ€ข " + dim("=======") + " separator\n" + "โ€ข " + green("Their version") + " (feature)\n\n" + dim("You decide what to keep!"), { padding: 1, borderColor: "red", } ) ); await sleep(1000); console.log( boxen( bold("Resolving Conflicts:\n\n") + "1. " + bold("Open") + " the conflicted file\n" + "2. " + bold("Find") + " the <<<<<<< markers\n" + "3. " + bold("Decide") + " what to keep:\n" + " โ€ข Keep yours\n" + " โ€ข Keep theirs\n" + " โ€ข Keep both\n" + " โ€ข Write something new\n" + "4. " + bold("Remove") + " the markers\n" + "5. " + cyan("git add") + " the file\n" + "6. " + cyan("git commit") + " to finish\n\n" + dim("You're the peacemaker! ๐Ÿ•Š๏ธ"), { padding: 1, margin: { top: 1 }, borderColor: "green", } ) ); await sleep(1000); console.log( boxen( bold("Example Resolution:\n\n") + "BEFORE (conflicted):\n" + "<<<<<<< HEAD\n" + "Welcome to our site!\n" + "=======\n" + "Welcome to our awesome site!\n" + ">>>>>>> feature\n\n" + "AFTER (resolved):\n" + green("Welcome to our awesome site!") + "\n\n" + dim("Picked the better version and removed markers!"), { padding: 1, borderColor: "gray", } ) ); await sleep(1000); await printWithTypingEffect( "\n๐Ÿ’ก Pro tip: Communicate with your team to avoid conflicts!" ); }, }, { id: "push_pull", title: "Push & Pull: Sharing Your Work", content: async () => { clearScreen(); console.log( boxen(green("Sharing with the World ๐ŸŒ"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nTime to learn the dance of " + bold("push") + " and " + bold("pull") + "!" ); await sleep(700); console.log( boxen( bold("The Collaboration Dance:\n\n") + cyan("git pull") + " โ†’ Download others' changes\n" + cyan("git push") + " โ†’ Upload your changes\n\n" + "Think of it like:\n" + "โ€ข " + cyan("pull") + " = Refresh your copy\n" + "โ€ข " + cyan("push") + " = Share your work\n\n" + dim("Like syncing your phone with the cloud! โ˜๏ธ"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "cyan", } ) ); await sleep(1000); console.log( boxen( bold("Pulling Updates:\n\n") + "$ " + cyan("git pull") + "\n" + "remote: Counting objects: 5, done.\n" + "remote: Compressing objects: 100% (3/3), done.\n" + green("Updating 7a3f9c2..5b4d8e1") + "\n" + green("Fast-forward") + "\n" + " README.md | 10 " + green("+++") + red("---") + "\n" + " 1 file changed, 7 insertions(+), 3 deletions(-)\n\n" + "What happened:\n" + "โ€ข Fetched changes from origin\n" + "โ€ข Merged into your branch\n" + "โ€ข Updated your files\n\n" + dim("Always pull before starting work!"), { padding: 1, borderColor: "gray", } ) ); await sleep(1000); console.log( boxen( bold("Pushing Your Work:\n\n") + "$ " + cyan("git push") + "\n" + "Counting objects: 3, done.\n" + "Delta compression using up to 8 threads.\n" + "Compressing objects: 100% (3/3), done.\n" + "Writing objects: 100% (3/3), 328 bytes | 328.00 KiB/s, done.\n" + "To https://github.com/user/project.git\n" + " 5b4d8e1..9c7f3a2 main -> main\n\n" + green("Success!") + " Your commits are now online!\n\n" + dim("Others can now pull your changes"), { padding: 1, margin: { top: 1 }, borderColor: "green", } ) ); await sleep(1000); // Visual push/pull console.log( boxen( bold("Visual: Push and Pull Flow\n\n") + " " + green("Local") + " " + blue("GitHub") + "\n" + " โ†“ " + cyan("push") + " โ†“\n" + " โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ— โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’ โ—โ”€โ”€โ”€โ—โ”€โ”€โ”€โ—\n" + " โ†โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n" + " " + cyan("pull") + "\n\n" + dim("Keep both in sync!"), { padding: 1, borderColor: "yellow", } ) ); await sleep(1000); await printWithTypingEffect( "\n๐ŸŽฏ Golden rule: " + cyan("pull") + " before you work, " + cyan("push") + " when you're done!" ); }, }, { id: "pull_requests", title: "Pull Requests: Code Reviews", content: async () => { clearScreen(); console.log( boxen(green("Pull Requests - Teamwork at its Best ๐Ÿค"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nPull Requests (PRs) are how teams " + bold("review") + " and " + bold("discuss") + " code!" ); await sleep(700); console.log( boxen( bold("What's a Pull Request?\n\n") + "It's like saying:\n" + dim('"Hey team, I finished this feature!') + "\n" + dim('Can you review it before we add it?"') + "\n\n" + "PRs include:\n" + "โ€ข Your branch's changes\n" + "โ€ข Description of what you did\n" + "โ€ข Discussion thread\n" + "โ€ข Review feedback\n" + "โ€ข Approval process\n\n" + dim("Like submitting homework for review! ๐Ÿ“"), { padding: 1, margin: { top: 1, bottom: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("PR Workflow:\n\n") + "1. " + cyan("Create") + " feature branch\n" + "2. " + cyan("Make") + " commits\n" + "3. " + cyan("Push") + " to GitHub\n" + "4. " + cyan("Open") + " Pull Request\n" + "5. Team " + cyan("reviews") + " code\n" + "6. " + cyan("Address") + " feedback\n" + "7. " + cyan("Merge") + " when approved\n\n" + dim("Collaboration at its finest! ๐ŸŒŸ"), { padding: 1, borderColor: "cyan", } ) ); await sleep(1000); console.log( boxen( bold("Good PR Description:\n\n") + green("Title:") + " Add dark mode toggle to settings\n\n" + green("Description:") + "\n" + "## What changed?\n" + "- Added toggle switch in settings menu\n" + "- Implemented dark theme CSS\n" + "- Saved preference to localStorage\n\n" + "## Why?\n" + "Users requested dark mode for late-night coding\n\n" + "## Screenshots\n" + "[Light mode] [Dark mode]\n\n" + "## Testing\n" + "- Tested on Chrome, Firefox, Safari\n" + "- Toggle persists after refresh", { padding: 1, margin: { top: 1 }, borderColor: "green", } ) ); await sleep(1000); console.log( boxen( "๐Ÿ’ป " + bold("Terminal says:") + "\n\n" + "\"Pull Requests are where the magic happens! โœจ\n\n" + "They're not just about code - they're about:\n" + "โ€ข Learning from others\n" + "โ€ข Sharing knowledge\n" + "โ€ข Improving quality\n" + "โ€ข Building better software together\n\n" + "Don't be shy - every PR is a chance to learn!\"", { padding: 1, margin: { top: 1 }, borderColor: "cyan", borderStyle: "round", } ) ); await sleep(1000); await printWithTypingEffect( "\n๐Ÿ’ก Remember: PRs are conversations, not judgments!" ); }, }, { id: "summary", title: "Your Collaboration Toolkit", content: async () => { clearScreen(); console.log( boxen(green("Git Collaboration Reference ๐Ÿค"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nHere's your complete collaboration toolkit:" ); await sleep(700); console.log( boxen( bold("๐Ÿ“ฅ Getting Code\n") + cyan("git clone [url]") + " โ†’ Copy entire project\n" + cyan("git pull") + " โ†’ Get latest changes\n\n" + bold("๐ŸŒณ Branching\n") + cyan("git branch") + " โ†’ List branches\n" + cyan("git checkout -b name") + " โ†’ Create & switch\n" + cyan("git checkout name") + " โ†’ Switch branch\n\n" + bold("๐Ÿค Merging\n") + cyan("git merge branch") + " โ†’ Combine branches\n" + dim("(Resolve conflicts if needed)\n\n") + bold("๐Ÿ“ค Sharing\n") + cyan("git push") + " โ†’ Upload changes\n" + cyan("git push -u origin branch") + " โ†’ First push\n\n" + bold("๐Ÿ”„ Pull Requests\n") + dim("Create on GitHub/GitLab for review"), { padding: 1, margin: { top: 1 }, borderColor: "yellow", } ) ); await sleep(1000); console.log( boxen( bold("Complete Collaboration Flow:\n\n") + dim("# Clone a project\n") + "$ " + cyan("git clone https://github.com/team/project.git") + "\n" + "$ " + cyan("cd project") + "\n\n" + dim("# Create feature branch\n") + "$ " + cyan("git checkout -b feature/add-search") + "\n\n" + dim("# Make changes and commit\n") + "$ " + dim("# edit files...") + "\n" + "$ " + cyan("git add .") + "\n" + "$ " + cyan('git commit -m "Add search functionality"') + "\n\n" + dim("# Push and create PR\n") + "$ " + cyan("git push -u origin feature/add-search") + "\n" + dim("# Open PR on GitHub") + "\n\n" + dim("Team collaboration achieved! ๐ŸŽ‰"), { padding: 1, margin: { top: 1 }, borderColor: "gray", } ) ); await sleep(1000); await printWithTypingEffect( "\n๐ŸŽฏ Next: Join an open source project and make your first PR!" ); }, }, ]; // Exercises for Chapter 5 const exercises = [ { id: "remote_understanding", type: "multiple_choice", title: "๐ŸŒ Understanding Remotes", scenario: "After cloning a repository, you run " + cyan("git remote -v") + " and see:\n\n" + "origin https://github.com/coolproject/app.git (fetch)\n" + "origin https://github.com/coolproject/app.git (push)\n", question: "What does 'origin' represent?", choices: [ "Your local repository", "The default name for the remote repository you cloned from", "The original author of the code", "The main branch name", ], answer: 1, explanation: "'origin' is Git's default nickname for the remote repository you cloned from. It's like a bookmark to GitHub/GitLab. You could rename it or add more remotes if needed!", wrongAnswerHints: { 0: "That's your local repo - origin points to the remote!", 2: "Origin refers to the remote location, not a person!", 3: "That would be 'main' or 'master' - origin is the remote!", }, }, { id: "branch_status", type: "multiple_choice", title: "๐ŸŒณ Branch Detective", scenario: "You need to check which branch you're currently on before making changes.", question: "Which command shows your current branch?", choices: [ cyan("git status"), cyan("git branch --current"), cyan("git checkout"), cyan("git show-branch"), ], answer: 0, explanation: cyan("git status") + " shows your current branch at the top: 'On branch main'. You can also use " + cyan("git branch") + " which marks the current branch with an asterisk (*). Both work!", wrongAnswerHints: { 1: "Close! Try 'git branch' without --current, or something even simpler!", 2: "That's for switching branches, not checking!", 3: "That's more complex - try the simplest status command!", }, }, { id: "merge_direction", type: "multiple_choice", title: "๐Ÿค Merge Direction", scenario: "You've finished work on 'feature/login' branch and want to merge it into 'main'.", question: "What's the correct sequence of commands?", choices: [ cyan("git checkout feature/login") + " then " + cyan("git merge main"), cyan("git checkout main") + " then " + cyan("git merge feature/login"), cyan("git merge main feature/login"), cyan("git push origin feature/login"), ], answer: 1, explanation: "First " + cyan("checkout main") + " (the target branch), then " + cyan("merge feature/login") + " into it. Think: 'I want to update main, so I go there first, then bring in the feature changes.'", wrongAnswerHints: { 0: "This would merge main INTO feature - backwards!", 2: "Merge doesn't work with two branch names like this!", 3: "This pushes but doesn't merge branches locally!", }, }, { id: "conflict_resolution", type: "multiple_choice", title: "โš”๏ธ Conflict Resolution", scenario: "During a merge, you see this in a file:\n\n" + "<<<<<<< HEAD\n" + "const title = " + red('"My App"') + "\n" + "=======\n" + "const title = " + green('"Our App"') + "\n" + ">>>>>>> feature/rebrand\n", question: "After deciding to keep 'Our App', what's the next step?", choices: [ "Just save the file and push", "Delete the file and start over", "Remove all conflict markers, save, then " + cyan("git add") + " and " + cyan("git commit"), "Run " + cyan("git merge --abort") + " to cancel", ], answer: 2, explanation: "After resolving conflicts: 1) Remove ALL the conflict markers (<<<, ===, >>>), 2) Keep the code you want, 3) Save the file, 4) " + cyan("git add") + " the resolved file, 5) " + cyan("git commit") + " to complete the merge!", wrongAnswerHints: { 0: "You need to stage and commit after resolving!", 1: "Don't delete! Just clean up the markers!", 3: "That cancels the merge - but you can resolve it!", }, }, { id: "collaboration_flow", type: "type_command", title: "๐Ÿš€ Complete Collaboration", scenario: "You've cloned a repo and want to create a new feature branch called 'feature/dark-mode' and switch to it immediately.\n\n" + "What single command creates AND switches to this new branch?", question: "Type the complete command:", answer: "git checkout -b feature/dark-mode", alternates: [ "git checkout -b 'feature/dark-mode'", 'git checkout -b "feature/dark-mode"', ], hints: [ "Use git checkout with a special flag", "The -b flag means 'create branch'", "git checkout -b [branch-name]", ], explanation: "Perfect! " + cyan("git checkout -b") + " is the shortcut that both creates a new branch AND switches to it. This is much faster than doing " + cyan("git branch") + " followed by " + cyan("git checkout") + ". Now you're ready to start working on your feature!", 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 collaboration workflow for the last exercise if (exercise.showBuilder) { console.log("\n" + bold("Let's visualize the collaboration flow:")); await sleep(1000); console.log( boxen( green("COLLABORATION WORKFLOW ๐Ÿค") + "\n\n" + dim("โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€") + "\n\n" + bold("Current state:") + "\n" + "โ— You're on 'main' branch\n" + "โ— Need feature/dark-mode branch\n\n" + bold("Option 1 (Two commands):") + "\n" + dim("git branch feature/dark-mode") + "\n" + dim("git checkout feature/dark-mode") + "\n\n" + bold("Option 2 (One command - better!):") + "\n" + cyan("git checkout -b feature/dark-mode") + "\n\n" + dim("โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€") + "\n\n" + green("Result:") + "\n" + "โœ“ New branch created\n" + "โœ“ Switched to new branch\n" + "โœ“ Ready to code!", { padding: 1, margin: { top: 1, bottom: 1 }, borderStyle: "bold", borderColor: "yellow", title: "๐Ÿค Interactive Builder", titleAlignment: "center", } ) ); await sleep(2000); await printWithTypingEffect("\nNow type the efficient command!"); 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! Collaboration 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("Collaboration Practice! ๐Ÿค") + "\n\n" + "Let's test your teamwork skills with " + bold("5 exercises") + "\n\n" + dim("Remember: Git is all about working together!"), { padding: 1, borderStyle: "double", borderColor: "green", } ) ); await sleep(2000); // Quick command review clearScreen(); console.log( boxen(green("๐ŸŽฏ Collaboration Cheat Sheet"), { padding: 1, borderStyle: "round", borderColor: "green", }) ); await printWithTypingEffect( "\nYour essential collaboration commands:" ); await sleep(700); console.log( boxen( bold("The Collaboration Dance:\n\n") + "1๏ธโƒฃ " + cyan("git clone") + " โ†’ Copy project\n" + "2๏ธโƒฃ " + cyan("git checkout -b") + " โ†’ Create branch\n" + "3๏ธโƒฃ " + cyan("git add & commit") + " โ†’ Make changes\n" + "4๏ธโƒฃ " + cyan("git push") + " โ†’ Share branch\n" + "5๏ธโƒฃ " + cyan("Create PR") + " โ†’ Request review\n" + "6๏ธโƒฃ " + cyan("git pull") + " โ†’ Get updates\n" + "7๏ธโƒฃ " + cyan("git merge") + " โ†’ Combine work\n\n" + "And remember:\n" + "โ€ข Conflicts are normal - don't panic!\n" + "โ€ข Communication prevents conflicts\n" + "โ€ข Every PR is a learning opportunity\n\n" + dim("Let's practice! ๐Ÿš€"), { 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 collaboration 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 one?", 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 collaboration champion!"; } else if (percentage >= 80) { message = "๐ŸŒŸ Excellent! You're ready for team projects!"; } else if (percentage >= 60) { message = "๐Ÿ‘ Good work! Your collaboration skills are growing!"; } else { message = "๐Ÿ’ช Keep practicing! Teamwork takes time to master!"; } console.log( boxen( green("๐ŸŽ‰ Chapter 5 Complete! ๐ŸŽ‰") + "\n\n" + bold(`Your Score: ${score}/${exercises.length} (${percentage}%)`) + "\n\n" + message + "\n\n" + "Collaboration skills unlocked:\n" + "โœ… Clone repositories from GitHub/GitLab\n" + "โœ… Create and switch between branches\n" + "โœ… Merge branches together\n" + "โœ… Resolve merge conflicts\n" + "โœ… Push and pull changes\n" + "โœ… Understand pull requests\n" + "โœ… Work effectively in teams\n\n" + yellow("๐ŸŽฏ Next Challenge:") + "\n" + "Find an open source project you like and:\n" + "1. Fork and clone it\n" + "2. Create a feature branch\n" + "3. Make a small improvement\n" + "4. Submit your first pull request!\n\n" + dim("Ready for Chapter 6: Git Mastery? 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 };