import {existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync} from 'node:fs'; import path from 'node:path'; import {sha256} from './images.mjs'; import {redact} from '../providers/http.mjs'; export function jsonFile(file, value, secret = '') { writeFileSync(file, `${redact(JSON.stringify(value, null, 2), secret)}\n`, 'utf8'); } export function prepareRun(outDir, fingerprint, resume) { const out = path.resolve(outDir); const manifestPath = path.join(out, 'manifest.json'); if (existsSync(out) && readdirSync(out).length) { if (!resume) throw new Error(`输出目录非空,请使用新目录或 --resume:${out}`); if (!existsSync(manifestPath)) throw new Error('无法复用:目录没有 manifest.json'); const previous = JSON.parse(readFileSync(manifestPath, 'utf8')); if (previous.fingerprint !== fingerprint) throw new Error('无法复用:模型、配置、输入或代码版本变化,请使用新的输出目录'); if (previous.status !== 'completed') throw new Error('上次任务没有完整结束,请保留诊断文件并使用新目录重跑'); for (const [name, hash] of Object.entries(previous.artifact_hashes || {})) { const file = path.join(out, name); if (!existsSync(file) || sha256(readFileSync(file)) !== hash) throw new Error(`缓存产物缺失或被修改:${name}`); } if (!Object.keys(previous.artifact_hashes || {}).length) throw new Error('缓存缺少产物校验记录'); return {out, manifestPath, reused: true}; } mkdirSync(out, {recursive: true}); return {out, manifestPath, reused: false}; } export function finishRun(run, manifest, artifacts, secret) { manifest.artifact_hashes = Object.fromEntries(artifacts.map((name) => [name, sha256(readFileSync(path.join(run.out, name)))])); manifest.status = 'completed'; jsonFile(run.manifestPath, manifest, secret); }