Smail/test/tools/verify-json-format.mjs
smail 560605a8e1
Some checks are pending
Security Checks / security (push) Waiting to run
统一配置JSON缩进排版并保持摘要与历史兼容
2026-09-08 21:08:36 +08:00

64 lines
3.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import assert from 'node:assert/strict'
import { createHash } from 'node:crypto'
import { readFileSync, readdirSync, writeFileSync } from 'node:fs'
import path from 'node:path'
const [workspace, evidence] = process.argv.slice(2)
if (!workspace || !evidence) throw new Error('请指定项目根目录和本次验证目录')
const digest = data => createHash('sha256').update(data).digest('hex')
const read = filename => readFileSync(filename, 'utf8')
const names = ['config.json', 'smtp.json', 'email.json', 'ssl.json', 'plugins.json']
// 只忽略字符串外的 JSON 空白,数字精度、中文、转义及字段顺序都必须保持。
function compact(raw) {
JSON.parse(raw)
let quoted = false
let escaped = false
let result = ''
for (const character of raw) {
if (quoted) {
result += character
if (escaped) escaped = false
else if (character === '\\') escaped = true
else if (character === '"') quoted = false
} else if (character === '"') {
quoted = true
result += character
} else if (!' \t\r\n'.includes(character)) result += character
}
return result
}
const report = { checkedAt: new Date().toISOString(), files: [] }
for (const [relative, backup] of [['config', 'root-config'], ['yunx-test/config', 'runtime-config']]) {
const directory = path.join(workspace, relative)
assert.deepEqual(readdirSync(directory).sort(), [...names].sort(), '配置目录出现额外文件')
const oldRoot = JSON.parse(read(path.join(evidence, backup, 'config.json')))
const newRoot = JSON.parse(read(path.join(directory, 'config.json')))
for (const name of names) {
const oldRaw = read(path.join(evidence, backup, name))
const newRaw = read(path.join(directory, name))
assert(!newRaw.startsWith('\ufeff'), '配置不能含 BOM')
assert(newRaw.includes('\n "'), '配置未按两空格缩进分行')
let expected = compact(oldRaw)
if (name === 'config.json') {
for (const [section, reference] of Object.entries(oldRoot.sections)) {
expected = expected.replace(`"sha256":"${reference.sha256}"`, `"sha256":"${newRoot.sections[section].sha256}"`)
}
} else {
const section = name.slice(0, -5)
assert.equal(newRoot.sections[section].sha256, digest(Buffer.from(newRaw)), '新文件摘要不匹配')
}
// 不输出配置原文或断言两侧内容,避免凭据进入测试日志。
assert(expected === compact(newRaw), `${relative}/${name} 内容发生变化`)
report.files.push({ file: `${relative}/${name}`, lines: newRaw.split('\n').length, valuesUnchanged: true })
}
}
const before = JSON.parse(read(path.join(evidence, 'before.json')).replace(/^\ufeff/, ''))
const audit = readFileSync(path.join(workspace, 'yunx-test/log/config-audit-v2.jsonl'))
report.auditUnchanged = digest(audit).toUpperCase() === before.auditSHA256
assert(report.auditUnchanged, '审计原始文件发生变化')
writeFileSync(path.join(evidence, 'verification.json'), JSON.stringify(report, null, 2) + '\n')
console.log('验证通过:10 个 JSON 仅变更空白和对应摘要;中文、密文、数字、版本及审计原字节保持不变。')
for (const file of report.files) console.log(`${file.file}:${file.lines} 行`)