---
docgen:
  source: npm/rules/docker/lib/docker-hadolint.mjs
  crc: b97701f3
---

# docker-hadolint.mjs

## Огляд

Модуль `docker-hadolint.mjs` інкапсулює спільну логіку запуску статичного аналізатора `hadolint` для перевірки `Dockerfile`-ів у межах правила `docker` (див. `.cursor/rules/docker.mdc` / `n-docker`). Він є тонким адаптером поверх системного бінарника `hadolint` та надає двом викликачам — `check.mjs` (skill `check-docker`) і `../../lint/lint.mjs` (skill `run-docker`) — єдину функцію перевірки одного `Dockerfile` плюс утиліту нормалізації шляхів.

Ключові архітектурні рішення:

- `hadolint` запускається **виключно як нативний бінарник**. Будь-який Docker-fallback (наприклад, `docker run hadolint/hadolint`) свідомо прибраний, тож модуль не залежить від запущеного Docker Engine.
- Розв’язання шляху до бінарника делеговано в `ensureTool('hadolint')`, який реалізує ланцюжок `PATH → локальний кеш інструментів → авто-install` через `brew` / `scoop` / GitHub Release, відповідно до платформи.
- Якщо `ensureTool` падає (інструмент відсутній і авто-install вимкнено через `N_CURSOR_NO_AUTO_INSTALL` або фізично не вдався), модуль **не кидає виняток назовні**, а повертає структурований результат `{ ok: false, stderr: <інструкція з установки> }`. Це дозволяє викликачам однаково обробляти і реальні порушення hadolint, і відсутність інструмента.
- Шляхи у виводі нормалізуються через `posixRel` (відносно `root`, з прямими слешами `/`), щоб лог і повідомлення про помилки були стабільними між macOS / Linux / Windows.

## Експорти / API

Модуль є ESM (`.mjs`) і має два іменовані експорти:

| Експорт                                     | Тип      | Призначення                                                                        |
| ------------------------------------------- | -------- | ---------------------------------------------------------------------------------- |
| `posixRel(root, absPath)`                   | function | Перетворити абсолютний шлях на відносний від `root` з прямими слешами.             |
| `lintDockerfileWithHadolint(root, absPath)` | function | Запустити `hadolint` для одного `Dockerfile` і повернути структурований результат. |

Default-експорту немає. Глобальних побічних ефектів на рівні модуля немає — імпорт лише підтягує `ensureTool`, не виконуючи його.

## Функції

### `posixRel(root, absPath)`

Утиліта нормалізації шляхів для стабільного логування й передачі в `hadolint`.

**Сигнатура:**

```js
export function posixRel(root: string, absPath: string): string
```

**Параметри:**

- `root` — абсолютний шлях до кореня репозиторію (або будь-якого опорного каталогу), відносно якого треба отримати шлях.
- `absPath` — абсолютний шлях до файлу, який треба представити відносно `root`.

**Повертає:**

