agent-ecosystem/src/main/utils/atomicWrite.ts
iliya ccee484adc fix: editor improvements — isDir bug, scroll-to-line, Quick Open, a11y
- Fix isDir heuristic: use backend-provided isDirectory instead of
  filename-based guessing (breaks for Makefile, .github, etc.)
- Add scroll-to-line on search result click via editorPendingGoToLine
- Add Cmd+Shift+W shortcut for toggling line wrap
- Rewrite Quick Open to fetch all project files from backend API
  instead of flattening the loaded tree (limited to expanded dirs)
- Fix fd leak in atomicWrite: close file handle in finally block
- Add a11y: role=dialog/alert, aria-modal, aria-label on modals
- Add type=button on error state buttons
2026-03-01 07:55:50 +02:00

41 lines
1.2 KiB
TypeScript

import { randomUUID } from 'crypto';
import * as fs from 'fs';
import * as path from 'path';
/**
* Async atomic write: write tmp file then rename over target.
* Uses best-effort fsync and EXDEV fallback for safety.
*/
export async function atomicWriteAsync(targetPath: string, data: string): Promise<void> {
const dir = path.dirname(targetPath);
const tmpPath = path.join(dir, `.tmp.${randomUUID()}`);
try {
await fs.promises.mkdir(dir, { recursive: true });
await fs.promises.writeFile(tmpPath, data, 'utf8');
let fd: fs.promises.FileHandle | null = null;
try {
fd = await fs.promises.open(tmpPath, 'r+');
await fd.sync();
} catch {
// fsync is best-effort.
} finally {
await fd?.close();
}
try {
await fs.promises.rename(tmpPath, targetPath);
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'EXDEV') {
await fs.promises.copyFile(tmpPath, targetPath);
await fs.promises.unlink(tmpPath).catch(() => undefined);
} else {
throw error;
}
}
} catch (error) {
await fs.promises.unlink(tmpPath).catch(() => undefined);
throw error;
}
}