# Обфускація збірки в CI

Де AfterPack стоїть у вашому пайплайні: лише продакшн, один раз зібрати і просувати скрізь, з рецептом для CI та правилами щодо сідів, артефактів, source map і недоступної хмари.

Source: https://www.afterpack.dev/uk/docs/builds

AfterPack є кроком після збірки: ваш бандлер створює JavaScript, а потім AfterPack його обфускує. Ми рекомендуємо обфускувати один раз, у продакшн-збірці, і просувати цей артефакт без змін через кожен етап. Один пункт нижче не є рекомендацією: розгорнутий source map повертає ваш оригінальний вихідний код, тож не допускайте source map у розгортання.

## Dev не обфускується

Обфускація працює на результаті збірки, тож проти dev-сервера їй нічого робити. Vite dev, `next dev` і HMR роздають незібраний або частково зібраний код.

- **Розробка.** Ваш бандлер працює як завжди. AfterPack простоює. Стек-трейси вказують на ваш справжній вихідний код, HMR миттєвий, налагодження звичайне.
- **Продакшн-збірка.** AfterPack запускається після того, як бандл закрито. [Плагін для фреймворку](https://www.afterpack.dev/uk/docs/frameworks) запускає його автоматично. На стеку з самим лише бандлером ви викликаєте його один раз.

Щоб зібрати продакшн без обфускації, наприклад для разової налагоджувальної збірки, задайте [`{ build: { autorun: false } }`](https://www.afterpack.dev/uk/docs/config#build-autorun) у конфігурації або [`AFTERPACK_build_autorun=false`](https://www.afterpack.dev/uk/docs/config#build-autorun) у середовищі.

## Зберіть один раз, просувайте артефакт

Обфускований бандл є артефактом, який ви випускаєте, тож найчистіший пайплайн збирає його один раз і переносить ті самі байти через кожен етап.

*(build-once, promote-everywhere flow diagram — see the web page)*

Альтернатива полягає в тому, щоб запускати AfterPack заново для кожного середовища, і вона коштує вам сигналу від тестів: кожен перехід створює бандл іншої форми, тож те, що перевіряв QA, не є тим, що роздає продакшн. Просування одного артефакту гарантує, що інтеграційні тести, які ви запускаєте в QA, працюють саме з тими байтами, що потрапляють у продакшн. Ваш цикл розробки в будь-якому разі залишається без обфускації.

## Інтерфейс команди

```bash
npx afterpack@latest dist/
```

Шлях до згенерованого JavaScript плюс [кожен ключ конфігурації як прапорець](https://www.afterpack.dev/uk/docs/cli#flags). У CI варто явно називати каталог збірки: автоматично визначений каталог є непомітним способом обфускувати не те дерево. `npx afterpack@latest verify [dir]` повторно перевіряє збірку за [квитанцією захисту](https://www.afterpack.dev/uk/docs/frameworks/nextjs#verify), яку вона записала; саме цей крок слід запускати перед розгортанням. Повний опис інтерфейсу є в [довіднику CLI](https://www.afterpack.dev/uk/docs/cli#usage).

## Випадковий сід за замовчуванням, закріплення для відтворюваності

[Сід](https://www.afterpack.dev/uk/docs/config#seed) робить кожен реліз структурно відмінним від попереднього, тож інструмент чи патч, зроблений під бандл минулого тижня, ламається на цьому. Новий випадковий сід для кожного релізу є типовим. Нічого налаштовувати не треба.

Сід не є секретом. Зберігайте його як будь-який інший параметр збірки, у змінній CI.

Закріплюйте його лише тоді, коли потрібен відтворюваний результат: влучання в кеш збірки, тести на еталонних знімках або придатні для аудиту побайтові перезбірки.

```bash
npx afterpack@latest dist/ --seed=git
```

[`--seed=git`](https://www.afterpack.dev/uk/docs/config#seed) перетворюється на `git rev-parse HEAD`. Поза репозиторієм він повертається до нового випадкового сіду й друкує повідомлення замість того, щоб завершити збірку помилкою. Будь-який інший рядок рушій хешує; ціле число використовується як є. Закріплений сід щоразу дає ідентичний результат.

> **Закріплюйте сід на значенні, що все одно змінюється з кожним релізом**
>
> Сід, сталий між релізами, переносить аналіз зловмисника з одного релізу на наступний, а саме від цього ви й встановлювали AfterPack. Коли закріплений сід справді виправданий, прив'яжіть його до значення, що змінюється з кожним релізом, наприклад до SHA коміту.

## Імена файлів і кешування

Плагін фреймворку дає кожному обфускованому файлу ім'я з хешем вмісту, яке слідує за його обфускованими байтами, тож одне ім'я ніколи не відповідає двом різним файлам, а браузери, CDN і сервіс-воркери ніколи не отримують застарілий файл під повторно використаним ім'ям. Ім'я виводиться з необфускованого вмісту файлу, а також із сіду, версії рушія, ваших налаштувань, ваших директив і [режиму збірки](https://www.afterpack.dev/uk/docs/config#build-mode).

Тож кешування залежить від сіду. Реліз, який бере новий сід, типово або з `--seed=git`, перейменовує кожен обфускований файл, тож браузери й сервіс-воркери завантажують кожен із них знову. Сталий сід зберігає байти та ім'я незміненого файлу між релізами, поки версія рушія й налаштування лишаються тими самими, тож довготривалі кеші зберігаються ціною, яку описує попередження вище.

З [`key`](https://www.afterpack.dev/uk/docs/config#key) версія хмарного рушія входить до імені. Коли збірка не може прочитати цю версію з API, вона попереджає й дає цій збірці випадкові імена: вони безпечні, але закріплений сід їх не відтворить.

[CLI](https://www.afterpack.dev/uk/docs/cli#content-hashed-file-names) так само називає файли на збірках Next.js, Angular, Vite і webpack. На будь-якому іншому результаті він залишає імена, які записав ваш бандлер, і друкує повідомлення; якщо там ви покладаєтеся на довготривале кешування, використовуйте плагін свого фреймворку або закріпіть сталий сід.

## Ротація за розкладом

Випадковий сід на кожен реліз уже змінює артефакт щоразу, коли ви випускаєте. Команди, які випускають рідко, часто додають поверх цього перезбірку за розкладом, щонічну або щотижневу, з того самого вихідного коду, тож розгорнутий бандл змінює форму кожного циклу, навіть коли код не змінюється.

Зловмисник, який почав розбирати збірку N, починає спочатку на збірці N+1: між двома збірками немає спільної структури, на яку можна було б перенести роботу. Найважливіше це для антибот-, античит- і стійкої до обходу CAPTCHA логіки, де в супротивника є час. Сама по собі ротація не зупиняє чітерство чи бот-трафік; клієнтській логіці все одно потрібна авторитетна перевірка на сервері.

## У CI

Пайплайн такий: збірка, потім обфускація, потім артефакти не потрапляють у розгортання. З плагіном для фреймворку крок обфускації є частиною вашого звичайного `build`, і явний виклик AfterPack можна прибрати. На стеку з самим лише бандлером запускайте його самостійно.

```yaml
# .github/workflows/release.yml (excerpt)
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # 1. Build. With @afterpack/<fw> installed, obfuscation runs here automatically.
      - run: npm ci
      - run: npm run build

      # 2. Pure-bundler stacks only (no framework plugin): obfuscate explicitly.
      #    Pin --seed for Turborepo/Nx remote-cache hits; omit it for a fresh
      #    random seed per release. --diagnostics.level=all prints every diagnostic
      #    instead of rolling the info ones up to one line.
      - run: npx afterpack@latest dist/ --seed=git --diagnostics.level=all --protectionMap.enabled

      # 3. Keep the Protection Map as a reviewable CI artifact. It is off under
      #    CI=true unless asked for: --protectionMap.enabled above, or
      #    protectionMap: { enabled: true } in your plugin options. It is written to
      #    the gitignored .afterpack/ at the PROJECT ROOT, never into the deploy
      #    tree. It embeds your ORIGINAL SOURCE — treat the upload as
      #    source-confidential.
      - uses: actions/upload-artifact@v4
        with:
          name: protection-map
          path: .afterpack/

      # 4. Deploy only the bundle. Never the .afterpack/ directory, never a .map.
      - run: ./deploy.sh dist/
```

Turborepo і Nx будують ключі віддаленого кешу на хешах результату. Випадковий сід дає інший бандл при кожному запуску, тож ці кеші завжди промахуються. Передайте [`--seed=git`](https://www.afterpack.dev/uk/docs/config#seed), щоб отримати влучання в кеш: SHA все одно змінюється з кожним релізом, тож збірки залишаються відтворюваними в межах коміту і водночас змінюються між релізами.

Додайте Protection Map до PR, щоб рецензент міг підтвердити, в яких ділянках змінився рівень захисту. Це найшвидший спосіб помітити рефакторинг, який випадково виніс секрет за межі позначеної ділянки. Вважайте її артефактом для рецензента.

> **Команди `afterpack audit ./dist` не існує**
>
> Аудит, на якому працює [сканер безпеки](https://www.afterpack.dev/uk/security-scanner), запускається проти URL і друкує текст; локальний каталог він не приймає. Під'єднайте його до URL вашого розгорнутого staging після кроку розгортання. Див. [Аудит](https://www.afterpack.dev/uk/docs/audit).

## Артефакти та як не допустити їх у розгортання

AfterPack записує три види локальних артефактів. Кожен із них чутливіший за бандл, який описує, і всі три за замовчуванням налаштовані безпечно.

| Артефакт | За замовчуванням | Куди потрапляє | Прапорець |
| --- | --- | --- | --- |
| [**Protection Map**](https://www.afterpack.dev/uk/docs/protection-map) (`protectionMap.html`) | [Увімкнено](https://www.afterpack.dev/uk/docs/config#protectionMap-enabled), коли бандлер видав source map, який можна знайти, і збірка не працює в CI, інакше вмикається за бажанням | У збірці Free завжди в каталог [`.afterpack/`](https://www.afterpack.dev/uk/docs/cli#files) у корені проєкту, внесений у gitignore, ніколи не поруч із збіркою, яку роздає сервер. Хмарна збірка Pro не записує локального файлу: її мапа зберігається в [дашборді](https://www.afterpack.dev/uk/docs/dashboard/builds), а збірка після завершення виводить посилання на неї | [`--protectionMap.enabled=false`](https://www.afterpack.dev/uk/docs/config#protectionMap-enabled) |
| **Резервна копія ваших оригінальних файлів** | [Увімкнено](https://www.afterpack.dev/uk/docs/config#build-backup) для CLI, вимкнено для плагіна фреймворку | `.afterpack/backups/<build dir>/` у корені проєкту для CLI; сусідній файл `.backup.<hash>` поруч із результатом, коли її запитано в плагіна | [`--build.backup=false`](https://www.afterpack.dev/uk/docs/config#build-backup), щоб відмовитися |
| **Сусідній source map `.map`** | [Вимкнено в продакшені](https://www.afterpack.dev/uk/docs/config#sourceMap-enabled), у dev видається, лише якщо знайдено вхідний map | Поруч зі згенерованим JS | [`--sourceMap.enabled=false`](https://www.afterpack.dev/uk/docs/config#sourceMap-enabled) |

Дві речі відбуваються автоматично, в кожній інтеграції:

- **`.afterpack/` ігнорує сам себе.** AfterPack записує `.gitignore` усередині `.afterpack/` і ніколи не редагує ваш власний. Файл `.map` або `.backup.<hash>`, записаний поруч зі збіркою, не ігнорується: самі не допускайте його в git і в розгортання.
- **Артефакт на шляху, який роздає сервер, викликає попередження.** AfterPack попереджає, якщо артефакт потрапляє в каталог, який роздає сервер. Source map, який роздає сервер, означає повну деобфускацію.

Резервна копія є вашим оригінальним вихідним кодом дослівно. CLI тримає її в корені проєкту, у внесеному в gitignore `.afterpack/backups/`, з маніфестом, який записує, чим став кожен файл після обфускації; `npx afterpack@latest restore` повертає оригінали на місце поверх результату запуску. Плагін, у якого запитано `build.backup`, натомість пише свою копію поруч із результатом, де її підхопить розгортання.

> **Витік source map зводить обфускацію нанівець**
>
> Source map пов'язують обфускований результат прямо з вашим оригінальним вихідним кодом, а [`sourcesContent`](https://www.afterpack.dev/uk/docs/config#sourceMap-sourcesContent) містить сам вихідний код. Ніколи не розгортайте їх.

[Продакшн-збірка](https://www.afterpack.dev/uk/docs/config#build-mode) також видаляє source map, які записав ваш бандлер, бо вони містять ваш оригінальний вихідний код: файли `.map`, приховані та вбудовані карти, коментарі `//# sourceMappingURL=` і CSS-карти. Це роблять CLI і кожен плагін. Коли увімкнено [`sourceMap.enabled`](https://www.afterpack.dev/uk/docs/config#sourceMap-enabled), карта AfterPack замінює JavaScript-карту бандлера, а CSS-карти залишаються. Карта, яку не вдається видалити, зупиняє збірку з помилкою. `node_modules` і будь-яку теку всередині каталогу збірки з власним `package.json` AfterPack не чіпає.

> **Файл, який рушій не може обфускувати, завершує збірку помилкою**
>
> Збірка зупиняється і називає файл. Використовуйте [`paths.exclude`](https://www.afterpack.dev/uk/docs/config#paths-exclude), щоб виділити файл, який має вийти незмінним; на сторінці [Діагностика](https://www.afterpack.dev/uk/docs/diagnostics) є коди і значення кожного з них.

## Перевірка в CI

Будь-який [ненульовий код виходу](https://www.afterpack.dev/uk/docs/diagnostics#exit-codes) CLI означає, що збірка не випустила те, що ви налаштували, тож перевірка має вигляд `if ! npx afterpack@latest dist/ ...; then fail; fi`, а не розгалуження за конкретним кодом.

Це охоплює помилку використання (`64`), ліміт розміру (`3`), рушій, який треба оновити (`6`), а як `1` — порожній каталог збірки, помилку рушія або збірку [Pro](https://www.afterpack.dev/uk/docs/tiers) з [`key`](https://www.afterpack.dev/uk/docs/config#key), яка не змогла дістатися до [хмари AfterPack](https://www.afterpack.dev/uk/docs/deployment-modes). Останній випадок завершує збірку помилкою замість повернення до слабшого результату, тож зелений запуск означає, що випущено саме той захист, який ви запитали. Збірки Free повністю локальні й ніколи не звертаються до хмари. Повний контракт і кожен код, який може видати збірка, описано на сторінці [Діагностика](https://www.afterpack.dev/uk/docs/diagnostics#exit-codes).

## Спостереження за збірками після випуску

Кожна збірка Pro Cloud потрапляє в дашборд: коли вона виконувалася, з якого коміту, скільки файлів обробила, скільки коштувала в [MB](https://www.afterpack.dev/uk/docs/dashboard/usage) і часі збірки, а також її покриття та [роздування](https://www.afterpack.dev/uk/docs/config#inflation-max). Використовуйте це, щоб стежити за частотою ротації та порівнювати збірки з часом. Див. [Збірки в дашборді](https://www.afterpack.dev/uk/docs/dashboard/builds).

Локальні збірки Free ніколи не доходять до AfterPack, тож там вони ніколи не з'являються.

## Далі

- [Найкращі практики](https://www.afterpack.dev/uk/docs/best-practices): що позначати, що не чіпати і як тримати CI чесним.
- [Збірки в дашборді](https://www.afterpack.dev/uk/docs/dashboard/builds): запис про кожну збірку з усім, що створила ця сторінка.
- [Модель загроз](https://www.afterpack.dev/uk/docs/threat-model): чому збірка, що змінює форму з кожним релізом, є основним захистом.
- [Довідник CLI](https://www.afterpack.dev/uk/docs/cli#flags): повна таблиця прапорців, що стоїть за наведеним вище синопсисом.
- [Діагностика](https://www.afterpack.dev/uk/docs/diagnostics#exit-codes): контракт кодів виходу і правило зупинки з помилкою (fail closed), повністю.
- [Protection Map](https://www.afterpack.dev/uk/docs/protection-map): як читати артефакт, який завантажив ваш CI-джоб.
