@nitra/cursor 1.8.200 → 1.8.202
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/bin/n-cursor.js +43 -0
- package/mdc/abie.mdc +41 -1
- package/package.json +1 -1
- package/scripts/check-abie.mjs +142 -1
- package/scripts/check-hasura.mjs +20 -9
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,25 @@
|
|
|
4
4
|
|
|
5
5
|
Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
|
|
6
6
|
|
|
7
|
+
## [1.8.202] - 2026-05-07
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `bin/n-cursor.js`: новий хелпер `reexecIfPackageVersionChanged(effectivePackageRoot)` і його виклик у `runSync` одразу після `upgradeNitraCursorToLatestAndBunInstall`. Якщо self-upgrade встановив у `node_modules/@nitra/cursor` версію, відмінну від тієї, з якої стартував поточний процес (типово — npx-кеш), CLI спавнить `process.execPath <newBin> <args…>` через `spawnSync` (`stdio: 'inherit'`), додає в env `NITRA_CURSOR_REEXEC=1` і завершується з exit-кодом дочірнього процесу. Обґрунтування: ES-модулі (`RULE_MIGRATIONS`, `detectAutoRulesAndSkills`, списки правил) уже завантажені у V8 і нова логіка з-під свіжо встановленого пакета без re-exec невидима для поточного запуску — `import()` не вирішує цього, бо процес виконується з `bin/` у npx-кеші, а не з `node_modules/`. Захист від нескінченного циклу — раннє повернення при `process.env.NITRA_CURSOR_REEXEC === '1'`; додатково нічого не робить, якщо `effectivePackageRoot === BUNDLED_PACKAGE_ROOT` (реального апгрейду не сталося), якщо `version` не вдалося прочитати з обох `package.json`, або якщо у новому корені відсутній `bin/n-cursor.js`. `runChecks` свідомо не патчиться — він не виконує self-upgrade, тож версія процесу і пакета там завжди узгоджені. Імпорт `spawnSync` із `node:child_process` — єдина нова зовнішня залежність.
|
|
12
|
+
|
|
13
|
+
## [1.8.201] - 2026-05-07
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- `check-hasura.mjs`: `INTERNAL_HASURA_URL_RE` тепер приймає **обидва** кластерні DNS-суфікси у `HASURA_GRAPHQL_ENDPOINT` — `<cluster>.internal` (GKE/GCP, наприклад `abie-dev` / `abie-ua`) **і** `cluster.local` (стандартний k8s / Yandex Cloud). Раніше regex вимагав літеральний `.internal` у кінці, тож URL виду `http://apruv-h-hl.ru-apruv.svc.cluster.local:8080` (типовий для YC-кластера ru) помилково відхилявся. `parseInternalHasuraEndpoint` для YC повертає `cluster: 'cluster.local'` як повний суфікс, для GKE — ім'я кластера без `.internal` (зворотньо сумісно з попередньою поведінкою). Текст помилки в `checkEnvFile` оновлено — згадує обидва допустимі формати.
|
|
18
|
+
- `abie.mdc` (v1.17 → v1.19): нова секція «Внутрішньокластерні URL у env-файлах (dev / ua / ru)». Правило стосується **будь-якого** internal URL у env-файлах abie-проєкту — не лише `HASURA_GRAPHQL_ENDPOINT`, а й KVCMS, `auth-run-hl`, `file-link-hl` тощо. Таблиця `dev.env` / `ua.env` / `ru.env` → namespace-префікс + DNS-суфікс кластера (dev → `abie-dev.internal` + `dev-…`, ua → `abie-ua.internal` + `ua-…`, ru → `cluster.local` + `ru-…`); приклади з двома сервісами в одному файлі (Hasura + KVCMS). Загальне правило про **внутрішній** URL замість публічного домену для `HASURA_GRAPHQL_ENDPOINT` лишається у `hasura.mdc` (для nitra та abie).
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- `check-abie.mjs`: новий валідатор `validateAbieEnvInternalUrls` (`String.prototype.matchAll` за `ABIE_INTERNAL_URL_GLOBAL_RE`) і helper `abieEnvNameFromBasename`. У функції `check()` додано крок `ensureAbieEnvFilesMatchClusterDns`, що сканує всі `*.env`-файли (basename `dev.env` / `ua.env` / `ru.env` опційно з провідною крапкою; `.env` без імені пропускається — як у `check-hasura.mjs`) і для **кожного** знайденого URL виду `http://<svc>.<ns>.svc.<dns>` перевіряє відповідність DNS-суфікса й namespace-префікса середовищу env-файла. Помилки додаються через `fail`, без зупинки на першому файлі — звіт показує всі порушення в усіх env-файлах одразу.
|
|
23
|
+
- `tests/check-hasura.test.mjs`: тести `parseInternalHasuraEndpoint` для GKE-style `abie-dev.internal` / `abie-ua.internal` та YC-style `cluster.local`; негативний кейс на сторонній суфікс (`svc.example.com`); інтеграційний тест `check()` для `hasura/.ru.env` з `cluster.local`.
|
|
24
|
+
- `tests/check-abie.test.mjs`: 7 unit-тестів на `abieEnvNameFromBasename` і `validateAbieEnvInternalUrls` (узгоджений dev/ua/ru, URL без порту, dev URL у ua-файлі, internal-суфікс у ru-файлі, ігнорування зовнішніх `https://` / `localhost`, кілька URL з різними порушеннями) і 4 інтеграційні (`.dev.env`+`.ua.env`+`.ru.env` узгоджені — 0; ua з dev URL у KVCMS — 1; ru з `.internal` замість `cluster.local` — 1; `.env` без імені пропускається).
|
|
25
|
+
|
|
7
26
|
## [1.8.200] - 2026-05-07
|
|
8
27
|
|
|
9
28
|
### Added
|
package/bin/n-cursor.js
CHANGED
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
* `node_modules/@nitra/cursor`, якщо пакет з’явився після встановлення.
|
|
50
50
|
*/
|
|
51
51
|
|
|
52
|
+
import { spawnSync } from 'node:child_process'
|
|
52
53
|
import { existsSync } from 'node:fs'
|
|
53
54
|
import { mkdir, readdir, readFile, rename, rm, unlink, writeFile } from 'node:fs/promises'
|
|
54
55
|
import { basename, dirname, join } from 'node:path'
|
|
@@ -1122,6 +1123,46 @@ async function readBundledVersionAt(packageRoot) {
|
|
|
1122
1123
|
}
|
|
1123
1124
|
}
|
|
1124
1125
|
|
|
1126
|
+
/**
|
|
1127
|
+
* Якщо `upgradeNitraCursorToLatestAndBunInstall` встановив у `node_modules/@nitra/cursor` версію,
|
|
1128
|
+
* відмінну від тієї, з якої стартував поточний процес (наприклад, з npx-кешу), запускає бінар нової
|
|
1129
|
+
* версії через `spawnSync` і завершує поточний процес із успадкованим exit-кодом. Re-exec потрібен,
|
|
1130
|
+
* бо ES-модулі вже завантажені у V8 (RULE_MIGRATIONS, detectAutoRulesAndSkills тощо) і нова логіка
|
|
1131
|
+
* без повної заміни процесу не підхопиться. Захист від нескінченного циклу — env `NITRA_CURSOR_REEXEC=1`.
|
|
1132
|
+
* @param {string} effectivePackageRoot шлях, повернутий `upgradeNitraCursorToLatestAndBunInstall`
|
|
1133
|
+
* @returns {Promise<void>} повертається лише якщо re-exec не потрібен (інакше викликає `process.exit`)
|
|
1134
|
+
*/
|
|
1135
|
+
async function reexecIfPackageVersionChanged(effectivePackageRoot) {
|
|
1136
|
+
if (process.env.NITRA_CURSOR_REEXEC === '1') {
|
|
1137
|
+
return
|
|
1138
|
+
}
|
|
1139
|
+
if (effectivePackageRoot === BUNDLED_PACKAGE_ROOT) {
|
|
1140
|
+
return
|
|
1141
|
+
}
|
|
1142
|
+
const currentVersion = await readBundledVersionAt(BUNDLED_PACKAGE_ROOT)
|
|
1143
|
+
const installedVersion = await readBundledVersionAt(effectivePackageRoot)
|
|
1144
|
+
if (!currentVersion || !installedVersion || currentVersion === installedVersion) {
|
|
1145
|
+
return
|
|
1146
|
+
}
|
|
1147
|
+
const newBinPath = join(effectivePackageRoot, 'bin', 'n-cursor.js')
|
|
1148
|
+
if (!existsSync(newBinPath)) {
|
|
1149
|
+
return
|
|
1150
|
+
}
|
|
1151
|
+
console.log(
|
|
1152
|
+
`🔁 Перезапуск ${PACKAGE_NAME}: процес стартував на ${currentVersion}, ` +
|
|
1153
|
+
`після self-upgrade встановлено ${installedVersion}.\n` +
|
|
1154
|
+
` Re-exec свіжого бінаря, щоб підхопити нову логіку (RULE_MIGRATIONS, auto-detect тощо).\n`
|
|
1155
|
+
)
|
|
1156
|
+
const result = spawnSync(process.execPath, [newBinPath, ...process.argv.slice(2)], {
|
|
1157
|
+
stdio: 'inherit',
|
|
1158
|
+
env: { ...process.env, NITRA_CURSOR_REEXEC: '1' }
|
|
1159
|
+
})
|
|
1160
|
+
if (result.error) {
|
|
1161
|
+
throw result.error
|
|
1162
|
+
}
|
|
1163
|
+
process.exit(typeof result.status === 'number' ? result.status : 1)
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1125
1166
|
/**
|
|
1126
1167
|
* Копіює правила з каталогу `mdc/` установленого пакету та синхронізує `.cursor/rules`
|
|
1127
1168
|
* @returns {Promise<void>}
|
|
@@ -1134,6 +1175,8 @@ async function runSync() {
|
|
|
1134
1175
|
upgradeNitraCursorToLatestAndBunInstall(projectRoot, BUNDLED_PACKAGE_ROOT)
|
|
1135
1176
|
)
|
|
1136
1177
|
|
|
1178
|
+
await reexecIfPackageVersionChanged(effectivePackageRoot)
|
|
1179
|
+
|
|
1137
1180
|
const bundledMdcDir = join(effectivePackageRoot, 'mdc')
|
|
1138
1181
|
const bundledSkillsDir = join(effectivePackageRoot, 'skills')
|
|
1139
1182
|
const bundledAgentsTemplatePath = join(effectivePackageRoot, AGENTS_TEMPLATE_FILE)
|
package/mdc/abie.mdc
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Правила для проєктів AbInBev Efes
|
|
3
3
|
alwaysApply: true
|
|
4
|
-
version: '1.
|
|
4
|
+
version: '1.19'
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
Правило **abie** для споживачів **@nitra/cursor**: **k8s** (Deployment + **HealthCheckPolicy** у **`hc.yaml`**, overlay **ua** / **ru** — **nodeSelector**, **HTTPRoute** (будь-який непорожній **`target.name`**, для спільних сервісів **`auth-run-hl`** / **`file-link-hl`** — **`namespace: dev`** у base та patch **`…/backendRefs/…/namespace`** у **ua** / **ru**), у overlay **ru** — кожен **Service** (у т. ч. **headless** / **`-hl`**) → **`spec.type: NodePort`** через **JSON6902** у **`kustomization.yaml`**, видалення **HealthCheckPolicy** у **ru**), гілки **dev**, **ua**, **ru** у **clean-merged-branch**, а також заборона тримати артефакти **Firebase Hosting** у **підкаталогах першого рівня** (безпосередні діти кореня репозиторію; у самому корені ці імена не вимагаються до видалення).
|
|
@@ -332,6 +332,46 @@ spec:
|
|
|
332
332
|
preem: 'true' # буде замінено через kustomize
|
|
333
333
|
```
|
|
334
334
|
|
|
335
|
+
## Внутрішньокластерні URL у env-файлах (dev / ua / ru)
|
|
336
|
+
|
|
337
|
+
Правило стосується **будь-якого** внутрішньокластерного URL у env-файлах abie-проєкту, а не лише `HASURA_GRAPHQL_ENDPOINT`. Це може бути URL до Hasura, KVCMS, `auth-run-hl`, `file-link-hl` чи будь-якого іншого Service у кластері — у всіх випадках DNS-суфікс і namespace-префікс мають відповідати **середовищу** з імені env-файлу.
|
|
338
|
+
|
|
339
|
+
abie-проєкти живуть у **трьох різних кластерах** (dev / ua у GKE, ru у Yandex Cloud), тож DNS-суфікс і namespace у URL відрізняються між `*.env`-файлами:
|
|
340
|
+
|
|
341
|
+
| env-файл (basename) | namespace-префікс у URL | DNS-суфікс кластера | примітка |
|
|
342
|
+
| --- | --- | --- | --- |
|
|
343
|
+
| `dev.env`, `.dev.env` | `dev-…` | `abie-dev.internal` | GKE-кластер dev |
|
|
344
|
+
| `ua.env`, `.ua.env` | `ua-…` | `abie-ua.internal` | GKE-кластер ua |
|
|
345
|
+
| `ru.env`, `.ru.env` | `ru-…` | `cluster.local` | YC-кластер ru, стандартний k8s DNS |
|
|
346
|
+
|
|
347
|
+
Канонічна форма URL — `http://<service>.<namespace>.svc.<cluster-dns-suffix>:<port>`. Для GKE (dev / ua) суфікс — `<cluster>.internal`; для YC (ru) — фіксований `cluster.local` (у YC у DNS сервісу немає окремого імені кластера).
|
|
348
|
+
|
|
349
|
+
Приклади для одного env-файлу з двома сервісами (Hasura + KVCMS):
|
|
350
|
+
|
|
351
|
+
```env title="hasura/.dev.env"
|
|
352
|
+
HASURA_GRAPHQL_ENDPOINT=http://apruv-h-hl.dev-apruv.svc.abie-dev.internal:8080
|
|
353
|
+
KVCMS_URL=http://kvcms-hl.dev-apruv.svc.abie-dev.internal:8080
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
```env title="hasura/.ua.env"
|
|
357
|
+
HASURA_GRAPHQL_ENDPOINT=http://apruv-h-hl.ua-apruv.svc.abie-ua.internal:8080
|
|
358
|
+
KVCMS_URL=http://kvcms-hl.ua-apruv.svc.abie-ua.internal:8080
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
```env title="hasura/.ru.env"
|
|
362
|
+
HASURA_GRAPHQL_ENDPOINT=http://apruv-h-hl.ru-apruv.svc.cluster.local:8080
|
|
363
|
+
KVCMS_URL=http://kvcms-hl.ru-apruv.svc.cluster.local:8080
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
`<namespace>` (наприклад `dev-apruv` / `ua-apruv` / `ru-apruv`) — `metadata.name` цільового namespace після kustomize-overlay для відповідного середовища; `<service>` — `metadata.name` headless Service (`-hl`) того сервісу, до якого йде URL.
|
|
367
|
+
|
|
368
|
+
**Перевірка `check-abie.mjs`** сканує всі `*.env` файли, basename яких збігається з `dev.env` / `ua.env` / `ru.env` (з провідною крапкою чи без), знаходить **усі** internal URL (`http://<svc>.<ns>.svc.<dns>` — як для Hasura-ендпоінта, так і для KVCMS чи будь-якого іншого) і вимагає, щоб для кожного:
|
|
369
|
+
|
|
370
|
+
- DNS-суфікс відповідав env: `abie-dev.internal` / `abie-ua.internal` / `cluster.local`;
|
|
371
|
+
- namespace починався з `dev-` / `ua-` / `ru-` відповідно.
|
|
372
|
+
|
|
373
|
+
Загальне правило про **внутрішній** URL (не публічний домен) для `HASURA_GRAPHQL_ENDPOINT` лишається у **`hasura.mdc`** (для nitra і abie) — `check-hasura.mjs` приймає обидва кластерні DNS-формати (`<cluster>.internal` і `cluster.local`).
|
|
374
|
+
|
|
335
375
|
## Firebase Hosting
|
|
336
376
|
|
|
337
377
|
У **кожному** підкаталозі, що лежить **безпосередньо** в корені репозиторію, не тримати конфіг і кеш **Firebase Hosting**: у таких каталогах не повинно бути **`.firebaserc`**, **`firebase.json`** та каталогу **`.firebase/`** (у **самому** корені репозиторію ці імена перевіркою abie **не** розглядаються; `node_modules` / `.git` зі скану вилучаються).
|
package/package.json
CHANGED
package/scripts/check-abie.mjs
CHANGED
|
@@ -37,10 +37,17 @@
|
|
|
37
37
|
*
|
|
38
38
|
* **Service (overlay ru):** для кожного **Service**, оголошеного в YAML під **`…/k8s/…`**, де шлях **не** проходить через **`k8s/ua/`** чи **`k8s/ru/`** (маніфести base / спільного шару, у т. ч. **headless** з **`clusterIP: None`** і **`-hl`**), якщо ще не **NodePort** / **LoadBalancer** / **ExternalName**,
|
|
39
39
|
* у файлі **`k8s/ru/kustomization.yaml`** того ж пакета (overlay середовища **ru**) — inline **JSON6902** на **`kind: Service`** з тим самим **`target.name`**: **`path: /spec/type`**, **`value: NodePort`**; якщо в base було **`spec.clusterIP: None`** — **`op: remove`** для **`/spec/clusterIP`**; якщо в base **явно** задано **`spec.clusterIPs`** — також **`remove`** для **`/spec/clusterIPs`** (інакше **API** може залишити **`None`** для **NodePort**; без ключа **`clusterIPs`** у base **`remove`** на **`/spec/clusterIPs`** ламає **`kubectl kustomize`**).
|
|
40
|
+
*
|
|
41
|
+
* **env→cluster DNS:** abie живе у трьох різних кластерах (GKE dev/ua + YC ru), тож DNS-суфікс і namespace-префікс у будь-якому
|
|
42
|
+
* **внутрішньокластерному** URL виду `http://<svc>.<ns>.svc.<dns>` мають відповідати імені env-файла. Скануються всі `*.env` файли,
|
|
43
|
+
* basename яких збігається з `dev.env` / `ua.env` / `ru.env` (опційно з провідною крапкою — `.dev.env` тощо). Для кожного знайденого
|
|
44
|
+
* internal URL у файлі (не лише `HASURA_GRAPHQL_ENDPOINT`, а й KVCMS, auth-run, file-link тощо) валідатор `validateAbieEnvInternalUrls`
|
|
45
|
+
* вимагає: для `dev.env` — DNS `abie-dev.internal` і namespace починається з `dev-`; для `ua.env` — `abie-ua.internal` + `ua-`;
|
|
46
|
+
* для `ru.env` — `cluster.local` + `ru-`. Файл `.env` без імені (локальний для розробника) виключено зі сканування — як і у `check-hasura.mjs`.
|
|
40
47
|
*/
|
|
41
48
|
import { existsSync } from 'node:fs'
|
|
42
49
|
import { readdir, readFile } from 'node:fs/promises'
|
|
43
|
-
import { dirname, join, relative } from 'node:path'
|
|
50
|
+
import { basename, dirname, join, relative } from 'node:path'
|
|
44
51
|
|
|
45
52
|
import { parseAllDocuments } from 'yaml'
|
|
46
53
|
|
|
@@ -119,6 +126,73 @@ const HTTPROUTE_BACKENDREF_PORT_8081_VALUE_FIRST_RE =
|
|
|
119
126
|
/** Гілки, які мають бути в **`ignore_branches`** за abie.mdc. */
|
|
120
127
|
export const ABIE_REQUIRED_IGNORE_BRANCHES = ['dev', 'ua', 'ru']
|
|
121
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Регекс basename env-файлу abie: `dev.env` / `ua.env` / `ru.env`, опційно з провідною крапкою (`.dev.env` тощо).
|
|
131
|
+
* Файл рівно `.env` (без імені) — виключення з правила: локальний файл розробника, `check-abie` його не сканує
|
|
132
|
+
* (так само як `check-hasura`, див. `isEnvFile`).
|
|
133
|
+
*/
|
|
134
|
+
const ABIE_ENV_FILE_BASENAME_RE = /^\.?(dev|ua|ru)\.env$/u
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Глобальний регекс кластерного internal URL у тексті env-файлу.
|
|
138
|
+
* Використовується з `String.prototype.matchAll`, тому має флаг `g`.
|
|
139
|
+
* Дозволяє два DNS-формати: `<cluster>.internal` (GKE) і `cluster.local` (YC / стандартний k8s).
|
|
140
|
+
* Порт необов'язковий — у KVCMS-конфігах інколи лежить URL без порту (8080 додається сервісом за замовчуванням).
|
|
141
|
+
*/
|
|
142
|
+
const ABIE_INTERNAL_URL_GLOBAL_RE =
|
|
143
|
+
/\bhttp:\/\/([a-z0-9][a-z0-9-]*)\.([a-z0-9][a-z0-9-]*)\.svc\.((?:[a-z0-9][a-z0-9-]*\.internal)|cluster\.local)(?::\d+)?(?:\/[^\s"'`]*)?/giu
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Очікуваний кластерний DNS-суфікс і namespace-префікс для кожного env-файлу abie.
|
|
147
|
+
* `dev` / `ua` живуть у GKE з власним `<cluster>.internal`; `ru` — у YC, де DNS-суфікс
|
|
148
|
+
* стандартний `cluster.local` (без імені кластера).
|
|
149
|
+
*/
|
|
150
|
+
const ABIE_ENV_CLUSTER_DNS_MAP = Object.freeze({
|
|
151
|
+
dev: Object.freeze({ clusterDns: 'abie-dev.internal', namespacePrefix: 'dev-' }),
|
|
152
|
+
ua: Object.freeze({ clusterDns: 'abie-ua.internal', namespacePrefix: 'ua-' }),
|
|
153
|
+
ru: Object.freeze({ clusterDns: 'cluster.local', namespacePrefix: 'ru-' })
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Дістає ім'я env (`dev` / `ua` / `ru`) з basename env-файлу abie.
|
|
158
|
+
* Для не-abie env-файлів (наприклад `production.env`, `.env` без імені) повертає `null`.
|
|
159
|
+
* @param {string} basenameOfEnvFile basename файла (без шляху)
|
|
160
|
+
* @returns {('dev' | 'ua' | 'ru') | null} ім'я env або `null`
|
|
161
|
+
*/
|
|
162
|
+
export function abieEnvNameFromBasename(basenameOfEnvFile) {
|
|
163
|
+
const m = basenameOfEnvFile.match(ABIE_ENV_FILE_BASENAME_RE)
|
|
164
|
+
return m ? /** @type {'dev' | 'ua' | 'ru'} */ (m[1]) : null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Сканує вміст env-файлу abie і повертає помилки невідповідності кластерного DNS / namespace
|
|
169
|
+
* для кожного знайденого internal URL. URL шукається глобально (`matchAll`), тож одне й те саме
|
|
170
|
+
* порушення в кількох змінних дасть стільки ж окремих помилок.
|
|
171
|
+
* @param {string} content вміст env-файлу (UTF-8)
|
|
172
|
+
* @param {'dev' | 'ua' | 'ru'} envName ім'я env, отримане з `abieEnvNameFromBasename`
|
|
173
|
+
* @returns {string[]} порожній масив, якщо все OK; інакше — список повідомлень про порушення
|
|
174
|
+
*/
|
|
175
|
+
export function validateAbieEnvInternalUrls(content, envName) {
|
|
176
|
+
const expected = ABIE_ENV_CLUSTER_DNS_MAP[envName]
|
|
177
|
+
if (!expected) return []
|
|
178
|
+
/** @type {string[]} */
|
|
179
|
+
const errors = []
|
|
180
|
+
for (const match of content.matchAll(ABIE_INTERNAL_URL_GLOBAL_RE)) {
|
|
181
|
+
const [fullUrl, , namespace, clusterDns] = match
|
|
182
|
+
if (clusterDns !== expected.clusterDns) {
|
|
183
|
+
errors.push(
|
|
184
|
+
`${fullUrl}: кластерний DNS "${clusterDns}" не відповідає env "${envName}" (очікується "${expected.clusterDns}")`
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
if (!namespace.startsWith(expected.namespacePrefix)) {
|
|
188
|
+
errors.push(
|
|
189
|
+
`${fullUrl}: namespace "${namespace}" не починається з "${expected.namespacePrefix}" (env "${envName}")`
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return errors
|
|
194
|
+
}
|
|
195
|
+
|
|
122
196
|
/**
|
|
123
197
|
* Чи відносний шлях вказує на **`ru/kustomization.yaml`** (сегмент **`ru`** перед ім'ям файлу) — специфіка abie overlay.
|
|
124
198
|
* @param {string} rel шлях від кореня репозиторію
|
|
@@ -2078,6 +2152,70 @@ async function ensureAbieNginxSidecarForHasura(root, yamlFilesAbs, fail, passFn)
|
|
|
2078
2152
|
}
|
|
2079
2153
|
}
|
|
2080
2154
|
|
|
2155
|
+
/**
|
|
2156
|
+
* Збирає всі `*.env` файли в дереві (за виключенням `node_modules`, `.git` та інших службових каталогів),
|
|
2157
|
+
* basename яких — abie env-файл (`dev.env` / `ua.env` / `ru.env` опційно з провідною крапкою). Файл `.env`
|
|
2158
|
+
* без імені виключається — як і у `check-hasura.mjs`.
|
|
2159
|
+
* @param {string} root корінь репозиторію
|
|
2160
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
2161
|
+
* @returns {Promise<string[]>} відсортовані абсолютні шляхи env-файлів abie
|
|
2162
|
+
*/
|
|
2163
|
+
async function collectAbieEnvFiles(root, ignorePaths) {
|
|
2164
|
+
/** @type {string[]} */
|
|
2165
|
+
const out = []
|
|
2166
|
+
await walkDir(
|
|
2167
|
+
root,
|
|
2168
|
+
absPath => {
|
|
2169
|
+
if (abieEnvNameFromBasename(basename(absPath)) !== null) {
|
|
2170
|
+
out.push(absPath)
|
|
2171
|
+
}
|
|
2172
|
+
},
|
|
2173
|
+
ignorePaths
|
|
2174
|
+
)
|
|
2175
|
+
return out.toSorted((a, b) => a.localeCompare(b))
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
/**
|
|
2179
|
+
* Сканує всі `*.env` файли abie (`.dev.env` / `.ua.env` / `.ru.env`) і для кожного знайденого
|
|
2180
|
+
* **внутрішньокластерного** URL (`http://<svc>.<ns>.svc.<dns>`) перевіряє, що DNS-суфікс і namespace-префікс
|
|
2181
|
+
* відповідають середовищу env-файла. Не лише `HASURA_GRAPHQL_ENDPOINT`, а й будь-який сервіс у env (KVCMS,
|
|
2182
|
+
* `auth-run-hl`, `file-link-hl` тощо) мусить мати кластер, що відповідає env: dev → `abie-dev.internal`,
|
|
2183
|
+
* ua → `abie-ua.internal`, ru → `cluster.local`.
|
|
2184
|
+
* @param {string} root корінь репозиторію
|
|
2185
|
+
* @param {string[]} ignorePaths абсолютні шляхи каталогів, повністю виключених з обходу
|
|
2186
|
+
* @param {(msg: string) => void} pass успішне повідомлення
|
|
2187
|
+
* @param {(msg: string) => void} fail повідомлення про порушення
|
|
2188
|
+
* @returns {Promise<void>}
|
|
2189
|
+
*/
|
|
2190
|
+
async function ensureAbieEnvFilesMatchClusterDns(root, ignorePaths, pass, fail) {
|
|
2191
|
+
const envFiles = await collectAbieEnvFiles(root, ignorePaths)
|
|
2192
|
+
if (envFiles.length === 0) {
|
|
2193
|
+
pass('Не знайдено dev.env / ua.env / ru.env у репозиторії — перевірку env→cluster DNS пропущено (abie.mdc)')
|
|
2194
|
+
return
|
|
2195
|
+
}
|
|
2196
|
+
for (const abs of envFiles) {
|
|
2197
|
+
const rel = relative(root, abs).replaceAll('\\', '/') || abs
|
|
2198
|
+
const envName = abieEnvNameFromBasename(basename(abs))
|
|
2199
|
+
if (envName === null) continue
|
|
2200
|
+
let raw
|
|
2201
|
+
try {
|
|
2202
|
+
raw = await readFile(abs, 'utf8')
|
|
2203
|
+
} catch (error) {
|
|
2204
|
+
const msg = error instanceof Error ? error.message : String(error)
|
|
2205
|
+
fail(`${rel}: не вдалося прочитати (${msg})`)
|
|
2206
|
+
continue
|
|
2207
|
+
}
|
|
2208
|
+
const errors = validateAbieEnvInternalUrls(raw, envName)
|
|
2209
|
+
if (errors.length === 0) {
|
|
2210
|
+
pass(`${rel}: усі внутрішні URL відповідають env "${envName}" (abie.mdc)`)
|
|
2211
|
+
} else {
|
|
2212
|
+
for (const err of errors) {
|
|
2213
|
+
fail(`${rel}: ${err} (abie.mdc)`)
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2081
2219
|
/**
|
|
2082
2220
|
* Перевіряє відповідність проєкту правилам abie.mdc.
|
|
2083
2221
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
@@ -2129,5 +2267,8 @@ export async function check() {
|
|
|
2129
2267
|
pass('Перевіряємо nginx-sidecar для Hasura WebSocket у ru (abie.mdc)')
|
|
2130
2268
|
await ensureAbieNginxSidecarForHasura(root, yamlFiles, fail, pass)
|
|
2131
2269
|
|
|
2270
|
+
pass('Перевіряємо env→cluster DNS у dev.env / ua.env / ru.env (abie.mdc)')
|
|
2271
|
+
await ensureAbieEnvFilesMatchClusterDns(root, ignorePaths, pass, fail)
|
|
2272
|
+
|
|
2132
2273
|
return reporter.getExitCode()
|
|
2133
2274
|
}
|
package/scripts/check-hasura.mjs
CHANGED
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* вказує на `https://github.com/nitra/...` або `https://github.com/abinbevefes/...`
|
|
8
8
|
* (інші репозиторії пропускаються без помилок — як у check-abie).
|
|
9
9
|
*
|
|
10
|
-
* Очікуваний формат URL
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* Очікуваний формат URL — два варіанти кластерного DNS-суфікса:
|
|
11
|
+
* - GKE / GCP: `http://<service>.<namespace>.svc.<cluster>.internal:<port>`
|
|
12
|
+
* приклад: `http://contract-h.ua-contract.svc.abie-ua.internal:8080`
|
|
13
|
+
* - стандартний k8s / Yandex Cloud: `http://<service>.<namespace>.svc.cluster.local:<port>`
|
|
14
|
+
* приклад: `http://apruv-h-hl.ru-apruv.svc.cluster.local:8080`
|
|
14
15
|
*
|
|
15
16
|
* Сегменти беруться з `hasura/k8s/base/svc-hl.yaml` (`metadata.name` —
|
|
16
17
|
* має закінчуватись на `-h`, headless-сервіс) і `hasura/k8s/base/namespace.yaml`
|
|
@@ -42,12 +43,20 @@ const HASURA_NAMESPACE_FILE = `${HASURA_BASE_DIR}/namespace.yaml`
|
|
|
42
43
|
|
|
43
44
|
const ENV_FILE_RE = /\.env$/u
|
|
44
45
|
const HASURA_ENDPOINT_LINE_RE = /^[ \t]*(?:export[ \t]+)?HASURA_GRAPHQL_ENDPOINT[ \t]*=[ \t]*['"]?([^'"\r\n#]+)/mu
|
|
45
|
-
|
|
46
|
+
// Дозволяємо два DNS-суфікси кластера: `<name>.internal` (GKE/GCP) і `cluster.local`
|
|
47
|
+
// (стандартний k8s / Yandex Cloud). У YC namespace.yaml + cluster mode дають коротший суфікс.
|
|
48
|
+
const INTERNAL_HASURA_URL_RE =
|
|
49
|
+
/^http:\/\/([^./]+)\.([^./]+)\.svc\.((?:[^./:]+\.internal)|cluster\.local):(\d+)\/?$/u
|
|
50
|
+
const CLUSTER_LOCAL_SUFFIX = 'cluster.local'
|
|
51
|
+
const INTERNAL_DNS_SUFFIX = '.internal'
|
|
46
52
|
|
|
47
53
|
/**
|
|
48
54
|
* Розбір значення `HASURA_GRAPHQL_ENDPOINT` як внутрішнього кластерного URL.
|
|
49
|
-
* Дозволяє лише `http://` (TLS усередині кластера зайвий)
|
|
50
|
-
* `<
|
|
55
|
+
* Дозволяє лише `http://` (TLS усередині кластера зайвий) та обидва кластерні
|
|
56
|
+
* DNS-суфікси: `<cluster>.internal` (GKE/GCP) і `cluster.local`
|
|
57
|
+
* (стандартний k8s / Yandex Cloud). Поле `cluster` для GKE містить ім'я
|
|
58
|
+
* кластера без `.internal` (наприклад `abie-ua`); для YC — повний суфікс
|
|
59
|
+
* `cluster.local` (бо своєї «назви кластера» в DNS немає).
|
|
51
60
|
* @param {string} url значення з `.env` (без огорнутих лапок)
|
|
52
61
|
* @returns {{ ok: true, service: string, namespace: string, cluster: string, port: string } | { ok: false }}
|
|
53
62
|
* розібрані сегменти або `{ ok: false }`, якщо формат не відповідає внутрішньому кластерному URL
|
|
@@ -57,7 +66,9 @@ export function parseInternalHasuraEndpoint(url) {
|
|
|
57
66
|
if (!m) {
|
|
58
67
|
return { ok: false }
|
|
59
68
|
}
|
|
60
|
-
|
|
69
|
+
const suffix = m[3]
|
|
70
|
+
const cluster = suffix.endsWith(INTERNAL_DNS_SUFFIX) ? suffix.slice(0, -INTERNAL_DNS_SUFFIX.length) : suffix
|
|
71
|
+
return { ok: true, service: m[1], namespace: m[2], cluster, port: m[4] }
|
|
61
72
|
}
|
|
62
73
|
|
|
63
74
|
/**
|
|
@@ -140,7 +151,7 @@ async function checkEnvFile(relPath, expected, reporter) {
|
|
|
140
151
|
const parsed = parseInternalHasuraEndpoint(value)
|
|
141
152
|
if (!parsed.ok) {
|
|
142
153
|
// eslint-disable-next-line @microsoft/sdl/no-insecure-url, sonarjs/no-clear-text-protocols -- hasura.mdc вимагає саме http:// для кластерного URL (TLS не використовується)
|
|
143
|
-
const example = 'http://<service>.<namespace>.svc.<cluster>.internal:<port>'
|
|
154
|
+
const example = 'http://<service>.<namespace>.svc.<cluster>.internal:<port> або http://<service>.<namespace>.svc.cluster.local:<port>'
|
|
144
155
|
fail(
|
|
145
156
|
`${relPath}: HASURA_GRAPHQL_ENDPOINT="${value}" — потрібен внутрішній кластерний URL виду ${example} (hasura.mdc)`
|
|
146
157
|
)
|