最終效果:每次提交代碼時(shí),自動(dòng)完成以下兩個(gè)功能
執(zhí)行代碼增量檢查,有代碼風(fēng)格錯(cuò)誤拋出異常。
檢查提交的文件列表,如果同時(shí)存在
dist目錄和非dist目錄,拋出異常。
前期準(zhǔn)備
在閱讀本文之前,請(qǐng)確保熟悉或了解 eslint、 eslint-staged 和 husky.
如果不了解,請(qǐng)點(diǎn)擊傳送門:Javascript代碼檢查那點(diǎn)事
代碼增量檢查
eslint 的具體配置不再贅述,在上述的傳送門中有詳細(xì)配置。
安裝相關(guān)依賴
npm install --save-dev husky
npm install --save-dev lint-staged
在 package.json 中增加如下配置
"husky": {
"hooks": {
"pre-commit": "node scripts/utils/pre-commit.js"
}
},
"lint-staged": {
"linters": {
"*.js": [
"eslint",
"git add"
],
"*.vue": [
"eslint",
"git add"
]
},
"ignore": ["dist/**"]
},
配置說明:當(dāng)執(zhí)行 git commit 時(shí),會(huì)執(zhí)行 husky.hooks.pre-commit, 也就是 node scripts/utils/pre-commit.js,該命令為執(zhí)行一段腳本,腳本如下。
提交文件檢查
檢查提交內(nèi)容的核心思路為:獲取本次提交的改動(dòng)文件列表信息 -> 檢查文件路徑 -> 判斷是否同時(shí)存在
dist和非dist目錄,如果同時(shí)存在,則拋出異常停止commit操作,如果不存在,則執(zhí)行eslint代碼檢查。詳細(xì)代碼如下(可直接拷貝到scripts/utils/pre-commit.js中)
const chalk = require('chalk')
const symbols = require('log-symbols')
const spawn = require('child_process').spawn
require('lint-staged')
// 檢查改動(dòng)的文件目錄
getDiffFiles().then(files => {
const filePaths = files.map(file => file.filename.split('/')[0])
let isDistFolder = false
let isOtherFiles = false
filePaths.forEach(path => {
if (path === 'dist') isDistFolder = true
else isOtherFiles = true
})
if (isDistFolder && isOtherFiles) {
throw new Error()
}
runCmd('lint-staged')
}).catch(() => {
/* eslint-disable */
console.error(`\n\n${symbols.error} ${chalk.redBright('Ops!Dist folder and other files cannot be submitted at the same time.')}`)
console.log(` (use "git reset" to cancel the "add" operation)\n`)
/* eslint-enable */
process.exit(1)
})
// 獲取本次改動(dòng)的文件列表
function getDiffFiles() {
return getHeadCommitId().then(head => {
if (head) {
const command = 'git -c core.quotepath=false diff-index --cached --name-status -M --diff-filter=ACM ' + head
return runCmd(command).then(({ err, stdout, stderr }) => {
return err || stderr ? err || new Error(stderr) : stdoutToResultsObject(stdout)
})
}
})
}
// 獲取最近一次提交的commit_id
function getHeadCommitId() {
return runCmd('git rev-parse --verify HEAD').then(({ err, stdout, stderr }) => {
if (err && err.message.indexOf('fatal: Needed a single revision') !== -1) {
return getFirstCommitId()
} else {
return err || stderr ? err || new Error('STDERR: ' + stderr) : stdout.replace('\n', '')
}
})
}
// 獲取第一次提交的commit_id
function getFirstCommitId() {
return runCmd('git hash-object -t tree /dev/null').then(({ err, stdout, stderr }) => {
return err || stderr ? err || new Error('STDERR: ' + stderr) : stdout.replace('\n', '')
})
}
// 執(zhí)行命令,監(jiān)聽控制臺(tái)信息
function runCmd(command) {
return new Promise((resolve) => {
// 解析命令獲取參數(shù)
const bits = command.split(' ')
const args = bits.slice(1)
// 執(zhí)行命令
const cmd = spawn(bits[0], args, { cwd: process.cwd() })
let stdout = ''
let stderr = ''
cmd.stdout.on('data', data => {
stdout += data.toString()
})
cmd.stderr.on('data', data => {
stderr += data.toString()
})
cmd.on('close', code => {
const err = code !== 0 ? new Error(stderr) : null
resolve({ err, stdout, stderr })
})
})
}
// 把stdout輸出信息轉(zhuǎn)化成Object對(duì)象
function stdoutToResultsObject(stdout) {
const results = []
const lines = stdout.split('\n')
let iLines = lines.length
while (iLines--) {
const line = lines[iLines]
if (line !== '') {
const parts = line.split('\t')
const result = {
filename: parts[2] || parts[1],
status: codeToStatus(parts[0]),
}
results.push(result)
}
}
return results
}
// git 枚舉映射
function codeToStatus(code) {
const map = {
A: 'Added',
C: 'Copied',
D: 'Deleted',
M: 'Modified',
R: 'Renamed',
T: 'Type-Change',
U: 'Unmerged',
X: 'Unknown',
B: 'Broken',
}
return map[code.charAt(0)]
}
當(dāng)執(zhí)行 git commit 操作時(shí),首先會(huì)執(zhí)行上述腳本,當(dāng)滿足要求時(shí),再執(zhí)行 eslint 代碼檢查,當(dāng)代碼檢查通過后,才會(huì)提交代碼到遠(yuǎn)程倉庫!