Build a Command Line Tool with Node.js

Build a Command Line Tool with Node.js

Introduction

Command Line Interfaces (CLI) like git, grep, and npm are our trusty companions for everyday tasks, offering unparalleled speed and efficiency. Today, let's take it a step further and learn how to create our own CLI tool.

Join me as we build a trivial quiz app from scratch, combining simplicity with sophistication. By the end, you'll have learned the art of CLI tool creation and opened up new possibilities for customized utilities!

Project Setup

For this tutorial, you will need

  1. Open the terminal (Linux/MacOS)/ command prompt (Windows), make a new directory and change the directory to the folder:

     mkdir cli-app
     cd cli-app
    
  2. Initialize a new Node.js project:

     npm init -y
    

    By executing this command, a new package.json file will be generated, enabling us to install Node dependencies for our project.

  3. Install Node dependencies:

     npm i chalk figlet inquirer
    
  4. Open the folder in your text editor and add a new file named index.js

  5. In the package.json file, include the "type": "module" entry. Your package.json file should look like this:

     {
       "name": "cli-app",
       "version": "1.0.0",
       "description": "",
       "main": "index.js",
       "type": "module",
       "scripts": {
         "test": "echo \"Error: no test specified\" && exit 1"
       },
       "keywords": [],
       "author": "",
       "license": "ISC",
       "dependencies": {
         "chalk": "^5.3.0",
         "figlet": "^1.6.0",
         "inquirer": "^9.2.8"
       }
     }
    

    By modifying the "type" field to "module" in the package.json file, we inform Node.js that we intend to employ ECMAScript Modules (ESM) syntax, enabling us to utilize import and export statements for module management.

