# run-v8r.mjs

## Огляд

Модуль `run-v8r.mjs` — це послідовний раннер CLI-валідатора схем `v8r` для всіх типів файлів, які він підтримує (`.json`, `.json5`, `.yml`, `.yaml`, `.toml`). Він є проміжним рівнем між кореневою командою `lint-text` та `bunx v8r`.

Призначення модуля — обійти специфічну ваду `v8r`: якщо у єдиний виклик передати кілька glob-патернів і хоча б один із них не знаходить жодного файлу, процес `v8r` падає з кодом виходу `98`, тому решта розширень узагалі не перевіряється. Щоб уникнути цього, скрипт виконує **окремий** `bunx v8r` на **кожен** glob і толерантно ставиться до коду `98` (порожнє співпадіння — не помилка).

Додатково модуль автоматично підставляє у виклик `v8r` параметр `-c <шлях до v8r-catalog.json>` — каталог схем пакета `@nitra/cursor`, який лежить у `npm/schemas/v8r-catalog.json` від кореня репозиторію. Завдяки цьому користувачі цього раннера не зобовʼязані пам’ятати про каталог схем.

Поведінка щодо виводу:

- Якщо v8r для конкретного glob завершився з кодом `0` (успіх) або `98` (порожнє співпадіння) — вивід **не друкується**, обхід продовжується.
- Якщо v8r повернув будь-який інший код — `stdout` і `stderr` друкуються «як є», а сам раннер повертає той самий код виходу і **припиняє** подальший обхід glob-патернів.

Файл одночасно є модулем (експортує API) і CLI-точкою входу: під час прямого запуску він читає glob-и з `process.argv` (починаючи з 3-го елемента) і виставляє `process.exitCode`.

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

Модуль експортує дві іменовані сутності:

| Імʼя                | Тип        | Призначення                                                                                                        |
| ------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------ |
| `DEFAULT_V8R_GLOBS` | `string[]` | Список glob-патернів за замовчуванням для форматів, які підтримує `v8r`.                                           |
| `V8R_CATALOG_PATH`  | `string`   | Абсолютний шлях до файлу `v8r-catalog.json` у каталозі `schemas` пакета `@nitra/cursor`.                           |
| `runV8rWithGlobs`   | `function` | Послідовний запуск `bunx v8r` по списку glob-патернів; повертає числовий код виходу. Не змінює `process.exitCode`. |

### `DEFAULT_V8R_GLOBS`

Значення:

```js
;['**/*.json', '**/*.json5', '**/*.yml', '**/*.yaml', '**/*.toml']
```

Використовується як значення за замовчуванням для `runV8rWithGlobs`, а також як fallback у CLI-режимі, коли користувач не передав власних glob-аргументів.

### `V8R_CATALOG_PATH`

Обчислюється на момент завантаження модуля з `import.meta.url`:

```js
join(dirname(fileURLToPath(import.meta.url)), '../../../schemas/v8r-catalog.json')
```

З урахуванням розташування самого модуля (`npm/rules/text/lint/run-v8r.mjs`) три рівні `..` піднімають шлях до `npm/`, після чого додається `schemas/v8r-catalog.json`. Результат — абсолютний шлях, який не залежить від поточної робочої директорії процесу.

### `runV8rWithGlobs(globs?)`

