klg-asutk-app/scripts/validate-manifest.js
Yuriy b147d16798 MVP: заглушки, auth, .env.example, связь с бэкендом, главная КЛГ
- Заполнены заглушки: user-friendly-messages, health, aria, keyboard
- backend: core/auth.py, /api/v1/stats; cached-api → backend-client при USE_MOCK_DATA=false
- .env.example, middleware auth (skip при USE_MOCK_DATA), убраны неиспользуемые deps
- Страницы: airworthiness, maintenance, defects, modifications; AircraftAddModal, Sidebar
- Главная страница: REFLY — Контроль лётной годности (вместо Numerology App)
- Линт/скрипты: eslintrc, security, cleanup, logs, api inbox/knowledge

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 16:43:53 +03:00

246 lines
8.3 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* Валидатор manifest.json
* Проверяет корректность структуры и данных в manifest.json
*
* Использование:
* node scripts/validate-manifest.js
*
* Или добавьте в package.json:
* "scripts": {
* "validate:manifest": "node scripts/validate-manifest.js"
* }
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const MANIFEST_PATH = path.join(__dirname, '../index/manifest.json');
const KNOWLEDGE_DIR = path.join(__dirname, '../knowledge');
// Обязательные поля документа
const REQUIRED_FIELDS = ['id', 'title', 'path', 'type', 'domain', 'version', 'status'];
// Допустимые типы документов
const VALID_TYPES = ['reglament', 'guide', 'sample', 'template', 'spec', 'dictionary', 'bootstrap'];
// Допустимые статусы
const VALID_STATUSES = ['draft', 'approved', 'deprecated'];
// Допустимые домены
const VALID_DOMAINS = ['core', 'tmc', 'inspection', 'finance', 'hr', 'procurement', 'quality', 'safety', 'regulations'];
// Регулярное выражение для семантической версии
const VERSION_REGEX = /^\d+\.\d+\.\d+$/;
let errors = [];
let warnings = [];
function validateManifest() {
// console.log('🔍 Валидация manifest.json...\n');
// Проверка существования файла
if (!fs.existsSync(MANIFEST_PATH)) {
errors.push(`Файл manifest.json не найден: ${MANIFEST_PATH}`);
printResults();
process.exit(1);
}
// Чтение и парсинг JSON
let manifest;
try {
const content = fs.readFileSync(MANIFEST_PATH, 'utf8');
manifest = JSON.parse(content);
} catch (error) {
errors.push(`Ошибка парсинга JSON: ${error.message}`);
printResults();
process.exit(1);
}
// Проверка наличия массива documents
if (!manifest.documents || !Array.isArray(manifest.documents)) {
errors.push('Отсутствует массив documents в manifest.json');
printResults();
process.exit(1);
}
const documents = manifest.documents;
const documentIds = new Set();
const documentPaths = new Set();
// Валидация каждого документа
documents.forEach((doc, index) => {
const docPrefix = `Документ #${index + 1} (id: ${doc.id || 'не указан'})`;
// Проверка обязательных полей
REQUIRED_FIELDS.forEach(field => {
if (!doc.hasOwnProperty(field)) {
errors.push(`${docPrefix}: отсутствует обязательное поле '${field}'`);
}
});
// Проверка типа документа
if (doc.type && !VALID_TYPES.includes(doc.type)) {
errors.push(`${docPrefix}: недопустимый тип '${doc.type}'. Допустимые: ${VALID_TYPES.join(', ')}`);
}
// Проверка статуса
if (doc.status && !VALID_STATUSES.includes(doc.status)) {
errors.push(`${docPrefix}: недопустимый статус '${doc.status}'. Допустимые: ${VALID_STATUSES.join(', ')}`);
}
// Проверка домена
if (doc.domain && !VALID_DOMAINS.includes(doc.domain)) {
warnings.push(`${docPrefix}: нестандартный домен '${doc.domain}'. Стандартные: ${VALID_DOMAINS.join(', ')}`);
}
// Проверка версии
if (doc.version && !VERSION_REGEX.test(doc.version)) {
errors.push(`${docPrefix}: неверный формат версии '${doc.version}'. Используйте семантическую версию (например, 1.0.0)`);
}
// Проверка дублей ID
if (doc.id) {
if (documentIds.has(doc.id)) {
errors.push(`${docPrefix}: дублирующийся ID '${doc.id}'`);
}
documentIds.add(doc.id);
}
// Проверка дублей путей
if (doc.path) {
if (documentPaths.has(doc.path)) {
errors.push(`${docPrefix}: дублирующийся путь '${doc.path}'`);
}
documentPaths.add(doc.path);
// Проверка существования файла
const filePath = path.join(__dirname, '..', doc.path);
if (!fs.existsSync(filePath)) {
warnings.push(`${docPrefix}: файл не найден по пути '${doc.path}'`);
}
}
// Проверка supersedes
if (doc.supersedes) {
if (!documentIds.has(doc.supersedes)) {
warnings.push(`${docPrefix}: ссылается на несуществующий документ '${doc.supersedes}' в поле supersedes`);
}
}
// Проверка effective_date для регламентов
if (doc.type === 'reglament' && doc.status === 'approved' && !doc.effective_date) {
warnings.push(`${docPrefix}: регламент со статусом approved должен иметь поле effective_date`);
}
// Проверка updated_at
if (doc.updated_at) {
const date = new Date(doc.updated_at);
if (isNaN(date.getTime())) {
errors.push(`${docPrefix}: неверный формат даты в updated_at '${doc.updated_at}'. Используйте ISO 8601`);
}
}
});
// Проверка deprecated документов
const deprecatedDocs = documents.filter(doc => doc.status === 'deprecated');
deprecatedDocs.forEach(doc => {
if (!doc.supersedes) {
warnings.push(`Документ '${doc.id}' помечен как deprecated, но не указан supersedes`);
}
});
// Проверка: все файлы из knowledge/ должны быть в manifest
checkKnowledgeFilesInManifest(manifest, documentPaths);
printResults();
if (errors.length > 0) {
process.exit(1);
}
}
function getAllKnowledgeFiles() {
const files = [];
if (!fs.existsSync(KNOWLEDGE_DIR)) {
return files;
}
['reglaments', 'guides', 'samples'].forEach(dir => {
const dirPath = path.join(KNOWLEDGE_DIR, dir);
if (!fs.existsSync(dirPath)) return;
const dirFiles = fs.readdirSync(dirPath)
.filter(file => {
const filePath = path.join(dirPath, file);
try {
const stat = fs.statSync(filePath);
return stat.isFile() && file !== '.DS_Store' && file !== 'README.md';
} catch (e) {
return false;
}
})
.map(file => {
const relativePath = `knowledge/${dir}/${file}`;
return {
fileName: file,
relativePath: relativePath,
fullPath: path.join(dirPath, file)
};
});
files.push(...dirFiles);
});
return files;
}
function checkKnowledgeFilesInManifest(manifest, manifestPaths) {
console.log('🔍 Проверка соответствия файлов knowledge/ и manifest...\n');
const knowledgeFiles = getAllKnowledgeFiles();
const manifestPathsSet = new Set(manifestPaths);
// Проверяем, что каждый файл из knowledge/ есть в manifest
knowledgeFiles.forEach(file => {
if (!manifestPathsSet.has(file.relativePath)) {
errors.push(`Файл из knowledge/ отсутствует в manifest: ${file.relativePath}`);
}
});
console.log(` Проверено файлов в knowledge/: ${knowledgeFiles.length}`);
console.log(` Записей в manifest: ${manifestPaths.size}\n`);
}
function printResults() {
if (errors.length > 0) {
console.log('❌ Ошибки:\n');
errors.forEach((error, index) => {
console.log(` ${index + 1}. ${error}`);
});
console.log('');
}
if (warnings.length > 0) {
console.log('⚠️ Предупреждения:\n');
warnings.forEach((warning, index) => {
console.log(` ${index + 1}. ${warning}`);
});
console.log('');
}
if (errors.length === 0 && warnings.length === 0) {
console.log('✅ Валидация пройдена успешно!\n');
} else if (errors.length === 0) {
console.log('✅ Критических ошибок не найдено, но есть предупреждения.\n');
}
}
// Запуск валидации
validateManifest();