fix(clean): 物理 tgz 按文件匹配删除 + semver 完整语义排序

- 新增 tgz.js:统一列出包目录物理 .tgz 文件,支持新版 {version}.tgz 精确匹配
  与旧版 {pkgname}-{version}.tgz 后缀匹配,删除按真实文件 size 统计
- semver.js 重写:完整 npm 语义比较(prerelease 逐标识比较、数字<非数字、
  公共前缀更长者更大),修复原实现剥离 prerelease 导致 beta 挤掉正式版的隐患
- clean.js / scanner.js / server.js 接入统一口径
- 新增 test/semver.test.js 与 test/tgz.test.js 回归测试(node --test)
This commit is contained in:
yinjun.chen1
2026-09-21 09:49:38 +08:00
parent 2e4977a090
commit 115c5ccc9a
8 changed files with 1730 additions and 47 deletions
+48 -13
View File
@@ -1,22 +1,57 @@
/**
* semver 数值比较(轻量实现,无第三方依赖)
* 支持 v 前缀与 prerelease 后缀(仅比较主版本号主体)
* semver 完整比较(与 npm semver 语义一致,无第三方依赖)
* - 支持 v 前缀、build 元数据(+xxx,比较时忽略)
* - prerelease 规则:正式版 > 任何带 prerelease 的版本;
* 两个 prerelease 逐标识比较:纯数字按数值、混合按 ASCII 字典序、数字 < 非数字
* - 用途:清理时「保留最新 N 个版本」的排序基准,保证不会把正式版删掉却留下 beta
*/
export default function compare(a, b) {
const pa = parse(a)
const pb = parse(b)
for (let i = 0; i < 3; i++) {
if (pa[i] !== pb[i]) return pa[i] - pb[i]
}
return 0
if (pa.major !== pb.major) return pa.major - pb.major
if (pa.minor !== pb.minor) return pa.minor - pb.minor
if (pa.patch !== pb.patch) return pa.patch - pb.patch
return comparePrerelease(pa.prerelease, pb.prerelease)
}
function parse(v) {
const main = String(v).replace(/^v/, '').split(/[-+]/)[0]
const parts = main.split('.')
return [
parseInt(parts[0], 10) || 0,
parseInt(parts[1], 10) || 0,
parseInt(parts[2], 10) || 0,
]
const s = String(v).replace(/^v/, '')
const [core, pre] = s.split('-')
const [majorRaw, minorRaw, patchRaw] = core.split('.')
return {
major: toInt(majorRaw),
minor: toInt(minorRaw),
patch: toInt(patchRaw),
// build 元数据(+metadata)在比较中忽略;prerelease 与 build 之间以第一个 + 分隔
prerelease: pre ? pre.split('+')[0].split('.') : null,
}
}
function toInt(s) {
const n = parseInt(s, 10)
return Number.isNaN(n) ? 0 : n
}
/**
* prerelease 标识数组比较(null 表示无 prerelease = 正式版)
* 结果语义与 compare 一致:负数 a<b / 0 / 正数 a>b
*/
function comparePrerelease(a, b) {
if (a === b) return 0
if (a === null) return 1 // 正式版 > prerelease
if (b === null) return -1
const len = Math.min(a.length, b.length)
for (let i = 0; i < len; i++) {
const ai = a[i]
const bi = b[i]
if (ai === bi) continue
const an = /^\d+$/.test(ai)
const bn = /^\d+$/.test(bi)
if (an && bn) return Number(ai) - Number(bi)
if (an) return -1 // 数字标识 < 非数字标识
if (bn) return 1
return ai < bi ? -1 : 1 // 非数字按 ASCII 字典序
}
// 公共前缀相同:标识更多者更大(1.0.0-rc < 1.0.0-rc.1)
return a.length - b.length
}