From 95e3d8dd9d72556123caac136cc2098dc3026207 Mon Sep 17 00:00:00 2001 From: aynazar-sylyyew-dev Date: Tue, 29 Sep 2026 10:39:44 +0500 Subject: [PATCH 1/3] ci: add per-page gzip budget check with 100 KB limit (proof that CI fails) --- .github/workflows/deploy.yml | 5 ++ package.json | 4 +- scripts/check-size.mjs | 118 +++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 scripts/check-size.mjs diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 89567ff..d94b2a2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -43,6 +43,10 @@ jobs: test -s "out/$f" || { echo "::error::out/$f is missing"; exit 1; } done + # 🛡️ Hard limit: проверка веса JS-бандлов на каждой странице + - name: Check bundle size limit + run: npm run check:size + - uses: actions/upload-pages-artifact@v5 if: github.event_name != 'pull_request' with: @@ -61,3 +65,4 @@ jobs: steps: - id: deployment uses: actions/deploy-pages@v5 + \ No newline at end of file diff --git a/package.json b/package.json index f77d4da..fdfb5e0 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "private": true, "scripts": { "dev": "next dev", - "build": "next build" + "build": "next build", + "start": "next start", + "check:size": "node scripts/check-size.mjs" }, "dependencies": { "next": "16.3.6", diff --git a/scripts/check-size.mjs b/scripts/check-size.mjs new file mode 100644 index 0000000..7b0caa4 --- /dev/null +++ b/scripts/check-size.mjs @@ -0,0 +1,118 @@ +// scripts/check-size.mjs +import fs from 'node:fs/promises'; +import path from 'node:path'; +import zlib from 'node:zlib'; + +// --- КОНФИГУРАЦИЯ --- +// Лимит размера для каждой страницы (в КБ). +// По заданию он должен быть константой наверху. +const LIMIT_KB = 100; +const OUT_DIR = 'out'; + +async function findHtmlFiles(dir) { + let entries; + try { + entries = await fs.readdir(dir, { withFileTypes: true }); + } catch (err) { + return []; + } + + const files = await Promise.all(entries.map(async (entry) => { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + return findHtmlFiles(fullPath); + } else if (entry.isFile() && entry.name === 'index.html') { + return fullPath; + } + return null; + })); + + return files.flat().filter(Boolean); +} + +async function getGzipSize(filePath) { + try { + const buffer = await fs.readFile(filePath); + // Используем zlib.gzipSync как указано в требованиях + const gzipped = zlib.gzipSync(buffer); + return gzipped.length; + } catch (err) { + // Если файл не читается (например, это внешний URL), просто возвращаем 0 + return 0; + } +} + +async function main() { + const htmlFiles = await findHtmlFiles(OUT_DIR); + + if (htmlFiles.length === 0) { + console.error(`❌ No index.html files found in ${OUT_DIR}/. Did you run 'npm run build' first?`); + process.exit(1); + } + + const results = []; + let hasFailed = false; + + for (const htmlFile of htmlFiles) { + const html = await fs.readFile(htmlFile, 'utf8'); + + // Надежный regex для поиска