CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes45downloads
commentJson.ts163 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';8import { parse, stringify } from 'comment-json';9import { writeStderrLine } from './stdioHelpers.js';10import { writeWithBackupSync } from './writeWithBackup.js';11 12/**13 * Updates a JSON file while preserving comments and formatting.14 *15 * In merge mode (default), updates are deep-merged into the existing file,16 * preserving keys not mentioned in the updates object.17 * A replacePath can be provided for a single updated subtree that should be18 * replaced exactly instead of deep-merged.19 *20 * In sync mode (sync=true), the file is synchronized to match the updates21 * object exactly — keys present in the original but not in updates are22 * removed, preventing zombie keys after migrations.23 *24 * Uses writeWithBackupSync internally for atomic temp-file + rename writes,25 * preventing file corruption if the process crashes mid-write.26 *27 * @returns true if the file was successfully written, false if the write28 * was refused (e.g. the result would not be valid JSON or file not parseable).29 */30export function updateSettingsFilePreservingFormat(31  filePath: string,32  updates: Record<string, unknown>,33  sync = false,34  replacePath: readonly string[] = [],35): boolean {36  if (!fs.existsSync(filePath)) {37    const content = stringify(updates, null, 2);38    writeWithBackupSync(filePath, content);39    return true;40  }41 42  const originalContent = fs.readFileSync(filePath, 'utf-8');43 44  let parsed: Record<string, unknown>;45  try {46    parsed = parse(originalContent) as Record<string, unknown>;47  } catch (_error) {48    writeStderrLine('Error parsing settings file.');49    writeStderrLine(50      `Settings file may be corrupted: ${_error instanceof Error ? _error.message : String(_error)}`,51    );52    return false;53  }54 55  // In sync mode, applyUpdates recursively removes keys not present in the56  // migrated object, preventing zombie keys at every nesting level.57  // In merge mode, only the specified updates are applied.58  const updatedStructure = applyUpdates(parsed, updates, sync, replacePath);59 60  const updatedContent = stringify(updatedStructure, null, 2);61 62  // Validate that the output is parseable before writing to disk.63  // This prevents corrupted settings files that would block startup.64  try {65    parse(updatedContent);66  } catch (validationError) {67    writeStderrLine(68      'Error: Refusing to write settings file — the result would not be valid JSON.',69    );70    writeStderrLine(71      validationError instanceof Error72        ? validationError.message73        : String(validationError),74    );75    return false;76  }77 78  writeWithBackupSync(filePath, updatedContent);79  return true;80}81 82export function applyUpdates(83  current: Record<string, unknown>,84  updates: Record<string, unknown>,85  sync = false,86  replacePath: readonly string[] = [],87  currentPath: readonly string[] = [],88): Record<string, unknown> {89  const result = current;90 91  if (sync) {92    // Sync mode: remove keys from current that are not present in updates,93    // then recursively apply updates. This prevents nested zombie keys94    // from persisting after migrations that restructure nested objects.95    const keysToRemove = Object.keys(result).filter((key) => !(key in updates));96    for (const key of keysToRemove) {97      delete result[key];98    }99  }100 101  for (const key of Object.getOwnPropertyNames(updates)) {102    if (key === '__proto__' || key === 'constructor' || key === 'prototype') {103      continue;104    }105 106    const value = updates[key];107    const nextPath = [...currentPath, key];108    const valueIsObject =109      typeof value === 'object' &&110      value !== null &&111      !Array.isArray(value) &&112      Object.keys(value).length > 0;113    if (pathsEqual(nextPath, replacePath)) {114      result[key] = valueIsObject115        ? applyUpdates({}, value as Record<string, unknown>)116        : value;117      continue;118    }119 120    if (121      valueIsObject &&122      (typeof result[key] !== 'object' ||123        result[key] === null ||124        Array.isArray(result[key]))125    ) {126      result[key] = applyUpdates(127        {},128        value as Record<string, unknown>,129        sync,130        replacePath,131        nextPath,132      );133    } else if (134      valueIsObject &&135      typeof result[key] === 'object' &&136      result[key] !== null &&137      !Array.isArray(result[key])138    ) {139      result[key] = applyUpdates(140        result[key] as Record<string, unknown>,141        value as Record<string, unknown>,142        sync,143        replacePath,144        nextPath,145      );146    } else {147      result[key] = value;148    }149  }150 151  return result;152}153 154function pathsEqual(155  left: readonly string[],156  right: readonly string[],157): boolean {158  return (159    left.length === right.length &&160    left.every((segment, index) => segment === right[index])161  );162}163 
basant307/AI_Governance_Project · CoolFace