#!/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'); // Модуль knowledge вынесен в отдельный сервис — проверка файлов в 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`); } }); printResults(); if (errors.length > 0) { process.exit(1); } } 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();