超详细新手命令行交互工具(cli)搭建教程

超详细新手命令行交互工具(cli)搭建教程

每次开发新项目需要手动把通用框架克隆至本地,再根据新项目信息来更改一些配置(项目名称, 项目描述, 项目开发人员, git远程仓库等),最后安装依赖,启动项目进行项目开发。为了减少这些重复性工作,专注于业务开发,学习了如何搭建脚手架, 在这里记录一下学习过程。

搭建思路
  • 项目初始化
  • 用户自定义项目相关信息
  • 将远程仓库的通用框架克隆至本地(克隆项目名称为用户输入的项目名称)
  • 将通用框架的package.json中的(项目名称, 项目描述, 项目作者)替换为用户自己定义的值
  • git仓库初始化
  • 安装通用框架的依赖
  • 发布npm

具体实现步骤

新建scli脚手架工程

  • 新建脚手架项目目录
  • npm init 项目初始化
  • 项目目录
├─package.json
├─util.js // 工具函数
├─bin // 脚本命令
|  ├─scli // 初始化命令
|  └scli-init // 初始化命令之后,用户与命令行交互行为

项目依赖包安装

npm install commander inquirer ora download-git-repo --save

用户自定义项目相关信息

  • package.json文件中添加bin命令
"bin": {
    "scli": "bin/scli"
  },
  • scli文件中,创建init命令,用来初始化项目。
#! /usr/bin/env node
// 解决了不同的用户node路径不同的问题,可以让系统动态的去查找node来执行你的脚本文件
const program = require('commander');

// 开始处理命令
program
  .version(require('../package.json').version) // 版本号
  .usage('<command> [options]') // 用户使用提示
  .command('init', '初始化项目') // 如果没有action 会在同目录下找x-init文件执行
  .parse(process.argv)
  • scli-init文件中,创建与用户进行交互的命令
#! /usr/bin/env node

const path = require('path');
const inquirer = require('inquirer');
const rootDir = process.cwd() || __dirname;

const prompts = [
  {
    type: 'input',
    name: 'name',
    message: '请输入项目名称',
  },
  {
    type: 'input',
    name: 'description',
    message: '请输入项目描述',
  },
  {
    type: 'input',
    name: 'author',
    message: '请输入作者',
  },
];

inquirer.prompt(prompts).then((params) => {
  // params 用户输入的内容
  // doSomething
  console.log(params);
});
  • 执行npm link,将脚手架项目链接到全局(效果同发布npm,用来本地开发), 可在任意地方执行scli init可看到项目运行情况

将远程仓库的通用框架代码克隆至本地

  • 项目名称验证。克隆项目名称为用户输入的项目名称, 所以需要对用户输入的项目名称进行验证。在scli-init文件中添加
+ const fs = require('fs');
let prompts = [
  {
    type: 'input',
    name: 'name',
    message: '请输入项目名称',
+    validate(input) {
+      if (!input) return '项目名称不能为空';
+      if (fs.existsSync(path.resolve(rootDir, input))) return '该项目名称已存在';
+      return true;
+    },
  },
];
  • 将远程仓库的通用框架代码克隆至本地。在scli-init文件中添加克隆操作
+ const { downloadGitProject } = require('../util');
+ const rootDir = process.cwd();
+ const gitPath = 'your git path'; // 通用框架git仓库地址
+ inquirer.prompt(prompts).then(async (params) => {
- inquirer.prompt(prompts).then((params) => {
-  // params 用户输入的内容
-  // doSomething
-  console.log(params);
+  const { name } = params;
+  const currentPath = path.resolve(rootDir, name);
+  await downloadGitProject(gitPath, currentPath);
});
  • util.js中添加克隆远程仓库代码函数
const ora = require('ora');
const download = require('download-git-repo');
function downloadGitProject(gitPath, targetPath) {
  const spinner = ora(`下载中……, 源地址:${gitPath}`);
  spinner.start();
  return new Promise(function (resolve, reject) {
    download(`direct:${gitPath}`, targetPath, { clone: true, }, function (err) {
      if (err) {
        spinner.color = 'red';
        spinner.fail(err);
        reject();
      } else {
        spinner.color = 'green';
        spinner.succeed('下载成功');
        resolve();
      }
    })
  });
}

将通用框架的package.json中的信息替换为用户自己定义的值

  • scli-init文件中添加替换操作
+ const { downloadGitProject, changePackageFile } = require('../util');
- const { downloadGitProject } = require('../util');
  inquirer.prompt(prompts).then(async (params) => {
   await downloadGitProject(gitPath, currentPath);
+  await changePackageFile(currentPath, params);
});
  • util.js中添加替换函数
const fs = require('fs');
async function changePackageFile(targetPath, newValues) {
  const file = `${targetPath}/package.json`;
  const spinner = ora('项目初始化中……');
  spinner.start();
  fs.readFile(file, 'utf-8', async function (err, data) {
    if (err) {
      spinner.color = 'red';
      spinner.fail(err);
      return false;
    }
    const pjson = JSON.parse(data);
    const newpjson = Object.assign({}, pjson, newValues);
    // JSON.stringify第三个参数可格式化JSON字符串
    await fs.writeFileSync(file, JSON.stringify(newpjson, null, 2));
    spinner.color = 'green';
    spinner.succeed('项目初始化');
  });
}

git仓库初始化

  • scli-init文件中添加git仓库初始化操作
+ const { downloadGitProject, changePackageFile, initGit } = require('../util');
- const { downloadGitProject, changePackageFile } = require('../util');
  inquirer.prompt(prompts).then(async (params) => {
   await changePackageFile(currentPath, params);
+  await initGit(currentPath);
});
  • util.js中添加git仓库初始化函数
const { spawn } = require('child_process');
function initGit(targetPath) {
  const spinner = ora(`正在初始化git仓库……`);
  return new Promise((resolve, reject) => {
    spinner.start();
    const ls = spawn('git', ['init'], {
      // 执行路径
      cwd: targetPath,
    });
    ls.on('error', (err) => {
      spinner.color = 'red';
      spinner.succeed('初始化git仓库失败');
      console.error(err);
      reject();
    });
    ls.on('exit', () => {
      spinner.color = 'green';
      spinner.succeed(`git仓库初始化
          
        添加远程仓库: git remote add origin path 
        推送本地代码到远程仓库: git push origin branch-name
        `);
      resolve();
    });
  });
}

安装通用框架的依赖

  • scli-init文件中添加安装依赖操作
+ const { downloadGitProject, changePackageFile, initGit, installDependencies } = require('../util');
- const { downloadGitProject, changePackageFile, initGit } = require('../util');
  inquirer.prompt(prompts).then(async (params) => {
   await initGit(currentPath);
+  installDependencies(currentPath, name);;
});
  • util.js中添加装通用框架的依赖函数
function installDependencies(targetPath, name) {
  return new Promise((resolve, reject) => {
    // 兼容windows
    const ls = spawn(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['install'], {
      cwd: targetPath,
      stdio: 'inherit', // 父子管道共用, 可以在父管道看到子管道的输出内容
    });
    ls.on('error', (err) => {
      console.error(err);
      reject();
    });;
    ls.on('exit', () => {
      console.log(`
      启动服务: cd ${name} && npm start
      打包项目:npm run build
      `);
      resolve();
    });
  });
}

发布npm

原文链接:https://juejin.cn/post/7238826935530504249 作者:ohayo_

(0)
上一篇 2023年5月31日 上午10:25
下一篇 2023年5月31日 上午10:36

相关推荐

发表回复

登录后才能评论