perf: convert sync file I/O to async in API routes (P0-2)

Replace all readFileSync/existsSync/readdirSync with fs.promises
equivalents to unblock the Node event loop under concurrency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
7418andClaude Opus 4.6 committed 2026-02-13 01:24:21 +08:00
1 parent f3c49a9e49
commit 128ccf951c
6 files changed
+41 -31

No files matched your search

+16 -12
View File
@@ -1,4 +1,4 @@
import fs from 'fs';
import fs from 'fs/promises';
import path from 'path';
import type { FileTreeNode, FilePreview } from '@/types';
@@ -74,22 +74,24 @@ export function isPathSafe(basePath: string, targetPath: string): boolean {
return resolvedTarget.startsWith(resolvedBase + path.sep) || resolvedTarget === resolvedBase;
}
export function scanDirectory(dir: string, depth: number = 3): FileTreeNode[] {
export async function scanDirectory(dir: string, depth: number = 3): Promise<FileTreeNode[]> {
const resolvedDir = path.resolve(dir);
if (!fs.existsSync(resolvedDir)) {
try {
await fs.access(resolvedDir);
} catch {
return [];
}
return scanDirectoryRecursive(resolvedDir, depth);
}
function scanDirectoryRecursive(dir: string, depth: number): FileTreeNode[] {
async function scanDirectoryRecursive(dir: string, depth: number): Promise<FileTreeNode[]> {
if (depth <= 0) return [];
let entries: fs.Dirent[];
let entries: import('fs').Dirent[];
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
entries = await fs.readdir(dir, { withFileTypes: true });
} catch {
return [];
}
@@ -114,7 +116,7 @@ function scanDirectoryRecursive(dir: string, depth: number): FileTreeNode[] {
if (entry.isDirectory()) {
if (IGNORED_DIRS.has(entry.name)) continue;
const children = scanDirectoryRecursive(fullPath, depth - 1);
const children = await scanDirectoryRecursive(fullPath, depth - 1);
nodes.push({
name: entry.name,
path: fullPath,
@@ -125,7 +127,7 @@ function scanDirectoryRecursive(dir: string, depth: number): FileTreeNode[] {
const ext = path.extname(entry.name).replace(/^\./, '');
let size: number | undefined;
try {
const stat = fs.statSync(fullPath);
const stat = await fs.stat(fullPath);
size = stat.size;
} catch {
// Skip files we can't stat
@@ -144,20 +146,22 @@ function scanDirectoryRecursive(dir: string, depth: number): FileTreeNode[] {
return nodes;
}
export function readFilePreview(filePath: string, maxLines: number = 200): FilePreview {
export async function readFilePreview(filePath: string, maxLines: number = 200): Promise<FilePreview> {
const resolvedPath = path.resolve(filePath);
if (!fs.existsSync(resolvedPath)) {
try {
await fs.access(resolvedPath);
} catch {
throw new Error(`File not found: ${filePath}`);
}
const stat = fs.statSync(resolvedPath);
const stat = await fs.stat(resolvedPath);
if (!stat.isFile()) {
throw new Error(`Not a file: ${filePath}`);
}
// Read the file content, limiting to maxLines
const content = fs.readFileSync(resolvedPath, 'utf-8');
const content = await fs.readFile(resolvedPath, 'utf-8');
const lines = content.split('\n');
const truncated = lines.slice(0, maxLines).join('\n');