Files
Genarrative/scripts/check-pingora-cutover-command-evidence.mjs
T
kdletters a7711d2dc3 引入Pingora独立网关正式化链路
新增 pingora-gateway 独立二进制 crate,覆盖路由、静态资源、压缩、接流保护、TLS 直连和访问日志能力。

新增 Nginx canary、realpath canary、direct preflight、direct live、direct enable 和 rollback 脚本。

新增 Pingora 切流证据包、命令证据、manifest 验真、根目录总审计和 release readiness 聚合门禁。

完善 API release、Jenkins、systemd、health patrol、生产部署和发布包自包含校验。

更新 Pingora 试点文档、Nginx README 与 Hermes 共享记忆。
2026-06-17 20:16:51 +08:00

909 lines
26 KiB
JavaScript

#!/usr/bin/env node
import { spawnSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import {
existsSync,
mkdirSync,
mkdtempSync,
readFileSync,
rmSync,
statSync,
symlinkSync,
writeFileSync,
} from 'node:fs';
import { tmpdir } from 'node:os';
import path from 'node:path';
const COMMAND_EVIDENCE_SCRIPT =
'scripts/ops/pingora-cutover-command-evidence.mjs';
const failures = [];
const tmpRoot = mkdtempSync(
path.join(tmpdir(), 'genarrative-pingora-cutover-command-evidence-'),
);
try {
main();
} finally {
rmSync(tmpRoot, { recursive: true, force: true });
}
if (failures.length > 0) {
console.error('[check:pingora-cutover-command-evidence] FAILED');
for (const failure of failures) {
console.error(`- ${failure}`);
}
process.exit(1);
}
console.log('[check:pingora-cutover-command-evidence] OK');
function main() {
assertScriptShape();
assertCommandEvidenceSucceedsAndWritesArtifacts();
assertCommandEvidenceWritesCutoverRunId();
assertCommandFailureStillWritesEvidenceAndFails();
assertCommandEvidenceRedactsProbeTokensFromArtifacts();
assertRejectsUnsafeNames();
assertRejectsRelativeOutputRoot();
assertRejectsFilesystemRootOutputRoot();
assertRejectsOutputRootWithControlCharacters();
assertRejectsSymlinkOutputRootBeforeCommand();
assertRejectsFileOutputRootBeforeCommand();
assertRejectsRelativeCommandPath();
assertRejectsBareCommandName();
assertRejectsFilesystemRootCommand();
assertRejectsCommandWithControlCharacters();
assertRejectsCommandArgWithControlCharacters();
assertRejectsRelativeExpectedExecutable();
assertRejectsFilesystemRootExpectedExecutable();
assertRejectsExpectedExecutableWithControlCharacters();
assertRejectsMismatchedExpectedExecutableBeforeCommand();
assertRejectsMissingRequiredArgBeforeCommand();
}
function assertScriptShape() {
const content = readFileSync(COMMAND_EVIDENCE_SCRIPT, 'utf8');
assertIncludes(
content,
'command.stdout.txt',
'命令证据脚本必须保存真实命令 stdout。',
);
assertIncludes(
content,
'command.stderr.txt',
'命令证据脚本必须保存真实命令 stderr。',
);
assertIncludes(
content,
'command-record.json',
'命令证据脚本必须保存结构化命令记录。',
);
assertIncludes(
content,
'manifest.json',
'命令证据脚本必须写 manifest。',
);
assertIncludes(
content,
'cutoverRunId',
'命令证据脚本必须支持写入切换批次 ID。',
);
assertIncludes(
content,
'--cutover-run-id',
'命令证据脚本 usage 必须公开切换批次 ID 参数。',
);
assertIncludes(
content,
'--expected-executable',
'命令证据脚本 usage 必须公开预期真实命令参数。',
);
assertIncludes(
content,
'--require-arg',
'命令证据脚本 usage 必须公开真实命令必需参数。',
);
assertIncludes(
content,
'SECRET_VALUE_FLAGS',
'命令证据脚本必须集中维护敏感参数脱敏列表。',
);
assertIncludes(
content,
'redactSecrets(text',
'命令证据脚本必须脱敏 stdout/stderr 中的敏感值。',
);
assertIncludes(
content,
"flag: 'wx'",
'命令证据脚本写证据文件时必须避免覆盖既有文件。',
);
assertIncludes(
content,
'BUNDLE_DIR_MODE = 0o750',
'命令证据目录必须显式固定权限。',
);
assertIncludes(
content,
'EVIDENCE_FILE_MODE = 0o640',
'命令证据文件必须显式固定权限。',
);
}
function assertCommandEvidenceSucceedsAndWritesArtifacts() {
const fixture = prepareFixture('success');
const result = runCommandEvidence(fixture, [
'--phase',
'enable-apply',
'--command-name',
'pingora-direct-enable-apply',
'--output-root',
fixture.outputRoot,
'--expected-executable',
fixture.commandScript,
'--',
fixture.commandScript,
'--message',
'hello-command',
]);
assertStatus(result, 0, '真实命令成功时命令证据脚本应成功退出。');
assertIncludes(
result.stdout,
'hello-command',
'命令证据脚本必须把真实命令 stdout 透传到终端。',
);
const output = parseLastJsonObject(result.stdout, '成功命令证据 stdout');
assertEqual(output.status, 'OK', 'stdout JSON 必须记录 OK 状态。');
assertFileExists(output.bundleDir, '命令证据目录必须存在。');
const manifest = readJson(path.join(output.bundleDir, 'manifest.json'));
assertEqual(manifest.summary.status, 'OK', 'manifest 必须记录 OK 状态。');
assertEqual(
manifest.command?.name,
'pingora-direct-enable-apply',
'manifest 必须记录命令名。',
);
assertEqual(
manifest.command?.exitCode,
0,
'manifest 必须记录真实命令退出码。',
);
assertEqual(
manifest.expectedExecutable,
fixture.commandScript,
'manifest 必须记录预期真实命令路径。',
);
assertEqual(
manifest.command?.expectedExecutable,
fixture.commandScript,
'command-record 必须记录预期真实命令路径。',
);
assertEqual(
manifest.command?.executable,
fixture.commandScript,
'manifest.command 必须记录真实命令绝对路径。',
);
const commandRecord = readJson(path.join(output.bundleDir, 'command-record.json'));
assertEqual(
commandRecord.executable,
fixture.commandScript,
'command-record.json 必须记录真实命令绝对路径。',
);
assertEqual(
output.expectedExecutable,
fixture.commandScript,
'stdout JSON 必须返回预期真实命令路径。',
);
assertIncludes(
manifest.command?.args || [],
'--message',
'manifest 必须记录结构化命令参数。',
);
assertManifestFileMetadata(
output.bundleDir,
manifest.files?.stdout,
'command.stdout.txt',
);
assertManifestFileMetadata(
output.bundleDir,
manifest.files?.stderr,
'command.stderr.txt',
);
assertManifestFileMetadata(
output.bundleDir,
manifest.files?.commandRecord,
'command-record.json',
);
assertFileExists(
path.join(output.bundleDir, 'command.stdout.txt'),
'命令证据必须保存 stdout。',
);
assertFileExists(
path.join(output.bundleDir, 'command.stderr.txt'),
'命令证据必须保存 stderr。',
);
assertFileExists(
path.join(output.bundleDir, 'command-record.json'),
'命令证据必须保存 command-record.json。',
);
assertMode(output.bundleDir, 0o750, '命令证据目录权限必须是 0750。');
for (const fileName of [
'manifest.json',
'command.stdout.txt',
'command.stderr.txt',
'command-record.json',
]) {
assertMode(
path.join(output.bundleDir, fileName),
0o640,
`${fileName} 权限必须是 0640。`,
);
}
}
function assertCommandEvidenceWritesCutoverRunId() {
const fixture = prepareFixture('cutover-run-id');
const result = runCommandEvidence(fixture, [
'--phase',
'enable-apply',
'--command-name',
'pingora-direct-enable-apply',
'--cutover-run-id',
'cutover-20260617T020000Z',
'--output-root',
fixture.outputRoot,
'--',
fixture.commandScript,
'--message',
'hello-cutover-run',
]);
assertStatus(result, 0, '提供 cutover run id 时命令证据脚本应成功退出。');
if (result.status !== 0) {
return;
}
const output = parseLastJsonObject(result.stdout, 'cutover run id 命令证据 stdout');
assertEqual(
output.cutoverRunId,
'cutover-20260617T020000Z',
'命令证据 stdout 必须返回 cutoverRunId。',
);
const manifest = readJson(path.join(output.bundleDir, 'manifest.json'));
assertEqual(
manifest.cutoverRunId,
'cutover-20260617T020000Z',
'命令证据 manifest 必须记录 cutoverRunId。',
);
assertEqual(
manifest.command?.cutoverRunId,
'cutover-20260617T020000Z',
'command-record 必须同步记录 cutoverRunId。',
);
}
function assertCommandFailureStillWritesEvidenceAndFails() {
const fixture = prepareFixture('failure');
const result = runCommandEvidence(fixture, [
'--phase',
'rollback-apply',
'--command-name',
'pingora-direct-rollback-apply',
'--output-root',
fixture.outputRoot,
'--',
fixture.commandScript,
'--message',
'rollback-output',
'--fail',
]);
assertStatus(result, 37, '真实命令失败时命令证据脚本应返回真实退出码。');
const output = parseLastJsonObject(result.stdout, '失败命令证据 stdout');
assertEqual(output.status, 'FAILED', '失败 stdout JSON 必须记录 FAILED。');
assertFileExists(output.bundleDir, '失败时仍必须保留命令证据目录。');
const manifest = readJson(path.join(output.bundleDir, 'manifest.json'));
assertEqual(
manifest.summary.exitCode,
37,
'失败 manifest 必须记录真实退出码。',
);
assertFileExists(
path.join(output.bundleDir, 'command.stdout.txt'),
'失败时仍必须保存 stdout。',
);
assertFileExists(
path.join(output.bundleDir, 'command.stderr.txt'),
'失败时仍必须保存 stderr。',
);
}
function assertCommandEvidenceRedactsProbeTokensFromArtifacts() {
const fixture = prepareFixture('redact-token');
const result = runCommandEvidence(fixture, [
'--phase',
'rollback-apply',
'--command-name',
'pingora-direct-rollback-apply',
'--output-root',
fixture.outputRoot,
'--',
fixture.commandScript,
'--message',
'probe-token=secret-shadow-token',
'--pingora-shadow-probe-token',
'secret-shadow-token',
'--direct-probe-token=secret-inline-token',
'--echo-env-token',
]);
assertStatus(result, 0, '带 token 的命令证据脚本应成功退出。');
for (const secret of [
'secret-shadow-token',
'secret-inline-token',
'secret-env-token',
]) {
assertNotIncludes(
result.stdout,
secret,
'命令证据脚本透传 stdout 时不能泄露 token。',
);
}
const output = parseLastJsonObject(result.stdout, '脱敏命令证据 stdout');
for (const fileName of [
'manifest.json',
'command.stdout.txt',
'command.stderr.txt',
'command-record.json',
]) {
const content = readFileSync(path.join(output.bundleDir, fileName), 'utf8');
for (const secret of [
'secret-shadow-token',
'secret-inline-token',
'secret-env-token',
]) {
assertNotIncludes(
content,
secret,
`${fileName} 不能泄露 token 原文。`,
);
}
assertIncludes(content, '<redacted>', `${fileName} 必须保留脱敏占位符。`);
}
}
function assertRejectsUnsafeNames() {
const fixture = prepareFixture('unsafe-name');
for (const [flag, value, expected] of [
['--phase', '../rollback', '--phase 只能包含 ASCII'],
['--command-name', '回退', '--command-name 只能包含 ASCII'],
['--cutover-run-id', 'cutover 1', '--cutover-run-id 只能包含 ASCII'],
]) {
const result = runCommandEvidence(fixture, [
flag,
value,
'--output-root',
fixture.outputRoot,
'--',
fixture.commandScript,
]);
if ((result.status ?? 0) === 0) {
failures.push(`${flag} 使用不安全值时必须失败。`);
}
assertIncludes(
`${result.stdout}\n${result.stderr}`,
expected,
`${flag} 不安全值必须给出明确错误。`,
);
assertNotIncludes(
`${result.stdout}\n${result.stderr}`,
'[fake-command]',
`${flag} 不安全值被拒绝时不应执行真实命令。`,
);
}
}
function assertRejectsRelativeOutputRoot() {
const fixture = prepareFixture('relative-output-root');
const result = runCommandEvidence(fixture, [
'--output-root',
'pingora-evidence',
'--',
fixture.commandScript,
]);
if ((result.status ?? 0) === 0) {
failures.push('命令证据脚本必须拒绝相对 --output-root。');
}
assertIncludes(
`${result.stdout}\n${result.stderr}`,
'--output-root 必须是绝对路径',
'相对 output-root 必须给出明确错误。',
);
}
function assertRejectsFilesystemRootOutputRoot() {
const fixture = prepareFixture('root-output-root');
const result = runCommandEvidence(fixture, [
'--output-root',
'/',
'--',
fixture.commandScript,
]);
if ((result.status ?? 0) === 0) {
failures.push('命令证据脚本必须拒绝文件系统根目录 output-root。');
}
assertIncludes(
`${result.stdout}\n${result.stderr}`,
'--output-root 不能是文件系统根目录',
'根目录 output-root 必须给出明确错误。',
);
}
function assertRejectsOutputRootWithControlCharacters() {
const fixture = prepareFixture('output-root-control-characters');
const result = runCommandEvidence(fixture, [
'--output-root',
`${fixture.outputRoot}\n--phase rollback-apply`,
'--',
fixture.commandScript,
]);
if ((result.status ?? 0) === 0) {
failures.push('命令证据脚本必须拒绝带换行的 --output-root。');
}
assertIncludes(
`${result.stdout}\n${result.stderr}`,
'--output-root 不能包含换行或 NUL 字符',
'带换行 output-root 必须给出明确错误。',
);
assertNotIncludes(
`${result.stdout}\n${result.stderr}`,
'[fake-command]',
'带换行 output-root 被拒绝时不应执行真实命令。',
);
}
function assertRejectsSymlinkOutputRootBeforeCommand() {
const fixture = prepareFixture('symlink-output-root');
const realTarget = path.join(tmpRoot, 'symlink-output-root-real-target');
rmSync(fixture.outputRoot, { recursive: true, force: true });
mkdirSync(realTarget, { recursive: true });
symlinkSync(realTarget, fixture.outputRoot, 'dir');
const result = runCommandEvidence(fixture, [
'--output-root',
fixture.outputRoot,
'--',
fixture.commandScript,
]);
if ((result.status ?? 0) === 0) {
failures.push('命令证据脚本必须拒绝符号链接 output-root。');
}
assertIncludes(
`${result.stdout}\n${result.stderr}`,
'--output-root 不能是符号链接',
'符号链接 output-root 必须给出明确错误。',
);
assertNotIncludes(
`${result.stdout}\n${result.stderr}`,
'[fake-command]',
'符号链接 output-root 被拒绝时不应执行真实命令。',
);
}
function assertRejectsFileOutputRootBeforeCommand() {
const fixture = prepareFixture('file-output-root');
rmSync(fixture.outputRoot, { recursive: true, force: true });
writeFileSync(fixture.outputRoot, 'not a directory\n', 'utf8');
const result = runCommandEvidence(fixture, [
'--output-root',
fixture.outputRoot,
'--',
fixture.commandScript,
]);
if ((result.status ?? 0) === 0) {
failures.push('命令证据脚本必须拒绝非目录 output-root。');
}
assertIncludes(
`${result.stdout}\n${result.stderr}`,
'--output-root 已存在但不是目录',
'非目录 output-root 必须给出明确错误。',
);
assertNotIncludes(
`${result.stdout}\n${result.stderr}`,
'[fake-command]',
'非目录 output-root 被拒绝时不应执行真实命令。',
);
}
function assertRejectsRelativeCommandPath() {
const fixture = prepareFixture('relative-command-path');
const result = runCommandEvidence(fixture, [
'--output-root',
fixture.outputRoot,
'--',
'scripts/deploy/pingora-direct-rollback.sh',
]);
if ((result.status ?? 0) === 0) {
failures.push('命令证据脚本必须拒绝带路径的相对命令。');
}
assertIncludes(
`${result.stdout}\n${result.stderr}`,
'真实命令必须是绝对路径',
'相对命令路径必须给出明确错误。',
);
}
function assertRejectsBareCommandName() {
const fixture = prepareFixture('bare-command-name');
const result = runCommandEvidence(fixture, [
'--output-root',
fixture.outputRoot,
'--',
'echo',
'SHOULD_NOT_RUN',
]);
if ((result.status ?? 0) === 0) {
failures.push('命令证据脚本必须拒绝 PATH 裸命令名。');
}
assertIncludes(
`${result.stdout}\n${result.stderr}`,
'真实命令必须是绝对路径',
'PATH 裸命令名必须给出明确错误。',
);
assertNotIncludes(
`${result.stdout}\n${result.stderr}`,
'SHOULD_NOT_RUN',
'PATH 裸命令名被拒绝时不应执行真实命令。',
);
}
function assertRejectsFilesystemRootCommand() {
const fixture = prepareFixture('filesystem-root-command');
const result = runCommandEvidence(fixture, [
'--output-root',
fixture.outputRoot,
'--',
path.parse(process.cwd()).root,
]);
if ((result.status ?? 0) === 0) {
failures.push('命令证据脚本必须拒绝文件系统根目录真实命令。');
}
assertIncludes(
`${result.stdout}\n${result.stderr}`,
'真实命令不能是文件系统根目录',
'文件系统根目录真实命令必须给出明确错误。',
);
}
function assertRejectsCommandWithControlCharacters() {
const fixture = prepareFixture('command-control-characters');
const result = runCommandEvidence(fixture, [
'--output-root',
fixture.outputRoot,
'--',
`${fixture.commandScript}\n`,
]);
if ((result.status ?? 0) === 0) {
failures.push('命令证据脚本必须拒绝带换行的真实命令。');
}
assertIncludes(
`${result.stdout}\n${result.stderr}`,
'真实命令不能包含换行或 NUL 字符',
'带换行的真实命令必须给出明确错误。',
);
assertNotIncludes(
`${result.stdout}\n${result.stderr}`,
'[fake-command]',
'带换行的真实命令被拒绝时不应执行真实命令。',
);
}
function assertRejectsCommandArgWithControlCharacters() {
const fixture = prepareFixture('command-arg-control-characters');
const result = runCommandEvidence(fixture, [
'--output-root',
fixture.outputRoot,
'--',
fixture.commandScript,
'--message',
'line1\nline2',
]);
if ((result.status ?? 0) === 0) {
failures.push('命令证据脚本必须拒绝带换行的真实命令参数。');
}
assertIncludes(
`${result.stdout}\n${result.stderr}`,
'真实命令参数不能包含换行或 NUL 字符',
'带换行的真实命令参数必须给出明确错误。',
);
assertNotIncludes(
`${result.stdout}\n${result.stderr}`,
'[fake-command]',
'带换行的真实命令参数被拒绝时不应执行真实命令。',
);
}
function assertRejectsRelativeExpectedExecutable() {
const fixture = prepareFixture('relative-expected-executable');
const result = runCommandEvidence(fixture, [
'--output-root',
fixture.outputRoot,
'--expected-executable',
'scripts/deploy/pingora-direct-enable.sh',
'--',
fixture.commandScript,
]);
if ((result.status ?? 0) === 0) {
failures.push('命令证据脚本必须拒绝相对 --expected-executable。');
}
assertIncludes(
`${result.stdout}\n${result.stderr}`,
'--expected-executable 必须是绝对路径',
'相对 expected executable 必须给出明确错误。',
);
assertNotIncludes(
`${result.stdout}\n${result.stderr}`,
'[fake-command]',
'相对 expected executable 被拒绝时不应执行真实命令。',
);
}
function assertRejectsFilesystemRootExpectedExecutable() {
const fixture = prepareFixture('filesystem-root-expected-executable');
const result = runCommandEvidence(fixture, [
'--output-root',
fixture.outputRoot,
'--expected-executable',
path.parse(process.cwd()).root,
'--',
fixture.commandScript,
]);
if ((result.status ?? 0) === 0) {
failures.push('命令证据脚本必须拒绝文件系统根目录 --expected-executable。');
}
assertIncludes(
`${result.stdout}\n${result.stderr}`,
'--expected-executable 不能是文件系统根目录',
'文件系统根目录 expected executable 必须给出明确错误。',
);
assertNotIncludes(
`${result.stdout}\n${result.stderr}`,
'[fake-command]',
'文件系统根目录 expected executable 被拒绝时不应执行真实命令。',
);
}
function assertRejectsExpectedExecutableWithControlCharacters() {
const fixture = prepareFixture('expected-executable-control-characters');
const result = runCommandEvidence(fixture, [
'--output-root',
fixture.outputRoot,
'--expected-executable',
`${fixture.commandScript}\n`,
'--',
fixture.commandScript,
]);
if ((result.status ?? 0) === 0) {
failures.push('命令证据脚本必须拒绝带换行的 --expected-executable。');
}
assertIncludes(
`${result.stdout}\n${result.stderr}`,
'--expected-executable 不能包含换行或 NUL 字符',
'带换行的 expected executable 必须给出明确错误。',
);
assertNotIncludes(
`${result.stdout}\n${result.stderr}`,
'[fake-command]',
'带换行 expected executable 被拒绝时不应执行真实命令。',
);
}
function assertRejectsMismatchedExpectedExecutableBeforeCommand() {
const fixture = prepareFixture('mismatched-expected-executable');
const expected = path.join(fixture.root, 'expected-enable.sh');
const result = runCommandEvidence(fixture, [
'--output-root',
fixture.outputRoot,
'--expected-executable',
expected,
'--',
fixture.commandScript,
]);
if ((result.status ?? 0) === 0) {
failures.push('命令证据脚本必须拒绝与 --expected-executable 不一致的真实命令。');
}
assertIncludes(
`${result.stdout}\n${result.stderr}`,
'真实命令与 --expected-executable 不一致',
'真实命令与 expected executable 不一致时必须给出明确错误。',
);
assertNotIncludes(
`${result.stdout}\n${result.stderr}`,
'[fake-command]',
'真实命令与 expected executable 不一致时不应执行真实命令。',
);
}
function assertRejectsMissingRequiredArgBeforeCommand() {
const fixture = prepareFixture('missing-required-arg');
const result = runCommandEvidence(fixture, [
'--output-root',
fixture.outputRoot,
'--require-arg',
'--apply',
'--',
fixture.commandScript,
'--message',
'dry-run-only',
]);
if ((result.status ?? 0) === 0) {
failures.push('命令证据脚本必须拒绝缺少 --require-arg 的真实命令。');
}
assertIncludes(
`${result.stdout}\n${result.stderr}`,
'真实命令参数缺少 --require-arg 要求的 --apply',
'真实命令缺少 required arg 时必须给出明确错误。',
);
assertNotIncludes(
`${result.stdout}\n${result.stderr}`,
'[fake-command]',
'真实命令缺少 required arg 时不应执行真实命令。',
);
}
function prepareFixture(name) {
const root = path.join(tmpRoot, name);
const outputRoot = path.join(root, 'evidence');
const commandScript = path.join(root, 'fake-command.sh');
mkdirSync(outputRoot, { recursive: true });
writeFileSync(
commandScript,
[
'#!/usr/bin/env bash',
'set -euo pipefail',
'message="ok"',
'exit_code=0',
'while [[ $# -gt 0 ]]; do',
' case "$1" in',
' --message)',
' message="$2"',
' shift 2',
' ;;',
' --fail)',
' exit_code=37',
' shift',
' ;;',
' --echo-env-token)',
' echo "[fake-command] env=${GENARRATIVE_PINGORA_DIRECT_PROBE_TOKEN:-}"',
' shift',
' ;;',
' --pingora-shadow-probe-token)',
' echo "[fake-command] token=$2" >&2',
' shift 2',
' ;;',
' *)',
' shift',
' ;;',
' esac',
'done',
'echo "[fake-command] stdout ${message}"',
'echo "[fake-command] stderr ${message}" >&2',
'exit "${exit_code}"',
'',
].join('\n'),
'utf8',
);
spawnSync('chmod', ['0755', commandScript], { encoding: 'utf8' });
return { root, outputRoot, commandScript };
}
function runCommandEvidence(fixture, args) {
return spawnSync('node', [COMMAND_EVIDENCE_SCRIPT, ...args], {
cwd: process.cwd(),
encoding: 'utf8',
env: {
...process.env,
GENARRATIVE_PINGORA_CUTOVER_RUN_ID: '',
GENARRATIVE_PINGORA_CUTOVER_EVIDENCE_ROOT: fixture.outputRoot,
GENARRATIVE_PINGORA_DIRECT_PROBE_TOKEN: 'secret-env-token',
},
});
}
function parseLastJsonObject(text, label) {
const end = text.lastIndexOf('}');
if (end < 0) {
failures.push(`${label} 未包含 JSON 结果。`);
return {};
}
for (let start = text.lastIndexOf('{', end); start >= 0; start = text.lastIndexOf('{', start - 1)) {
try {
return JSON.parse(text.slice(start, end + 1));
} catch {
// 继续向前找外层 JSON 起点。
}
}
failures.push(`${label} 未包含合法 JSON 结果。`);
return {};
}
function readJson(filePath) {
try {
return JSON.parse(readFileSync(filePath, 'utf8'));
} catch (error) {
failures.push(`${filePath} 不是合法 JSON: ${error.message}`);
return {};
}
}
function assertManifestFileMetadata(bundleDir, metadata, expectedPath) {
if (!metadata || typeof metadata !== 'object') {
failures.push(`${expectedPath} 必须在 manifest.files 中记录结构化元数据。`);
return;
}
assertEqual(
metadata.path,
expectedPath,
`${expectedPath} manifest 元数据必须记录文件名。`,
);
const filePath = path.join(bundleDir, expectedPath);
const content = readFileSync(filePath);
assertEqual(
metadata.sizeBytes,
content.length,
`${expectedPath} manifest 元数据必须记录文件大小。`,
);
assertEqual(
metadata.sha256,
createHash('sha256').update(content).digest('hex'),
`${expectedPath} manifest 元数据必须记录 sha256。`,
);
}
function assertFileExists(filePath, reason) {
if (!existsSync(filePath)) {
failures.push(`${reason} 缺少: ${filePath}`);
}
}
function assertMode(filePath, expected, reason) {
if (!existsSync(filePath)) {
return;
}
const actual = statSync(filePath).mode & 0o777;
if (actual !== expected) {
failures.push(
`${reason} 实际 ${actual.toString(8)},预期 ${expected.toString(8)}。`,
);
}
}
function assertStatus(result, expected, reason) {
const actual = result.status ?? 0;
if (actual !== expected) {
failures.push(
`${reason} 预期退出码 ${expected},实际 ${actual}。\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`,
);
}
}
function assertEqual(actual, expected, reason) {
if (actual !== expected) {
failures.push(`${reason} 实际 ${JSON.stringify(actual)},预期 ${JSON.stringify(expected)}。`);
}
}
function assertIncludes(value, expected, reason) {
if (!String(value).includes(expected)) {
failures.push(`${reason} 缺少: ${expected}`);
}
}
function assertNotIncludes(value, unexpected, reason) {
if (String(value).includes(unexpected)) {
failures.push(`${reason} 不应包含: ${unexpected}`);
}
}