CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes48downloads
writeWithBackup.ts185 linesDownload Raw Back to utils
1/**2 * @license3 * Copyright 2025 Google LLC4 * SPDX-License-Identifier: Apache-2.05 */6 7import * as fs from 'node:fs';8 9/**10 * Options for writeWithBackup function.11 */12export interface WriteWithBackupOptions {13  /** Suffix for backup file (default: '.orig') */14  backupSuffix?: string;15  /** File encoding (default: 'utf-8') */16  encoding?: BufferEncoding;17}18 19/**20 * Safely writes content to a file with backup protection.21 *22 * This function ensures data safety by:23 * 1. Writing content to a temporary file first24 * 2. Backing up the existing target file (if any)25 * 3. Renaming the temporary file to the target path26 *27 * If any step fails, an error is thrown and no partial changes are left on disk.28 * The backup acts as an in-flight safety net: if the final rename fails the29 * original is restored from it. On success the backup is removed so it does not30 * linger next to the target file (which would pollute the user's project).31 *32 * Note: This is not 100% atomic but provides good protection. In the worst case33 * (a crash between the backup rename and the final rename), a .orig file remains34 * holding the last good content and can be manually restored.35 *36 * @param targetPath - The path to write to37 * @param content - The content to write38 * @param options - Optional configuration39 * @throws Error if any step of the write process fails40 *41 * @example42 * ```typescript43 * await writeWithBackup('/path/to/settings.json', JSON.stringify(settings, null, 2));44 * // On success only /path/to/settings.json exists; the .orig backup is cleaned up.45 * ```46 */47export async function writeWithBackup(48  targetPath: string,49  content: string,50  options: WriteWithBackupOptions = {},51): Promise<void> {52  // Async version delegates to sync version since file operations are synchronous53  writeWithBackupSync(targetPath, content, options);54}55 56/**57 * Synchronous version of writeWithBackup.58 *59 * @param targetPath - The path to write to60 * @param content - The content to write61 * @param options - Optional configuration62 * @throws Error if any step of the write process fails63 */64export function writeWithBackupSync(65  targetPath: string,66  content: string,67  options: WriteWithBackupOptions = {},68): void {69  const { backupSuffix = '.orig', encoding = 'utf-8' } = options;70  const tempPath = `${targetPath}.tmp`;71  const backupPath = `${targetPath}${backupSuffix}`;72  let backupCreated = false;73 74  // Clean up any existing temp file from previous failed attempts75  try {76    if (fs.existsSync(tempPath)) {77      fs.unlinkSync(tempPath);78    }79  } catch (_e) {80    // Ignore cleanup errors81  }82 83  try {84    // Step 1: Write to temporary file85    fs.writeFileSync(tempPath, content, { encoding, flush: true });86 87    // Step 2: If target exists, back it up88    if (fs.existsSync(targetPath)) {89      // Check if target is a directory - we can't write to a directory90      const targetStat = fs.statSync(targetPath);91      if (targetStat.isDirectory()) {92        // Clean up temp file before throwing93        try {94          fs.unlinkSync(tempPath);95        } catch (_e) {96          // Ignore cleanup error97        }98        throw new Error(99          `Cannot write to '${targetPath}' because it is a directory`,100        );101      }102 103      try {104        fs.renameSync(targetPath, backupPath);105        backupCreated = true;106      } catch (backupError) {107        // Clean up temp file before throwing108        try {109          fs.unlinkSync(tempPath);110        } catch (_e) {111          // Ignore cleanup error112        }113        throw new Error(114          `Failed to backup existing file: ${backupError instanceof Error ? backupError.message : String(backupError)}`,115        );116      }117    }118 119    // Step 3: Rename temp file to target120    try {121      fs.renameSync(tempPath, targetPath);122 123      // Step 4: Write succeeded — the backup was only an in-flight safety net,124      // so remove it instead of leaving a .orig file behind in the user's dir.125      if (backupCreated) {126        try {127          fs.unlinkSync(backupPath);128        } catch (_e) {129          // Best-effort cleanup; a stray backup is harmless and non-fatal.130        }131      }132    } catch (renameError) {133      let restoreFailedMessage: string | undefined;134      let backupExisted = false;135 136      // Attempt to restore backup if rename failed137      if (fs.existsSync(backupPath)) {138        backupExisted = true;139        try {140          fs.renameSync(backupPath, targetPath);141        } catch (restoreError) {142          restoreFailedMessage =143            restoreError instanceof Error144              ? restoreError.message145              : String(restoreError);146        }147      }148 149      const writeFailureMessage =150        renameError instanceof Error151          ? renameError.message152          : String(renameError);153 154      if (restoreFailedMessage) {155        throw new Error(156          `Failed to write file: ${writeFailureMessage}. ` +157            `Automatic restore failed: ${restoreFailedMessage}. ` +158            `Manual recovery may be required using backup file '${backupPath}'.`,159        );160      }161 162      if (backupExisted) {163        throw new Error(164          `Failed to write file: ${writeFailureMessage}. ` +165            `Target was automatically restored from backup '${backupPath}'.`,166        );167      }168 169      throw new Error(170        `Failed to write file: ${writeFailureMessage}. No backup file was available for restoration.`,171      );172    }173  } catch (error) {174    // Ensure temp file is cleaned up on any error175    try {176      if (fs.existsSync(tempPath)) {177        fs.unlinkSync(tempPath);178      }179    } catch (_e) {180      // Ignore cleanup error181    }182    throw error;183  }184}185 
basant307/AI_Governance_Project · CoolFace