sync-rules
Version:
CLI tool to synchronize AI coding assistant rule files between projects.
51 lines (50 loc) • 1.87 kB
JavaScript
import { mkdir, writeFile, rename, rm } from "node:fs/promises";
import { dirname, join } from "node:path";
import { randomUUID } from "node:crypto";
import { normalizePath } from "../utils/paths.js";
import { SyncError, ensureError } from "../utils/errors.js";
/**
* Execute write actions atomically with optional dry-run support.
*
* Uses atomic write strategy: writes to temporary file then renames to target path.
* This ensures partial writes never corrupt existing files.
*
* @param actions - Array of write actions to execute
* @param flags - Execution flags (e.g., `{ dryRun: true }` to skip actual writes)
* @returns Report containing paths that were (or would be) written
*/
export async function executeActions(actions, flags = { dryRun: false }) {
const { dryRun } = flags;
const report = {
written: [],
};
if (actions.length === 0) {
return report;
}
const normalized = actions.map((a) => ({
...a,
path: normalizePath(a.path),
}));
for (const { path, content } of normalized) {
if (dryRun) {
report.written.push(path);
continue;
}
const tmp = join(dirname(path), `.${randomUUID()}.tmp`);
try {
await mkdir(dirname(path), { recursive: true });
// Atomic write: write to a temp file in the same directory, then rename
await writeFile(tmp, content, "utf8");
await rename(tmp, path);
report.written.push(path);
}
catch (err) {
// Best-effort cleanup: remove temp file if it exists
await rm(tmp, { force: true }).catch(() => {
/* ignore cleanup errors */
});
throw new SyncError(`Failed to write ${path}`, { action: "write", path }, ensureError(err));
}
}
return report;
}