Build the App

  1. Open the index.js file and add the "shebang":

     #!/usr/bin/env node
    

    The shebang line at the beginning of the script makes your code executable with the local Node.js version on different systems. This enables others to use the script without explicitly specifying the Node.js interpreter version.

  2. Import the libraries required for the project:

     import chalk from "chalk"
     import inquirer from "inquirer"
     import figlet from "figlet"
    

    chalk is a library that adds colors and styling to the terminal output. For example:

     console.log(chalk.blue('This text is blue!'));
     console.log(chalk.red('This text is red!'));
     console.log(chalk.bold.green('This text is bold and green!'));
    

    When the script above is executed with the command node ., it produces the following output:

    inquirer is a JavaScript library used for creating interactive command-line interfaces (CLIs). It allows developers to prompt users with questions and receive their input.

    figlet is a JavaScript library used for creating ASCII art text banners. It allows developers to convert regular text into stylized text using a variety of font styles. For example:

       _   _      _ _
      | | | |    | | |
      | |_| | ___| | | ___
      |  _  |/ _ \ | |/ _ \
      | | | |  __/ | | (_) |
      \_| |_/\___|_|_|\___/
    
  3. Create a global variable for player name and score:

     let playerName = "Player"
     let score = 0
    
  4. Now, write a function to display the welcome message:

     function displayWelcomeMessage() {
       console.clear()
       console.log(
         chalk.yellow(
           figlet.textSync("Trivial Pursuit", {
             font: "Standard",
             horizontalLayout: "default",
             verticalLayout: "default",
           })
         )
       )
    
       console.log(chalk.blue("Welcome to the Trivia Challenge!\n"))
       console.log(
         chalk.green("You will be asked a series of fun trivia questions.")
       )
       console.log(chalk.green("Answer as many as you can.\n"))
       console.log(chalk.cyan("Let the games begin!\n"))
     }
    

    console.clear(): This line clears the console before displaying the welcome message.

    console.log(chalk.yellow(figlet.textSync("Trivial Pursuit", { ... }))): This line prints the title of the game, "Trivial Pursuit," in yellow color, using a large ASCII art font from the figlet package. The options { font: "Standard", horizontalLayout: "default", verticalLayout: "default" } specify the style and layout of the ASCII art text.

    Output:

  5. Write a function to ask for the player's name:

     async function askPlayerName() {
       const answers = await inquirer.prompt({
         name: "player_name",
         type: "input",
         message: "Enter your name:",
         default() {
           return playerName
         },
       })
    
       playerName = answers.player_name
     }
    

    This function is responsible for asking the player to enter their name and updating the playerName variable with the provided name.

    the inquirer.prompt() displays a prompt to the player and wait for their response. The await keyword is used to wait for the inquirer.prompt() function to resolve, meaning it will wait for the player to enter their name before continuing to the next line of code.

    type:"input": This line specifies the type of the prompt, which is an input field. It allows the player to enter their name using the keyboard.

    message: "Enter your name:": This line provides the message that is displayed to the player when asking for their name.

  6. Create a list of questions, choices and answer for the trivial game:

     const triviaQuestions = [
       {
         question: 'Which planet is known as the "Red Planet"?',
         choices: ["Venus", "Mars", "Jupiter", "Saturn"],
         correctAnswer: "Mars",
       },
       {
         question: "What is the largest organ in the human body?",
         choices: ["Liver", "Brain", "Skin", "Heart"],
         correctAnswer: "Skin",
       },
       {
         question: "How many players are there on a standard soccer team?",
         choices: ["9", "11", "7", "10"],
         correctAnswer: "11",
       },
       {
         question: "What is the largest mammal in the world?",
         choices: ["Elephant", "Blue Whale", "Giraffe", "Hippopotamus"],
         correctAnswer: "Blue Whale",
       },
       {
         question: "Which is the largest ocean on Earth?",
         choices: [
           "Atlantic Ocean",
           "Indian Ocean",
           "Arctic Ocean",
           "Pacific Ocean",
         ],
         correctAnswer: "Pacific Ocean",
       },
     ]
    
  7. Create a function to ask a question:

     // Function to ask a trivia question
     async function askQuestion(question) {
       console.log(chalk.blue(`\n${question.question}`))
    
       const answers = await inquirer.prompt({
         name: "player_answer",
         type: "list",
         message: "Choose your answer:",
         choices: question.choices,
       })
    
       return answers.player_answer === question.correctAnswer
     }
    

    This function provides an interactive way to ask a question, obtain the player's response, and determine if their response is correct. The returned Boolean value is later used to update the player's score and provide feedback on whether the answer was right or wrong during the trivia game.

    output:

  8. Create a function to manage the player's correct answers:

     // Function to display a colorful ASCII art for correct answer feedback
     function displayCorrectFeedback() {
       console.log(
         chalk.green(
           figlet.textSync("Correct!", {
             font: "Doom",
             horizontalLayout: "default",
             verticalLayout: "default",
           })
         )
       )
       console.log(chalk.green("Great job, keep it up!\n"))
     }
    

    Output:

  9. Create a function to manage the player's incorrect answers:

     // Function to display a colorful ASCII art for incorrect answer feedback
     function displayIncorrectFeedback() {
       console.log(
         chalk.red(
           figlet.textSync("Wrong!", {
             font: "Graffiti",
             horizontalLayout: "default",
             verticalLayout: "default",
           })
         )
       )
       console.log(chalk.red("Oops, that was not the correct answer.\n"))
     }
    

    Output:

  10. Write the main function for the game:

    const sleep = (ms = 2000) => new Promise((resolve) => setTimeout(resolve, ms))
    // Main function to run the trivia game
    async function runTriviaGame() {
      displayWelcomeMessage()
      await askPlayerName()
    
      for (const question of triviaQuestions) {
        console.log(chalk.cyan(`\n${playerName}, here's your next question:`))
        const isAnswerCorrect = await askQuestion(question)
    
        if (isAnswerCorrect) {
          displayCorrectFeedback()
          score++
        } else {
          displayIncorrectFeedback()
        }
    
        await sleep(2000) // Pause for a moment before showing the next question
      }
    
      displayGameResult()
    }
    
    // Function to ask the player's name
    async function askPlayerName() {
      const answers = await inquirer.prompt({
        name: "player_name",
        type: "input",
        message: "Enter your name:",
        default() {
          return playerName
        },
      })
    
      playerName = answers.player_name
    }
    
    function displayGameResult() {
      console.clear()
      if (score === triviaQuestions.length) {
        console.log(
          chalk.yellow(
            figlet.textSync(`CONGRATS , ${playerName} !`, {
              font: "Big",
              horizontalLayout: "default",
              verticalLayout: "default",
            })
          )
        )
        console.log(chalk.green("\nYou are a Trivial Pursuit Champion! ๐Ÿ†"))
      } else {
        console.log(
          chalk.cyan(
            `${playerName}, your final score is: ${score}/${triviaQuestions.length}`
          )
        )
        console.log(
          chalk.yellow(
            figlet.textSync("Game Over!", {
              font: "Big",
              horizontalLayout: "default",
              verticalLayout: "default",
            })
          )
        )
        console.log(
          chalk.red("\nKeep learning and try again for a perfect score next time!")
        )
      }
    }
    
    // Run the trivia game 
    runTriviaGame()
    

    Here's a breakdown of what the code does:

    1. displayWelcomeMessage(): This function is called to display a welcome message and the title of the trivia game when the game starts.

    2. await askPlayerName(): This line waits for the askPlayerName function to complete before continuing. The askPlayerName function asks the player to enter their name and stores the player's name in the playerName variable.

    3. for (const question of triviaQuestions) : This loop iterates through each trivia question in the triviaQuestions array.

    4. `console.log(chalk.cyan(...))` : This line displays a message to the player before presenting each question. It includes the player's name to personalize the message.

    5. const isAnswerCorrect = await askQuestion(question): This line awaits the askQuestion function to complete before proceeding. The askQuestion function presents the current trivia question to the player, records their response, and returns a Boolean value indicating whether the response is correct or not.

    6. if (isAnswerCorrect) { ... } else { ... }: Based on whether the player's response is correct (isAnswerCorrect is true) or not (isAnswerCorrect is false), the function calls either displayCorrectFeedback() or displayIncorrectFeedback() to provide feedback to the player. It also updates the score accordingly.

    7. await sleep(2000): This line pauses the game for 2000 milliseconds (2 seconds) after each question is displayed to give the player a moment to read the feedback and prepare for the next question.

