mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-10 12:46:15 -08:00
95 lines
6.7 KiB
JavaScript
95 lines
6.7 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
import lockfile from '@yarnpkg/lockfile'
|
|
import { hash, refactorDir } from './releases.mjs'
|
|
|
|
const root = path.resolve(refactorDir, '..')
|
|
const read = name => JSON.parse(fs.readFileSync(path.join(refactorDir, name), 'utf8'))
|
|
export function referencedFindings() {
|
|
const files = ['baselines/lifecycle-coverage.md', 'baselines/dom-coverage.md', 'baselines/consumer-coverage.md', 'baselines/performance-coverage.md']
|
|
return [...new Set(files.flatMap(name => fs.readFileSync(path.join(refactorDir, name), 'utf8').match(/BASE-(?:LIFE|DOM|DEMO|TYPE|DIST|PERF)-\d{2}/g) || []))].sort()
|
|
}
|
|
function evidenceExists(name) {
|
|
const absolute = path.resolve(root, name)
|
|
assert(absolute.startsWith(root + path.sep) && fs.existsSync(absolute), `Missing or invalid evidence: ${name}`)
|
|
}
|
|
export function validateRisks(register) {
|
|
const tasks = new Set(read('tasks.json').tasks.map(task => task.id))
|
|
const ids = new Set(register.items.map(item => item.id))
|
|
assert.equal(ids.size, register.items.length, 'Duplicate risk ID')
|
|
for (const id of referencedFindings()) assert(ids.has(id), `Unregistered baseline finding: ${id}`)
|
|
for (const item of register.items) {
|
|
assert(['reproduced', 'source-observed', 'unverified'].includes(item.confirmation))
|
|
assert(['open', 'resolved', 'accepted-with-scope'].includes(item.status))
|
|
assert(item.owners.length && item.owners.every(id => tasks.has(id)), `Unowned risk: ${item.id}`)
|
|
assert(item.evidence.length && item.compatibleResolution && item.closureCriteria, `Incomplete risk: ${item.id}`)
|
|
item.evidence.forEach(evidenceExists)
|
|
if (item.status !== 'open') {
|
|
assert(item.resolutionEvidence?.length && item.resolutionRationale, `Premature closure: ${item.id}`)
|
|
item.resolutionEvidence.forEach(evidenceExists)
|
|
}
|
|
}
|
|
}
|
|
export function assetFingerprints(inputs) {
|
|
const walk = name => {
|
|
const absolute = path.resolve(root, name)
|
|
evidenceExists(name)
|
|
return fs.statSync(absolute).isDirectory() ? fs.readdirSync(absolute).flatMap(child => walk(`${name}/${child}`)) : [name]
|
|
}
|
|
return inputs.flatMap(walk).sort().map(name => {
|
|
const bytes = fs.readFileSync(path.join(root, name))
|
|
const text = /\.(?:[cm]?[jt]s|css|less|json|html|svg|txt|md)$/.test(name)
|
|
return { path: name, algorithm: text ? 'sha256-lf' : 'sha256', sha256: hash(text ? bytes.toString('utf8').replaceAll('\r\n', '\n') : bytes) }
|
|
})
|
|
}
|
|
export function validateThirdParty(inventory, register) {
|
|
const tasks = new Set(read('tasks.json').tasks.map(task => task.id))
|
|
const risks = new Set(register.items.map(item => item.id))
|
|
const initial = read('package-inventory.json').packages.map(pkg => pkg.name).sort()
|
|
assert.deepEqual(inventory.packages.map(pkg => pkg.name).sort(), initial, 'External dependency package coverage missing')
|
|
const lock = lockfile.parse(fs.readFileSync(path.join(root, 'yarn.lock'), 'utf8')).object
|
|
for (const pkg of inventory.packages) {
|
|
const current = JSON.parse(fs.readFileSync(path.join(root, 'packages', pkg.name, 'package.json'), 'utf8'))
|
|
assert.deepEqual(pkg.declaredRuntimeDependencies, current.dependencies || {}, `Update dependency inventory: ${pkg.name}`)
|
|
assert.deepEqual(pkg.developmentDependencies, current.devDependencies || {}, `Update development dependency inventory: ${pkg.name}`)
|
|
assert.deepEqual(pkg.resolvedRuntimeDependencies.map(dependency => dependency.name).sort(), Object.keys(current.dependencies || {}).sort(), 'Missing resolved dependency')
|
|
for (const dependency of pkg.resolvedRuntimeDependencies) {
|
|
assert.equal(dependency.range, current.dependencies[dependency.name], 'Dependency range changed')
|
|
const installed = JSON.parse(fs.readFileSync(path.join(root, 'node_modules', dependency.name, 'package.json'), 'utf8'))
|
|
assert.equal(dependency.lockedVersion, lock[`${dependency.name}@${dependency.range}`]?.version, 'Dependency lock changed; update provenance')
|
|
assert.equal(dependency.installedVersion, installed.version, 'Installed dependency changed')
|
|
assert.equal(dependency.manifestLicense, installed.license || null, 'Dependency license metadata changed')
|
|
}
|
|
}
|
|
assert.deepEqual(inventory.vendored.map(vendor => vendor.riskId).sort(), [...risks].filter(id => id.startsWith('VENDOR-')).sort(), 'Missing vendored group')
|
|
assert.deepEqual(inventory.integrations.map(sdk => sdk.riskId).sort(), [...risks].filter(id => id.startsWith('SDK-')).sort(), 'Missing SDK integration')
|
|
for (const vendor of inventory.vendored) {
|
|
assert(risks.has(vendor.riskId) && vendor.owners.length && vendor.owners.every(id => tasks.has(id)))
|
|
assert(vendor.sourceStatus && vendor.licenseStatus && vendor.updatePolicy)
|
|
assert.deepEqual(assetFingerprints(vendor.inputs), vendor.fingerprints, `Vendored files changed without reviewed provenance: ${vendor.id}`)
|
|
}
|
|
for (const sdk of inventory.integrations) {
|
|
assert(risks.has(sdk.riskId) && sdk.owners.length && sdk.owners.every(id => tasks.has(id)))
|
|
assert(sdk.sourceFiles.length && sdk.validationStatus && sdk.contract)
|
|
sdk.sourceFiles.forEach(evidenceExists)
|
|
}
|
|
}
|
|
export function renderRisks(register) {
|
|
const labels = { reproduced: '已复现', 'source-observed': '源码/产物事实', unverified: '待取证' }
|
|
const escape = text => text.replaceAll('|', '\\|').replaceAll('\n', ' ')
|
|
return `# 重构风险与差异索引\n\n由 risks.json 生成;运行 node refactor/scripts/risk-register.mjs --write 更新。状态 open 表示尚缺关闭证据,不等于每项都是已复现缺陷。处理方案、关闭条件与证据见 [机器台账](risks.json),来源与范围见 [维护说明](risk-guide.md)。\n\n| ID | 状态 / 证据等级 | 条目 | 后续任务 |\n| --- | --- | --- | --- |\n${register.items.map(item => `| ${item.id} | ${item.status} / ${labels[item.confirmation]} | ${escape(item.title)} | ${item.owners.join(', ')} |`).join('\n')}\n`
|
|
}
|
|
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
assert(process.argv.slice(2).every(arg => ['--check', '--write'].includes(arg)), 'Use --check or --write')
|
|
const register = read('risks.json')
|
|
const inventory = read('third-party.json')
|
|
validateRisks(register)
|
|
validateThirdParty(inventory, register)
|
|
const rendered = renderRisks(register)
|
|
if (process.argv.includes('--write')) fs.writeFileSync(path.join(refactorDir, 'risk-table.md'), rendered)
|
|
else assert.equal(fs.readFileSync(path.join(refactorDir, 'risk-table.md'), 'utf8').replaceAll('\r\n', '\n'), rendered, 'Regenerate risk-table.md')
|
|
console.log(`Risk register checked: ${register.items.length} items, ${inventory.vendored.length} vendored groups, ${inventory.integrations.length} integrations; no unresolved item is waived`)
|
|
}
|