如何使用 NodeJS 构建 CLI 💻 先决条件 设置项目 构建 CLI 安装和使用 Inquirer 添加逻辑 使用 Chalk 美化 将其发布到 npm 🚀 结束

2025-06-08

如何使用 NodeJS 构建 CLI

先决条件

设置项目

构建 CLI

安装和使用 Inquirer

添加逻辑

用粉笔美化

将其发布到 npm

结束

如何使用 NodeJS 构建 CLI

CLI(命令行界面)是人类迄今为止创建的最基础、最强大的应用程序之一。我们每天都在使用 CLI,无论是 npm、git 还是其他任何 CLI。你的日常工作流程中是否有一些需要反复执行的操作?🤔。很有可能,这些操作都可以使用 CLI 实现自动化✨

那么让我们开始吧🏄

今天我们将构建一个 CLI,它将生成预装了 TailwindCSS、ESLint 和 Prettier 的入门模板。

先决条件

以下是您在学习本教程时需要用到的一些工具:

  1. 安装了NodeJS的 LTS(长期支持)版本
  2. 文本编辑器。

设置项目

让我们初始化一个 NodeJS 项目

  1. 打开你的终端
  2. 为您的项目创建一个文件夹
mkdir tailwindcli
Enter fullscreen mode Exit fullscreen mode
  1. 导航至
cd tailwindcli
Enter fullscreen mode Exit fullscreen mode
  1. 初始化 NodeJS 项目
npm init
Enter fullscreen mode Exit fullscreen mode

构建 CLI

现在我们已经准备好 NodeJS 设置了。让我们开始构建 CLI

  1. bin在项目文件夹的根目录中创建一个名为的文件夹。
  2. index.js在文件夹中创建一个名为 的文件bin。这将是 CLI 的主文件。
  3. 现在打开package.json文件并将键的值更改main./bin/index.js
  4. package.json现在在文件中添加一个条目bin,并将其键设置为,tcli并将其值设置为./bin/index.js

该词tcli是我们用来调用 CLI 的关键字。

进行更改后,package.json文件应如下所示:

