willson-wang / Blog

随笔
https://blog.willson-wang.com/
MIT License
70 stars 10 forks source link

node shell pre-commit eslint prettier #48

Open willson-wang opened 5 years ago

willson-wang commented 5 years ago

很多事情有因才有果,因为工作项目的关系,我们的目录结构大致如下

project
    package.json
    node_modules/
    packages/
         component1
               .git
               src
               package.json
         component1
               .git
               src
               package.json

真正的代码都在component这一层,且每一个为独立的git仓库,所有的依赖都安装在最外层的node_modules目录下;当我们需要使用到git的pre-commit等钩子时,存在三个问题,第一个就是不能使用成熟的githooks插件如husky等,第二个就是所有的依赖都安装在最外层,内层component1这层是不允许装依赖的;第三个问题就是就算自己写了pre-commit等钩子,团队其它成员怎么用;

最开始因为git的hooks都是shell脚本,而自己对shell脚本不怎么熟,但是知道可以使用node来写shell脚本,所以直接写的是node的方案;

在写node脚本的过程中碰到几个问题;

1 node内怎么去执行命令,如npm run start、git diff、eslint .等

通过查找资料我们可以知道node内提供来一个child_process模块,而这个模块提供来一些异步以及通过去执行command的方法;如child_process.exec(command[, options][, callback])、child_process.spawn(command[, args][, options])、child_process.execSync(command[, options])、child_process.spawnSync(command[, args][, options])方法等,具体的使用方法查找node文档即可,不过主要有两点就是这几个可执行命令的方法都是基于spawn的封装;第二点就是exec可以直接执行命令,如exec('git log') 而spawn方法则是需要使用参数的形式spawn('git', ['log'])

2 怎样去获取提交到暂存区的文件

通过git diff命令可以获取到暂存区的文件,如获取js文件git diff --cached --name-only --diff-filter=ACM -- '*.js'

通过execSync or exec命令来获取到暂存区的文件,execSync('git diff --cached --name-only --diff-filter=ACM -- '*.js'').toString().trim().split('\n') 注意execSync与exec的返回值取的的地方不一样;execSync方法返回值一般是buffer or 字符串这里使用toString()方法将buffer转换成字符串,然后在转换成包含各个文件名的数组

3 怎么去处理Eslint的返回信息

如果使用的是execSync方法来执行eslint命令,需要用try catch的方式在catch内捕捉错误,一开始没有使用try catch方法,而是直接使用返回值,想要用返回值来判断,但是这样导致报 comman failed的错误,而我把这个命令直接放在bash内执行的时候,又是正常的;这就是我没有使用try catch来捕捉的结果

// 使用try catch来捕捉eslint返回的信息,而不是直接使用返回值
try {
    execSync(`${PRETTIER_PATH} --parser ${filetype.includes('js') ? 'flow' : 'vue'} --write ${files[i]}`)
        execSync(`${ESLINT_PATH} ${files[i]}`, {cwd})
  } catch (error) {
    PASS = false
    console.log('error', error.stdout.toString())
  }

// 不是直接使用返回值,这样会直接报comman failed
const result = execSync(`${ESLINT_PATH} ${files[i]}`, {cwd})

image

4 因为使用prettier来格式化代码,而格式化的时候无法自动识别文件并使用不同的引擎来格式化代码,所以分开获取了js文件与vue文件

5 node子进程内通过precess.exit()来终端进程,而shell脚本内,通过返回值0跟1来区分脚本是成功还是失败;0是成功,1是失败;

6 通过node的读写文件功能,让其它同时也能够方便使用

最终如下所示

#!/usr/bin/env node

const execSync = require('child_process').execSync
const path = require('path')

const ESLINT_PATH = path.normalize("./node_modules/.bin/eslint")
const PRETTIER_PATH = path.normalize("./node_modules/.bin/prettier")

console.log('检查是否安装eslint')
try {
    execSync(ESLINT_PATH)
}catch(e) {
    console.log('未安装eslint', e.stdout.toString(), e.stderr.toString())
    process.exit(1)
}

const STAGE_FILES_JS_COMMAND="git diff --cached --name-only --diff-filter=ACM -- '*.js'"
const STAGE_FILES_VUE_COMMAND="git diff --cached --name-only --diff-filter=ACM -- '*.vue'"

const STAGE_FILES_JS = execSync(STAGE_FILES_JS_COMMAND).toString().trim().split('\n')
const STAGE_FILES_VUE = execSync(STAGE_FILES_VUE_COMMAND).toString().trim().split('\n')

console.log('STAGE_FILES_JS', STAGE_FILES_JS, STAGE_FILES_VUE)
let promises = null
const cwd = process.cwd() || process.env.PWD;

function checkFile (files, filetype) {
    if (files.length) {

        let PASS = true

        for (let i = 0; i < files.length; i++) {
            try {
                execSync(`${PRETTIER_PATH} --parser ${filetype.includes('js') ? 'flow' : 'vue'} --write ${files[i]}`)
                execSync(`${ESLINT_PATH} ${files[i]}`, {cwd})
            } catch (error) {
                PASS = false
                console.log('error', error.stdout.toString())
            }
        }

        if (!PASS) {
            console.log(`eslint ${filetype}未通过`)
            process.exit(1)
        } else {
            console.log(`eslint ${filetype}通过`)
        }

    } else {
        console.log(`暂存区没有需要检查的${filetype}文件`)
    }
}

checkFile(STAGE_FILES_JS, 'js')
checkFile(STAGE_FILES_VUE, 'vue')

process.exit(0)
yarn createHooks => "createHooks": "node createHooks.js create"

console.log('create hooks', process.argv)
const fs = require('fs')

const type = process.argv[2]

const packagesList = ['./packages/test/', './packages/git-hooks/']

const templateUrl = './hooks/pre-commit'

function createHooks() {
    console.log('createHooks')
    for (let i = 0; i < packagesList.length; i += 1) {
        if (!fs.existsSync(`${packagesList[i]}.git/`)) {
            break
        }
        if (!fs.existsSync(`${packagesList[i]}.git/hooks/`)) {
            break
        }
        if (!fs.existsSync(`${packagesList[i]}.git/hooks/pre-commit`)) {
            const preCommit = fs.readFileSync(templateUrl)

            fs.writeFileSync(`${packagesList[i]}.git/hooks/pre-commit`, preCommit, {
                encoding: 'utf8',
                mode: 0o777,
            })
        }
    }
}

function deleteHooks() {

}

function deleteAllHooks() {

}

console.log('type', type)

switch (type) {
case 'create':
    createHooks()
    break
case 'delet':
    deleteHooks()
    break
case 'deletAll':
    deleteAllHooks()
    break
default:
    createHooks()
}