Node.js除了可以編寫“傳統(tǒng)“的Web應(yīng)用外沐兰,還有其他更廣泛的用途哆档。微服務(wù)、REST API
住闯、工具瓜浸、物聯(lián)網(wǎng)澳淑,甚至桌面應(yīng)用,它能滿足你的任何開發(fā)需求插佛。
本文要做的事情就是利用Node.js
來構(gòu)建命令行工具CLI
杠巡。我們先來看一些用于創(chuàng)建命令行的第三方npm包
,然后雇寇,從零開始構(gòu)建命令行工具氢拥。
我們將要實現(xiàn)一個命令行工具,它的作用是初始化Git倉庫锨侯。當(dāng)然嫩海,它不僅僅是在后臺運行git init
,他還會做一些別的事情囚痴。我們可以通過它來初始化Git倉庫叁怪,并且允許用戶通過交互的方式創(chuàng)建.gitignore
文件,最終執(zhí)行提交并推送代碼到遠(yuǎn)端倉庫深滚。
與以往一樣奕谭,大家可以在GitHub(https://github.com/sssssssh/ginit)上找到本教程隨附的代碼。
一痴荐、為什么用Node.js來構(gòu)建命令行工具
在深入研究之前血柳,我們有必要了解一下為什么我們選擇Node.js
來構(gòu)建命令行工具。
最明顯的好處是生兆,如果你在閱讀本文那么大概率是因為你對JavaScript
已經(jīng)很了解难捌。
另一個關(guān)鍵優(yōu)勢是,使用Node.js
的生態(tài)意味著你可以利用成千上萬種實現(xiàn)各種目的的npm包
皂贩。其中有很多是為了構(gòu)建強大的命令行工具而生的栖榨。
最后昆汹,我們可以通過npm
管理依賴明刷,不需要擔(dān)心特定系統(tǒng)的包管理工具帶來的兼容問題,例如apt
满粗、yum
辈末、homebrew
。
二映皆、創(chuàng)建一個命令行工具: ginit
通過這個教程挤聘,我們將構(gòu)建一個叫ginit
的命令行工具。它實現(xiàn)了git init
捅彻,但又不僅僅只有這個功能组去。
你可能想知道它到底是干啥用的。
眾所周知步淹,git init
會在當(dāng)前文件夾初始化git倉庫
从隆。但是诚撵,通常這是將新項目或者已有項目關(guān)聯(lián)到Git
上的眾多重復(fù)步驟中的一步。例如键闺,作為一個經(jīng)典的工作流程中的一部分寿烟,你可能會:
- 通過
git init
初始化本地倉庫 - 創(chuàng)建遠(yuǎn)程倉庫,這一步通常需要通過瀏覽器來完成
- 添加到遠(yuǎn)端
- 創(chuàng)建
.gitignore
文件 - 添加你自己的項目文件
- 提交初始項目文件
- 推送到遠(yuǎn)程倉庫
通常會涉及到更多操作辛燥,但是筛武,出于教學(xué)目的,在本教程中我們僅僅實現(xiàn)上面的步驟挎塌。這些步驟都是重復(fù)的徘六,我們通過命令行工具來實現(xiàn)豈不是比粘貼復(fù)制git
倉庫的鏈接更好呢?
因此勃蜘,ginit
要做的就是在當(dāng)前文件夾中創(chuàng)建Git
倉庫硕噩,創(chuàng)建一個遠(yuǎn)程倉庫(這里我們用git),然后將它添加為遠(yuǎn)程倉庫缭贡,然后炉擅,它將提供一個簡單的交互式向?qū)韯?chuàng)建.gitignore
,添加文件并將其推送到遠(yuǎn)端阳惹。他可能不會減少你的時間谍失,但是,會減少一些你的重復(fù)勞動莹汤。
基于這一點讓我們開始吧快鱼!
三、項目依賴
可以肯定的一件事:就外觀而言纲岭,控制臺永遠(yuǎn)不會具有圖形用戶界面的復(fù)雜度抹竹。不過,這并不意味著他必須是丑陋的單色文本止潮。你可能會驚訝于在保持功能正常的情況下腔丧,命令行工具也可以做的很好看激捏。我們找到了幾個增強界面展示的庫:chalk
用于在終端中輸出彩色的文字谅辣;clui
用于添加一些UI組件漾橙。還有好玩的,我們會利用figlet
創(chuàng)建一個基于ASCII
的炫酷橫幅燃乍,并且利用clear
來清空控制臺唆樊。
在輸入和輸出方面,Node.js
底層的Readline
模塊用于提示用戶輸入綽綽有余刻蟹。但是逗旁,我們將利用一個第三方庫Inquirer
,它提供了更多復(fù)雜的功能舆瘪。除了實現(xiàn)詢問用戶的功能外片效,它在控制臺中還提供了單選框和復(fù)選框的功能仓洼。
我們還會使用minimist
來解析命令行中輸入的參數(shù)。
這是我們在開發(fā)命令行工具中使用到的完整的npm
包列表:
-
chalk
: 讓我們的輸出變得有色彩堤舒; -
clear
: 清空終端屏幕色建; -
clui
: 繪制命令行中的表格、儀表盤舌缤、加載指示器等箕戳; -
figlet
: 生成基于ASCII
的藝術(shù)字; -
inquirer
: 創(chuàng)建交互式的命令行界面国撵; -
minimist
: 解析命令行參數(shù)陵吸; -
configstore
: 輕松的加載和保存配置信息;
另外介牙,我們還會使用下面的包:
-
@octokit/rest
: 基于Node.js
的Github REST API工具壮虫; -
@octokit/auth-basic
:Github
身份驗證策略的一種實現(xiàn); -
lodash
: JavaScript 工具庫环础; -
simple-git
: 在Node.js
中執(zhí)行Git
命令的工具囚似; -
touch
: 實現(xiàn)Unix touch
命令的工具;
四线得、開始你的表演
盡管我們是從頭開始創(chuàng)建這個命令行工具饶唤,但是不要忘記你也可以從本文附帶的GitHub倉庫(https://github.com/sssssssh/ginit)中拷貝一份代碼。
為這個項目創(chuàng)建一個新的目錄贯钩,當(dāng)然募狂,你可以給他起別的名字,不必一定叫他ginit
:
mkdir ginit
cd ginit
創(chuàng)建一個新的package.json
文件:
npm init -y
最終將會生成一個這樣的package.json
文件
{
"name": "ginit",
"version": "1.0.0",
"description": "'git init' on steroids",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"Git",
"CLI"
],
"license": "ISC"
}
現(xiàn)在開始安裝項目依賴:
npm install chalk clear clui figlet inquirer minimist configstore @octokit/rest @octokit/auth-basic lodash simple-git touch
在項目中創(chuàng)建一個index.js
文件角雷,加上如下代碼:
// index.js
const chalk = require('chalk');
const clear = require('clear');
const figlet = require('figlet');
五祸穷、增加一些有用的方法
在目錄中創(chuàng)建一個lib
目錄,并將我們的代碼分為以下模塊:
-
file.js
:基礎(chǔ)的文件管理 -
inquirer.js
:處理命令行中的用戶交互勺三; -
github.js
:管用戶的git token; -
repo.js
:Git倉庫管理雷滚;
讓我們開始寫lib/file.js
中的代碼。我們需要做以下事情:
- 獲取當(dāng)前目錄(作為當(dāng)前倉庫的默認(rèn)名稱)檩咱;
- 檢查目錄是否存在(通過檢查
.git
目錄是否存在揭措,來判斷當(dāng)前目錄是否存在git倉庫)胯舷;
這聽起來很簡單刻蚯,但是,有幾個問題需要考慮桑嘶。
首先炊汹,你可能會想到用fs
模塊的realpathSync
方法來獲取當(dāng)前目錄:
path.basename(path.dirname(fs.realpathSync(__filename)));
當(dāng)我們從命令行所在文件的根目錄下調(diào)用時,這個方法沒啥問題逃顶。但是讨便,我們的命令行工具可以在任何目錄下調(diào)用充甚。這意味著我們需要獲得的是當(dāng)前工作目錄的名稱,而不是命令行代碼所在目錄的名稱霸褒。所以伴找,你最好使用process.cwd()
:
path.basename(process.cwd());
第二,檢查文件是否存在的最佳方法一直在變化废菱。目前最好的方法是使用existsSync
技矮,如果文件存在他會返回true
,否則返回false
殊轴。
結(jié)合上面所說的衰倦,讓我們在lib/files.js
中添加如下代碼:
// files.js
const fs = require('fs');
const path = require('path');
module.exports = {
// 獲取目錄名稱
getCurrentDirectoryBase: () => {
return path.basename(process.cwd());
},
// 判斷目錄是否存在
directoryExists: (filePath) => {
return fs.existsSync(filePath);
},
};
在index.js
中,添加下面的代碼:
// index.js
const files = require('./lib/files');
有了這個旁理,我們就可以動手開發(fā)我們的命令行工具了樊零。
六、初始化命令行工具
現(xiàn)在讓我們來實現(xiàn)命令行工具的啟動階段孽文。
為了展示我們安裝的增強控制臺輸出的模塊驻襟,我們先清空屏幕,再展示一個banner芋哭,在index.js
中添加如下代碼:
// index.js
// 清除命令行
clear();
// 輸出Logo
console.log(chalk.yellow(figlet.textSync('Ginit', { horizontalLayout: 'full' })));
你可以通過運行node index.js
來執(zhí)行它塑悼,輸出效果如下:
接下來,讓我們進(jìn)行一個簡單的檢查楷掉,以確保當(dāng)前目錄不存在git倉庫厢蒜。這很簡單,只需要利用我們創(chuàng)建的方法來檢查.git
方法是否存在即可烹植,在index.js
中添加如下代碼:
// 判斷是否存在.git文件
if (files.directoryExists('.git')) {
console.log(chalk.red('已經(jīng)存在一個本地倉庫!'));
process.exit();
}
七斑鸦、提示用戶輸入
接下來,我們需要創(chuàng)建一個函數(shù)來引導(dǎo)用戶輸入他們的GitHub
賬號和密碼草雕。
我們可以使用Inquirer
來實現(xiàn)巷屿,它提供了很多種類型的提示方法。這些方法有些類似于HTML
中的控件墩虹。為了收集用戶的GitHub
賬號和密碼嘱巾,我們需要使用到input和password類型的控件。
首先诫钓,在lib/inquirer.js
中添加如下代碼:
// inquirer.js
const inquirer = require('inquirer');
const files = require('./files');
module.exports = {
// 詢問git賬號信息
askGithubCredentials: () => {
const questions = [
{
name: 'username',
type: 'input',
message: '請輸入你的git賬號或郵箱地址:',
validate: function (value) {
if (value.length) {
return true;
} else {
return '請輸入你的git賬號或郵箱地址.';
}
},
},
{
name: 'password',
type: 'password',
message: '請輸入你的密碼:',
validate: function (value) {
if (value.length) {
return true;
} else {
return '請輸入你的密碼.';
}
},
},
];
return inquirer.prompt(questions);
}
};
如你所見旬昭,通過inquirer.prompt()
向用戶詢問一系列問題,我們將這些問題以數(shù)組的形式傳遞給函數(shù)prompt
菌湃。每一問題都由一個對象構(gòu)成问拘,其中,name
表示該字段的名稱,type
表示我們要使用控件類型骤坐,message
是我們要展示給用戶的話绪杏,validate
是校驗用戶輸入字段的函數(shù)。
inquirer.prompt()
將會返回一個Promise
對象纽绍,如果校驗通過蕾久,我們將會得到一個擁有name
和password
2個屬性的對象。
將如下代碼添加在index.js
:
// index.js
const inquirer = require('./lib/inquirer');
const run = async () => {
const credentials = await inquirer.askGithubCredentials();
console.log(credentials);
};
run();
運行node index.js
結(jié)果如下:
提示:當(dāng)你完成測試后拌夏,不要忘了把const inquirer = require('./lib/inquirer');
從index.js
中刪除腔彰,因為我們不需要它。
八辖佣、處理GitHub授權(quán)
下一步是創(chuàng)建一個函數(shù)霹抛,用于獲取GitHub API
的OAuth TOKEN
。實際上卷谈,我們就是通過賬號和密碼來獲取token
杯拐。
當(dāng)然,我們不希望用戶每次使用我們的工具時世蔗,都需要輸入賬號和密碼端逼。相反,我們將保存OAuth
令牌用于后續(xù)的請求污淋。這就要用到configstore
這個包啦顶滩。
九、保存配置
保存配置信息表面上看很簡單寸爆,你可以簡單的讀寫一個JSON文件就好了礁鲁。但是,configstore
這個包有以下優(yōu)勢:
- 它會根據(jù)你的操作系統(tǒng)和當(dāng)前用戶來決定最佳的文件存儲位置赁豆;
- 不需要讀取文件仅醇,只需要修改
configstore
對象即可,后面的事他幫你搞定魔种;
用法很簡單析二,創(chuàng)建一個實例,傳入一個標(biāo)識符即可节预,例如:
const Configstore = require('configstore');
const conf = new Configstore('ginit');
如果configstore
文件不存在叶摄,他會返回一個空對象并且在后臺創(chuàng)建一個文件。如果文件存在安拟,你可以直接利用里面的內(nèi)容蛤吓。你現(xiàn)在可以根據(jù)需要直接修改conf
對象的屬性。同時去扣,你也不需要擔(dān)心怎么去保存它柱衔,它自己會處理好的。
提示:在macOS
系統(tǒng)上愉棱,文件將會保存在/Users/[YOUR-USERNME]/.config/configstore/ginit.json
下唆铐。在Linux
系統(tǒng)上文件保存在/home/[YOUR-USERNME]/.config/configstore/ginit.json
。
十奔滑、與GitHub API通信
讓我們來創(chuàng)建一個文件來處理GitHub Token
艾岂。創(chuàng)建lib/github.js
并將下列代碼拷入:
// github.js
const CLI = require('clui');
const Configstore = require('configstore');
const Spinner = CLI.Spinner;
const { Octokit } = require("@octokit/rest")
const { createBasicAuth } = require('@octokit/auth-basic');
const inquirer = require('./inquirer');
const pkg = require('../package.json');
// 初始化本地的存儲配置
const conf = new Configstore(pkg.name);
現(xiàn)在讓我們來創(chuàng)建一個函數(shù)來檢查我們是否擁有token
。我們還創(chuàng)建了一個函數(shù)朋其,方便其他模塊獲取到octokit
實例王浴。在lib/github.js
中增加下列代碼:
// github.js
// ...初始化
// 模塊內(nèi)部的單例
let octokit;
module.exports = {
// 獲取octokit實例
getInstance: () => {
return octokit;
},
// 獲取本地token
getStoredGithubToken: () => {
return conf.get('github.token');
}
}
如果conf
對象存在且github.token
屬性也存在,就表示token
存在梅猿。這里我們就可以把token
返回給調(diào)用函數(shù)氓辣。我們稍后會講它。
如果沒檢查到token
袱蚓,則需要去獲取一個钞啸。當(dāng)然,獲取OAuth token
涉及到網(wǎng)絡(luò)請求喇潘,這意味著用戶需要短暫的等待体斩。借此機會,我們可以看到clui
提供控制帶UI增強功能颖低,loading效果就是其中一個絮吵。
創(chuàng)建一個loading效果很簡單:
const status = new Spinner('Authenticating you, please wait...');
status.start();
完成后,只需要停止他忱屑,他就會在屏幕上消失:
status.stop();
提示:你也可以用update
來動態(tài)的設(shè)置文字蹬敲。如果你需要一個進(jìn)度指示器,例如展示當(dāng)前的進(jìn)度的百分比莺戒,這可能非常有用粱栖。
將下面代碼拷貝到lib/github.js
中,這是完成GitHub
認(rèn)證的代碼:
// github.js
module.exports = {
// 獲取實例
getInstance: () => { ... },
// 獲取本地token
getStoredGithubToken: () => { ... },
// 通過個人賬號信息獲取token
getPersonalAccessToken: async () => {
const credentials = await inquirer.askGithubCredentials();
const status = new Spinner('驗證身份中脏毯,請等待...');
status.start();
const auth = createBasicAuth({
username: credentials.username,
password: credentials.password,
async on2Fa() {
// 等待實現(xiàn)
},
token: {
scopes: ['user', 'public_repo', 'repo', 'repo:status'],
note: 'ginit, the command-line tool for initalizing Git repos',
},
});
try {
const res = await auth();
if (res.token) {
conf.set('github.token', res.token);
return res.token;
} else {
throw new Error('獲取GitHub token失敗');
}
} finally {
status.stop();
}
}
};
讓我們來逐步完成:
- 用之前我們定義的函數(shù)
askGithubCredentials
來詢問用戶的賬號和密碼闹究; - 我們使用
createBasicAuth
來創(chuàng)建一個auth
函數(shù),方便后面調(diào)用食店。需要給這個函數(shù)傳遞用戶的用戶名和密碼渣淤,同時還需要傳遞一個token
對象,它擁有下面2個屬性:-
note
:記錄獲取token的用途吉嫩; -
scopes
:一個授權(quán)信息使用范圍的列表价认,你可以在GitHub上了解更多信息;
-
- 我們將會
try catch
中利用await
語法等待函數(shù)的返回結(jié)果自娩; - 如果授權(quán)成功用踩,我們將會獲取到
token
渠退,可以把它放到configstore
中,方便下次直接使用脐彩; - 如果因為某些原因?qū)е率跈?quán)失敗碎乃,我們將在捕捉到它,根據(jù)狀態(tài)碼處理異常的情況惠奸;
您創(chuàng)建的任何token
(無論是手動創(chuàng)建的還是通過API生成的)都可以在此處看到梅誓。 在開發(fā)過程中,你可能需要刪除ginit
的token
(可以通過上面提供的note
參數(shù)識別)佛南,以便重新生成它梗掰。
更新index.js
中的代碼:
// index.js
const github = require('./lib/github');
...
const run = async () => {
// 從本地獲取token記錄
let token = github.getStoredGithubToken();
if(!token) {
// 通過賬號、密碼獲取token
token = await github.getPersonalAccessToken();
}
console.log(token);
};
第一次運行時嗅回,你需要輸入你的用戶名和密碼及穗。我們將會在github
上創(chuàng)建一個token
并把它保存起來。下次運行時绵载,我們將直接使用保存起來的token
做身份認(rèn)證拥坛。
十一、處理雙重認(rèn)證
希望你注意到上面代碼中的on2Fa
函數(shù)尘分。當(dāng)用戶的賬號使用雙重認(rèn)證時猜惋,將會調(diào)用到這個函數(shù)。讓我們在lib/inquirer.js
中插入如下代碼:
// inquirer.js
const inquirer = require('inquirer');
module.exports = {
// 詢問git賬號信息
askGithubCredentials: () => { ... },
// 詢問雙重認(rèn)證碼
getTwoFactorAuthenticationCode: () => {
return inquirer.prompt({
name: 'twoFactorAuthenticationCode',
type: 'input',
message: '請輸入你的雙重認(rèn)證驗證碼:',
validate: function (value) {
if (value.length) {
return true;
} else {
return '請輸入你的雙重認(rèn)證驗證碼:.';
}
},
});
}
}
修改lib/github.js
中的on2Fa
函數(shù):
// github.js
async on2Fa() {
status.stop();
const res = await inquirer.getTwoFactorAuthenticationCode();
status.start();
return res.twoFactorAuthenticationCode;
}
現(xiàn)在我們的程序可以處理GitHub
雙重認(rèn)證培愁。
十二著摔、創(chuàng)建倉庫
獲取Oauth
令牌之后,我們就可以利用它來創(chuàng)建遠(yuǎn)程倉庫定续。
同樣谍咆,我們可以利用Inquirer
來問一系列問題。我們需要獲取一個倉庫名字私股,我們可以要求用戶選填一個描述摹察,還需要詢問倉庫是共有還是私有。
我們可以利用minimist
來從命令行參數(shù)中獲取倉庫名稱和描述倡鲸。
ginit my-repo "just a test repository"
下面的代碼將會解析出一個數(shù)組:
const argv = require('minimist')(process.argv.slice(2));
// { _: [ 'my-repo', 'just a test repository' ] }
我們將通過代碼來實現(xiàn)上面所說的提問供嚎。首先將下列代碼拷貝到lib/inquirer.js
中:
// inquirer.js
const inquirer = require('inquirer');
const files = require('./files');
module.exports = {
// 詢問git賬號信息
askGithubCredentials: () => { ... },
// 詢問雙重認(rèn)證碼
getTwoFactorAuthenticationCode: () => { ... },
// 詢問倉庫詳細(xì)信息
askRepoDetails: () => {
const argv = require('minimist')(process.argv.slice(2));
const questions = [
{
type: 'input',
name: 'name',
message: '請輸入git倉庫名稱:',
default: argv._[0] || files.getCurrentDirectoryBase(),
validate: function (value) {
if (value.length) {
return true;
} else {
return '請輸入git倉庫名稱.';
}
},
},
{
type: 'input',
name: 'description',
default: argv._[1] || null,
message: '請輸入倉庫描述(選填):',
},
{
type: 'list',
name: 'visibility',
message: '共有倉庫 或 私有倉庫:',
choices: ['public', 'private'],
default: 'public',
},
];
return inquirer.prompt(questions);
}
};
創(chuàng)建lib/repo.js
文件,并添加如下代碼:
// repo.js
const CLI = require('clui');
const fs = require('fs');
const git = require('simple-git/promise')();
const Spinner = CLI.Spinner;
const touch = require('touch');
const _ = require('lodash');
const inquirer = require('./inquirer');
const gh = require('./github');
module.exports = {
// 創(chuàng)建遠(yuǎn)程倉庫
createRemoteRepo: async () => {
const github = gh.getInstance();
const answers = await inquirer.askRepoDetails();
const data = {
name: answers.name,
description: answers.description,
private: answers.visibility === 'private',
};
const status = new Spinner('創(chuàng)建遠(yuǎn)程倉庫中...');
status.start();
try {
const response = await github.repos.createForAuthenticatedUser(data);
return response.data.ssh_url;
} finally {
status.stop();
}
}
}
獲取以上信息后峭状,我就可以創(chuàng)建Git倉庫了克滴。我們這本地將生成好的倉庫設(shè)置成我們的遠(yuǎn)程倉庫。但是优床,在這之前讓我們以交互的方式來創(chuàng)建一個.gitignore
文件吧劝赔。
十三、創(chuàng)建 .gitignore
下一步胆敞,我們將要創(chuàng)建一個簡單的命令行“向?qū)А眮砩?code>.gitignore文件着帽。如果用戶在現(xiàn)有項目目錄中執(zhí)行我們的命令行工具杂伟,請向他們展示當(dāng)前目錄已經(jīng)存在的文件和目錄,并允許他們選擇需要忽略的文件和文件夾仍翰。
inquirer
提供了一個復(fù)選框給我們使用赫粥。
我們需要掃描當(dāng)前目錄中.git
和.gitignore
以外的文件。
const filelist = _.without(fs.readdirSync('.'), '.git', '.gitignore');
如果沒有文件需要添加到'.gitignore'中歉备,那么直接創(chuàng)建一個.gitignore
文件即可
if (filelist.length) {
...
} else {
touch('.gitignore');
}
讓我們在lib/inquirer.js
中添加如下代碼:
// inquirer.js
// 選擇需要忽略的文件
askIgnoreFiles: (fileList) => {
const questions = [
{
type: 'checkbox',
name: 'ignore',
message: '請選擇你想要忽略的文件:',
choices: fileList,
default: ['node_modules', 'bower_components'],
},
];
return inquirer.prompt(questions);
},
注意:我們可以提供一些默認(rèn)選項傅是,如果node_modules
和bower_components
存在的話匪燕,我們將提前選中蕾羊。
在lib/repo.js
中,我們添加如下代碼:
// repo.js
// 創(chuàng)建git ignore
createGitignore: async () => {
const fileList = _.without(fs.readdirSync('.'), '.git', '.gitignore');
if (fileList.length) {
const answers = await inquirer.askIgnoreFiles(fileList);
if (answers.ignore.length) {
// 寫入信息
fs.writeFileSync('.gitignore', answers.ignore.join('\n'));
} else {
// 創(chuàng)建文件
touch('.gitignore');
}
} else {
// 創(chuàng)建文件
touch('.gitignore');
}
},
一旦提交帽驯,我們將會根據(jù)選中的文件生成一個.gitignore
文件龟再。既然已經(jīng)可以生成.gitignore
文件了,讓我們初始化git倉庫吧尼变。
十四利凑、在命令行中與git交互
有很多實現(xiàn)和git
交互的方法,但是嫌术,最簡單的方法可能是simple-git哀澈。這個庫提供了一批可以鏈?zhǔn)秸{(diào)用的異步函數(shù),在后臺執(zhí)行g(shù)it命令度气。
這是我們需要做的重復(fù)任務(wù):
- 運行
git init
- 添加
.gitignore
- 添加目錄中的其余文件
- 完成初次提交
- 添加新創(chuàng)建的遠(yuǎn)程倉庫
- 將代碼推送到遠(yuǎn)端
在lib/repo.js
中添加如下代碼:
// repo.js
// 設(shè)置
setupRepo: async (url) => {
const status = new Spinner('初始化本地倉庫并推送到遠(yuǎn)端倉庫中...');
status.start();
try {
await git.init();
await git.add('.gitignore');
await git.add('./*');
await git.commit('Initial commit')
await git.addRemote('origin', url);
await git.push('origin', 'master');
} finally {
status.stop();
}
},
十五割按、把代碼串起來
首先,我們需要在lib/github.js
文件中增加一個函數(shù)磷籍,該函數(shù)的作用是建立一個oauth
認(rèn)證:
// github.js
// 通過token登陸
githubAuth: (token) => {
octokit = new Octokit({
auth: token,
});
},
然后适荣,我們需要創(chuàng)建一個函數(shù)來控制獲取token
的邏輯。在run
函數(shù)前院领,增加如下代碼:
// index.js
// 獲取github token
const getGithubToken = async () => {
// 從本地獲取token記錄
let token = github.getStoredGithubToken();
if (token) {
return token;
}
// 通過賬號弛矛、密碼獲取token
token = await github.getPersonalAccessToken();
return token;
};
最后,我們用下面的代碼來更新我們的run
函數(shù):
// index.js
const run = async () => {
try {
// 獲取token
const token = await getGithubToken();
github.githubAuth(token);
// 創(chuàng)建遠(yuǎn)程倉庫
const url = await repo.createRemoteRepo();
// 創(chuàng)建 .gitignore
await repo.createGitignore();
// 初始化本地倉庫并推送到遠(yuǎn)端
await repo.setupRepo(url);
console.log(chalk.green('All done!'));
} catch (err) {
if (err) {
switch (err.status) {
case 401:
console.log(chalk.red("登陸失敗比然,請?zhí)峁┱_的登陸信息"));
break;
case 422:
console.log(chalk.red('遠(yuǎn)端已存在同名倉庫'));
break;
default:
console.log(chalk.red(err));
}
}
}
};
如你所見丈氓,在調(diào)用我們其他的函數(shù)之前(createRemoteRepo()
, createGitignore()
, setupRepo()
),我們確保用戶已經(jīng)通過了身份驗證强法。而且扒寄,還處理任何錯誤并且給用戶適當(dāng)?shù)姆答仭?/p>
你可以在git倉庫中找到完整的代碼。
現(xiàn)在拟烫,你就擁有了一個可以運行的命令行工具了该编。運行一下,看看他是不是按照你的預(yù)期工作硕淑。
十六课竣、讓ginit命令全局可用
還有一件需要做的事就是讓我們的命令行全局可用嘉赎。為了實現(xiàn)這個事,我們需要在index.js
文件頭部加上shebang于樟。
#!/usr/bin/env node
然后公条,我們需要在package.json
中增加一個bin
屬性。用于綁定命令名稱ginit
和被執(zhí)行的文件迂曲。
"bin": {
"ginit": "./index.js"
}
然后全局安裝模塊靶橱,命令行工具就可以用了。
npm install -g
如果你想確認(rèn)安裝是否生效路捧,你可以把本機上全局安裝的node模塊列出來看看:
npm ls -g --depth=0
十七关霸、展望
我們已經(jīng)創(chuàng)建了一個漂亮且簡潔的初始化Git
倉庫的命令行工具。而且你還可以做很多事情去提升它杰扫。
如果你是一個Bitbucket
用戶队寇,你可以利用Bitbucket API
給這個命令行增加一個創(chuàng)建Bitbucket
倉庫的功能。這個node包 bitbucket-api對你會有幫助章姓。你可以增加另外一個命令行選項或者詢問用戶是否要使用Bitbucket
佳遣,或者直接把現(xiàn)在處理GitHub
的代碼替換成Bitbucket
。
你可以提供一個.gitignore
默認(rèn)的文件集合凡伊,而不是硬編碼零渐。preferences
這個包很適合這個場景,或者你可以提供一個模版系忙,提示用戶輸入對應(yīng)的模版類型即可诵盼。也可以把它集成到.gitignore.io中。
除此之外笨觅,你還可以增加其他驗證拦耐,提供跳過某些步驟的功能等等。
這是一篇老文章了见剩,不過杀糯,今年2月份作者又更新了一部分內(nèi)容,剔除了其中失效的依賴苍苞。同時固翰,在閱讀的過程中,我也優(yōu)化了一下示例代碼羹呵。
關(guān)于我
我是一個莫得感情的代碼搬運工骂际,每周會更新1至2篇前端相關(guān)的文章,有興趣的老鐵可以掃描下面的二維碼關(guān)注或者直接微信搜索前端補習(xí)班
關(guān)注冈欢。
精通前端很難歉铝,讓我們來一起補補課吧!
好啦凑耻,翻譯完畢啦太示,原文鏈接在此 Build a JavaScript Command Line Interface (CLI) with Node.js柠贤。