fix(scan): 今日更新统计修复——按北京时间换算并计入 .tgz 缓存日
- lastUpdated 原取 package.json time 字段的 UTC 日期截断,跨 8 小时会显示为前一天(如 UTC 09-18 20:00 = 北京 09-19),导致「今日更新包数」漏计 - 版本发布日改用北京时间(Intl.DateTimeFormat Asia/Shanghai)换算 - 新增 .tgz 文件 mtime 作为补充:今天拉取缓存的包即使发布时间是昨天,也计入「今天更新」
This commit is contained in:
+41
-11
@@ -88,6 +88,27 @@ function parseVersion(v) {
|
||||
return (String(v).replace(/^v/, '') || '').trim()
|
||||
}
|
||||
|
||||
// —— 日期口径(项目统一北京时间,避免 UTC slice 跨日偏差) ——
|
||||
const SH_DATE = new Intl.DateTimeFormat('en-CA', {
|
||||
timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
})
|
||||
/** ISO 字符串 → 北京时间日期(YYYY-MM-DD);time 字段为 UTC,直接 slice 会与本地跨日 */
|
||||
function shDate(iso) {
|
||||
if (!iso) return null
|
||||
const d = new Date(iso)
|
||||
return Number.isNaN(d.getTime()) ? null : SH_DATE.format(d)
|
||||
}
|
||||
/** 本地时区日期(YYYY-MM-DD):.tgz 文件 mtime 为本地时间,与前端 todayStr 同口径 */
|
||||
function localDate(d) {
|
||||
const p = n => String(n).padStart(2, '0')
|
||||
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`
|
||||
}
|
||||
/** 最近更新 = max(元数据最新版本发布日, 最新 .tgz 缓存日) —— 今天拉取缓存也算「今天更新」 */
|
||||
function latestDate(metaUpdated, tgzMtime) {
|
||||
const tgzUpdated = tgzMtime ? localDate(new Date(tgzMtime)) : null
|
||||
return [metaUpdated, tgzUpdated].filter(Boolean).sort().pop() || null
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描 storage 目录
|
||||
* @returns {Array} 包列表(versionsDetail: [{version, size(MB), date}])
|
||||
@@ -112,12 +133,15 @@ export function scanStorage(storageDir) {
|
||||
// 先统计再读元数据:即使元数据损坏也能算出真实占用
|
||||
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 size = fs.statSync(path.join(pkgDir, f)).size
|
||||
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 {}
|
||||
|
||||
@@ -129,7 +153,7 @@ export function scanStorage(storageDir) {
|
||||
status: 'broken',
|
||||
versions: 0,
|
||||
size: fmtMb(tgzTotal),
|
||||
lastUpdated: null,
|
||||
lastUpdated: latestDate(null, tgzLatest),
|
||||
versionsDetail: [],
|
||||
})
|
||||
continue
|
||||
@@ -168,16 +192,17 @@ export function scanStorage(storageDir) {
|
||||
versionsDetail.push({
|
||||
version: parseVersion(v),
|
||||
size: fmtMb(size),
|
||||
date: timeObj[v] ? timeObj[v].slice(0, 10) : null,
|
||||
date: timeObj[v] ? shDate(timeObj[v]) : null,
|
||||
})
|
||||
}
|
||||
// 按 semver 数值排序(旧 → 新)
|
||||
versionsDetail.sort((a, b) => a.version.localeCompare(b.version, undefined, { numeric: true }))
|
||||
|
||||
// 最近更新时间:取 time 字段最大
|
||||
const lastUpdated = Object.keys(timeObj).length
|
||||
? Object.entries(timeObj).sort((a, b) => b[1].localeCompare(a[1]))[0][1].slice(0, 10)
|
||||
// 最近更新:取「元数据最新版本发布日」与「最新 .tgz 缓存日」中较新者(今天拉缓存也算今天更新)
|
||||
const metaUpdated = Object.keys(timeObj).length
|
||||
? Object.entries(timeObj).map(([, iso]) => shDate(iso)).filter(Boolean).sort().pop()
|
||||
: null
|
||||
const lastUpdated = latestDate(metaUpdated, tgzLatest)
|
||||
|
||||
packages.push({
|
||||
name: pkgNameFromDir(pkgDir, storageRoot),
|
||||
@@ -224,13 +249,16 @@ export async function scanStorageAsync(storageDir, onProgress) {
|
||||
|
||||
const tgzSizes = new Map()
|
||||
let tgzTotal = 0
|
||||
let tgzLatest = 0 // 最新 .tgz 文件 mtime(毫秒):今天缓存了新版本也算「今天更新」
|
||||
try {
|
||||
for (const f of fs.readdirSync(pkgDir)) {
|
||||
const size = fs.statSync(path.join(pkgDir, f)).size
|
||||
const st = fs.statSync(path.join(pkgDir, f))
|
||||
const size = st.size
|
||||
scannedBytes += size
|
||||
if (f.endsWith('.tgz')) {
|
||||
tgzTotal += size
|
||||
tgzSizes.set(f.slice(0, -4), size)
|
||||
if (st.mtimeMs > tgzLatest) tgzLatest = st.mtimeMs
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
@@ -243,7 +271,7 @@ export async function scanStorageAsync(storageDir, onProgress) {
|
||||
status: 'broken',
|
||||
versions: 0,
|
||||
size: fmtMb(tgzTotal),
|
||||
lastUpdated: null,
|
||||
lastUpdated: latestDate(null, tgzLatest),
|
||||
versionsDetail: [],
|
||||
})
|
||||
} else {
|
||||
@@ -277,13 +305,15 @@ export async function scanStorageAsync(storageDir, onProgress) {
|
||||
versionsDetail.push({
|
||||
version: parseVersion(v),
|
||||
size: fmtMb(size),
|
||||
date: timeObj[v] ? timeObj[v].slice(0, 10) : null,
|
||||
date: timeObj[v] ? shDate(timeObj[v]) : null,
|
||||
})
|
||||
}
|
||||
versionsDetail.sort((a, b) => a.version.localeCompare(b.version, undefined, { numeric: true }))
|
||||
const lastUpdated = Object.keys(timeObj).length
|
||||
? Object.entries(timeObj).sort((a, b) => b[1].localeCompare(a[1]))[0][1].slice(0, 10)
|
||||
// 最近更新:取「元数据最新版本发布日」与「最新 .tgz 缓存日」中较新者(今天拉缓存也算今天更新)
|
||||
const metaUpdated = Object.keys(timeObj).length
|
||||
? Object.entries(timeObj).map(([, iso]) => shDate(iso)).filter(Boolean).sort().pop()
|
||||
: null
|
||||
const lastUpdated = latestDate(metaUpdated, tgzLatest)
|
||||
packages.push({
|
||||
name: pkgNameFromDir(pkgDir, storageRoot),
|
||||
versions: versionsDetail.length,
|
||||
|
||||
Reference in New Issue
Block a user