检测项目中的包版本变化

概述

由于项目中的某个依赖版本意外的升级导致项目中的部分功能不可使用

目的

每次 package.json 有更新时检测依赖版本是否有更新,小版本更新提示,大版本更新直接警告,避免依赖包的意外升级

准备

  • git 检测指定文件的内容变更
  • 正则等方式分割变更内容,提取出对应的信息
  • 在 node 脚本中运行上述命令
  • 在 commit 之前检测变更并提示

实现

提取内容变更

  • git diff 查看尚未暂存的文件更新了哪些部分
  • git diff filename 查看尚未暂存的某个文件更新了哪些
  • git diff –cached 查看已经暂存起来的文件和上次提交的版本之间的差异
  • git diff –cached filename 查看已经暂存起来的某个文件和上次提交的版本之间的差异
  • git diff commitHash commitHash 查看某两个版本之间的差异
  • git diff commitHash:filename commitHash:filename 查看某两个版本的某个文件之间的差异

node 脚本中运行命令

输出内容

1
2
+  "packageA": "~x.y.z",
- "packageB": "^x.y.z",

观察得出变化内容以 +- 开头,依赖包的格式固定,每行变更内容以换行符分割,所以先用换行符分割内容,再检查 +- 筛选出变化内容,最后用正则等方法分离出依赖包的名称和版本,最后通过前后版本的比较获取变化的包及包版本

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#! /usr/bin/env node
const childProcess = require('child_process')
const path = require('path')

const { logger, warn, info } = require('./logger')

function resolveRootPath(...args) {
return path.resolve(process.cwd(), ...args)
}

// 获取变化
function getDiff(isCached = false) {
try {
const result = childProcess.execSync(
`git diff ${isCached ? '--cached' : ''} ${resolveRootPath(
'./package.json'
)}`,
{ encoding: 'utf8' }
)
return result
} catch (e) {
return ''
}
}

// 序列化版本为对象
function serializeDiff(diffStr) {
const VERSION_REG = /\"\S+\"\s*:\s*\"(~|\-|\^)?(\d+)\.(\d+)\.(\d+)/
const arr = `${diffStr || ''}`
.split('\n')
.filter((str) => /^(\-|\+)\s+/.test(str))
.filter((str) => VERSION_REG.test(str))
const beforeArr = arr
.filter((str) => /^\-\s+/.test(str))
.map((str) => str.replace(/^\-\s+/, ''))
.map((str) => str.replace(/\,$/, ''))
const afterArr = arr
.filter((str) => /^\+\s+/.test(str))
.map((str) => str.replace(/^\+\s+/, ''))
.map((str) => str.replace(/\,$/, ''))

const beforeObj = beforeArr.reduce((prev, curr) => {
const [key, value] = curr.replace(/"/g, '').split(/\s*:\s*/)
prev[key] = value
return prev
}, {})
const afterObj = afterArr.reduce((prev, curr) => {
const [key, value] = curr.replace(/"/g, '').split(/\s*:\s*/)
prev[key] = value
return prev
}, {})
return [beforeObj, afterObj]
}

function getChange(before, after) {
const beforeKeys = Object.keys(before)
const afterKeys = Object.keys(after)

const deletePackage = beforeKeys
.filter((key) => !after[key])
.map((key) => `${key}: ${after[key]}`)
const addPackage = afterKeys
.filter((key) => !before[key])
.map((key) => `${key}: ${after[key]}`)
const updatePackage = beforeKeys
.filter((key) => after[key])
.map((key) => `${key}: ${before[key]} ===> ${after[key]}`)

const waringUpdatePackage = beforeKeys
.filter((key) => after[key])
.filter((key) => {
const beforeVision = Number(/(~|\-|\^)?(\d+)\./.exec(before[key])[2])
const afterVision = Number(/(~|\-|\^)?(\d+)\./.exec(after[key])[2])
return beforeVision !== afterVision
})
.map((key) => `${key}: ${before[key]} ===> ${after[key]}`)
const outList = [
{
label: `此次删除的依赖包有: \n ${deletePackage.join('\n')}`,
show: !!deletePackage.length,
log: warn,
},
{
label: `此次新增的依赖包有: \n ${addPackage.join('\n')}`,
show: !!addPackage.length,
log: info,
},
{
label: `此次更新的依赖包有: \n ${updatePackage.join('\n')}`,
show: !!updatePackage.length,
},
{
label: `此次大版本更新的依赖包有: \n ${waringUpdatePackage.join('\n')}`,
show: !!waringUpdatePackage.length,
log: warn,
},
].filter((item) => item.show)

if (outList.length) {
console.log('\n')
outList.forEach((item) => {
const { log = logger, label } = item
log(label)
})
console.log('\n')
}
}

const diffStr = getDiff(true)
const [before, after] = serializeDiff(diffStr)
getChange(before, after)

扩展

  • 放入 package.json 中每次提交确认变化
  • 配置提示方式和确认方法等