CLI(Command-line interface)命令行工具。常用的git 、npm、vim、node脚本、shell脚本等都是cli工具,比如我们可以通过 git clone 等命令简单把远程代码复制到本地。可以用来制作命令行脚手架工具vue-cli、create-reate-app、angular-cli、gulp-cli等在本地快速构建开发项目。
帮你减少做重复性工作的工具,比如创建项目、打包构建上传OSS
解决痛点:
process.argv 接收命令行参数
// 输入 s-cli init create-demo
process.argv
/* [
'D:\\Program Files\\nodejs\\node.exe',
'C:\\Users\\USER\\AppData\\Roaming\\npm\\node_modules\\s-cli\\bin\\cli',
'init',
'create-demo'
] */
process.argv.slice(2) // [ 'init', 'create-name' ] 不带s-cli时
用来编写指令和处理命令行参数的(参数解析 --help其实就借助了他)
const program = require("commander");
// 定义指令
program
.version('0.0.1')
.command('init ' )
.description('Generate a new project from a template')
.action(name => { // 回调函数
console.log('init' + name);
})
.command('create ' , 'Generate a new project from a template') // 这种写法相当于把文件分离出去,执行 s-cli create demo时,找cli-create.js文件
// 解析命令行参数
program.parse(process.argv);
// 测试
s-cli -V
s-cli init demo
官方文档 使用参考
类似commander,还有minimist库,都是处理命令行参数
var argv = require('yargs').argv;
console.log('hello ', argv.name);
// 测试
s-cli --name=xiaoming // hello xiaoming
输入交互库,就是你在创建项目的时候,输入项目名,项目描述等等
const inquirer = require('inquirer');
const promptList = [{
type: "checkbox",
message: "选择颜色:",
name: "color",
choices: [
{
name: "red"
},
{
name: "blur",
checked: true // 默认选中
},
{
name: "green"
},
{
name: "yellow"
}
]
}];
inquirer.prompt(promptList).then(answers => {
console.log(answers); // 返回的结果
})
// 测试
s-cli
参考
-f:强制删除文件;
-i:删除之前先询问用户;
-r:递归处理目录;
-v:显示处理过程;
echo:在控制台输出指定内容
exit(code):以退出码为code退出当前进程
const shell = require('shelljs')
shell.rm('-rf','out/Release');//强制递归删除out/Release目录
shell.cp('-r','stuff/','out/Release');//将`stuff/`中所有内容拷贝至`out/Release`目录
shell.mv(`./ssr-with-${language}`, `./${option.appName}`) // 把移到文件
使用参考
显示loading动画
const ora = require('ora')('正在构建');
ora.start();
setTimeout(function(){
ora.succeed('构建成功');
}, 1000)
用来修改控制台输出内容样式的,比如颜色
const chalk = require('chalk')
console.log(chalk.green('Project initialization finished!')) // 绿色字
console.log(chalk.red('The project already exists')) // 红色字
const log = require("tracer").colorConsole()
log.info("模板已建立");
log.error("请填写正确的项目名称!");
const { promisify } = require('util')
const downloadWithPromise = promisify(require('download-git-repo'))
downloadWithPromise('github:sayid760/koa-react-ssr#master/example/react-ssr-js', path.resolve(__dirname, '../demo'));
const clone = require("git-clone")
var url = "https://github.com/sayid760/vue3-vite-template.git"
clone(url, process.cwd()+'/demo', null, function(){
console.log('拉取完成')
})
扩展:
Github API 接口
// RC 配置下载模板的地方,给 github 的 api 使用
https://api.github.com/users/xxx/repos // https://api.github.com/${type}/${registry}/repos
curl -u name:secret https://api.github.com/user/repos
https://api.github.com/users/xxx // xxx的用户信息,包含粉丝数
https://api.github.com/users/xxx/keys // xxx的 ssh key
https://api.github.com/users/xxx/gpg_keys // xxx的gpg
https://api.github.com/users/xxx/followers?page=2 // xxx的粉丝
https://api.github.com/users/xxx/following?page=2 // xxx关注谁
https://api.github.com/orgs/bitcoin bitcoin项目的信息
https://api.github.com/orgs/bitcoin/repos bitcoin项目的repos
https://api.github.com/repos/bitcoin/bitcoin
https://api.github.com/repos/bitcoin/bitcoin/contributors bitcoin的贡献者
官方文档
Github API 调用
如何将Github API与Node.js服务器连接
const Handlebars = require('handlebars')
var template = Handlebars.compile("Handlebars {{doesWhat}}");
console.log(template({ doesWhat: "rocks!" })); // 进行编译并输出
/*
思路:
- 将文件内容提取出来,转换为字符串
- 使用 handlebar 将模板内容字符串替换成用户输入的值
- Metalsmith build 成最终文件
- 删除下载下来的模板文件
*/
// down/index.js
'Handlebars {{doesWhat}}'
// main.js
const MetalSmith = require('metalsmith');
const Handlebars = require('handlebars')
const rm = require('rimraf').sync
const path = require('path')
MetalSmith(path.join(__dirname, './down/index.js')) // 遍历文件夹
.metadata({doesWhat: "rocks!"}) //metadata 为用户输入的内容
.source(path.join(__dirname, './down')) // 模板文件 path
.destination(path.join(__dirname, './new')) // 最终编译好的文件存放位置
.use((files, metalsmith, done) => {
Object.keys(files).forEach(fileName => {
const fileContentsString = files[fileName].contents.toString() //Handlebar compile 前需要转换为字符创
files[fileName].contents = Handlebars.compile(fileContentsString)(metalsmith.metadata())
})
done()
})
.build(function(err) { // 打包
rm(path.join(__dirname, './down')) // 删除模板文件
if (err) throw err;
console.log('Build finished!');
})
const symbol = require('log-symbols')
console.log(symbol.success) // √
console.log(symbol.error) // ×
// index.js
const fs = require('fs'), { decode, encode } = require('ini')
const config = decode.parse(fs.readFileSync('./config.ini', 'utf-8'))
/*
{
'': true,
'',
'': true,
'',
'',
'Document ': true,
'': true,
'': true,
'ddddddddddd
': true,
'': true,
'': true
}
*/
// 测试
node index.js
// package.json
"bin": {
"m-cli": "./main.js"
}
// main.js
#!/usr/bin/env node
const clone = require("git-clone")
const program = require("commander")
const shell = require("shelljs")
const log = require("tracer").colorConsole()
program
.version("0.0.1")
.description("vue项目基础模板")
.program.command("create [template]" )
.option("-tsx, --typescript", "typescript tsx template")
.action(function (project, template, cmd) {
var ts_tpl = !!cmd.typescript;
log.info(`create ${project} ${ts_tpl ? "--tsx" : ""}`);
if (project) {
var pwd = shell.pwd();
var url = "https://github.com/xxx/width-js-template.git";
if (ts_tpl)
url = "https://github.com/xxx/width-ts-template.git";
log.info("正在拉取远端模板,请稍等..");
clone(url, pwd + `/${project}`, null, function () {
shell.rm("-rf", pwd + `/${project}/.git`);
log.info("模板已建立");
});
} else {
log.error("请填写正确的项目名称!");
}
});
program.parse(process.argv);
// 输入
npm link
m-cli create vuedemo -tsx
扩展:npm link把s-cli注册到全局,然后链接到工程目录,可以直接去执行(把当前目录放到当前环境变量)
C:\Users\USER\AppData\Roaming\npm\s-cli -> C:\Users\USER\AppData\Roaming\npm\node_modules\s-cli\bin\www
C:\Users\USER\AppData\Roaming\npm\node_modules\s-cli -> C:\cli-template\s-cli
思路:
// 目录
├── bin
│ └── cli // 可执行文件
└── src
├── cache.js // 拉取模板
├── check.js // 创建文件名时判断是否已存在
├── clientRender.js // 处理dev和build的应用
├── config.js // 应用配置环节
├── init.js // init command
├── main.js // 入口文件
└── utils
├── index.js
└── webpack.js
├── package.json
├── README.md
// 1. 初始化
say-cli npm init -y
// 2. 创建bin/cli,输入
#! /usr/bin/env node // 告诉当前代码需要使用node环境来执行
require('../src/main.js'); // 一个可执行的文件
// src/main.js
console.log('hello')
// 3. package.json添加
"bin": {
"saycli": "./bin/cli" // 执行s-cli这个命令就会帮我执行这个文件
}
// 4. 输入npm link,把命令链接到全局下(解除 npm unlink)
npm link
// 5. 测试,会打印hello
saycli
// src/main.js
const yargs = require('yargs')
const {init} = require('./init')
const {processError} = require('./util')
/* 配置交互命令:输入不同的命令实现不同的内容
saycli init:输入名字,选择vue2或vue3,js或ts,根据不同的选择拉取相应模板
saycli dev:读取webpack.config.js的内容当作参数传给webpack,启动一个webpack,
使用webpack-dev-server把webpack编译后文件会输出到内存中
saycli build:使用webpack来打包
*/
yargs
.command('init [appName]', 'init the program', {}, async (argv) => {
const option = {
appName: argv.appName || 'app',
language: 'javascript',
vueVersion: 'vue2'
}
try {
await init(option)
} catch (error) {
processError(error)
}
})
.command('dev', 'start clientRender', {}, async () => {
process.env.NODE_ENV = 'development'
const { dev } = require('./clientRender')
await dev(yargs.argv)
})
.command('build', 'start clientBuild', {}, async () => {
process.env.NODE_ENV = 'production'
const { build } = require('./clientRender')
await build()
})
.demandCommand(1, 'You need at least one command before moving on')
.option('version', {
alias: 'v',
default: false
})
.parse()
// init:判断是否已存在相同文件名,选择应用版本,到github拉取模板
const answers = await inquirer.prompt([{
type: 'input',
message: '应用名称:',
name: 'appName',
default: option.appName
}, {
type: 'list',
message: 'vue版本',
name: 'vueVersion',
default: 'vue2',
choices: [
'vue2',
'vue3'
]
},
{
type: 'list',
message: '开发语言',
name: 'language',
default: 'javascript',
choices: [
'javascript',
'typescript'
],
when: function(answers) { // 当vueVersion为vue2的时候才会提问当前问题
return answers.vueVersion == 'vue2'
}
}])
// dev:使用webpack-dev-server把打包出来的内容输出到内存中,轮询刷新内容
const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')
const clientConfig = require(resolve(cwd, baseDir, './build/webpack.config'))
const compiler = webpack(clientConfig)
let server = new WebpackDevServer(compiler)
server.listen(8080)
// build:启动webpack打包
const { promisify } = require('util')
const webpack = require('webpack')
const webpackWithPromise = promisify(webpack)
const clientConfig = require(resolve(cwd, baseDir, './build/webpack.config'))
const stats = await webpackWithPromise(clientConfig)
github代码参考
npm addUser // 没有注册、有登录
npm publish // 发包、更新
npm unpublish --force // 卸包
/* 发布一个简单的包,初始化项目
mkdir npm-util
cd mkdir
npm inti -y
// index.js
module.exports = (message)=> message.toUpperCase()
*/
// 发布npm
1. 在npm官网注册一个账号,https://www.npmjs.com/signup
2. 进入需要发布的包下
3. npm login // 命令行中登录账号,需要输入用户名、密码、邮箱
4. npm publish
// 使用npm包
npm i npm-util -D
const uppercaser = require('npm-util');
console.log(uppercaser('hello world!'));
注意:发包前先去官网查看是否已有重复名的包,npm文档、包和模块
npm i -g typescript
tsc --init // 生成tsconfig.json
// tsconfig.json
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"declaration": true // 表示将要生成d.ts类型声明文件
}
}
// 编译ts文件生成成js、d.ts的类型声明文件
tsc index.ts
在package.json中声明类型文件位置:
// package.json,加入types字段
{
"name": "@penggy/mysql",
"version": "1.0.6",
"description": "Promise wrapper of mysql",
"main": "./index.js",
"types": "./index.d.ts", // 声明类型文件位置
"scripts": {
"test": "node test.js"
},
"files": [ // files 字段指定工程目录下面哪些文件需要发布到npm
"index.js",
"index.d.ts",
"README.md"
],
努力填坑中…
npm 包的版本号一般都是 x.y.z 的形式(开发包一般用 x.y.z-a)。
其中 x 表示主版本号,通常有重大改变或者达到里程碑才改变;
y 表示次要版本号,或二级版本号,在保证主体功能基本不变的情况下,如果适当增加了新功能可以更新此版本号;
z 表示尾版本号或者补丁号,一些小范围的修修补补就可以更新补丁号。
第一版本通常是 0.0.1 或者 1.0.0,当修改了代码,需要更新版本号重新发布到 npm,不知道的小伙伴肯定会手动修改 package.json 的 version 字段,而高级的玩法是直接使用 npm version <update_type> 命令自动搞定。
版本格式:主版本号.次版本号.修订号(先行版本号及版本编译元数据可以加到它的后面,作为延伸)
- 主版本号:当你做了不兼容的 API 修改
- 次版本号:当你做了向下兼容的功能性新增
- 修订号:当你做了向下兼容的问题修正
先行版本:
alpha: 内部版本
beta: 公测版本
rc: 即Release candiate,正式版本的候选版本
2.
// 当前版本 1.0.0
"version": "1.0.0"
// 升级大版本好
npm version major
// 执行后当前结果
"version": "2.0.0"