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
+7 -12
View File
@@ -7,6 +7,7 @@
import fs from 'node:fs'
import path from 'node:path'
import { getSetting } from './db.js'
import { listTgzFiles } from './tgz.js'
// —— storage 目录解析 ——
export function resolveStorageDir() {
@@ -153,22 +154,16 @@ export function scanStorage(storageDir) {
for (const pkgDir of pkgDirs) {
const pkgFile = path.join(pkgDir, 'package.json')
// 物理 .tgz 文件:先列目录建立文件名 → 大小索引
// (Verdaccio 各版本命名不同:{version}.tgz 或 {pkgname}-{version}.tgz,不猜、直接扫)
// 物理 .tgz 文件:命名规则({version}.tgz / {pkgname}-{version}.tgz)统一由 tgz.js 解析
// 先统计再读元数据:即使元数据损坏也能算出真实占用
const tgzSizes = new Map()
let tgzTotal = 0
let tgzLatest = 0 // 最新 .tgz 文件 mtime(毫秒):今天缓存了新版本也算「今天更新」
try {
for (const f of fs.readdirSync(pkgDir)) {
if (!f.endsWith('.tgz')) continue
const st = fs.statSync(path.join(pkgDir, f))
const size = st.size
tgzTotal += size
tgzSizes.set(f.slice(0, -4), size)
if (st.mtimeMs > tgzLatest) tgzLatest = st.mtimeMs
}
} catch {}
for (const f of listTgzFiles(pkgDir)) {
tgzTotal += f.size
tgzSizes.set(f.key, f.size)
if (f.mtimeMs > tgzLatest) tgzLatest = f.mtimeMs
}
const meta = readJson(pkgFile)
if (!meta) {