# cicdTest **Repository Path**: andy-code1/cicd-test ## Basic Information - **Project Name**: cicdTest - **Description**: cicd的示例和git hook的模板 实现github上面的流程,可以在push自动发短信。自定义eslint检验和audit扫描 和进行单元测试。hook实现husky和lint-staged还有version自动增加和commit提交文本的规范(这里用python的hook来做) - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2022-09-16 - **Last Updated**: 2022-09-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 1.cicd github 上面上传就可以了。 详情看.github/workflows/main.yml 下面是一些示例图片 总结一下,还是github好用 CI(持续集成)CD(持续部署):当代码 push 到远程仓库后,借助 `WebHooks` 对当前代码在构建服务器(即 CI 服务器,也称作 Runner)中进行自动构建、测试及部署等 `CICD` 集成于 CICD 工具及代码托管服务。CICD 有时也可理解为进行 CICD 的构建服务器,而提供 CICD 的服务,如以下产品,将会提供构建服务与 github/gitlab 集成在一起。 cicd策略 1. 主分支禁止直接 PUSH 代码 2. 代码都必须通过 PR 才能合并到主分支 3. **分支必须 CI 成功才能合并到主分支** 4. 代码必须经过 Code Review (关于该 PR 下的所有 Review 必须解决) 5. 代码必须两个人同意才能合并到主分支 ``` workflow (流程):持续集成一次运行的过程,就是一个 workflow。 job (任务):一个 workflow 由一个或多个 jobs 构成,含义是一次持续集成的运行,可以完成多个任务。 step(步骤):每个 job 由多个 step 构成,一步步完成。 action (动作):每个 step 可以依次执行一个或多个命令(action) ``` ## step1:开通和helloworld https://gitee.com/features/gitee-go 中开通,或者具体的项目中开通gitee go 当我们选择用node.js的方式进行创建的时候会生成master-pipeline.yml gitee ```yaml version: '1.0' name: master-pipeline displayName: MasterPipeline stages: - stage: name: compile displayName: 编译 steps: - step: build@nodejs name: build_nodejs displayName: Nodejs 构建 # 支持8.16.2、10.17.0、12.16.1、14.16.0、15.12.0五个版本 nodeVersion: 14.16.0 # 构建命令:安装依赖 -> 清除上次打包产物残留 -> 执行构建 【请根据项目实际产出进行填写】 commands: - npm install && rm -rf ./dist && npm run build # 非必填字段,开启后表示将构建产物暂存,但不会上传到制品库中,7天后自动清除 artifacts: # 构建产物名字,作为产物的唯一标识可向下传递,支持自定义,默认为BUILD_ARTIFACT。在下游可以通过${BUILD_ARTIFACT}方式引用来获取构建物地址 - name: BUILD_ARTIFACT # 构建产物获取路径,是指代码编译完毕之后构建物的所在路径 path: - ./dist - step: publish@general_artifacts name: publish_general_artifacts displayName: 上传制品 # 上游构建任务定义的产物名,默认BUILD_ARTIFACT dependArtifact: BUILD_ARTIFACT # 上传到制品库时的制品命名,默认output artifactName: output dependsOn: build_nodejs - stage: name: release displayName: 发布 steps: - step: publish@release_artifacts name: publish_release_artifacts displayName: '发布' # 上游上传制品任务的产出 dependArtifact: output # 发布制品版本号 version: '1.0.0.0' # 是否开启版本号自增,默认开启 autoIncrement: true triggers: # 什么时候会触发,这里是在push代码的时候会触发 push: branches: include: - master ``` github 点击actions,点击 set up a workflow yourself ```yaml # 流程名字 name: CI # 什么时候会触发 on: #schedule: # 每天8:30做一些事情 #- cron: '30 8 * * *' push: branches: [ "main" ] pull_request: branches: [ "main" ] types: - opened# 当新建了一个 PR 时 - synchronize# 当提交 PR 的分支,未合并前并拥有新的 Commit 时 workflow_dispatch: # 具体做的事情 jobs: # 命名这玩意叫做build build: # 表示在什么系统下面运行的 runs-on: ubuntu-latest # 每一个- 代表一个步骤 steps: # 把代码下载下来 - uses: actions/checkout@v3 # 运行一行脚本 - name: Run a one-line script run: echo Hello, world! # 运行两行脚本 - name: Run a multi-line script run: | echo Add other actions to build, echo test, and deploy your project. ``` ## step2:实战(github) 每一次提交之前我们通过 Lint4j、TSLint、ESLint 来进行代码检验 Test一般是指单元测试 ``` 1. 任务的并行与串行 在 CI 中,互不干扰的任务并行执行,可以节省很大时间。如 Lint 和 Test 无任何交集,就可以并行执行。 但是 Lint 和 Test 都需要依赖安装 (Install),在依赖安装结束后再执行,此时就是串行的。 「而进行串行时,如果前一个任务失败,则下一个任务也无法继续。即如果测试无法通过,则无法进行 Preview,更无法上线。」 ``` node文件 ```js const axios = require('axios') var nodemailer = require('nodemailer'); function getApi(address) { return new Promise((resolve) => { axios .get('https://restapi.amap.com/v3/geocode/geo', { params: { key: '02173ea51a9245ef63966988c96a3a67', address, }, }) .then((resX) => { axios .get('https://restapi.amap.com/v3/weather/weatherInfo', { params: { key: '02173ea51a9245ef63966988c96a3a67', city: +resX.data.geocodes[0].adcode, }, }) .then((res) => { resolve(res.data) }) }) }) } async function main() { let params = "广东省广州市天河区"; let res = await getApi(params) console.error("天气:", res.lives[0].temperature) // 创建一个SMTP客户端配置 var config = { host: 'smtp.qq.com',//网易163邮箱 smtp.163.com port: 465,//网易邮箱端口 25 auth: { user: '3451613934@qq.com', //邮箱账号 pass: 'exhpspuprkyecidd' //邮箱的授权码 } }; // 创建一个SMTP客户端对象 var transporter = nodemailer.createTransport(config); // 发送邮件 function send(mail) { transporter.sendMail(mail, function (error, info) { if (error) { return console.log(error); } console.log('mail sent:', info.response); }); }; // 创建一个邮件对象 var mail = { // 发件人 from: '3451613934@qq.com', // 主题 subject: "天气: "+res.lives[0].temperature, // 收件人 to: '895361337@qq.com', // 邮件内容,HTML格式 text: res.lives[0].temperature //可以是链接,也可以是验证码 }; send(mail); } main() ``` .github/workflows ```yaml # 关于本次 workflow 的名字 name: CI # 执行 CI 的时机: 当 git push 代码到 github 时 on: # schedule: # 每天8:30做一些事情 # - cron: '30 8 * * *' push: branches: [ "main" ] pull_request: branches: [ "main" ] types: # 当新建了一个 PR 时 # 当提交 PR 的分支,未合并前并拥有新的 Commit 时 - opened - synchronize # 执行所有的 jobs jobs: #命名成lint lint: runs-on: ubuntu-latest steps: # 切出代码,使用该 Action 将可以拉取最新代码 #- uses: actions/checkout@v2 # 配置 node.js 环境,此时使用的是 node14 # 注意此处 node.js 版本,与 Docker 中版本一致,与 package.json 中 engines.node 版本一致 # 如果需要测试不同 node.js 环境下的表现,可使用 matrix # uses: actions/checkout@v2 这玩意可以在 ubuntu-20.04, ubuntu-18.04,上进行测试 - name: Setup Node uses: actions/checkout@v2 with: node-version: 14.x. # 安装依赖 - name: Install Dependencies run: npm install # 在 cra 中,使用 npm run lint 来模拟 ESLint - name: ESLint run: npm run lint # 测试这玩意 - name: node脚本获取天气 run: node test.js # 在 npm audit --json - name: audit依赖包扫描 run: npm audit --json #运行一段脚本试试水 - name: Run a one-line script run: echo Hello, world! #命名成error error: runs-on: ubuntu-latest steps: # 运行bash试试水 - name: Run a multi-line script shell: bash run: | sh pulish001.sh ``` ## step3:实战(gitee) 不行放弃 ```yaml version: '1.0' name: 测试 displayName: MasterPipeline stages: - stage: name: compile displayName: 编译 steps: - step: build@nodejs name: build_nodejs displayName: Nodejs 构建 # 支持8.16.2、10.17.0、12.16.1、14.16.0、15.12.0五个版本 nodeVersion: 14.16.0 # 构建命令:安装依赖 -> 清除上次打包产物残留 -> 执行构建 【请根据项目实际产出进行填写】 commands: - npm install - stage: name: release displayName: lint测试 steps: - step: publish@release_artifacts name: publish_release_artifacts displayName: '发布' commands: - npm run lint triggers: push: branches: include: - master ``` ## step4:高级CI检查 除了上面的lint和test 检查外 1.npm audit 可以分析不安全的依赖以及不是可能有问题的依赖 # 2.git hook 最终效果
## 2.1 git hook ### hook demo 为了代码的规范有必要进行 log 规范化检查。而检查的入口可以从 git hook 切入,而 git hook 却又有无限的遐想。 钩子都被存储在 Git 目录下的 hooks 子目录中。 也即绝大部分项目中的 `.git/hooks`,默认存在的都是示例,其名字都是以 `.sample` 结尾,如果你想启用它们,得先移除这个后缀。把一个正确命名且可执行的文件放入 Git 目录下的 hooks 子目录中,即可激活该钩子脚本。 这样一来,它就能被 Git 调用。 你可以用来检查消息、检查代码,可以用来触发任意流程,譬如自动规范检查等等 ``` 有两种类型的hook 一种是服务端的hook, receive之类的 一种是客户端的hook。precommiit之类的 有几种钩子的情况 msg(应用程序消息) pre(应用前批处理) post(应用程序批处理后) hook,这其实是计算机领域中一个很常见的概念,hook 翻译过来的意思是钩子或者勾住,而在计算机领域中则要分为两种解释: 拦截消息,在消息到达目标前,提前对消息进行处理 对特定的事件进行监听,当某个事件或动作被触发时也会同时触发对应的 hook 也就是说 hook 本身也是一段程序,只是它会在特定的时机被触发。 ``` 在.git/hook/ ```python #!/usr/bin/env python # coding=utf-8 # # commit msg check import sys import re import io import os if hasattr(sys.stdout, 'buffer'): sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') TIPS_INFO = ''' 不符合commit规范,提交失败(当前状态等于没作刚刚的commit操做)! commit规范: 类型 详细消息 规范样例: git commit -m "xxxxx xxxxxxxxxxxxx" !!!!提交失败!!!! ''' def check_commit_line1_format(msg): regOther = r'\S{5,} (.){10,100}' matchObj = re.match(regOther, msg) return matchObj if __name__=="__main__": print("进行lint扫描") os.system("npm run lint") print("进行audit扫描") os.system("npm audit") with open(sys.argv[1], 'r') as f: for line in f: if (check_commit_line1_format(line)): sys.exit(0) else: print(TIPS_INFO) sys.exit(1) ``` **一段好用的可以自动更新版本的工具js **package_version_auto_add.js ```js const execSync = require('child_process').execSync const path = require('path') const fs = require('fs') console.log('------------ 开发自动升级package.json版本号 ------------'); const projectPath = path.join(__dirname, './') const packageJsonStr = fs.readFileSync('./package.json').toString() try { const packageJson = JSON.parse(packageJsonStr) // 升级版本号 const arr = packageJson.version.split('.') if (arr[2] < 9) { arr[2] = +arr[2] + 1 } else if (arr[1] < 9) { arr[1] = +arr[1] + 1 arr[2] = 0 } else { arr[0] = +arr[0] + 1 arr[1] = 0 arr[2] = 0 } const newVersion = arr.join('.') packageJson.version = newVersion // console.log(packageJson); fs.writeFileSync('./package.json', JSON.stringify(packageJson, null, '\t')) // add new package.json execSync(`git add package.json`) } catch (e) { console.error('处理package.json失败,请重试', e.message); process.exit(1) } ``` package.json中 ```js "scripts": { "serve": "cross-env NODE_ENV=production vue-cli-service serve", "dev": "vue-cli-service serve --mode=development", "build": "vue-cli-service build", "lint": "vue-cli-service lint", "analyz": "webpack-bundle-analyzer --port 8888 ./dist/stats.json", "test": "node test.js", "prepare": "husky install", "package_version_auto_add": "node package_version_auto_add.js ", "eslint:check": "eslint src/*.vue" }, ``` 这里有一个问题,就是我们如果进行了git代码提交后,git上面的源码并不会得到保留 ### **因此这里我们要用到husty** husky的原理是在.git/config文件的[core]中添加 hooksPath = .husky就是原理了 官网:https://typicode.github.io/husky/#/?id=manual step1:初始化 ```js 命令行中 npm install husky@8 -D package.json中添加 "scripts": { "prepare": "husky install", }, ``` npm run prepare,构建一般目录 step2:添加钩子 然后如果是在powershell中我们构建,我们可以进行运行 ``` npx husky add .husky/pre-commit "npm-run-lint" ``` 如果在cmd中我们可以 ``` npx husky add .husky/pre-commit "npm run lint" ``` 我们还可以在husky文件夹下面新建precommit,我们写入 ```sh #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" npm run lint 是一样的效果 ``` 校验名字-这玩意我写的贼牛皮-commit-msg ```python #!/usr/bin/env python # coding=utf-8 # # commit msg check import sys import re import io import os if hasattr(sys.stdout, 'buffer'): sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') TIPS_INFO = ''' 不符合commit规范,提交失败(当前状态等于没作刚刚的commit操做)! commit规范: 类型 详细消息 规范样例: feat:类型是feat表示在代码库中新增了一个功能 git commit -m "feat: 增加了xxx功能" fix:类型是fix表示在代码库中修复了了一个bug git commit -m "fix: 修复了xxx功能" !!!!提交失败!!!! ''' def check_commit_line1_format(msg): print(msg) # regOther = r'\S{5,} (.){10,100}' ^fix:|^feat: ((修复了)|(增加了))(.){2,100}功能 regOther = r'^fix:|^feat: ((修复了)|(增加了))(.){1,100}功能' matchObj = re.match(regOther, msg) return matchObj if __name__=="__main__": # print("进行lint扫描") # os.system("npm run lint") print("进行audit扫描") os.system("npm audit") # print(sys) with open(sys.argv[1], 'r',encoding="utf-8") as f: for line in f: if (check_commit_line1_format(line)): sys.exit(0) else: print(TIPS_INFO) sys.exit(1) ``` ## 2.2 npx 和 npm 区别1.一个永久存在,一个临时安装,用完后删除 区别2.npx 会帮你执行依赖包里的二进制文件。 区别3.npx可以执行文件,但是npm不可以 ## 2.3 eslint 配置(格式化工具) 根目录下面新建.eslintrc.js step1:eslint安装初始化 ``` npm i eslint@7.32 -D ``` package.json中 ```json 写入 "scripts": { "eslint:check": "eslint src/*.vue" "eslint:check": "eslint ./" //就是检查所有的东西 ,这一部分依赖于package.json同级目录下面的.eslintrc.js文件 }, ``` step2: .eslintrc.js中,我们在这里配置具体的规则 ```js module.exports = { root: true, env: { browser: true, node: true, }, extends: [ // 额外添加的规则可查看 https://vuejs.github.io/eslint-plugin-vue/rules/ 'plugin:vue/essential', //加了这个才能当作vue来进行解析,不然只会当成js来进行解析 ], //不加这个会报错Parsing error: The keyword 'import' is reserved parserOptions: { // 指定解析器 parser "ecmaVersion": 7, "sourceType": "module" // parser: 'babel-eslint', // sourceType: 'module', // ecmaVersion: 12, // allowImportExportEverywhere: true, // 不限制eslint对import使用位置 }, settings: { }, // 取消没必要的校验 0 是 不报错 , 1是warn 2 是punishing // "off"或者0 //关闭规则关闭 // "warn"或者1 //在打开的规则作为警告(不影响退出代码) // "error"或者2 //把规则作为一个错误(退出代码触发时为1) rules: { // "camelcase": ["error", { "allow": ["aa_bb"] }], // 属性名 // "max-lines": ["error", {"max": 20, "skipComments": true}], // 每一个文件最多的行数 // "no-console": 2,//禁止使用console // "no-mixed-spaces-and-tabs": [2, false],//禁止混用tab和空格 // "no-multiple-empty-lines": [1, {"max": 2}],//空行最多不能超过2行 // "no-multi-spaces": 1,//不能用多余的空格 // "indent": [1, 4],//缩进风格 缩进四个空格 "max-lines-per-function": [0, { "max": 2 }], // 指定每个function最多多少行 "no-unused-vars":1, //没被使用 'consistent-return': 0, // 有函数返回值 'no-underscore-dangle': 0, // 不允许有下划线 'no-plusplus': 1, // 不能用++ 'no-eval': 0, // 不能用eval 'linebreak-style': [0, 'error', 'window'], // 换行风格 'camelcase': 'warn', //像是xx_xx这种会报错 }, }; ``` package.json里面,这里是为了vue运行的时候在里面添加校验的东西------后来发现靠webpack检验不是很靠谱,会有延迟的现象。于是就在vs code下面下载eslint插件还更好用一点。eslint v2.2.2 插件 ```json "eslintConfig": { "root": true, "env": { "node": true }, "extends": [ "plugin:vue/essential", "eslint:recommended" ], "parserOptions": { "parser": "@babel/eslint-parser" }, "rules": { "max-lines-per-function": [0, { "max": 2 }], "no-unused-vars":1, "consistent-return": 0, "no-underscore-dangle": 0, "no-plusplus": 1, "no-eval": 0, "linebreak-style": [0, "error", "window"], "camelcase": "warn" } }, ``` step3:如果说是遇到了奇怪的eslint报错,可以新建.eslintignore文件把他忽略就行了 ```js *.js ``` ## 2.4 prettier 安装 step1:安装prettier ``` npm i -D eslint-config-prettier@8.5 eslint-plugin-prettier@4.0 prettier@2.6.2 prettier-eslint-cli@5.0.1 ``` 安装了之后我们在vscode中下载prettic eslint 5.0.4 step2: 新建.prettierrc.js我们 ,如果要进行内容实时的更新。我们点击一下右下角的按钮就可以刷新 ```js module.exports = { // 最大长度160个字符 printWidth: 120, // 行末分号 semi: false, // 单引号 singleQuote: false, // JSX双引号 jsxSingleQuote: false, // 尽可能使用尾随逗号(包括函数参数) trailingComma: 'none', // 在对象文字中打印括号之间的空格。 bracketSpacing: true, // > 标签放在最后一行的末尾,而不是单独放在下一行 jsxBracketSameLine: false, // 箭头圆括号 arrowParens: 'avoid', // 在文件顶部插入一个特殊的 @format 标记,指定文件格式需要被格式化。 insertPragma: false, // 缩进 tabWidth: 2, // 使用tab还是空格 useTabs: false, // 行尾换行格式 endOfLine: 'auto', HTMLWhitespaceSensitivity: 'ignore' } ``` ## 2.5 lint-staged 在代码提交之前,进行代码规则检查能够确保进入git库的代码都是符合代码规则的。但是整个项目上运行lint速度会很慢,lint-staged能够让lint只检测暂存区的文件,所以速度很快。 step1:初始化 ``` npm install lint-staged@11.1.2 -D ``` step2:package.json添加 ```js { "name": "vue_helloworld", "version": "0.1.3", "scripts": { "serve": "cross-env NODE_ENV=production vue-cli-service serve", "dev": "vue-cli-service serve --mode=development", "build": "vue-cli-service build", "lint": "vue-cli-service lint", "analyz": "webpack-bundle-analyzer --port 8888 ./dist/stats.json", "test": "node test.js", "prepare": "husky install", "lint-staged": "lint-staged", "eslint:check": "eslint ./", "package_version_auto_add": "node package_version_auto_add.js" }, "dependencies": { "@popperjs/core": "^2.11.6", "@swc/core": "1.3", "axios": "^0.27.2", "babel-eslint": "^10.2.0", "bootstrap": "^5.1.3", "core-js": "^3.8.3", "echarts": "^5.3.2", "element-ui": "^2.15.8", "frontutilpackage": "0.0.1", "less": "^4.1.2", "less-loader": "^12.0.0", "nodemailer": "^6.7.8", "qs": "^6.12.0", "swc": "^2.0.11", "swc-loader": "^0.1.16", "vue": "^2.6.14", "vue-router": "^3.1.3", "vuex": "^3.6.2", "webpack": "5.2" }, "devDependencies": { "@babel/core": "^7.12.16", "@babel/eslint-parser": "^7.12.16", "@vue/cli-plugin-babel": "~5.0.0", "@vue/cli-plugin-eslint": "~5.0.0", "@vue/cli-service": "~5.0.0", "cross-env": "^7.0.3", "eslint": "^7.32.0", "eslint-plugin-vue": "^8.0.3", "husky": "^8.0.1", "jquery": "^3.6.0", "lint-staged": "^11.1.2", "prettier": "^2.7.1", "terser-webpack-plugin": "^5.3.6", "thread-loader": "^3.0.4", "vue-template-compiler": "^2.6.14", "webpack-bundle-analyzer": "^4.6.1" }, "husky": { }, "lint-staged": { "*.js": [ "npm run test", "git add" ], "*.vue": [ "npm run lint", "git add" ] }, "browserslist": [ "> 1%", "last 2 versions", "not dead" ] } ``` step3:**创建 .lintstagedrc** ```js { "src/**/*.{js,ts,tsx,vue}": "npm run lint:eslint", "src/**/*.{vue,css,scss}": "npm run lint:stylelint" } ------------------------ { "*.{js,ts,tsx,vue}": "npm run package_version_auto_add", } { "./src/*.vue": "npm run eslint:check" } ``` step4 :注意 eslint ecmaVersion不用8的话async await会报错 ```js // npm install babel-eslint --save module.exports = { root: true, env: { browser: true, node: true, }, extends: [ // 额外添加的规则可查看 https://vuejs.github.io/eslint-plugin-vue/rules/ 'plugin:vue/essential', //加了这个才能当作vue来进行解析,不然只会当成js来进行解析 ], //不加这个会报错Parsing error: The keyword 'import' is reserved parserOptions: { // 指定解析器 parser "ecmaVersion": 8, "sourceType": "module", // "parser": 'babel-eslint', // sourceType: 'module', // ecmaVersion: 12, // allowImportExportEverywhere: true, // 不限制eslint对import使用位置 }, settings: { }, // 取消没必要的校验 0 是 不报错 , 1是warn 2 是punishing // "off"或者0 //关闭规则关闭 // "warn"或者1 //在打开的规则作为警告(不影响退出代码) // "error"或者2 //把规则作为一个错误(退出代码触发时为1) rules: { // "camelcase": ["error", { "allow": ["aa_bb"] }], // 属性名 // "max-lines": ["error", {"max": 20, "skipComments": true}], // 每一个文件最多的行数 // "no-console": 2,//禁止使用console // "no-mixed-spaces-and-tabs": [2, false],//禁止混用tab和空格 // "no-multiple-empty-lines": [1, {"max": 2}],//空行最多不能超过2行 // "no-multi-spaces": 1,//不能用多余的空格 // "indent": [1, 4],//缩进风格 缩进四个空格 // "max-lines-per-function": [0, { "max": 2 }], // 指定每个function最多多少行 "no-unused-vars":1, //没被使用 'consistent-return': 0, // 有函数返回值 'no-underscore-dangle': 0, // 不允许有下划线 'no-plusplus': 1, // 不能用++ 'no-eval': 0, // 不能用eval 'linebreak-style': [0, 'error', 'window'], // 换行风格 'camelcase': 'warn', //像是xx_xx这种会报错 }, }; ``` step5:husty/pre-commit 中写入 ```sh #!/usr/bin/env sh # npm run lint # npm run lint-staged ```