{
  "name": "tailwindcli",
  "version": "1.0.0",
  "description": "A CLI for generating starter files with TailwindCSS pre-installed",
  "main": "./bin/index.js",
  "bin": {
    "tcli": "./bin/index.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": ["cli", "tailwindcss", "nodejs"],
  "author": "Your name",
  "license": "MIT"
}
Enter fullscreen mode Exit fullscreen mode
  1. 打开bin/index.js文件并在文件顶部添加此行
#! /usr/bin/env node
Enter fullscreen mode Exit fullscreen mode

以 开头的行#!称为 Shebang 行。Shebang 行用于向将运行以下代码的解释器提供绝对路径。

让我们添加一些 JS 代码,以便我们可以测试 CLI 🚀。

  1. 添加一些 JS 代码
console.log('The CLI is working 🚀');
Enter fullscreen mode Exit fullscreen mode
  1. 安装并测试 CLI

CLI 可以在系统的任何位置调用,因此我们使用以下命令进行全局安装

npm install -g .
Enter fullscreen mode Exit fullscreen mode

让我们通过运行tcli命令来测试我们的 CLI。

🎉 Tada,我们的 CLI 正在运行

安装和使用 Inquirer

Inquirer 是一个用于创建交互式 CLI 界面的软件包。例如:

要安装,请运行以下命令

npm install inquirer
Enter fullscreen mode Exit fullscreen mode

添加查询者的样板

这是查询者的样板

#! /usr/bin/env node

const inquirer = require('inquirer');

inquirer
  .prompt([
    /* Pass your questions in here */
  ])
  .then((answers) => {
    // Use user feedback for... whatever!!
  });
Enter fullscreen mode Exit fullscreen mode

添加问题

我们必须将问题作为对象传递。让我们添加第一个关于 JS 框架的问题。

#! /usr/bin/env node

const inquirer = require('inquirer');

inquirer
  .prompt([
    {
      type: 'list',
      name: 'framework',
      message: 'Choose the JS framework which you are using:',
      choices: ['React', 'NextJS', 'Angular', 'Svelte', 'VueJS'],
    },
  ])
  .then((answers) => {});
Enter fullscreen mode Exit fullscreen mode

让我们分解一下,了解每个部分的含义

  • type:Inquirer 目前有 9 种不同的 CLI 用户界面。




  • name:查询器以对象的形式返回答案。例如:

    • 如果我们添加console.log(answers);,那么我们会得到这样的结果

所以这里namekey对象的

  • message:这是向用户显示的问题
  • choices:这些是提供给用户的选项

清理代码库[可选]

bin我们可以在名为 的文件夹中创建一个文件夹,并在名为 的文件夹utils中创建一个文件。我们可以在其中存储问题并将其导入到文件中utilsquestions.jsquestions.jsindex.js

utils/questions.js

// This question would be shown at the starting
const questions = [
  {
    type: 'list',
    name: 'framework',
    message: 'Choose the JS framework which you are using:',
    choices: ['React', 'NextJS', 'Angular', 'Svelte', 'VueJS'],
  },
];

// This question would be shown only when the user choose either React or NextJS
const questionsTs = [
  {
    type: 'list',
    name: 'typescript',
    message: 'Does your project use TypeScript?',
    choices: ['Yes', 'No'],
  },
];

module.exports.questions = questions;
module.exports.questionsTs = questionsTs;
Enter fullscreen mode Exit fullscreen mode

index.js

#! /usr/bin/env node

const inquirer = require('inquirer');

const { questions, questionsTs } = require('./utils/questions.js');

inquirer.prompt(questions).then((answers) => {
  // Use user feedback for... whatever!!
});
Enter fullscreen mode Exit fullscreen mode

添加逻辑

在我们创建问题时,是时候添加一些逻辑了。

访问问题的答案类似于从对象中访问键的值。特定问题的答案的值是answers.<name-of-the-question>

在创建启动文件时,让我们使用ShellJS运行以下命令git clonemkdir...

安装 ShellJS

要安装 ShellJS,请运行以下命令

npm install shelljs
Enter fullscreen mode Exit fullscreen mode

使用 ShellJS

让我们添加一些 if 和 else 块来实现逻辑

#! /usr/bin/env node

const inquirer = require('inquirer');
const shell = require('shelljs');

const { questions, questionsTs } = require('./utils/questions.js');

inquirer.prompt(questions).then((answers) => {
  if (answers.framework === 'React') {
    inquirer.prompt(questionsTs).then((answersTs) => {
      if (answersTs.typescript === 'Yes') {
        // If the user has choosen React and want to use TypeScript
      } else {
        // If the user has choosen React but doesn't want to use TypeScript
      }
    });
  } else if (answers.framework === 'NextJS') {
    inquirer.prompt(questionsTs).then((answersTs) => {
      if (answersTs.typescript === 'Yes') {
        // If the user has choosen NextJS and want to use TypeScript
      } else {
        // If the user has choosen NextJS but doesn't want to use TypeScript
      }
    });
  else if (answers.framework === 'Svelte') {
    // If the user has choosen Svelte
  } else {
    // If the user has choosen VueJS
  }
});
Enter fullscreen mode Exit fullscreen mode

让我们找到一些与 TailwindCSS 集成的 JS 框架的模板

非常感谢那些为社区制作这些精彩模板的优秀人士✨

要运行git clone命令,请使用 ShellJS 我们刚刚使用的exec方法

shell.exec('git clone <repo-link>');
Enter fullscreen mode Exit fullscreen mode

现在让我们填写 if 和 else 块

#! /usr/bin/env node

const inquirer = require('inquirer');
const shell = require('shelljs');

const path = process.cwd();

const { questions, questionsTs } = require('./utils/questions.js');

inquirer.prompt(questions).then((answers) => {
  if (answers.framework === 'React') {
    inquirer.prompt(questionsTs).then((answersTs) => {
      if (answersTs.typescript === 'Yes') {
        shell.exec(`mkdir ${answers.projectName}`);
        shell.exec(
          `git clone https://github.com/GKaszewski/react-tailwind-typescript-template ${answers.projectName}`
        );
        shell.cd(`${path}/${answers.projectName}`);
        shell.exec(`npm i`);
        console.log(
          '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
        );
      } else {
        shell.exec(`mkdir ${answers.projectName}`);
        shell.exec(
          `git clone https://github.com/YashKumarVerma/react-tailwind-template ${answers.projectName}`
        );
        console.log('🛠️  Successfully build the required files');
        shell.cd(`${path}/${answers.projectName}`);
        shell.exec(`npm i`);
        console.log(
          '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
        );
      }
    });
  } else if (answers.framework === 'NextJS') {
    inquirer.prompt(questionsTs).then((answersTs) => {
      if (answersTs.typescript === 'Yes') {
        shell.exec(`mkdir ${answers.projectName}`);
        shell.exec(
          `git clone https://github.com/avneesh0612/next-starter ${answers.projectName}`
        );
        shell.cd(`${path}/${answers.projectName}`);
        shell.exec(`npm i`);
        console.log(
          '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
        );
      } else {
        shell.exec(`mkdir ${answers.projectName}`);
        shell.exec(
          `git clone https://github.com/Neeraj1005/Nextjs-tailwind-template ${answers.projectName}`
        );
        shell.cd(`${path}/${answers.projectName}`);
        shell.exec(`npm i`);
        console.log(
          '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
        );
      }
    });
  } else if (answers.framework === 'Svelte') {
    shell.exec(`mkdir ${answers.projectName}`);
    shell.exec(
      `git clone https://github.com/jhanca-vm/Svelte-Tailwind ${answers.projectName}`
    );
    shell.cd(`${path}/${answers.projectName}`);
    shell.exec(`npm i`);
    console.log(
      '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
    );
  } else {
    shell.exec(`mkdir ${answers.projectName}`);
    shell.exec(
      `git clone https://github.com/web2033/vite-vue3-tailwind-starter ${answers.projectName}`
    );
    shell.cd(`${path}/${answers.projectName}`);
    shell.exec(`npm i`);
    console.log(
      '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
    );
  }
});
Enter fullscreen mode Exit fullscreen mode

清理代码库[可选]

让我们在utils名为 的文件夹中创建一个名为 的新文件links.js。让我们创建一个哈希表,用于存储模板存储库的 GitHub 存储库链接。

let links = new Map([
  ['React', 'https://github.com/YashKumarVerma/react-tailwind-template'],
  [
    'React-TS',
    'https://github.com/GKaszewski/react-tailwind-typescript-template',
  ],
  ['NextJS', 'https://github.com/Neeraj1005/Nextjs-tailwind-template'],
  ['NextJS-TS', 'https://github.com/avneesh0612/next-starter'],
  ['Svelte', 'https://github.com/jhanca-vm/Svelte-Tailwind'],
  ['Vue', 'https://github.com/web2033/vite-vue3-tailwind-starter'],
]);

module.exports = links;
Enter fullscreen mode Exit fullscreen mode

让我们导入utils/index.js并替换 GitHub 模板存储库链接。

#! /usr/bin/env node

const inquirer = require('inquirer');
const shell = require('shelljs');

const path = process.cwd();

const { questions, questionsTs } = require('./utils/questions.js');
const links = require('./utils/links.js');

inquirer.prompt(questions).then((answers) => {
  if (answers.framework === 'React') {
    inquirer.prompt(questionsTs).then((answersTs) => {
      if (answersTs.typescript === 'Yes') {
        shell.exec(`mkdir ${answers.projectName}`);
        console.log('📁 Created a folder for the project');
        shell.exec(`git clone ${links.get('React-TS')} ${answers.projectName}`);
        console.log(`🖨️  Cloned started files into ${answers.projectName}`);
        shell.cd(`${path}/${answers.projectName}`);
        shell.exec(`npm i`);
        console.log(
          '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
        );
      } else {
        shell.exec(`mkdir ${answers.projectName}`);
        console.log('📁 Created a folder for the project');
        shell.exec(`git clone ${links.get('React')} ${answers.projectName}`);
        console.log(`🖨️  Cloned started files into ${answers.projectName}`);
        shell.cd(`${path}/${answers.projectName}`);
        shell.exec(`npm i`);
        console.log(
          '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
        );
      }
    });
  } else if (answers.framework === 'NextJS') {
    inquirer.prompt(questionsTs).then((answersTs) => {
      if (answersTs.typescript === 'Yes') {
        shell.exec(`mkdir ${answers.projectName}`);
        console.log('📁 Created a folder for the project');
        shell.exec(
          `git clone ${links.get('NextJS-TS')} ${answers.projectName}`
        );
        console.log(`🖨️  Cloned started files into ${answers.projectName}`);
        shell.cd(`${path}/${answers.projectName}`);
        shell.exec(`npm i`);
        console.log(
          '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
        );
      } else {
        shell.exec(`mkdir ${answers.projectName}`);
        console.log('📁 Created a folder for the project');
        shell.exec(`git clone ${links.get('NextJS')} ${answers.projectName}`);
        console.log(`🖨️  Cloned started files into ${answers.projectName}`);
        shell.cd(`${path}/${answers.projectName}`);
        shell.exec(`npm i`);
        console.log(
          '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
        );
      }
    });
  } else if (answers.framework === 'Svelte') {
    shell.exec(`mkdir ${answers.projectName}`);
    console.log('📁 Created a folder for the project');
    shell.exec(`git clone ${links.get('Svelte')} ${answers.projectName}`);
    console.log(`🖨️  Cloned started files into ${answers.projectName}`);
    shell.cd(`${path}/${answers.projectName}`);
    shell.exec(`npm i`);
    console.log(
      '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
    );
  } else {
    shell.exec(`mkdir ${answers.projectName}`);
    console.log('📁 Created a folder for the project');
    shell.exec(`git clone ${links.get('Vue')} ${answers.projectName}`);
    console.log(`🖨️  Cloned started files into ${answers.projectName}`);
    shell.cd(`${path}/${answers.projectName}`);
    shell.exec(`npm i`);
    console.log(
      '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
    );
  }
});
Enter fullscreen mode Exit fullscreen mode

用粉笔美化

我们用粉笔给文本添加颜色

要安装 chalk,请使用以下命令:

npm install chalk
Enter fullscreen mode Exit fullscreen mode

现在让我们将粉笔导入到我们的index.js文件中

const chalk = require('chalk');
Enter fullscreen mode Exit fullscreen mode

粉笔有几种预先建立的颜色方法

Chalk 还提供了一种hex方法,通过它你可以使用任何颜色

让我们为成功输出添加绿色

console.log(chalk.green('Hey 👀, I am a green colored text')); // This is how we can add colors by using chalk
Enter fullscreen mode Exit fullscreen mode
#! /usr/bin/env node

const inquirer = require('inquirer');
const shell = require('shelljs');
const chalk = require('chalk');

const path = process.cwd();

const { questions, questionsTs } = require('./utils/questions.js');
const links = require('./utils/links.js');

inquirer.prompt(questions).then((answers) => {
  if (answers.framework === 'React') {
    inquirer.prompt(questionsTs).then((answersTs) => {
      if (answersTs.typescript === 'Yes') {
        shell.exec(`mkdir ${answers.projectName}`);
        console.log(chalk.green('📁 Created a folder for the project'));
        shell.exec(`git clone ${links.get('React-TS')} ${answers.projectName}`);
        console.log(
          chalk.green(`🖨️  Cloned started files into ${answers.projectName}`)
        );
        shell.cd(`${path}/${answers.projectName}`);
        shell.exec(`npm i`);
        console.log(
          chalk.green(
            '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
          )
        );
      } else {
        shell.exec(`mkdir ${answers.projectName}`);
        console.log(chalk.green('📁 Created a folder for the project'));
        shell.exec(`git clone ${links.get('React')} ${answers.projectName}`);
        console.log(
          chalk.green(`🖨️  Cloned started files into ${answers.projectName}`)
        );
        shell.cd(`${path}/${answers.projectName}`);
        shell.exec(`npm i`);
        console.log(
          chalk.green(
            '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
          )
        );
      }
    });
  } else if (answers.framework === 'NextJS') {
    inquirer.prompt(questionsTs).then((answersTs) => {
      if (answersTs.typescript === 'Yes') {
        shell.exec(`mkdir ${answers.projectName}`);
        console.log(chalk.green('📁 Created a folder for the project'));
        shell.exec(
          `git clone ${links.get('NextJS-TS')} ${answers.projectName}`
        );
        console.log(
          chalk.green(`🖨️  Cloned started files into ${answers.projectName}`)
        );
        shell.cd(`${path}/${answers.projectName}`);
        shell.exec(`npm i`);
        console.log(
          chalk.green(
            '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
          )
        );
      } else {
        shell.exec(`mkdir ${answers.projectName}`);
        console.log(chalk.green('📁 Created a folder for the project'));
        shell.exec(`git clone ${links.get('NextJS')} ${answers.projectName}`);
        console.log(
          chalk.green(`🖨️  Cloned started files into ${answers.projectName}`)
        );
        shell.cd(`${path}/${answers.projectName}`);
        shell.exec(`npm i`);
        console.log(
          chalk.green(
            '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
          )
        );
      }
    });
  } else if (answers.framework === 'Svelte') {
    shell.exec(`mkdir ${answers.projectName}`);
    console.log(chalk.green('📁 Created a folder for the project'));
    shell.exec(`git clone ${links.get('Svelte')} ${answers.projectName}`);
    console.log(
      chalk.green(`🖨️  Cloned started files into ${answers.projectName}`)
    );
    shell.cd(`${path}/${answers.projectName}`);
    shell.exec(`npm i`);
    console.log(
      chalk.green(
        '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
      )
    );
  } else {
    shell.exec(`mkdir ${answers.projectName}`);
    console.log(chalk.green('📁 Created a folder for the project'));
    shell.exec(`git clone ${links.get('Vue')} ${answers.projectName}`);
    console.log(
      chalk.green(`🖨️  Cloned started files into ${answers.projectName}`)
    );
    shell.cd(`${path}/${answers.projectName}`);
    shell.exec(`npm i`);
    console.log(
      chalk.green(
        '👨‍💻  Successfully installed all the required dependencies\nHappy hacking 🚀'
      )
    );
  }
});
Enter fullscreen mode Exit fullscreen mode

将其发布到 npm

我们已经成功构建了 CLI 🥳。现在让我们将它部署到 npm,以便其他开发者可以使用我们的 CLI。

创建 npm 帐户

前往npmjs.org创建一个帐户,并确保你也验证了它

唯一的包名称

npm 软件包的名称都是独一无二的。npm 不允许发布名称已被占用的软件包。请访问npmjs.org并检查您的软件包名称是否已被占用。

tailwindcli已被包占用。因此我必须将其名称更改为tailwindcsscli

更改包的名称

如果您的包裹是唯一的并且未被占用,请跳过此步骤,如果不是,则请按照此步骤操作。

  1. 打开package.json文件
  2. 将键的值更改name为唯一名称,在本例中,我将其更改为tailwindcsscli

添加关键字

让我们添加一些与我们的包相关的关键字。由于我们在本教程中构建了一个 CLI,因此我们将以下内容作为关键字:

  • 命令行
  • 尾风CSS
  • nodejs

添加许可证

请查看license-templates GitHub 仓库,获取可在项目中使用的许可证模板。我使用的是 MIT 许可证

添加存储库链接

如果你在任何 Git 提供商(例如 GitHub、GitLab)上有一个仓库,你可以在一个新条目中链接到该仓库,repository该条目的键名为typeurl,值分别为gitgit+<your-git-repo-link>.git。它看起来像这样

"repository": {
  "type": "git",
  "url": "git+<your-git-repo-link>.git"
}
Enter fullscreen mode Exit fullscreen mode

就我而言,repo 链接是https://github.com/Kira272921/tailwindcsscli。因此它看起来像这样

"repository": {
  "type": "git",
  "url": "git+https://github.com/Kira272921/tailwindcsscli.git"
}
Enter fullscreen mode Exit fullscreen mode

添加错误报告链接

让我们添加一个指向用户报告我们软件包 bug 的站点/地方的链接。通常,它会是 GitHub 仓库中问题页面的链接。

"bugs": {
  "url": "https://github.com/Kira272921/tailwindcsscli/issues"
}
Enter fullscreen mode Exit fullscreen mode

添加主页链接

让我们添加指向 npm 包主页的链接。通常,它会指向 GitHub 仓库的 README 链接。

"homepage": "https://github.com/Kira272921/tailwindcsscli/issues#readme"
Enter fullscreen mode Exit fullscreen mode

通过 npm CLI 登录你的 npm 帐户

现在让我们通过 npm CLI 登录我们的 npm 帐户,以便将包发布到 npm。要登录您的 npm 帐户,请运行以下命令并输入正确的凭据。

npm login
Enter fullscreen mode Exit fullscreen mode

发布你的 npm 包

现在让我们使用以下命令发布我们的 npm 包

npm publish
Enter fullscreen mode Exit fullscreen mode

😱 哦不!我收到错误信息

让我们相应地更改包的名称,然后使用推荐的命令发布。我的包package.json现在看起来像这样

{
  "name": "@kira272921/tailwindcsscli",
  "version": "1.0.0",
  "description": "A CLI for generating starter files for different JS frameworks with tailwindCSS pre-installed",
  "main": "./bin/index.js",
  "bin": {
    "tcli": "./bin/index.js"
  },
  "scripts": {
    "start": "node ./bin/index.js"
  },
  "keywords": ["cli", "tailwindcss", "nodejs"],
  "author": "Kira272921",
  "license": "MIT",
  "dependencies": {
    "inquirer": "^8.2.0",
    "shelljs": "^0.8.4"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/Kira272921/tailwindcsscli.git"
  },
  "bugs": {
    "url": "https://github.com/Kira272921/tailwindcsscli/issues"
  },
  "homepage": "https://github.com/Kira272921/tailwindcsscli/issues#readme"
}
Enter fullscreen mode Exit fullscreen mode

现在让我们尝试使用以下命令再次发布它

npm publish --access=public
Enter fullscreen mode Exit fullscreen mode

祈祷一切顺利🤞。耶!我们的 CLI 成功发布到 npm 了🥳

结束

本教程的代码可在 Github 上找到
https://github.com/Kira272921/tailwindcsscli

这篇博文就到这里啦,希望大家能从这篇博文中学到一些新东西。下篇博文再见👋。

鏂囩珷鏉ユ簮锛�https://dev.to/byteslash/how-to-build-a-cli-using-nodejs-4bl2
PREV
适用于 Vue 或 React 的 Electron 入门代码生成器 Elecrue 是什么?如何安装 Elecure?如何使用?常见问题解答
NEXT
字节大小:编码历史的味道(即将推出!)