Node 后端 git 提交规范化处理
创建项目
以 koa 为例
使用 koa-generator
创建项目
# 安装
npm install -g koa-generator
# 创建项目
koa2 test-demo
# 安装依赖
cd test-demo
npm install
# 启动
npm run dev
访问 http://localhost:3000/
创建 .gitignore
忽略掉 node_modules
node_modules/
windows 系统 注意把 dev 路径的 斜杠(/)替换成 (\\)
提交前检查
代码格式
使用 eslint
+ prettier
# 安装
npm install eslint eslint-config-airbnb-base eslint-plugin-import eslint-config-prettier eslint-plugin-prettier -D
创建 .eslintrc.js
module.exports = {
env: {
commonjs: true,
es2020: true,
node: true,
jest: true,
es6: true,
},
extends: ['airbnb-base', 'plugin:prettier/recommended'],
parserOptions: {
ecmaVersion: 12,
},
plugins: ['prettier'],
rules: {
'no-unused-vars': 0,
'no-console': 'off',
'max-classes-per-file': 0,
'prettier/prettier': [
'error',
{ singleQuote: true, semi: false, arrowParens: 'avoid' },
],
'arrow-body-style': 'off',
'prefer-arrow-callback': 'off',
},
}
创建 .eslintignore
node_modules/
在跟目录创建一个 src 文件夹,将 public,routes,view 放到 src 下
同时也将 app.js 放到 src 下,并且修改 bin/www 将 const app = require('../app')
改为 const app = require('../src/app')
在 package.json 创建脚本
"script": {
...,
"lint": "eslint \"src/**/*.{js,ts}\"",
"lint-fix": "eslint --fix \"src/**/*.{js,ts}\""
}
# lint 检查
npm run lint
参考
代码测试
这里使用 jest
# 安装
npm install --save-dev jest
在根目录创建 __test__
文件夹
在目录下创建 sum.test.js
function sum(a, b) {
return a + b
}
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3)
})
配置执行脚本,参数
"script": {
"test": "jest --runInBand --passWithNoTests --colors --forceExit",
}
# 执行
npm run test
接口测试
这里使用 supertest
# 安装
npm install supertest --save-dev
# 安装 axios
npm install axios --save-dev
在 __test__
下创建 apis 文件夹
创建 _server.js
文件,使用 supertest mock 请求接口
const axios = require('axios')
const supertest = require('supertest')
let request
const app = require('../../src/app')
const isTestLocal = process.env.NODE_ENV === 'local'
const isTestRemote = process.env.NODE_ENV === 'remote'
if (isTestLocal) {
// 通过 supertest 创建服务
const server = app.callback()
request = supertest(server)
}
// 测试机 host
const REMOTE_HOST = 'http://localhost:3000'
/**
* 发送请求
* @param {*} method
* @param {*} url
* @param {*} bodyOrParams
* @param {*} headers
*/
async function ajax(method = 'get', url = '', bodyOrParams = {}, headers = {}) {
// 如果有 token 可以在 headers 设置
let result
// 本身测试,使用 supertest
if (isTestLocal) {
let res
if (method === 'get') {
res = await request[method](url).query(bodyOrParams).set(headers)
} else {
res = await request[method](url).send(bodyOrParams).set(headers)
}
result = res
}
// 远程测试机,使用 axios
if (isTestRemote) {
const remoteUrl = `${REMOTE_HOST}${url}`
const conf = {
method,
url: remoteUrl,
headers,
}
if (method === 'get') {
conf.params = bodyOrParams
} else {
conf.body = bodyOrParams
}
const res = await axios(conf)
result = res
}
// 返回结果
return result
}
module.exports = {
async get(url, params, headers) {
const res = await ajax('get', url, params, headers)
return res
},
async post(url, body, headers) {
const res = await ajax('post', url, body, headers)
return res
},
async patch(url, body, headers) {
const res = await ajax('patch', url, body, headers)
return res
},
async del(url, body, headers) {
const res = await ajax('delete', url, body, headers)
return res
},
}
由于使用了环境变量 NODE_ENV
# 安装
npm install cross-env --save-dev
修改脚本
"script": {
"test:local": "cross-env NODE_ENV=local jest --runInBand --passWithNoTests --colors --forceExit",
"test:remote": "cross-env NODE_ENV=remote jest --runInBand --passWithNoTests --colors --forceExit"
}
创建测试文件 user-check.js
const { get } = require('./_server')
it('users/bar 测试', async () => {
const { text, status } = await get('/users/bar')
expect(status).toBe(200)
expect(text).toBe('this is a users/bar response')
})
在 apis 文件夹下创建 index.test.js
// 导入
require('./user-check')
# 执行
npm run test:local
提交前处理
使用 pre-commit
+ husky
+ lint-staged
# 安装
npm i pre-commit husky lint-staged --save-dev
# 初始化 husky
npm set-script prepare "husky install"
npm run prepare
在 package.json 中配置 lint-staged
{
"lint-staged": {
"*.js": [
"npm run lint",
"npm run test:local"
]
}
}
# 如果没有安装 npx 命令
npm install -g npx
# 添加 pre-commit 配置
npx husky add .husky/pre-commit 'npx lint-staged "$1"'
随便修改一个 src 下的 .js 文件,用于测试
# 执行
git add .
git commit -m "测试 commit 前的代码检查"
会根据 lint-staged 配置的,检查 git 缓存,匹配 *.js 文件,然后执行脚本检查代码
# 推送
git push
参考
提交信息规范
使用 @commitlint
# 安装
npm install -g @commitlint/cli @commitlint/config-conventional
配置 husky
npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
在跟目录下添加 commitlint.config.js
文件
module.exports = { extends: ['@commitlint/config-conventional'] }
# 测试
git add .
git commit -m "commitlint 测试"
# No staged files match any configured task.
# ⧗ input: commitlint 测试
# ✖ subject may not be empty [subject-empty]
# ✖ type may not be empty [type-empty]
# ✖ found 2 problems, 0 warnings
# ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint
# 提交格式
git commit -m <type>[optional scope]: <description>
类型 | 描述 |
---|---|
build | 编译相关的修改,例如发布版本、对项目构建或者依赖的改动 |
chore | 其他修改, 比如改变构建流程、或者增加依赖库、工具等 |
ci | 持续集成 |
docs | 文档修改 |
feat | 新特性、新功能 |
fix | 修复 bug |
perf | 代码优化相关,比如提升性能、体验 |
refactor | 代码重构 |
revert | 回滚到上一个版本 |
style | 代码格式修改, 注意不是 css 修改 |
test | 测试相关,如测试用例修改 |
# 正确写法,注意分号后面的空格
git commit -m "feat: 增加 commitlint 功能"
参考
commitlint - Lint commit messages
npx 使用教程 - 阮一峰的网络日志 (ruanyifeng.com)
提交信息提示功能
使用 commitizen
# 全局安装
npm install -g commitizen
# 安装配置
commitizen init cz-conventional-changelog --save-dev --save-exact
# 测试
git add .
# 使用 git cz 进行提交,然后根据提示,输入内容,便可
git cz
# 推送
git push
使用 cz-customizable
自定义提示内容
# 安装
npm install cz-customizable --save-dev
// 在 package.json 将 cz-conventional-changelog 的配置
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
// 更改为,不再使用 cz-conventional-changelog
"config": {
"commitizen": {
"path": "./node_modules/cz-customizable"
}
}
在根目录下创建 .cz-config.js
cz-customizable/cz-config-EXAMPLE.js at master · leoforfree/cz-customizable (github.com)
// 这里使用的是样例的配置,可根据需要自定义
module.exports = {
types: [
{ value: 'feat', name: 'feat: A new feature' },
{ value: 'fix', name: 'fix: A bug fix' },
{ value: 'docs', name: 'docs: Documentation only changes' },
{
value: 'style',
name:
'style: Changes that do not affect the meaning of the code\n (white-space, formatting, missing semi-colons, etc)',
},
{
value: 'refactor',
name: 'refactor: A code change that neither fixes a bug nor adds a feature',
},
{
value: 'perf',
name: 'perf: A code change that improves performance',
},
{ value: 'test', name: 'test: Adding missing tests' },
{
value: 'chore',
name:
'chore: Changes to the build process or auxiliary tools\n and libraries such as documentation generation',
},
{ value: 'revert', name: 'revert: Revert to a commit' },
{ value: 'WIP', name: 'WIP: Work in progress' },
],
scopes: [{ name: 'accounts' }, { name: 'admin' }, { name: 'exampleScope' }, { name: 'changeMe' }],
allowTicketNumber: false,
isTicketNumberRequired: false,
ticketNumberPrefix: 'TICKET-',
ticketNumberRegExp: '\\d{1,5}',
// it needs to match the value for field type. Eg.: 'fix'
/*
scopeOverrides: {
fix: [
{name: 'merge'},
{name: 'style'},
{name: 'e2eTest'},
{name: 'unitTest'}
]
},
*/
// override the messages, defaults are as follows
messages: {
type: "Select the type of change that you're committing:",
scope: '\nDenote the SCOPE of this change (optional):',
// used if allowCustomScopes is true
customScope: 'Denote the SCOPE of this change:',
subject: 'Write a SHORT, IMPERATIVE tense description of the change:\n',
body: 'Provide a LONGER description of the change (optional). Use "|" to break new line:\n',
breaking: 'List any BREAKING CHANGES (optional):\n',
footer: 'List any ISSUES CLOSED by this change (optional). E.g.: #31, #34:\n',
confirmCommit: 'Are you sure you want to proceed with the commit above?',
},
allowCustomScopes: true,
allowBreakingChanges: ['feat', 'fix'],
// skip any questions you want
skipQuestions: ['body'],
// limit subject length
subjectLimit: 100,
// breaklineChar: '|', // It is supported for fields body and footer.
// footerPrefix : 'ISSUES CLOSED:'
// askForBreakingChangeFirst : true, // default is false
};