import { randomUUID } from 'node:crypto'
import fs from 'node:fs/promises'
import path from 'node:path'
export async function writeFileSafely(file: string, contents: string) {
// Resolve symlinks so the rename replaces the actual target file rather than
// replacing the symlink itself with a regular file.
let realFile = await fs.realpath(file).catch(() => file)
// Start by creating a new file in the current directory that is guaranteed to
// be unique (via `uuid`). We can embed the `process.id` in case we need to
// debug things later.
//
// While we can write this to a more global `/tmp` folder, I want to be 100%
// sure that we are on the same file system (same drive) so the rename
// operation is atomic. Once the file is written, we will rename the file. If
// this fails, the old file is still intact, if it works we have an updated
// file.
//
// If this still causes problems (but it will slow things down):
// 1. We could make sure that we inherit the file permissions
// 2. Use an explicit fsync to force a flush to disk
let temporaryFile = path.join(
path.dirname(realFile),
`.${path.basename(realFile)}.tailwind-upgrade.${process.pid}.${randomUUID()}.tmp`,
)
// Write file uses the `w` flag by default, which is defined as:
// > Open file for writing. The file is created (if it does not exist) or truncated (if it exists).
// > https://nodejs.org/api/fs.html#file-system-flags
//
// Which means that if this function is actively running, and you cancel the
// process at the wrong time, then the truncated files are present. Since all
// these migrations happen in parallel, multiple files are open and available
// to be written to, it could mean in multiple truncated files.
//
// Writing to a temp file first means that if the process is cancelled at this
// point, that the old original file is still correct.
//
// The rename part should be atomic (especially because we guarantee it to be
// on the same file system) so this either succeeds or doesn't happen.
try {
await fs.writeFile(temporaryFile, contents, 'utf8')
await fs.rename(temporaryFile, realFile)
} catch (error) {
await fs.unlink(temporaryFile).catch(() => {})
throw error
}
}