Here is the complete code for the application:

    import chalk from "chalk"
    import inquirer from "inquirer"
    import figlet from "figlet"

    // Sample trivia questions
    const triviaQuestions = [
      {
        question: 'Which planet is known as the "Red Planet"?',
        choices: ["Venus", "Mars", "Jupiter", "Saturn"],
        correctAnswer: "Mars",
      },
      {
        question: "What is the largest organ in the human body?",
        choices: ["Liver", "Brain", "Skin", "Heart"],
        correctAnswer: "Skin",
      },
      {
        question: "How many players are there on a standard soccer team?",
        choices: ["9", "11", "7", "10"],
        correctAnswer: "11",
      },
      {
        question: "What is the largest mammal in the world?",
        choices: ["Elephant", "Blue Whale", "Giraffe", "Hippopotamus"],
        correctAnswer: "Blue Whale",
      },
      {
        question: "Which is the largest ocean on Earth?",
        choices: [
          "Atlantic Ocean",
          "Indian Ocean",
          "Arctic Ocean",
          "Pacific Ocean",
        ],
        correctAnswer: "Pacific Ocean",
      },
    ]

    let playerName = "Player"
    let score = 0

    const sleep = (ms = 2000) => new Promise((resolve) => setTimeout(resolve, ms))

    // Function to display the cool welcome message with ASCII art
    function displayWelcomeMessage() {
      console.clear()
      console.log(
        chalk.yellow(
          figlet.textSync("Trivial Pursuit", {
            font: "Standard",
            horizontalLayout: "default",
            verticalLayout: "default",
          })
        )
      )

      console.log(chalk.blue("Welcome to the Trivia Challenge!\n"))
      console.log(
        chalk.green("You will be asked a series of fun trivia questions.")
      )
      console.log(chalk.green("Answer as many as you can.\n"))
      console.log(chalk.cyan("Let the games begin!\n"))
    }

    // Function to display a colorful ASCII art for correct answer feedback
    function displayCorrectFeedback() {
      console.log(
        chalk.green(
          figlet.textSync("Correct!", {
            font: "Doom",
            horizontalLayout: "default",
            verticalLayout: "default",
          })
        )
      )
      console.log(chalk.green("Great job, keep it up!\n"))
    }

    // Function to display a colorful ASCII art for incorrect answer feedback
    function displayIncorrectFeedback() {
      console.log(
        chalk.red(
          figlet.textSync("Wrong!", {
            font: "Graffiti",
            horizontalLayout: "default",
            verticalLayout: "default",
          })
        )
      )
      console.log(chalk.red("Oops, that was not the correct answer.\n"))
    }

    // Function to ask a trivia question
    async function askQuestion(question) {
      console.log(chalk.blue(`\n${question.question}`))

      const answers = await inquirer.prompt({
        name: "player_answer",
        type: "list",
        message: "Choose your answer:",
        choices: question.choices,
      })

      return answers.player_answer === question.correctAnswer
    }

    // Main function to run the trivia game
    async function runTriviaGame() {
      displayWelcomeMessage()
      await askPlayerName()

      for (const question of triviaQuestions) {
        console.log(chalk.cyan(`\n${playerName}, here's your next question:`))
        const isAnswerCorrect = await askQuestion(question)

        if (isAnswerCorrect) {
          displayCorrectFeedback()
          score++
        } else {
          displayIncorrectFeedback()
        }

        await sleep(2000) // Pause for a moment before showing the next question
      }

      displayGameResult()
    }

    // Function to ask the player's name
    async function askPlayerName() {
      const answers = await inquirer.prompt({
        name: "player_name",
        type: "input",
        message: "Enter your name:",
        default() {
          return playerName
        },
      })

      playerName = answers.player_name
    }

    function displayGameResult() {
      console.clear()
      if (score === triviaQuestions.length) {
        console.log(
          chalk.yellow(
            figlet.textSync(`CONGRATS , ${playerName} !`, {
              font: "Big",
              horizontalLayout: "default",
              verticalLayout: "default",
            })
          )
        )
        console.log(chalk.green("\nYou are a Trivial Pursuit Champion! ๐Ÿ†"))
      } else {
        console.log(
          chalk.cyan(
            `${playerName}, your final score is: ${score}/${triviaQuestions.length}`
          )
        )
        console.log(
          chalk.yellow(
            figlet.textSync("Game Over!", {
              font: "Big",
              horizontalLayout: "default",
              verticalLayout: "default",
            })
          )
        )
        console.log(
          chalk.red("\nKeep learning and try again for a perfect score next time!")
        )
      }
    }

    // Run the trivia game with top-level await
    runTriviaGame()

Conclusion

Congratulations! You've just built a simple Trivial Pursuit game using Node.js, inquirer, chalk, and figlet. Feel free to expand the game with more questions, and new features. Trivia games are always a hit with friends and family, so share your creation and have fun challenging each other's knowledge!