超详细新手命令行交互工具(cli)搭建教程
每次开发新项目需要手动把通用框架克隆至本地,再根据新项目信息来更改一些配置(项目名称, 项目描述, 项目开发人员, git远程仓库等),最后安装依赖,启动项目进行项目开发。为了减少这些重复性工作,专注于业务开发,学习了如何搭建脚手架, 在这里记录一下学习过程。
搭建思路
- 项目初始化
- 用户自定义项目相关信息
- 将远程仓库的通用框架克隆至本地(克隆项目名称为用户输入的项目名称)
- 将通用框架的
package.json
中的(项目名称, 项目描述, 项目作者)替换为用户自己定义的值 - git仓库初始化
- 安装通用框架的依赖
- 发布npm
具体实现步骤
新建scli脚手架工程
- 新建脚手架项目目录
npm init
项目初始化- 项目目录
├─package.json
├─util.js // 工具函数
├─bin // 脚本命令
| ├─scli // 初始化命令
| └scli-init // 初始化命令之后,用户与命令行交互行为
项目依赖包安装
- commander 创建命令行交互
- inquirer 用户与命令行交互,获取用户自定义项目相关信息
- ora 美化命令行提示效果
- download-git-repo 下载git仓库代码
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_