提起 CLI
,不禁得會想起 vue-cli
和 angular-cli
,它們都是基於 Node
的命令行工具。javascript
CLI
假設你如今要創建一個新項目 ,這個項目配置和以前的項目配置是同樣的。在你沒有 CLI
的時候,你只能經過複製、粘貼來進行。然而,當你有了 CLI
,你就能夠經過命令來完成這些步驟。固然,你能夠說就新建一個項目,徹底不必再開發一個 CLI
工具。那若是你要新建 n 個項目呢?這個時候,有 CLI
和沒有 CLI
的區別就體現出來了。html
CLI
開發一個 CLI
,須要用到如下工具:vue
新建一個文件夾,名稱起作 demo-cli
,並在文件夾內 npm init
。在 demo-cli
文件夾內,新建 bin
文件夾,並在該文件夾內新建 index.js
文件。緊接着,打開 demo-cli
文件夾內的 package.json
文件,在裏面新增以下命令。java
{
"bin": {
"demo": "./bin/index.js"
}
}
複製代碼
這句代碼的意思是指,在你使用 demo
命令的時候,會去執行 bin
文件夾下的 index.js
文件。node
這時候,咱們在 index.js
文件,寫入如下代碼。git
#!/usr/bin/env node
console.log('hello CLI');
複製代碼
在 demo-cli
目錄下依次運行 npm link
、demo
,這個時候,你會發現控制檯輸出了 hello CLI
。github
備註:vue-cli
#!/usr/bin/env node
告訴操做系統用Node
來運行此文件npm link
做用主要是,在開發npm
模塊的時候,咱們會但願邊開發邊調試。這個時候,npm link
就派上用場了。
index.js
文件內,寫入如下代碼。#!/usr/bin/env node
const program = require('commander');
program
.version('1.0.0', '-v, --version')
.command('init <dir>', 'generate a new project')
.parse(process.argv);
複製代碼
commander
提供了一種使用 node.js
來開發命令行的可能性。咱們能夠經過 commander
的 option
方法,來定義 commander
的選項,固然,這些定義的選項也會被做爲該命令的幫助文檔。shell
version
:用來定義版本號。commander
默認幫咱們添加 -V, --version
選項。固然,咱們也能夠重設它。command
:<>
表明必填,[]
表明選填。當 .command()
帶有描述參數時,不能採用 .action(callback)
來處理子命令,不然會出錯。這告訴 commander
,你將採用單獨的可執行文件做爲子命令。parse
:解析 process.argv
,解析完成後的數據會存放到 new Command().args
數組中。process.argv
裏面存儲內容以下:program.args[0]
來取出
dir
的值。
問題:爲何當
command
沒有描述參數,且parse
方法使用鏈式調用會報錯?(猜測:command
有desc
參數時,返回的是this
,當沒有desc
參數時,返回的是新對象,根據API Document
得出)npm
```js
// 正確
program
.version('1.0.0', '-v, --version')
.command('init <dir>', 'generate a new project')
.action(function(dir, cmd){
console.log(dir, cmd)
})
.parse(process.argv);
// 正確
program
.version('1.0.0', '-v, --version')
.command('init <dir>', 'generate a new project')
.action(function(dir, cmd){
console.log(dir, cmd)
})
program.parse(process.argv);
// 正確
program
.version('1.0.0', '-v, --version')
.command('init <dir>')
.action(function(dir, cmd){
console.log(dir, cmd)
})
program.parse(process.argv);
// 錯誤
program
.version('1.0.0', '-v, --version')
.command('init <dir>')
.action(function(dir, cmd){
console.log(dir, cmd)
})
.parse(process.argv);
```
複製代碼
bin
文件下建立 demo-init.js
文件,部分代碼以下:#!/usr/bin/env node
const shell = require('shelljs');
const program = require('commander');
const inquirer = require('inquirer');
const download = require('download-git-repo');
const ora = require('ora');
const fs = require('fs');
const path = require('path');
const spinner = ora();
program.parse(process.argv);
let dir = program.args[0];
const questions = [{
type: 'input',
name: 'name',
message: '請輸入項目名稱',
default: 'demo-static',
validate: (name)=>{
if(/^[a-z]+/.test(name)){
return true;
}else{
return '項目名稱必須以小寫字母開頭';
}
}
}]
inquirer.prompt(questions).then((answers)=>{
// 初始化模板文件
downloadTemplate(answers);
})
function downloadTemplate(params){
spinner.start('loading');
let isHasDir = fs.existsSync(path.resolve(dir));
if(isHasDir){
spinner.fail('當前目錄已存在!');
return false;
}
// 開始下載模板文件
download('gitlab:git.gitlab.com/demo-static', dir, {clone: true}, function(err){
if(err){
spinner.fail(err);
};
updateTemplateFile(params);
})
}
function updateTemplateFile(params){
let { name, description } = params;
fs.readFile(`${path.resolve(dir)}/public/package.json`, (err, buffer)=>{
if(err) {
console.log(chalk.red(err));
return false;
}
shell.rm('-f', `${path.resolve(dir)}/.git`);
shell.rm('-f', `${path.resolve(dir)}/public/CHANGELOG.md`);
let packageJson = JSON.parse(buffer);
Object.assign(packageJson, params);
fs.writeFileSync(`${path.resolve(dir)}/public/package.json`, JSON.stringify(packageJson, null, 2));
fs.writeFileSync(`${path.resolve(dir)}/README.md`, `# ${name}\n> ${description}`);
spinner.succeed('建立完畢');
});
}
複製代碼
inquirer
主要提供交互式命令的功能。validate
返回 true
表明輸入值驗證合法,若是返回任意字符串,則會替代默認的錯誤消息返回。Node
中 fs
模塊來判斷文件夾是否已存在。
path.resolve
方法用於將相對路徑轉爲絕對路徑。它能夠接受多個參數,依次表示所要進入的路徑,直到將最後一個參數轉爲絕對路徑。若是根據參數沒法獲得絕對路徑,就以當前所在路徑做爲基準。除了根目錄,該方法的返回值都不帶尾部的斜槓。