import { promises as fs } from "fs" import path from "path" export async function backupFile(filePath: string): Promise { if (!(await pathExists(filePath))) return null try { const timestamp = new Date().toISOString().replace(/[:.]/g, "-") const backupPath = `${filePath}.bak.${timestamp}` await fs.copyFile(filePath, backupPath) return backupPath } catch { return null } } export async function pathExists(filePath: string): Promise { try { await fs.access(filePath) return true } catch { return false } } export async function ensureDir(dirPath: string): Promise { await fs.mkdir(dirPath, { recursive: true }) } export async function readText(filePath: string): Promise { return fs.readFile(filePath, "utf8") } export async function readJson(filePath: string): Promise { const raw = await readText(filePath) return JSON.parse(raw) as T } export async function writeText(filePath: string, content: string): Promise { await ensureDir(path.dirname(filePath)) await fs.writeFile(filePath, content, "utf8") } export async function writeJson(filePath: string, data: unknown): Promise { const content = JSON.stringify(data, null, 2) await writeText(filePath, content + "\n") } /** Write JSON with restrictive permissions (0o600) for files containing secrets */ export async function writeJsonSecure(filePath: string, data: unknown): Promise { const content = JSON.stringify(data, null, 2) await ensureDir(path.dirname(filePath)) await fs.writeFile(filePath, content + "\n", { encoding: "utf8", mode: 0o600 }) } export async function walkFiles(root: string): Promise { const entries = await fs.readdir(root, { withFileTypes: true }) const results: string[] = [] for (const entry of entries) { const fullPath = path.join(root, entry.name) if (entry.isDirectory()) { const nested = await walkFiles(fullPath) results.push(...nested) } else if (entry.isFile()) { results.push(fullPath) } } return results } export async function copyDir(sourceDir: string, targetDir: string): Promise { await ensureDir(targetDir) const entries = await fs.readdir(sourceDir, { withFileTypes: true }) for (const entry of entries) { const sourcePath = path.join(sourceDir, entry.name) const targetPath = path.join(targetDir, entry.name) if (entry.isDirectory()) { await copyDir(sourcePath, targetPath) } else if (entry.isFile()) { await ensureDir(path.dirname(targetPath)) await fs.copyFile(sourcePath, targetPath) } } }