/** * run command — full backup run. */ import { rm, mkdir } from 'fs/promises'; import { join } from 'path'; import { run as spawn } from '../spawn.js'; import { getBackend } from '../backends/index.js'; import { readState, writeState, PHASES } from '../state.js'; export async function runCommand(config) { const { source, prev, pend, deltas, backend: backendName, dryRun } = config; const backend = getBackend(backendName); const dry = dryRun; if (dry) console.log('[dry-run] No changes will be made.\n'); // ── Load state ────────────────────────────────────────────── const state = await readState(deltas); const seq = state.next_seq; console.log(`Starting run — seq ${seq} (last complete: ${state.last_complete})`); // TODO: detect and handle partially-committed previous run // ── Phase 1: Clear PEND ───────────────────────────────────── await setPhase(deltas, state, PHASES.CLEARING_PEND, dry); console.log('\n── Clear PEND ──'); if (!dry) { await rm(pend, { recursive: true, force: true }); await mkdir(pend, { recursive: true }); } else { console.log(`[dry-run] rm -rf ${pend} && mkdir -p ${pend}`); } // ── Phase 2: rsync PREV → PEND (local seed) ───────────────── await setPhase(deltas, state, PHASES.RSYNC_LOCAL, dry); console.log('\n── rsync PREV → PEND (local seed) ──'); await spawn('rsync', ['-aP', trailingSlash(prev), pend], { dryRun: dry }); // ── Phase 3: rsync SOURCE → PEND (remote changes) ─────────── await setPhase(deltas, state, PHASES.RSYNC_REMOTE, dry); console.log('\n── rsync SOURCE → PEND ──'); await spawn('rsync', ['-aP', trailingSlash(source), pend], { dryRun: dry }); // ── Phase 4: Generate delta ────────────────────────────────── await setPhase(deltas, state, PHASES.GENERATING, dry); console.log('\n── Generate delta ──'); // TODO: walk PREV and PEND, diff per file, build manifest // ── Phase 5: Commit delta ──────────────────────────────────── await setPhase(deltas, state, PHASES.COMMITTING, dry); console.log('\n── Commit delta ──'); // TODO: atomic rename DELTAS/tmp/N → DELTAS/N // ── Phase 6: Promote PEND → PREV ──────────────────────────── await setPhase(deltas, state, PHASES.PROMOTING, dry); console.log('\n── Promote PEND → PREV ──'); // TODO: mv PEND PREV (swap) // ── Done ───────────────────────────────────────────────────── state.last_complete = seq; state.next_seq = seq + 1; state.phase = PHASES.IDLE; if (!dry) await writeState(deltas, state); console.log(`\nRun complete — seq ${seq} committed.`); } async function setPhase(deltas, state, phase, dry) { state.phase = phase; if (!dry) await writeState(deltas, state); } function trailingSlash(p) { return p.endsWith('/') ? p : p + '/'; }