import type { IncomingMessage, ServerResponse } from 'http'; import fs from 'fs'; import path from 'path'; const TARGET_EXTENSIONS = ['.js', '.ts', '.jsx', '.tsx']; async function getAllFilePaths(dirPath: string): Promise { let files: string[] = []; try { const items = await fs.promises.readdir(dirPath, { withFileTypes: true }); for (const item of items) { const fullPath = path.join(dirPath, item.name); if (item.isDirectory()) { files = files.concat(await getAllFilePaths(fullPath)); } else if (item.isFile()) { const ext = path.extname(item.name).toLowerCase(); if (TARGET_EXTENSIONS.includes(ext)) { files.push(fullPath); } } } } catch (err) { console.error(`读取目录失败: ${dirPath}`, err); } return files; } type TextReplacement = { searchText: string }; async function countMatches(dirPath: string, searchText: string): Promise { let totalCount = 0; const files = await getAllFilePaths(dirPath); for (const file of files) { try { const content = await fs.promises.readFile(file, 'utf-8'); const count = content.split(searchText).length - 1; if (count > 0) { totalCount += count; } } catch (err) { console.error(`无法读取文件: ${file}`, err); } } return totalCount; } async function countMatchesBatch(dirPath: string, replacements: TextReplacement[]): Promise> { const counts: Record = {}; const files = await getAllFilePaths(dirPath); const searchTexts = replacements .map(item => item.searchText) .filter(text => typeof text === 'string' && text.length > 0); searchTexts.forEach(text => { counts[text] = 0; }); if (searchTexts.length === 0) return counts; for (const file of files) { try { const content = await fs.promises.readFile(file, 'utf-8'); for (const searchText of searchTexts) { const count = content.split(searchText).length - 1; if (count > 0) { counts[searchText] += count; } } } catch (err) { console.error(`无法读取文件: ${file}`, err); } } return counts; } export function handleTextReplaceCount(req: IncomingMessage, res: ServerResponse): boolean { if (req.method === 'POST' && req.url?.startsWith('/api/text-replace/count')) { let body = ''; req.on('data', chunk => { body += chunk.toString(); }); req.on('end', async () => { try { const { path: targetPath, searchText, searchTexts, replacements } = JSON.parse(body); if (!targetPath || (!searchText && !Array.isArray(searchTexts) && !Array.isArray(replacements))) { res.statusCode = 400; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ error: 'path and search text data are required' })); return; } const pathParts = targetPath.split('/').filter(Boolean); if (pathParts.length < 2 || !['components', 'prototypes'].includes(pathParts[0])) { res.statusCode = 400; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ error: 'Invalid path format. Expected: components/xxx or prototypes/xxx' })); return; } const fullPath = path.resolve(process.cwd(), 'src', targetPath); if (!fs.existsSync(fullPath)) { res.statusCode = 404; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ error: 'Path not found' })); return; } if (Array.isArray(replacements) || Array.isArray(searchTexts)) { const list = Array.isArray(replacements) ? replacements .filter((item: any) => item && typeof item.searchText === 'string') .map((item: any) => ({ searchText: String(item.searchText) })) : (searchTexts || []) .filter((item: any) => typeof item === 'string') .map((item: any) => ({ searchText: String(item) })); if (list.length === 0) { res.statusCode = 400; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ error: 'searchTexts is empty' })); return; } const countsMap = await countMatchesBatch(fullPath, list); const totalCount = Object.values(countsMap).reduce((sum, value) => sum + value, 0); res.statusCode = 200; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ success: true, counts: countsMap, totalCount })); return; } const count = await countMatches(fullPath, searchText); console.log(`[API] 统计文本 "${searchText}" 出现次数: ${count}`); res.statusCode = 200; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ success: true, count, totalCount: count })); } catch (err) { console.error('[API] ❌ 统计文本失败:', err); res.statusCode = 500; res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ error: 'Failed to count text matches' })); } }); return true; } return false; }