- Рядок із відносним шляхом, де всі системні розділювачі (`path.sep`) замінено на прямий слеш `/`. На POSIX-системах результат фактично збігається з `path.relative`, на Windows — конвертує `\` на `/`.

**Алгоритм:**

1. Викликає `relative(root, absPath)` з `node:path` для обчислення відносного шляху.
2. Розбиває результат за поточним `sep` (`path.sep`) і склеює знову через `'/'`.

**Side effects:** немає; функція чиста й детермінована.

**Примітка:** функція не валідовує, чи `absPath` справді абсолютний і чи він знаходиться під `root` — у такому випадку `relative` поверне відносний шлях із `..`, що теж буде нормалізовано до прямих слешів.

---

### `lintDockerfileWithHadolint(root, absPath)`

Основна точка входу модуля: виконує `hadolint` для одного `Dockerfile` і повертає уніфікований результат.

**Сигнатура:**

```js
export function lintDockerfileWithHadolint(
  root: string,
  absPath: string
): {
  ok: boolean,
  stdout: string,
  stderr: string,
  via: string
}
```

**Параметри:**

- `root` — абсолютний шлях до кореня репозиторію. Використовується одночасно як `cwd` для дочірнього процесу і як база для `posixRel`.
- `absPath` — абсолютний шлях до конкретного `Dockerfile` (або файлу, який треба перевірити hadolint’ом).

**Повертає об’єкт із полями:**

- `ok` — `true`, якщо `hadolint` завершився з кодом виходу `0` (порушень немає). `false` — у будь-якому іншому випадку: інструмент знайшов проблеми, не запустився, або не зміг бути встановленим/знайденим.
- `stdout` — stdout процесу hadolint як рядок (UTF-8). При помилці отримання інструмента — порожній рядок.
- `stderr` — stderr процесу hadolint як рядок. Якщо інструмент не вдалося отримати, тут міститься українська інструкція з установки (`brew install hadolint` / `scoop install hadolint` / посилання на GitHub Releases) разом із повідомленням оригінальної помилки.
- `via` — завжди константа `'hadolint'`. Поле залишене для зворотної сумісності з API, де раніше міг бути ще варіант на кшталт `'docker'`; зараз інших значень не повертається.

**Алгоритм:**

1. Обчислює відносний шлях `rel = posixRel(root, absPath)`.
2. Намагається отримати шлях до бінарника через `hadolintPath = ensureTool('hadolint')`.
   - Якщо `ensureTool` кидає виняток, функція **перехоплює** його і повертає об’єкт з `ok: false`, порожнім `stdout`, інструктивним `stderr` (текст помилки + поради з установки) і `via: 'hadolint'`. На цьому виконання завершується.
3. Викликає `spawnSync(hadolintPath, [rel], { cwd: root, encoding: 'utf8', maxBuffer: 10 * 1024 * 1024 })`:
   - `cwd: root` — щоб hadolint бачив правильну робочу директорію і `rel` коректно резолвився.
   - `encoding: 'utf8'` — `stdout`/`stderr` одразу приходять як рядки, а не `Buffer`.
   - `maxBuffer: 10 МіБ` — захист від великих обсягів виводу при множинних попередженнях.
4. Формує результат: `ok = local.status === 0`, `stdout`/`stderr` приходять з процесу (з фолбеком на порожній рядок через `??`), `via: 'hadolint'`.

**Side effects:**

- Може ініціювати **авто-install** `hadolint` через `ensureTool` (мережа, brew/scoop/завантаження GitHub Release, запис у локальний кеш інструментів), якщо інструмент відсутній і авто-install не вимкнено `N_CURSOR_NO_AUTO_INSTALL`.
- Синхронно запускає дочірній процес `hadolint`, що блокує цикл подій Node.js до завершення (через `spawnSync`).
- Не пише в stdout/stderr поточного процесу самостійно — увесь вивід hadolint повертає викликачу.
- Не змінює `process.cwd()` поточного процесу (тільки `cwd` дочірнього).

**Обробка помилок:**

- Виняток від `ensureTool` ловиться і конвертується у м’який `ok: false` з підказкою.
- Помилки запуску `spawnSync` (наприклад, права доступу) **не обгортаються окремо**: у такому разі `spawnSync` повертає об’єкт з `status: null` і `error`-полем, відповідно `ok` буде `false`, а `stdout`/`stderr` — порожніми (через `??`). Поле `error` об’єкта `spawnSync` у результат не транслюється.
- Ненульовий `status` від самого `hadolint` (тобто знайдені порушення) трактується як `ok: false`, але `stderr`/`stdout` несуть звіт hadolint для подальшого парсингу/виводу користувачу.

## Залежності

### Node.js (стандартні)

- `node:child_process` — імпортується `spawnSync` для синхронного запуску `hadolint`.
- `node:path` — імпортуються `relative` і `sep` для побудови відносного шляху з нормалізацією роздільників.

### Внутрішні (моноpепо)

- `../../../scripts/lib/ensure-tool.mjs` — функція `ensureTool(name)`, яка:
  - повертає абсолютний шлях до бінарника;
  - намагається знайти його у `PATH`, у локальному кеші інструментів;
  - за потреби виконує авто-install через `brew` (macOS), `scoop` (Windows) чи GitHub Release (Linux);
  - кидає виняток, якщо інструмент недоступний і авто-install неможливий / вимкнено `N_CURSOR_NO_AUTO_INSTALL`.

### Зовнішні (runtime)

- Бінарник `hadolint` (нативний; брати з `brew`, `scoop`, GitHub Release). **Не** використовується `docker run hadolint/hadolint` — Docker-fallback явно прибраний.

### Імпорт-споживачі (всередині правила `docker`)

- `./check.mjs` — скіл `check-docker`: разова перевірка обраних `Dockerfile`-ів.
- `../../lint/lint.mjs` — скіл `run-docker`: інтеграція в загальний `lint`-конвеєр.

## Потік виконання / Використання

Типовий сценарій використання модуля викликачем:

1. Викликач (наприклад, `check-docker` або `run-docker`) визначає `root` репозиторію і список абсолютних шляхів до `Dockerfile`-ів.
2. Для кожного шляху викликається `lintDockerfileWithHadolint(root, absPath)`.
3. Модуль:
   - нормалізує шлях у POSIX-форму (`posixRel`);
   - резолвить `hadolint` через `ensureTool` (можливо з авто-install);
   - синхронно запускає `hadolint <rel>` у `cwd = root`;
   - повертає `{ ok, stdout, stderr, via }`.
4. Викликач інтерпретує результат:
   - `ok === true` → файл чистий, нічого не виводити (або вивести позитивний статус);
   - `ok === false` → показати `stdout`/`stderr` користувачу, зарахувати як порушення; якщо `stderr` містить інструкцію з установки — повідомити користувача, як отримати інструмент.

### Приклад використання

```js
import { lintDockerfileWithHadolint, posixRel } from './docker-hadolint.mjs'

