adc62957a7
CI / Multi-repo Path Gate (AST-grep) (windows-latest) (push) Has been cancelled
CI / Version Consistency Check (push) Has been cancelled
CI / npm pack + install test (push) Has been cancelled
CI / Lint & Type Check (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Test (Windows path suite) (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / No Committed Build Artifacts (push) Has been cancelled
CI / Multi-repo Path Gate (AST-grep) (ubuntu-latest) (push) Has been cancelled
Upgrade Test / omc update + session-start hook (push) Has been cancelled
96 lines
2.2 KiB
JavaScript
96 lines
2.2 KiB
JavaScript
/**
|
|
* Atomic file writes for oh-my-claudecode hooks.
|
|
* Self-contained module with no external dependencies.
|
|
*
|
|
* Mirrors templates/hooks/lib/atomic-write.mjs for use by plugin hook scripts.
|
|
*/
|
|
|
|
import { openSync, writeSync, fsyncSync, closeSync, renameSync, unlinkSync, mkdirSync, existsSync } from 'fs';
|
|
import { dirname, basename, join } from 'path';
|
|
import { randomUUID } from 'crypto';
|
|
|
|
/**
|
|
* Ensure directory exists
|
|
*/
|
|
export function ensureDirSync(dir) {
|
|
if (existsSync(dir)) {
|
|
return;
|
|
}
|
|
try {
|
|
mkdirSync(dir, { recursive: true });
|
|
} catch (err) {
|
|
if (err.code === 'EEXIST') {
|
|
return;
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Write string content atomically to a file.
|
|
* Uses temp file + atomic rename pattern with fsync for durability.
|
|
*
|
|
* @param {string} filePath Target file path
|
|
* @param {string} content String content to write
|
|
*/
|
|
export function atomicWriteFileSync(filePath, content) {
|
|
const dir = dirname(filePath);
|
|
const base = basename(filePath);
|
|
const tempPath = join(dir, `.${base}.tmp.${randomUUID()}`);
|
|
|
|
let fd = null;
|
|
let success = false;
|
|
|
|
try {
|
|
// Ensure parent directory exists
|
|
ensureDirSync(dir);
|
|
|
|
// Open temp file with exclusive creation (O_CREAT | O_EXCL | O_WRONLY)
|
|
fd = openSync(tempPath, 'wx', 0o600);
|
|
|
|
// Write content
|
|
writeSync(fd, content, 0, 'utf-8');
|
|
|
|
// Sync file data to disk before rename
|
|
fsyncSync(fd);
|
|
|
|
// Close before rename
|
|
closeSync(fd);
|
|
fd = null;
|
|
|
|
// Atomic rename - replaces target file if it exists
|
|
renameSync(tempPath, filePath);
|
|
|
|
success = true;
|
|
|
|
// Best-effort directory fsync to ensure rename is durable
|
|
try {
|
|
const dirFd = openSync(dir, 'r');
|
|
try {
|
|
fsyncSync(dirFd);
|
|
} finally {
|
|
closeSync(dirFd);
|
|
}
|
|
} catch {
|
|
// Some platforms don't support directory fsync - that's okay
|
|
}
|
|
} finally {
|
|
// Close fd if still open
|
|
if (fd !== null) {
|
|
try {
|
|
closeSync(fd);
|
|
} catch {
|
|
// Ignore close errors
|
|
}
|
|
}
|
|
// Clean up temp file on error
|
|
if (!success) {
|
|
try {
|
|
unlinkSync(tempPath);
|
|
} catch {
|
|
// Ignore cleanup errors
|
|
}
|
|
}
|
|
}
|
|
}
|