fix(scan): 包描述/主页/仓库/作者/许可读取不到
Verdaccio 落盘的元数据顶层不含 description/homepage 等字段(实测 只有 name/versions/time/dist-tags/readme),但这些字段完整存在于 每个 versions[v] 里。新增 metaField 回退逻辑:顶层取不到时按 semver 排序取最新版本里的字段,修复检索页/详情抽屉描述全为「—」。
This commit is contained in:
+37
-18
@@ -109,6 +109,31 @@ function latestDate(metaUpdated, tgzMtime) {
|
||||
return [metaUpdated, tgzUpdated].filter(Boolean).sort().pop() || null
|
||||
}
|
||||
|
||||
/**
|
||||
* 包信息字段提取(description/homepage/repository/author/license)
|
||||
* Verdaccio 落盘的元数据顶层不含这些字段(实测只有 name/versions/time/dist-tags/readme 等),
|
||||
* 但每个 versions[v] 里都有完整字段。因此先读顶层,取不到时按 semver 数值排序回退到最新版本。
|
||||
* @param {object} meta 落盘的 package.json 元数据
|
||||
* @param {object} versionsObj meta.versions
|
||||
* @param {string} key 字段名
|
||||
* @param {(src:any)=>string} [picker] 字段为对象时(repository.url / author.name)取值
|
||||
*/
|
||||
function metaField(meta, versionsObj, key, picker) {
|
||||
const pick = src => {
|
||||
if (!src) return null
|
||||
const v = typeof src === 'string' ? src : (picker ? picker(src) : null)
|
||||
return typeof v === 'string' && v ? v : null
|
||||
}
|
||||
const top = pick(meta[key])
|
||||
if (top) return top
|
||||
const sorted = Object.entries(versionsObj).sort((a, b) => a[0].localeCompare(b[0], undefined, { numeric: true }))
|
||||
for (let i = sorted.length - 1; i >= 0; i--) {
|
||||
const v = pick(sorted[i][1] && sorted[i][1][key])
|
||||
if (v) return v
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫描 storage 目录
|
||||
* @returns {Array} 包列表(versionsDetail: [{version, size(MB), date}])
|
||||
@@ -163,15 +188,12 @@ export function scanStorage(storageDir) {
|
||||
const timeObj = meta.time || {}
|
||||
const distTags = meta['dist-tags'] || {}
|
||||
// 包信息(来自本地元数据,离线可用):介绍/主页/仓库/作者/许可
|
||||
const description = typeof meta.description === 'string' ? meta.description : null
|
||||
const homepage = typeof meta.homepage === 'string' && meta.homepage ? meta.homepage : null
|
||||
const repository = typeof meta.repository === 'string'
|
||||
? meta.repository
|
||||
: (meta.repository && typeof meta.repository.url === 'string' ? meta.repository.url : null)
|
||||
const author = typeof meta.author === 'string'
|
||||
? meta.author
|
||||
: (meta.author && typeof meta.author.name === 'string' ? meta.author.name : null)
|
||||
const license = typeof meta.license === 'string' ? meta.license : null
|
||||
// 顶层缺失时回退到最新版本里的字段(Verdaccio 落盘元数据顶层不含这些)
|
||||
const description = metaField(meta, versionsObj, 'description')
|
||||
const homepage = metaField(meta, versionsObj, 'homepage')
|
||||
const repository = metaField(meta, versionsObj, 'repository', r => r.url)
|
||||
const author = metaField(meta, versionsObj, 'author', a => a.name)
|
||||
const license = metaField(meta, versionsObj, 'license')
|
||||
|
||||
const versionsDetail = []
|
||||
// 总大小以实际 .tgz 文件为准
|
||||
@@ -279,15 +301,12 @@ export async function scanStorageAsync(storageDir, onProgress) {
|
||||
const timeObj = meta.time || {}
|
||||
const distTags = meta['dist-tags'] || {}
|
||||
// 包信息(来自本地元数据,离线可用):介绍/主页/仓库/作者/许可
|
||||
const description = typeof meta.description === 'string' ? meta.description : null
|
||||
const homepage = typeof meta.homepage === 'string' && meta.homepage ? meta.homepage : null
|
||||
const repository = typeof meta.repository === 'string'
|
||||
? meta.repository
|
||||
: (meta.repository && typeof meta.repository.url === 'string' ? meta.repository.url : null)
|
||||
const author = typeof meta.author === 'string'
|
||||
? meta.author
|
||||
: (meta.author && typeof meta.author.name === 'string' ? meta.author.name : null)
|
||||
const license = typeof meta.license === 'string' ? meta.license : null
|
||||
// 顶层缺失时回退到最新版本里的字段(Verdaccio 落盘元数据顶层不含这些)
|
||||
const description = metaField(meta, versionsObj, 'description')
|
||||
const homepage = metaField(meta, versionsObj, 'homepage')
|
||||
const repository = metaField(meta, versionsObj, 'repository', r => r.url)
|
||||
const author = metaField(meta, versionsObj, 'author', a => a.name)
|
||||
const license = metaField(meta, versionsObj, 'license')
|
||||
const versionsDetail = []
|
||||
const totalBytes = tgzTotal
|
||||
for (const [v, info] of Object.entries(versionsObj)) {
|
||||
|
||||
Reference in New Issue
Block a user