const root = process.cwd()
const absPath = '/abs/path/to/repo/services/api/Dockerfile'

const result = lintDockerfileWithHadolint(root, absPath)

if (!result.ok) {
  console.error(`hadolint failed for ${posixRel(root, absPath)} (via=${result.via})`)
  if (result.stdout) console.error(result.stdout)
  if (result.stderr) console.error(result.stderr)
  process.exitCode = 1
}
```

### Поведінка за відсутності `hadolint`

- Якщо `hadolint` відсутній і `N_CURSOR_NO_AUTO_INSTALL` **не** виставлена, `ensureTool` спробує встановити бінарник автоматично (за платформою).
- Якщо `N_CURSOR_NO_AUTO_INSTALL` виставлена або авто-install не вдався, `lintDockerfileWithHadolint` повертає:
  - `ok: false`,
  - `stdout: ''`,
  - `stderr: 'Не вдалося отримати hadolint (<повідомлення>). Встанови: brew install hadolint (macOS) / scoop install hadolint (Windows) / https://github.com/hadolint/hadolint/releases (Linux).'`,
  - `via: 'hadolint'`.
- Завдяки м’якій обробці викликач може коректно показати інструкцію користувачу замість аварійного завершення.

### Особливості та обмеження

- **Синхронність**: усі виклики блокуючі (`spawnSync`). Для пакетної перевірки сотень файлів варіант — викликати модуль із зовнішнього оркестратора, який сам керує паралелізмом (з урахуванням обмежень середовища).
- **Один файл за виклик**: функція передає у `hadolint` рівно один аргумент `rel`. Для масової перевірки слід викликати функцію в циклі — це свідомий вибір, щоб уніфікувати результат і прив’язку до конкретного файлу.
- **Розмір буфера**: `maxBuffer = 10 МіБ` достатньо для звичайних `Dockerfile`-звітів; для аномально великих виводів значення доведеться змінювати на рівні форку модуля.
- **`via` константно `'hadolint'`**: поле залишене для майбутньої сумісності, але наразі ніколи не приймає інших значень.
- **Стабільність шляхів**: завдяки `posixRel` повідомлення про порушення містять однакові шляхи з прямими слешами на всіх ОС — це важливо для дифу логів у CI.
