feature/worker #111

Merged
primakov merged 190 commits from feature/worker into master 2025-12-05 16:59:42 +03:00
Showing only changes of commit 2d0b97be44 - Show all commits

View File

@@ -13,48 +13,110 @@ const folderPath = path.resolve(__dirname, './routers')
const getUrl = (url) => `${process.env.NODE_ENV === 'development' ? '' : '/ms'}${url}`
// Определение типов
interface EndpointStats {
total: number;
mock: number;
real: number;
}
interface FileInfo {
path: string;
type: 'file';
endpoints: number;
endpoints: EndpointStats;
}
interface DirectoryInfo {
path: string;
type: 'directory';
endpoints: number;
endpoints: EndpointStats;
children: (FileInfo | DirectoryInfo)[];
}
interface DirScanResult {
items: (FileInfo | DirectoryInfo)[];
totalEndpoints: number;
totalEndpoints: EndpointStats;
}
// Функция для поиска эндпоинтов в файлах
function countEndpoints(filePath) {
if (!fs.existsSync(filePath) || !filePath.endsWith('.js') && !filePath.endsWith('.ts')) {
return 0;
return { total: 0, mock: 0, real: 0 };
}
try {
const content = fs.readFileSync(filePath, 'utf8');
const httpMethods = ['get', 'post', 'put', 'delete', 'patch'];
return httpMethods.reduce((count, method) => {
const regex = new RegExp(`router\\.${method}\\(`, 'gi');
const matches = content.match(regex) || [];
return count + matches.length;
}, 0);
const endpointMatches = [];
let totalCount = 0;
// Собираем все эндпоинты и их контекст
httpMethods.forEach(method => {
const regex = new RegExp(`router\\.${method}\\([^{]*{([\\s\\S]*?)(?:}\\s*\\)|},)`, 'gi');
let match;
while ((match = regex.exec(content)) !== null) {
totalCount++;
endpointMatches.push({
method,
body: match[1]
});
}
});
// Проверяем каждый эндпоинт - работает с БД или моковый
let mockCount = 0;
let realCount = 0;
endpointMatches.forEach(endpoint => {
const body = endpoint.body;
// Признаки работы с базой данных - модели Mongoose и их методы
const hasDbInteraction = /\b(find|findOne|findById|create|update|delete|remove|aggregate|count|model)\b.*\(/.test(body) ||
/\bmongoose\b/.test(body) ||
/\.[a-zA-Z]+Model\b/.test(body);
// Проверка на отправку файлов (считается реальным эндпоинтом)
const hasFileServing = /res\.sendFile\(/.test(body);
// Проверка на отправку ошибок в JSON (считается реальным эндпоинтом)
const hasErrorResponse = /res\.json\(\s*{.*?(error|success\s*:\s*false).*?}\)/.test(body) ||
/res\.status\(.*?\)\.json\(/.test(body);
// Признаки моковых данных - только явный импорт JSON файлов или отправка JSON без ошибок
const hasMockJsonImport = /require\s*\(\s*['"`].*\.json['"`]\s*\)/.test(body) ||
/import\s+.*\s+from\s+['"`].*\.json['"`]/.test(body);
// JSON ответ, который не является ошибкой (упрощенная проверка)
const hasJsonResponse = /res\.json\(/.test(body) && !hasErrorResponse;
// Определяем тип эндпоинта
if (hasDbInteraction || hasFileServing || hasErrorResponse) {
// Если работает с БД, отправляет файлы или возвращает ошибки - считаем реальным
realCount++;
} else if (hasMockJsonImport || hasJsonResponse) {
// Если импортирует JSON или отправляет JSON без ошибок - считаем моком
mockCount++;
} else {
// По умолчанию считаем реальным
realCount++;
}
});
return {
total: totalCount,
mock: mockCount,
real: realCount
};
} catch (err) {
return 0;
return { total: 0, mock: 0, real: 0 };
}
}
// Функция для рекурсивного обхода директорий
function getAllDirs(dir, basePath = ''): DirScanResult {
const items: (FileInfo | DirectoryInfo)[] = [];
let totalEndpoints = 0;
let totalEndpoints = { total: 0, mock: 0, real: 0 };
try {
const dirItems = fs.readdirSync(dir);
@@ -66,7 +128,9 @@ function getAllDirs(dir, basePath = ''): DirScanResult {
if (stat.isDirectory()) {
const dirResult = getAllDirs(itemPath, relativePath);
totalEndpoints += dirResult.totalEndpoints;
totalEndpoints.total += dirResult.totalEndpoints.total;
totalEndpoints.mock += dirResult.totalEndpoints.mock;
totalEndpoints.real += dirResult.totalEndpoints.real;
items.push({
path: relativePath,
@@ -76,7 +140,9 @@ function getAllDirs(dir, basePath = ''): DirScanResult {
});
} else {
const fileEndpoints = countEndpoints(itemPath);
totalEndpoints += fileEndpoints;
totalEndpoints.total += fileEndpoints.total;
totalEndpoints.mock += fileEndpoints.mock;
totalEndpoints.real += fileEndpoints.real;
items.push({
path: relativePath,
@@ -103,9 +169,13 @@ function generateDirList(dirs: (FileInfo | DirectoryInfo)[], level = 0) {
return `<ul class="directory-list" style="padding-left: ${indent}px">${dirs.map(item => {
if (item.type === 'directory') {
const endpointClass = item.endpoints > 0 ? 'has-endpoints' : '';
const endpointInfo = item.endpoints > 0
? `<span class="endpoint-count" title="Количество эндпоинтов в директории">${item.endpoints}</span>`
const endpointClass = item.endpoints.total > 0 ? 'has-endpoints' : '';
const endpointInfo = item.endpoints.total > 0
? `<div class="endpoint-stats">
<span class="endpoint-count total" title="Всего эндпоинтов">${item.endpoints.total}</span>
<span class="endpoint-count mock" title="Моковые эндпоинты">${item.endpoints.mock}</span>
<span class="endpoint-count real" title="Реальные эндпоинты">${item.endpoints.real}</span>
</div>`
: '';
return `
@@ -121,9 +191,13 @@ function generateDirList(dirs: (FileInfo | DirectoryInfo)[], level = 0) {
</li>
`;
} else {
const endpointClass = item.endpoints > 0 ? 'has-endpoints' : '';
const endpointInfo = item.endpoints > 0
? `<span class="endpoint-count" title="Количество эндпоинтов">${item.endpoints}</span>`
const endpointClass = item.endpoints.total > 0 ? 'has-endpoints' : '';
const endpointInfo = item.endpoints.total > 0
? `<div class="endpoint-stats">
<span class="endpoint-count total" title="Всего эндпоинтов">${item.endpoints.total}</span>
<span class="endpoint-count mock" title="Моковые эндпоинты">${item.endpoints.mock}</span>
<span class="endpoint-count real" title="Реальные эндпоинты">${item.endpoints.real}</span>
</div>`
: '';
return `
@@ -200,7 +274,9 @@ router.get('/', async (req, res) => {
const routerFolders = fs.readdirSync(routersPath);
const directoryResult = getAllDirs(projectRoot);
const totalEndpoints = directoryResult.totalEndpoints;
const totalEndpoints = directoryResult.totalEndpoints.total;
const mockEndpoints = directoryResult.totalEndpoints.mock;
const realEndpoints = directoryResult.totalEndpoints.real;
// Получаем последние 10 ошибок
const latestErrors = await ErrorLog.find().sort({ createdAt: -1 }).limit(10);
@@ -376,6 +452,8 @@ router.get('/', async (req, res) => {
:root {
--primary-color: #3f51b5;
--secondary-color: #f50057;
--mock-color: #ff9800;
--real-color: #4caf50;
--bg-color: #f9f9f9;
--card-bg: #ffffff;
--text-color: #333333;
@@ -501,16 +579,32 @@ router.get('/', async (req, res) => {
transform: rotate(90deg);
}
.endpoint-count {
.endpoint-stats {
display: flex;
gap: 5px;
margin-left: 10px;
}
.endpoint-count {
font-size: 0.75rem;
color: #fff;
background-color: var(--secondary-color);
padding: 2px 8px;
border-radius: 12px;
font-weight: 500;
}
.endpoint-count.total {
background-color: var(--secondary-color);
}
.endpoint-count.mock {
background-color: var(--mock-color);
}
.endpoint-count.real {
background-color: var(--real-color);
}
.has-endpoints .dir-item {
border-left: 3px solid var(--secondary-color);
}
@@ -519,6 +613,20 @@ router.get('/', async (req, res) => {
border-left: 3px solid var(--secondary-color);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
}
.stats-card.mock {
background: linear-gradient(135deg, #ff9800, #f57c00);
}
.stats-card.real {
background: linear-gradient(135deg, #4caf50, #388e3c);
}
.section {
margin-bottom: 30px;
padding: 20px;
@@ -848,9 +956,19 @@ router.get('/', async (req, res) => {
<div class="container">
<div class="header">
<h1>Multy Stub v${pkg.version}</h1>
<div class="stats-card">
<div class="stats-value">${totalEndpoints}</div>
<div class="stats-label">Всего эндпоинтов</div>
<div class="stats-grid">
<div class="stats-card">
<div class="stats-value">${totalEndpoints}</div>
<div class="stats-label">Всего эндпоинтов</div>
</div>
<div class="stats-card mock">
<div class="stats-value">${mockEndpoints}</div>
<div class="stats-label">Моковые эндпоинты</div>
</div>
<div class="stats-card real">
<div class="stats-value">${realEndpoints}</div>
<div class="stats-label">Реальные эндпоинты</div>
</div>
</div>
</div>