UNPKG

@morodomi/ait3

Version:

AIT³ Development Platform - AI + Ticket + Test + Tool driven development methodology

194 lines (193 loc) 6.38 kB
/** * Common file operations utilities * Reduces code duplication for directory creation and file handling */ import { mkdir, writeFile, access, rename, rm } from 'fs/promises'; import { dirname } from 'path'; import { randomBytes } from 'crypto'; import { STYLES } from './styles.js'; /** * Ensure directory exists, creating it if necessary */ export async function ensureDirectoryExists(dirPath) { try { await access(dirPath); return false; // Directory already existed } catch { await mkdir(dirPath, { recursive: true }); return true; // Directory was created } } /** * Check if file exists and handle force overwrite logic */ export async function checkFileExists(filePath, force = false) { try { await access(filePath); // File exists if (!force) { return { exists: true, shouldWrite: false, message: `${STYLES.warning('⚠')} File ${STYLES.info(filePath)} already exists (use --force to overwrite)` }; } return { exists: true, shouldWrite: true, message: `${STYLES.warning('⚠')} Overwriting existing file: ${STYLES.info(filePath)}` }; } catch { // File doesn't exist return { exists: false, shouldWrite: true }; } } /** * Write file with directory creation and error handling */ export async function writeFileWithDirectoryCreation(filePath, content, force = false) { const fileDir = dirname(filePath); try { // Ensure directory exists const directoryCreated = await ensureDirectoryExists(fileDir); // Check if file exists and handle force logic const fileCheck = await checkFileExists(filePath, force); if (!fileCheck.shouldWrite) { return { success: false, directoryCreated, fileOverwritten: false, message: fileCheck.message }; } // Write file await writeFile(filePath, content, 'utf-8'); return { success: true, directoryCreated, fileOverwritten: fileCheck.exists, message: fileCheck.message }; } catch (error) { return { success: false, directoryCreated: false, fileOverwritten: false, message: `${STYLES.danger('ERROR: Failed to create')}: ${filePath}\n${error instanceof Error ? error.message : 'Unknown error'}` }; } } /** * Create multiple directories in parallel */ export async function ensureMultipleDirectories(dirPaths) { try { const results = await Promise.all(dirPaths.map(async (dirPath) => { const created = await ensureDirectoryExists(dirPath); return { dirPath, created }; })); const createdDirectories = results .filter(result => result.created) .map(result => result.dirPath); return { success: true, createdDirectories }; } catch (error) { return { success: false, createdDirectories: [], message: `${STYLES.danger('ERROR: Failed to create directories')}: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } /** * Generate success message for file operations */ export function generateFileOperationMessage(filePath, result, nextActionText) { const messages = []; if (result.directoryCreated) { const dirPath = dirname(filePath); messages.push(`${STYLES.success('SUCCESS:')} Created directory: ${STYLES.info(dirPath)}`); } if (result.fileOverwritten) { messages.push(`${STYLES.warning('⚠')} Overwriting existing file: ${STYLES.info(filePath)}`); } messages.push(`${STYLES.success('SUCCESS:')} File generated: ${STYLES.info(filePath)}`); if (nextActionText) { messages.push(''); messages.push(`${STYLES.bold('Next Action')}:`); messages.push(nextActionText); } return messages.join('\n'); } /** * Write file atomically using write-then-rename pattern * Ensures that concurrent reads never see partial content */ export async function atomicWriteFile(filePath, content) { // Generate unique temporary file name to avoid conflicts const hash = randomBytes(8).toString('hex'); const tempFilePath = `${filePath}.tmp-${hash}`; try { // Step 1: Write content to temporary file await writeFile(tempFilePath, content, 'utf-8'); // Step 2: Atomically rename temporary file to target file await rename(tempFilePath, filePath); } catch (error) { // Clean up temporary file on any error try { await rm(tempFilePath, { force: true }); } catch { // Ignore cleanup errors - temp file might not exist } // Re-throw original error throw error; } } /** * Write file atomically with directory creation and force overwrite logic * Combines atomic write with existing directory creation patterns */ export async function atomicWriteFileWithDirectoryCreation(filePath, content, force = false) { const fileDir = dirname(filePath); try { // Ensure directory exists const directoryCreated = await ensureDirectoryExists(fileDir); // Check if file exists and handle force logic const fileCheck = await checkFileExists(filePath, force); if (!fileCheck.shouldWrite) { return { success: false, directoryCreated, fileOverwritten: false, message: fileCheck.message }; } // Write file atomically await atomicWriteFile(filePath, content); return { success: true, directoryCreated, fileOverwritten: fileCheck.exists, message: fileCheck.message }; } catch (error) { return { success: false, directoryCreated: false, fileOverwritten: false, message: `${STYLES.danger('ERROR: Failed to create')}: ${filePath}\n${error instanceof Error ? error.message : 'Unknown error'}` }; } }