@nitra/cursor 1.8.200 → 1.8.201

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 CHANGED
@@ -4,6 +4,19 @@
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.201] - 2026-05-07
8
+
9
+ ### Changed
10
+
11
+ - `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` оновлено — згадує обидва допустимі формати.
12
+ - `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).
13
+
14
+ ### Added
15
+
16
+ - `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-файлах одразу.
17
+ - `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`.
18
+ - `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` без імені пропускається).
19
+
7
20
  ## [1.8.200] - 2026-05-07
8
21
 
9
22
  ### Added
package/mdc/abie.mdc CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Правила для проєктів AbInBev Efes
3
3
  alwaysApply: true
4
- version: '1.17'
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.8.200",
3
+ "version": "1.8.201",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -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
  }
@@ -7,10 +7,11 @@
7
7
  * вказує на `https://github.com/nitra/...` або `https://github.com/abinbevefes/...`
8
8
  * (інші репозиторії пропускаються без помилок — як у check-abie).
9
9
  *
10
- * Очікуваний формат URL:
11
- * `http://<service>.<namespace>.svc.<cluster>.internal:<port>`
12
- *
13
- * приклад: `http://contract-h.ua-contract.svc.abie-ua.internal:8080`
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
- const INTERNAL_HASURA_URL_RE = /^http:\/\/([^./]+)\.([^./]+)\.svc\.([^./]+)\.internal:(\d+)\/?$/u
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
- * `<service>.<namespace>.svc.<cluster>.internal` та явного порту.
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
- return { ok: true, service: m[1], namespace: m[2], cluster: m[3], port: m[4] }
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
  )