import { execFileSync } from 'node:child_process'; import { mkdirSync, readFileSync } from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const prepared = new Map(); export function holodoriGit(repositoryPath, args) { return execFileSync('git', ['-c', `safe.directory=${repositoryPath.replaceAll('\\', '/')}`, '-C', repositoryPath, ...args], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, stdio: ['ignore', 'pipe', 'pipe'] }).trim(); } const git = holodoriGit; // Each process fetches the authoritative remote once per locale, then pins all // reads to that commit. Neither checkout files nor stale remote refs are inputs. export function prepareHolodoriDbSource(locale, { review = false } = {}) { if (!['eng', 'kor', 'jpn'].includes(locale)) throw new Error(`Unknown HolodoriDB locale: ${locale}`); if (process.argv.includes('--offline')) throw new Error('HolodoriDB collection requires the remote repository; --offline is not supported.'); if (prepared.has(locale)) { const source = prepared.get(locale); if (!review) requireReviewedSource(locale, source); return source; } const repository = `https://github.com/HolodoriDB/holodori-db-${locale}-diff.git`; const repositoryPath = path.join(projectRoot, 'data', 'raw', 'holodoridb-remote', locale); mkdirSync(repositoryPath, { recursive: true }); git(repositoryPath, ['init', '--bare']); try { git(repositoryPath, ['fetch', '--depth', '1', '--no-tags', repository, '+refs/heads/main:refs/heads/source-main']); } catch (error) { throw new Error(`Cannot fetch HolodoriDB ${locale} remote main; collection stopped without using cached data.`, { cause: error }); } const commit = git(repositoryPath, ['rev-parse', 'refs/heads/source-main']); const source = { repository, repositoryPath, commit, commitDate: git(repositoryPath, ['show', '-s', '--format=%cI', commit]), commitMessage: git(repositoryPath, ['show', '-s', '--format=%s', commit]) }; prepared.set(locale, source); if (!review) requireReviewedSource(locale, source); return source; } function requireReviewedSource(locale, source) { let report; try { report = JSON.parse(readFileSync(path.join(projectRoot, 'data/holodoridb-review/latest.json'), 'utf8')); } catch { throw new Error('Run npm run data:sync and present the full change report before collecting calculator data.'); } const manifest = JSON.parse(readFileSync(path.join(projectRoot, 'data/holodoridb/manifest.json'), 'utf8')); const entry = report.sources?.[locale]; if (entry?.to !== source.commit || ![entry.from, entry.to].includes(manifest.sources[locale].commit)) { throw new Error(`HolodoriDB ${locale} changed since review. Run npm run data:sync and present the updated report first.`); } } export function readHolodoriDbText(source, filename) { return git(source.repositoryPath, ['show', `${source.commit}:${filename}`]); } export function readHolodoriDbTable(locale, filename) { return JSON.parse(readHolodoriDbText(prepareHolodoriDbSource(locale), filename)); }