f45452ae6c
openSync with read-only mode fails fsync on Windows because FlushFileBuffers requires write-permission FD. This caused atomic writes to fail silently, leaving migrated config unwritten and triggering repeated migration + .bak.<timestamp> generation on every startup. Same root cause as PR #3644 (#3643). Hyperplan disappear is a secondary symptom of plugin load instability. Fixes #3877
42 lines
1006 B
TypeScript
42 lines
1006 B
TypeScript
import {
|
|
closeSync,
|
|
type fsyncSync as FsyncSync,
|
|
openSync,
|
|
renameSync,
|
|
unlinkSync,
|
|
writeFileSync,
|
|
} from "node:fs"
|
|
|
|
import { tolerantFsyncSync } from "./tolerant-fsync"
|
|
|
|
export function writeFileAtomically(
|
|
filePath: string,
|
|
content: string,
|
|
deps: { fsyncSync?: typeof FsyncSync } = {},
|
|
): void {
|
|
const tempPath = `${filePath}.tmp`
|
|
writeFileSync(tempPath, content, "utf-8")
|
|
const tempFileDescriptor = openSync(tempPath, "r+")
|
|
try {
|
|
tolerantFsyncSync(tempFileDescriptor, `writeFileAtomically:${filePath}`, deps.fsyncSync)
|
|
} finally {
|
|
closeSync(tempFileDescriptor)
|
|
}
|
|
|
|
try {
|
|
renameSync(tempPath, filePath)
|
|
} catch (error) {
|
|
const isWindows = process.platform === "win32"
|
|
const isPermissionError =
|
|
error instanceof Error &&
|
|
(error.message.includes("EPERM") || error.message.includes("EACCES"))
|
|
|
|
if (isWindows && isPermissionError) {
|
|
unlinkSync(filePath)
|
|
renameSync(tempPath, filePath)
|
|
} else {
|
|
throw error
|
|
}
|
|
}
|
|
}
|