为了改善项目质量目前在推动公司项目锁主分支,开发者通过提交 PR rebase master 来项目协作以及 code review 文化。在经过一段时间的开发过程中不断积累 eslint 和 prettier 规范之后也是基本统一下来了(我负责的项目部分),review 过代码的小伙伴都知道,需要不断人工检查代码中的类型错误以及基础语法错误真的很费时费力,所以作为喜欢偷懒的我们肯定得上一点自动化(CI)科技了。

下面我记录一下手把手配置 jenkins 的 eslint 和 type 检查流水线,并将检查结果利用 🤖 机器人评论到 PR 中。

刚开始是每个项目都创建了一个 jenkins 项目来做检查的事情,后面发现项目太多管理起来也麻烦(还有一些自动部署 “CD” 的任务看的眼花缭乱)于是干脆将检查放到一个项目中这样类似的项目就仅需要配置一下就可以复用相同的项目来做检查任务了,我将其取名为 unified-ui-code-quality-check 计划。

首先来张全部检查通过的预览图欣赏一下,检查失败也是能够溯源和跟踪错误位置的。

前端工程化系列之配置 Jenkins 给代码托管在 Gitee 的前端项目做 PR code 质量检查-天真的小窝
前端工程化系列之配置 Jenkins 给代码托管在 Gitee 的前端项目做 PR code 质量检查-天真的小窝

编写 Jenkinsfile

要做到 Jenkins 和 gitee 交互需要安装 https://github.com/jenkinsci/gitee-plugin 这个插件。用于提供 gitee webhooks 触发服务提供,以及在 pipeline 中发表 PR 评论。

在项目中创建一个 deploy/check.Jenkinsfile 文件

pipeline {
    agent { label "ubuntu-22" }
    environment {
	    CI = true
        CHECK_OUT_FILE = "./build/check.out.log"
    }
    stages {
        stage('checkout') {
            steps {
                script {
                    def TARGET_COMMIT
                    if (env.giteeSourceBranch) {
                        TARGET_COMMIT = env.giteeSourceBranch
                    } else {
                        TARGET_COMMIT = "master"
                    }
                    echo "THE TARGET COMMIT IS: ${env.GIT_BRANCH}   ${TARGET_COMMIT}"

                    checkout([
                        $class: 'GitSCM',
                        branches: [[name: TARGET_COMMIT]],
                        extensions: [
                            submodule(parentCredentials: true, reference: '', timeout: 25)
                        ],
                        userRemoteConfigs: [[credentialsId: 'gitee', url: env.giteeSourceRepoSshUrl]]
                    ])

                    // sh "make -C deploy clean"
                }
            }
        }
        stage('check:lint') {
            steps {
                script {
                    def outFile = env.CHECK_OUT_FILE
                    status = sh(
                        label: 'make',
                        returnStatus: true, 
                        // 合并标准错误 StdErr 到文件和标准输出 StdOut
                        script: "mkdir -p build && make -C deploy ui-check-lint >${outFile} 2>&1" 
                    )
                    // 从文件读出标准错误 StdErr 信息
                    out = readFile(file: outFile) 
                    if (status != 0) {
                        // env.REG_RESULT = out
                        env.FAILED_STEP = "code check eslint"
                        println out
                        // 使当前 stage 运行失败,同时输出错误信息。
                        error("${env.FAILED_STEP} CHECK FAILED.")
                    }
                }
            }
        }
        stage('check:type') {
            steps {
                script {
                    def outFile = env.CHECK_OUT_FILE
                    status = sh(
                        label: 'make',
                        returnStatus: true, 
                        // 合并标准错误 StdErr 到文件和标准输出 StdOut
                        script: "mkdir -p build && make -C deploy ui-check-type >${outFile} 2>&1" 
                    )
                    // 从文件读出标准错误 StdErr 信息
                    out = readFile(file: outFile) 
                    if (status != 0) {
                        // env.REG_RESULT = out
                        env.FAILED_STEP = "code check typecheck"
                        println out
                        // 使当前 stage 运行失败,同时输出错误信息。
                        error("${env.FAILED_STEP} CHECK FAILED.")
                    }
                }
            }
        }
    }
    post {
        always {
            script {
                // def message = currentBuild.result == 'SUCCESS' ? '' : "Internal Error: ${env.BUILD_URL}console"
                def messageHead = "🤖 thank you for your contribution.\n🤖 [**PR Check:${currentBuild.result}**] see check result details: <${env.BUILD_URL}console>\n\n"
                def message = "Internal Error: ${env.BUILD_URL}console"
                if (currentBuild.result == 'SUCCESS') {
                    message = "${messageHead}✅ eslint step success.\n✅ typescript check step success."
                } else {
                    def headResult = sh(script: "head -n20 ${env.CHECK_OUT_FILE}", returnStdout: true).trim()
                    def tailResult = sh(script: "tail -50 ${env.CHECK_OUT_FILE}", returnStdout: true).trim()
                    def logs = "${headResult}\n……\n${tailResult}"
                    echo "REG_RESULT<${currentBuild.result}>: ${logs}"
                    message = "${messageHead}<details>\n<summary>🚫 ${env.FAILED_STEP} step failed.</summary>\n\n```log\n${logs}\n```\n\n</details>"
                }
                addGiteeMRComment comment: "${message}"
            }
        }
    }
}

