src / fileIO.ts
src / fileIO.ts
import * as fs from "fs";
import * as path from "path";
import { FileStats, DirectoryEntry } from "./types";
import { expandPath, validateWritePath } from "./securityEnhanced";
// ── File Operations ──
export function readFile(filePath: string): string {
try {
return fs.readFileSync(expandPath(filePath), "utf8");
} catch (err) {
throw new Error(`Failed to read file: ${filePath} — ${(err as Error).message}`);
}
}
export function writeFileSync(filePath: string, content: string): void {
const validation = validateWritePath(filePath);
if (!validation.valid) {
throw new Error(validation.error!);
}
const safePath = validation.sanitized!;
try {
const dir = path.dirname(safePath);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(safePath, content, "utf8");
} catch (err) {
throw new Error(`Failed to write file: ${filePath} — ${(err as Error).message}`);
}
}
export function appendFileSync(filePath: string, content: string): void {
const validation = validateWritePath(filePath);
if (!validation.valid) {
throw new Error(validation.error!);
}
const safePath = validation.sanitized!;
try {
fs.appendFileSync(safePath, content, "utf8");
} catch (err) {
throw new Error(`Failed to append to file: ${filePath} — ${(err as Error).message}`);
}
}
export function deleteFileSync(filePath: string): void {
const validation = validateWritePath(filePath);
if (!validation.valid) {
throw new Error(validation.error!);
}
const safePath = validation.sanitized!;
try {
fs.unlinkSync(safePath);
} catch (err) {
throw new Error(`Failed to delete file: ${filePath} — ${(err as Error).message}`);
}
}
export function copyFileSync(srcPath: string, destPath: string): void {
const validation = validateWritePath(destPath);
if (!validation.valid) {
throw new Error(validation.error!);
}
const safeDest = validation.sanitized!;
try {
fs.copyFileSync(expandPath(srcPath), safeDest);
} catch (err) {
throw new Error(`Failed to copy file: ${srcPath} → ${destPath} — ${(err as Error).message}`);
}
}
export function moveFileSync(srcPath: string, destPath: string): void {
const validation = validateWritePath(destPath);
if (!validation.valid) {
throw new Error(validation.error!);
}
const safeDest = validation.sanitized!;
try {
fs.renameSync(expandPath(srcPath), safeDest);
} catch (err) {
throw new Error(`Failed to move file: ${srcPath} → ${destPath} — ${(err as Error).message}`);
}
}
export function ensureDirectory(dirPath: string): void {
const validation = validateWritePath(dirPath);
if (!validation.valid) {
throw new Error(validation.error!);
}
const safePath = validation.sanitized!;
try {
fs.mkdirSync(safePath, { recursive: true });
} catch (err) {
throw new Error(`Failed to create directory: ${dirPath} — ${(err as Error).message}`);
}
}
export function fileExists(filePath: string): boolean {
try {
return fs.existsSync(expandPath(filePath));
} catch {
return false;
}
}
export function isDirectory(dirPath: string): boolean {
try {
return fs.statSync(expandPath(dirPath)).isDirectory();
} catch {
return false;
}
}
export function getFileStats(filePath: string): FileStats {
const stat = fs.statSync(expandPath(filePath));
return {
size: stat.size,
createdAt: stat.birthtime,
modifiedAt: stat.mtime,
accessedAt: stat.atime,
isDirectory: stat.isDirectory(),
isFile: stat.isFile(),
isSymbolicLink: stat.isSymbolicLink(),
};
}
export function listDirectory(dirPath: string, options: { recursive?: boolean; maxDepth?: number } = {}): DirectoryEntry[] {
const entries: DirectoryEntry[] = [];
const maxDepth = options.maxDepth ?? Infinity;
function scan(dir: string, depth: number): void {
if (depth > maxDepth) return;
const files = fs.readdirSync(expandPath(dir), { withFileTypes: true });
for (const file of files) {
const fullPath = path.join(dir, file.name);
let size = 0;
let birthtime = new Date(0);
let mtime = new Date(0);
if (!file.isDirectory()) {
try {
const stat = fs.statSync(fullPath);
size = stat.size;
birthtime = stat.birthtime;
mtime = stat.mtime;
} catch {}
}
entries.push({
name: file.name,
path: fullPath,
isDirectory: file.isDirectory(),
isFile: file.isFile(),
size,
createdAt: birthtime,
modifiedAt: mtime,
});
if (file.isDirectory() && options.recursive) {
scan(fullPath, depth + 1);
}
}
}
scan(dirPath, 0);
return entries;
}
import * as fs from "fs";
import * as path from "path";
import { FileStats, DirectoryEntry } from "./types";
import { expandPath, validateWritePath } from "./securityEnhanced";
// ── File Operations ──
export function readFile(filePath: string): string {
try {
return fs.readFileSync(expandPath(filePath), "utf8");
} catch (err) {
throw new Error(`Failed to read file: ${filePath} — ${(err as Error).message}`);
}
}
export function writeFileSync(filePath: string, content: string): void {
const validation = validateWritePath(filePath);
if (!validation.valid) {
throw new Error(validation.error!);
}
const safePath = validation.sanitized!;
try {
const dir = path.dirname(safePath);
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
fs.writeFileSync(safePath, content, "utf8");
} catch (err) {
throw new Error(`Failed to write file: ${filePath} — ${(err as Error).message}`);
}
}
export function appendFileSync(filePath: string, content: string): void {
const validation = validateWritePath(filePath);
if (!validation.valid) {
throw new Error(validation.error!);
}
const safePath = validation.sanitized!;
try {
fs.appendFileSync(safePath, content, "utf8");
} catch (err) {
throw new Error(`Failed to append to file: ${filePath} — ${(err as Error).message}`);
}
}
export function deleteFileSync(filePath: string): void {
const validation = validateWritePath(filePath);
if (!validation.valid) {
throw new Error(validation.error!);
}
const safePath = validation.sanitized!;
try {
fs.unlinkSync(safePath);
} catch (err) {
throw new Error(`Failed to delete file: ${filePath} — ${(err as Error).message}`);
}
}
export function copyFileSync(srcPath: string, destPath: string): void {
const validation = validateWritePath(destPath);
if (!validation.valid) {
throw new Error(validation.error!);
}
const safeDest = validation.sanitized!;
try {
fs.copyFileSync(expandPath(srcPath), safeDest);
} catch (err) {
throw new Error(`Failed to copy file: ${srcPath} → ${destPath} — ${(err as Error).message}`);
}
}
export function moveFileSync(srcPath: string, destPath: string): void {
const validation = validateWritePath(destPath);
if (!validation.valid) {
throw new Error(validation.error!);
}
const safeDest = validation.sanitized!;
try {
fs.renameSync(expandPath(srcPath), safeDest);
} catch (err) {
throw new Error(`Failed to move file: ${srcPath} → ${destPath} — ${(err as Error).message}`);
}
}
export function ensureDirectory(dirPath: string): void {
const validation = validateWritePath(dirPath);
if (!validation.valid) {
throw new Error(validation.error!);
}
const safePath = validation.sanitized!;
try {
fs.mkdirSync(safePath, { recursive: true });
} catch (err) {
throw new Error(`Failed to create directory: ${dirPath} — ${(err as Error).message}`);
}
}
export function fileExists(filePath: string): boolean {
try {
return fs.existsSync(expandPath(filePath));
} catch {
return false;
}
}
export function isDirectory(dirPath: string): boolean {
try {
return fs.statSync(expandPath(dirPath)).isDirectory();
} catch {
return false;
}
}
export function getFileStats(filePath: string): FileStats {
const stat = fs.statSync(expandPath(filePath));
return {
size: stat.size,
createdAt: stat.birthtime,
modifiedAt: stat.mtime,
accessedAt: stat.atime,
isDirectory: stat.isDirectory(),
isFile: stat.isFile(),
isSymbolicLink: stat.isSymbolicLink(),
};
}
export function listDirectory(dirPath: string, options: { recursive?: boolean; maxDepth?: number } = {}): DirectoryEntry[] {
const entries: DirectoryEntry[] = [];
const maxDepth = options.maxDepth ?? Infinity;
function scan(dir: string, depth: number): void {
if (depth > maxDepth) return;
const files = fs.readdirSync(expandPath(dir), { withFileTypes: true });
for (const file of files) {
const fullPath = path.join(dir, file.name);
let size = 0;
let birthtime = new Date(0);
let mtime = new Date(0);
if (!file.isDirectory()) {
try {
const stat = fs.statSync(fullPath);
size = stat.size;
birthtime = stat.birthtime;
mtime = stat.mtime;
} catch {}
}
entries.push({
name: file.name,
path: fullPath,
isDirectory: file.isDirectory(),
isFile: file.isFile(),
size,
createdAt: birthtime,
modifiedAt: mtime,
});
if (file.isDirectory() && options.recursive) {
scan(fullPath, depth + 1);
}
}
}
scan(dirPath, 0);
return entries;
}