| import { randomBytes } from 'node:crypto'; |
| import { closeSync, fsyncSync, openSync } from 'node:fs'; |
| import * as nodeFs from 'node:fs'; |
| import { open, rename, unlink } from 'node:fs/promises'; |
|
|
| export async function syncDir(dirPath: string): Promise<void> { |
| if (process.platform === 'win32') return; |
| const dirFh = await open(dirPath, 'r'); |
| try { |
| await dirFh.sync(); |
| } finally { |
| await dirFh.close(); |
| } |
| } |
|
|
| export function syncDirSync(dirPath: string): void { |
| if (process.platform === 'win32') return; |
| const fd = openSync(dirPath, 'r'); |
| try { |
| fsyncSync(fd); |
| } finally { |
| closeSync(fd); |
| } |
| } |
|
|
| function syncFd(fd: number): Promise<void> { |
| return new Promise<void>((resolve, reject) => { |
| nodeFs.fsync(fd, (err) => { |
| if (err) { |
| reject(err); |
| return; |
| } |
| resolve(); |
| }); |
| }); |
| } |
|
|
| export async function atomicWrite( |
| filePath: string, |
| content: string | Uint8Array, |
| _syncOverride?: (fd: number) => Promise<void>, |
| ): Promise<void> { |
| const hex = randomBytes(4).toString('hex'); |
| const tmpPath = `${filePath}.tmp.${process.pid}.${hex}`; |
| let renamed = false; |
| try { |
| const fh = await open(tmpPath, 'w'); |
| try { |
| await fh.writeFile(content); |
| await (_syncOverride ?? syncFd)(fh.fd); |
| } finally { |
| await fh.close(); |
| } |
| if (process.platform === 'win32') { |
| try { |
| await unlink(filePath); |
| } catch (error) { |
| const code = (error as NodeJS.ErrnoException).code; |
| if (code !== 'ENOENT') throw error; |
| } |
| } |
| await rename(tmpPath, filePath); |
| renamed = true; |
| } finally { |
| if (!renamed) { |
| try { |
| await unlink(tmpPath); |
| } catch { |
| } |
| } |
| } |
| } |
|
|