当然这边具体的检查指令都是依靠 makefile 实现的,所以还需要在 deploy/Makefile 文件补充以下配置

UI_DIR = ../
UI_OUTPUT = $(UI_DIR)build

$(UI_DIR)node_modules/.package-lock.json: $(UI_DIR)package-lock.json
	cd $(UI_DIR); npm clean-install

# code check
ui-check-lint: $(UI_DIR)node_modules/.package-lock.json
	cd $(UI_DIR); npm run check:lint

ui-check-type: $(UI_DIR)node_modules/.package-lock.json
	cd $(UI_DIR); npm run check:type

clean:
	rm -rf $(UI_OUTPUT)

当然在 package.json 需要定义项目的 check:lint 和 check:type 检查命令 (建议参考如下配置 cache 可以使检查时间得到不少优化),如果不是 vue 项目或不使用 eslint 可以根据需求替换为相应的检查命令

{
    "name": "ci-example",
    "type": "module",
    "version": "0.0.1",
    "private": true,
    "scripts": {
        "check:lint": "eslint . --cache --cache-file node_modules/.cache/eslint/.eslintcache --quiet",
        "check:type": "vue-tsc --project . --noEmit --incremental --tsBuildInfoFile node_modules/.cache/tsc/tsconfig.tsbuildinfo",
        "check:clean": "rm -rf node_modules/.cache/eslint/.eslintcache node_modules/.cache/tsc/tsconfig.tsbuildinfo",
        "lint": "eslint ."
    },
……

创建并配置 Jenkins 项目

如何安装 jenkins gitee 插件以及获取 gitee api token 在这里就不多废话了,安装插件之后在 jenkins system config 页面配置 gitee token 也挺简单的,配置位置大致如下图所示

前端工程化系列之配置 Jenkins 给代码托管在 Gitee 的前端项目做 PR code 质量检查-天真的小窝

接下来打开 unified-ui-code-quality-check 项目配置页面,没有的话创建一个 Pipeline 项目。

前端工程化系列之配置 Jenkins 给代码托管在 Gitee 的前端项目做 PR code 质量检查-天真的小窝

新创建的项目的话在项目配置页面主要需要关注三个配置 Gitee connection(主要是机器人发布 PR 评论时用到)、Triggers(gitee webhook 触发 CI 的配置)、Pipeline(具体 CI 流水线任务配置,如果是添加项目的话基本上只需要在这个步骤中添加仓库地址即可,小伙伴们给自己负责的项目代码仓库加上 CI 检查配置非常简单快来试试吧)

Gitee connection

首先是 Gitee connection 这个位置选择刚刚添加在 system 配置中的 bot token 即可

前端工程化系列之配置 Jenkins 给代码托管在 Gitee 的前端项目做 PR code 质量检查-天真的小窝

Triggers

在 Triggers 配置部分主要关注 Gitee webhook URL 部分和下面的 Secret Token,后续在 gitee 的项目 hook 配置中需要填写这个部分的地址可 token。其他配置项大家可以根据自己需求调整。

Pipeline

新创建的项目在这个部分先选择 Definition 类型为 Pipeline script from SCM,这样就能通过代码仓库中配置好的 Jenkinsfile 来执行脚本了,Script Path 当然就是我们上面创建的 deploy/check.Jenkinsfile 文件

前端工程化系列之配置 Jenkins 给代码托管在 Gitee 的前端项目做 PR code 质量检查-天真的小窝

如果你是在已有项目基础上添加多个代码仓库来执行任务,只需要添加一个仓库,输入 git 地址和选择部署 key 即可。

前端工程化系列之配置 Jenkins 给代码托管在 Gitee 的前端项目做 PR code 质量检查-天真的小窝

配置 gitee webhook

到这里就基本准备好 Jenkins 了,最后还需要在 gitee 的项目配置中添加一个 webhook 来触发 CI 工作即可,如果没有配置部署 key 的话需要配置一下,以及记得将 bot 账号作为开发者添加到仓库,配置流程可参考下图。

前端工程化系列之配置 Jenkins 给代码托管在 Gitee 的前端项目做 PR code 质量检查-天真的小窝
前端工程化系列之配置 Jenkins 给代码托管在 Gitee 的前端项目做 PR code 质量检查-天真的小窝

这里填写的 URL 和 密码就是上面在 Jenkins Triggers 中获取的 URL 和 token,后续添加任何项目只需要三步即可配置好 CI 代码质量检查。

  1. 在代码仓库推送 deploy/check.Jenkinsfile 文件
  2. 在 Jenkins Pipeline 配置中添加仓库地址
  3. 在 gitee webhook 配置触发事件,添加 bot 账号到代码仓库

当然,在此基础上我们还可以补充完善 Jest/vitest 测试、样式变更快照检查、nightly 预览编译部署(用于 PR 审查时基于 commit hash 预览更改)等有助于项目质量提升的自动化工作…