import { createHash } from 'node:crypto'; import { readFileSync } from 'node:fs'; import { resolve, join } from 'node:path'; import { spawnSync } from 'node:child_process'; import { evaluatePayloadGuard } from './updater-lifecycle-3.4.mjs'; const sha256 = value => createHash('sha256').update(value).digest('hex'); function git(repo, args, { allowFailure = false, encoding = 'utf8' } = {}) { const result = spawnSync('git', ['-C', repo, ...args], { encoding, maxBuffer: 64 * 1024 * 1024 }); if (!allowFailure && result.status !== 0) { throw new Error(`git ${args.join(' ')} fehlgeschlagen: ${(result.stderr || result.stdout || '').trim()}`); } return result; } function readGitFile(repo, commit, path) { const result = git(repo, ['show', `${commit}:${path}`], { allowFailure: true, encoding: null }); if (result.status !== 0) return null; return Buffer.from(result.stdout); } function normalize(path) { return String(path || '').replaceAll('\\', '/').replace(/^\.\//, ''); } export function inspectPayload({ packageRoot, repositoryRoot, baseCommit, currentMainCommit }) { const source = resolve(packageRoot); const repo = resolve(repositoryRoot); const manifest = JSON.parse(readFileSync(join(source, 'release-manifest.json'), 'utf8')); const baseExists = git(repo, ['cat-file', '-e', `${baseCommit}^{commit}`], { allowFailure: true }).status === 0; const mainExists = git(repo, ['cat-file', '-e', `${currentMainCommit}^{commit}`], { allowFailure: true }).status === 0; if (!baseExists || !mainExists) { return { ...evaluatePayloadGuard({ baseCommit: baseExists ? baseCommit : '', currentMainCommit: mainExists ? currentMainCommit : '', baseIsAncestor: false }), payloadChangedPaths: [], mainChangedPaths: [], }; } const baseIsAncestor = git(repo, ['merge-base', '--is-ancestor', baseCommit, currentMainCommit], { allowFailure: true }).status === 0; const mainChangedPaths = new Set( String(git(repo, ['diff', '--name-only', `${baseCommit}..${currentMainCommit}`, '--']).stdout || '') .split(/\r?\n/).map(normalize).filter(Boolean), ); const payloadChangedPaths = new Set(); const desired = new Map(); for (const entry of manifest.files || []) { const path = normalize(entry.path); const baseBytes = readGitFile(repo, baseCommit, path); const baseHash = baseBytes === null ? null : sha256(baseBytes); if (baseHash !== String(entry.sha256)) payloadChangedPaths.add(path); desired.set(path, String(entry.sha256)); } for (const value of manifest.removedFiles || []) { const path = normalize(value); if (readGitFile(repo, baseCommit, path) !== null) payloadChangedPaths.add(path); desired.set(path, null); } const conflictingPaths = []; for (const path of payloadChangedPaths) { if (!mainChangedPaths.has(path)) continue; const currentBytes = readGitFile(repo, currentMainCommit, path); const currentHash = currentBytes === null ? null : sha256(currentBytes); if (currentHash !== desired.get(path)) conflictingPaths.push(path); } return { ...evaluatePayloadGuard({ baseCommit, currentMainCommit, baseIsAncestor, conflictingPaths }), payloadChangedPaths: [...payloadChangedPaths].sort(), mainChangedPaths: [...mainChangedPaths].sort(), }; } if (process.argv[1] && import.meta.url === new URL(`file://${process.argv[1].replaceAll('\\', '/')}`).href) { const [packageRoot, repositoryRoot, baseCommit, currentMainCommit] = process.argv.slice(2); try { if (!packageRoot || !repositoryRoot || !baseCommit || !currentMainCommit) { throw new Error('Aufruf: node check-release-payload-3.4.mjs '); } const result = inspectPayload({ packageRoot, repositoryRoot, baseCommit, currentMainCommit }); process.stdout.write(`${JSON.stringify(result)}\n`); if (!result.allowed) process.exitCode = 2; } catch (error) { process.stderr.write(`[FEHLER] ${error.message}\n`); process.exit(1); } }