Див. секцію [«Функції»](#функції).

## Функції

### `runV8rWithGlobs(globs = DEFAULT_V8R_GLOBS)`

Сигнатура:

```js
/**
 * @param {string[]} [globs] патерни; за замовчуванням DEFAULT_V8R_GLOBS
 * @returns {number} 0 — OK, 1 — помилка spawn, 2 — немає каталогу схем, інше — код v8r
 */
export function runV8rWithGlobs(globs = DEFAULT_V8R_GLOBS)
```

#### Параметри

- `globs` (`string[]`, опційно) — масив glob-патернів, які буде передано в `v8r` по черзі (по одному на виклик). За замовчуванням — `DEFAULT_V8R_GLOBS`.

#### Повертає

Числовий код виходу, який повинен стати `process.exitCode` у CLI-режимі:

- `0` — усі glob-и оброблено успішно (або вони порожні);
- `1` — `spawnSync` повернув `result.error` (наприклад, не вдалося запустити процес);
- `2` — не знайдено файл `V8R_CATALOG_PATH` (відсутній каталог схем `npm/schemas/v8r-catalog.json`);
- будь-яке інше число — реальний код виходу від `v8r` для першого glob-а, який не дав `0` або `98`.

#### Side effects

- Може писати в `process.stderr` повідомлення про відсутній каталог схем (код `2`) або текст помилки `spawnSync.error.message` (код `1`).
- Для невдалого `v8r` (код, відмінний від `0`/`98`) друкує його `stdout` і `stderr` без змін.
- **Не** мутує `process.exitCode` і **не** викликає `process.exit`. Цією операцією займається CLI-блок наприкінці файла.
- Виконує синхронний `spawnSync` (`bun x v8r ...`) — блокуючий виклик на кожен glob.

#### Алгоритм

1. Перевіряє існування `V8R_CATALOG_PATH` через `existsSync`. Якщо файла немає — пише повідомлення в `stderr` і повертає `2`.
2. Перебирає `globs` у тому ж порядку, у якому вони передані:
   1. Резолвить `bun` через `resolveCmd('bun')`; якщо `bun` не знайдено в `PATH`, використовує `process.execPath` як fallback (тобто запустить поточний інтерпретатор, що збігається з шляхом до `node`/`bun`-runtime, який запустив скрипт).
   2. Викликає `spawnSync(bunPath, ['x', 'v8r', pattern, '-c', V8R_CATALOG_PATH], { encoding: 'utf8', maxBuffer: 50 * 1024 * 1024, shell: false, stdio: ['ignore', 'pipe', 'pipe'] })`.
   3. Якщо `result.error` — пише `result.error.message` у `stderr` і повертає `1`.
   4. Бере `result.status ?? 1` (якщо процес був вбитий сигналом, `status === null`, тоді трактує це як `1`).
   5. Якщо код **не** `0` і **не** `98` — друкує накопичений `stdout`/`stderr` (якщо вони непорожні) і повертає цей код, **зупиняючи** подальший обхід.
3. Якщо всі glob-и пройшли — повертає `0`.

### CLI-блок (нижній рівень файла)

Сигнатура (не функція, а side-effect блок):

```js
if (isRunAsCli(import.meta.url)) {
  const globs = process.argv.length > 2 ? process.argv.slice(2) : DEFAULT_V8R_GLOBS
  process.exitCode = runV8rWithGlobs(globs)
}
```

#### Поведінка

- `isRunAsCli(import.meta.url)` повертає `true`, коли модуль викликано як точку входу (а не імпортовано).
- Якщо передано хоча б один аргумент після назви скрипта (`process.argv.length > 2`) — усі аргументи (зрізані з індексу 2) використовуються як glob-патерни.
- Інакше — `DEFAULT_V8R_GLOBS`.
- Результат `runV8rWithGlobs(...)` записується у `process.exitCode`, що дозволяє завершити процес із потрібним кодом без переривання поточного call-stack.

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

### Внутрішні (Node.js core)

- `node:child_process` → `spawnSync` — синхронний запуск зовнішнього процесу `bun x v8r ...`.
- `node:fs` → `existsSync` — перевірка наявності файлу каталогу схем.
- `node:path` → `dirname`, `join` — побудова шляху до `v8r-catalog.json`.
- `node:url` → `fileURLToPath` — конвертація `import.meta.url` в абсолютний файловий шлях.

### Внутрішні (`@nitra/cursor`)

- `../../../scripts/cli-entry.mjs` → `isRunAsCli(metaUrl)` — детектор «запущено як CLI чи імпортовано як модуль».
- `../../../scripts/utils/resolve-cmd.mjs` → `resolveCmd(name)` — пошук абсолютного шляху до бінарника за `PATH`-у (з повним fallback-резолвером, на відміну від простого `which`).

### Зовнішні (через `bunx`)

- `v8r` — CLI-валідатор JSON/YAML/TOML за каталогом JSON-схем. Запускається через `bun x v8r ...`.
- `bun` — runtime, через який запускається `v8r`. Якщо `bun` відсутній у `PATH`, використовується `process.execPath`.

### Конфігурація / артефакти

- `npm/schemas/v8r-catalog.json` (в межах пакета `@nitra/cursor`) — обовʼязковий файл-каталог відповідностей `glob → schemaURL`. Без нього раннер повертає код `2`.

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

### Як частина команди `lint-text`

Раннер призначений для виклику з кореневої лінт-команди репозиторію (`lint-text` у `package.json`). Замість того, щоб запускати `v8r` чотирма (або п’ятьма) окремими командами в `package.json`, використовується один виклик цього модуля, який сам обходить усі типові glob-и:

```bash
node ./npm/rules/text/lint/run-v8r.mjs
```

Це еквівалентно послідовним викликам:

```bash
bunx v8r '**/*.json'   -c npm/schemas/v8r-catalog.json
bunx v8r '**/*.json5'  -c npm/schemas/v8r-catalog.json
bunx v8r '**/*.yml'    -c npm/schemas/v8r-catalog.json
bunx v8r '**/*.yaml'   -c npm/schemas/v8r-catalog.json
bunx v8r '**/*.toml'   -c npm/schemas/v8r-catalog.json
```

але без падіння на коді `98`, коли якесь розширення не зустрічається в проєкті.

### Запуск з кастомними glob-ами

```bash
node ./npm/rules/text/lint/run-v8r.mjs '**/*.json' 'configs/*.yaml'
```

У цьому режимі `DEFAULT_V8R_GLOBS` ігноруються, обходяться лише передані патерни.

### Імпорт як модуль

```js
import { runV8rWithGlobs, DEFAULT_V8R_GLOBS, V8R_CATALOG_PATH } from './run-v8r.mjs'

const code = runV8rWithGlobs(['**/*.json'])
if (code !== 0) {
  // обробити збій валідації
}
```

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

### Семантика кодів виходу

| Код  | Значення                                                                                                                     |
| ---- | ---------------------------------------------------------------------------------------------------------------------------- |
| `0`  | Усі glob-и пройшли (валідно або порожньо).                                                                                   |
| `1`  | Внутрішня помилка запуску процесу (`spawnSync.error`); зокрема, якщо ані `bun`, ані `process.execPath` не змогли стартувати. |
| `2`  | Не знайдено каталог схем `npm/schemas/v8r-catalog.json` у пакеті `@nitra/cursor`.                                            |
| `98` | **Не повертається**: внутрішньо трактується як «успіх для цього glob» і обхід триває далі.                                   |
| інше | Дослівний код виходу від `v8r` для першого glob, на якому валідація провалилася.                                             |

### Особливості реалізації, на які варто звернути увагу

- `shell: false` запобігає інтерполяції glob-патернів shell-ом — їх обробляє безпосередньо `v8r`.
- `maxBuffer: 50 * 1024 * 1024` (50 MiB) — захист від обрізання довгого виводу `v8r` на великих репозиторіях.
- `stdio: ['ignore', 'pipe', 'pipe']` — `stdin` процесу не використовується; вивід буферизується й друкується лише при помилці.
- Обхід «лінивий» по помилкам: на першому невдалому glob цикл завершується. Це навмисно — повертати треба перший реальний код помилки, а решта валідації не має сенсу до її усунення.
- Раннер **не** мутує `process.exitCode` усередині `runV8rWithGlobs`, тому імпортери можуть безпечно викликати функцію кілька разів і самостійно вирішувати, що з кодом робити.
