fix(sync): harden sync CLI, IPC, and UI paths from CodeRabbit review

- reject option-like tokens as flag values (--snapshot --force wrote a
  file named --force); --flag=-value still works
- PowerShell remote quoting uses single-quoted literals so $() in a
  quoted path cannot expand
- sync-hosts.json written via temp file + rename; a crash mid-write
  truncated it and the reader's corrupt-fallback dropped every host
- cancelled sync child escalates SIGTERM -> SIGKILL after 5s grace
- NDJSON progress events validated field-by-field before casting
- snapshot filenames include milliseconds to avoid same-second overwrite
- syncAutoScheduler.stop() wired into will-quit cleanup
- sync --ui exclusivity also rejects --make-temp/--remove-temp/--json
- document --sync-window in app help; group --make-temp/--remove-temp
  under modes in sync usage
This commit is contained in:
2026-07-12 02:10:04 -07:00
parent 25cca8ce24
commit c9f85473bb
19 changed files with 257 additions and 27 deletions
+19 -1
View File
@@ -180,16 +180,34 @@ export function readSyncHostsState(
}
}
// Written via a sibling temp file + rename so an interrupted write cannot leave
// a truncated sync-hosts.json behind (readSyncHostsState would silently fall
// back to defaults, dropping every configured host).
export function writeSyncHostsState(
filePath: string,
state: SyncHostsState,
deps?: {
mkdirSync?: (candidate: string, options: { recursive: true }) => void;
writeFileSync?: (candidate: string, content: string, encoding: BufferEncoding) => void;
renameSync?: (from: string, to: string) => void;
rmSync?: (target: string, options: { force: true }) => void;
},
): void {
const mkdirSync = deps?.mkdirSync ?? fs.mkdirSync;
const writeFileSync = deps?.writeFileSync ?? fs.writeFileSync;
const renameSync = deps?.renameSync ?? fs.renameSync;
const rmSync = deps?.rmSync ?? fs.rmSync;
mkdirSync(path.dirname(filePath), { recursive: true });
writeFileSync(filePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
const tempPath = `${filePath}.tmp`;
writeFileSync(tempPath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
try {
renameSync(tempPath, filePath);
} catch (error) {
try {
rmSync(tempPath, { force: true });
} catch {
// best effort: the temp file is disposable
}
throw error;
}
}