import { readFile } from 'node:fs/promises'; import { cleanReaderHtml, readerBodyText } from '../lib/editorial-content.mjs'; import { editorialRevisions } from '../data/editorial-revisions.mjs'; const archive = JSON.parse(await readFile(new URL('../data/articles.json', import.meta.url), 'utf8')); const revisionBySlug = new Map(editorialRevisions.map((revision) => [revision.slug, revision])); const articles = archive.map((article) => ({ ...article, ...revisionBySlug.get(article.slug) })); const hardPatterns = [ /synthetic-plan-hand-off/i, /productionEffect/i, /future-only/i, /plan\/scenario/i, /source cutoff/i, /editorial date/i, /planDate/i, /not-collected/i, /not-attempted/i, /future owner/i, /author trajectory/i, /развитие автора/i, /В современном мире/i, /следует отметить/i, /нужно понимать, что/i, ]; function count(content, expression) { return (content.match(expression) || []).length; } function proseWithoutCode(content) { return cleanReaderHtml(content) .replace(/

Проверяемые источники<\/h2>[\s\S]*?(?=

|$)/, '') .replace(/[\s\S]*?<\/table>/g, '') .replace(/
[\s\S]*?<\/figure>/g, '') .replace(//g, '') .replace(/
    [\s\S]*?<\/ul>/g, '') .replace(/
      [\s\S]*?<\/ol>/g, '') .replace(/<\/(?:p|h2|h3)>/g, '. ') .replace(/[\s\S]*?<\/code>/g, '') .replace(/```[\s\S]*?```/g, ' ') .replace(/<[^>]+>/g, ' ') .replace(/&(?:quot|amp|lt|gt|#039);/g, ' ') .replace(/\s+/g, ' ') .trim(); } const failures = []; const longSentenceWarnings = []; let totalBodyCharacters = 0; for (const article of articles) { const content = cleanReaderHtml(article.contentHtml || ''); const body = readerBodyText(content); totalBodyCharacters += body.length; const issues = []; if (body.length < 5000 || body.length > 15000) issues.push('body вне 5 000–15 000 знаков'); if (count(content, /
      /g) < 1) issues.push('нет рисунка'); if (count(content, /
/g) < 1) issues.push('нет таблицы'); if (count(content, /
/g) < 1) issues.push('нет кода');
  if (count(content, /
    /g) < 1) issues.push('нет порядка действий'); const meta = hardPatterns.find((pattern) => pattern.test(body)); if (meta) issues.push('внутренняя мета-лексика: ' + meta); if (issues.length) failures.push({ slug: article.slug, issues }); const sentences = proseWithoutCode(content) .split(/[.!?]+\s+/) .map((sentence) => sentence.trim()) .filter(Boolean); const overlong = sentences .map((sentence) => ({ words: sentence.split(/\s+/).length, sample: sentence.slice(0, 180) })) .filter((sentence) => sentence.words > 45) .sort((left, right) => right.words - left.words); if (overlong.length) longSentenceWarnings.push({ slug: article.slug, count: overlong.length, maxWords: overlong[0].words, sample: overlong[0].sample }); } console.log(JSON.stringify({ articles: articles.length, totalBodyCharacters, failures, longSentenceWarnings: longSentenceWarnings.sort((left, right) => right.maxWords - left.maxWords).slice(0, 20), longSentenceWarningCount: longSentenceWarnings.reduce((sum, item) => sum + item.count, 0), }, null, 2)); if (failures.length) process.exitCode = 1;