@nitra/cursor 1.8.99 → 1.8.102
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/mdc/abie.mdc +4 -4
- package/package.json +1 -1
- package/scripts/check-abie.mjs +41 -22
- package/skills/lint/SKILL.md +1 -1
package/mdc/abie.mdc
CHANGED
|
@@ -115,9 +115,9 @@ spec:
|
|
|
115
115
|
|
|
116
116
|
## k8s: overlay **ru** і **Service** (у т. ч. headless → NodePort)
|
|
117
117
|
|
|
118
|
-
Для кожного **Service** в YAML під **`…/k8s/…`**, де шлях файлу **не** містить **`k8s/ua/`** чи **`k8s/ru/`** (маніфести base / спільного шару; у т. ч. **headless** з **`spec.clusterIP: None`** і **`-hl`**), якщо ще не **`spec.type: NodePort`** / **`LoadBalancer`** / **`ExternalName`**, у **`k8s/ru/kustomization.yaml`** того ж пакета (overlay **ru**) додай **inline** **JSON6902** у **`patches`**: **`target.kind: Service`**, **`target.name`** як у маніфеста, **`path: /spec/type`**, **`value: NodePort`**. Якщо в base було **`spec.clusterIP: None
|
|
118
|
+
Для кожного **Service** в YAML під **`…/k8s/…`**, де шлях файлу **не** містить **`k8s/ua/`** чи **`k8s/ru/`** (маніфести base / спільного шару; у т. ч. **headless** з **`spec.clusterIP: None`** і **`-hl`**), якщо ще не **`spec.type: NodePort`** / **`LoadBalancer`** / **`ExternalName`**, у **`k8s/ru/kustomization.yaml`** того ж пакета (overlay **ru**) додай **inline** **JSON6902** у **`patches`**: **`target.kind: Service`**, **`target.name`** як у маніфеста, **`path: /spec/type`**, **`value: NodePort`**. Якщо в base було **`spec.clusterIP: None`**, у тому ж **patch** додай **`op: remove`** для **`/spec/clusterIP`**. Якщо в base **явно** задано **`spec.clusterIPs`** (зокрема **`['None']`**), додай **`op: remove`** і для **`/spec/clusterIPs`** — інакше **API** може відхилити **NodePort** (*`spec.clusterIPs[0]: Invalid value: "None"`*). **Не** додавай **`remove`** на **`/spec/clusterIPs`**, якщо ключа **немає** в base: **`kubectl kustomize`** тоді падає (*Unable to remove nonexistent key*). Якщо в base лише **`clusterIP: None`**, а помилка лишається після **`kubectl apply -k`** (злиття з уже існуючим **Service** у кластері), тимчасово **видали** **`Service`** у **`ru`** і застосуй знову, або додай у base поле **`clusterIPs`**, щоб **`remove`** у patch був валідний для **kustomize**. Деталі — **`check-abie.mjs`**.
|
|
119
119
|
|
|
120
|
-
```yaml title="…/ru/kustomization.yaml (фрагмент, headless → NodePort)"
|
|
120
|
+
```yaml title="…/ru/kustomization.yaml (фрагмент, headless → NodePort, без clusterIPs у base)"
|
|
121
121
|
patches:
|
|
122
122
|
- target:
|
|
123
123
|
kind: Service
|
|
@@ -128,10 +128,10 @@ patches:
|
|
|
128
128
|
value: NodePort
|
|
129
129
|
- op: remove
|
|
130
130
|
path: /spec/clusterIP
|
|
131
|
-
- op: remove
|
|
132
|
-
path: /spec/clusterIPs
|
|
133
131
|
```
|
|
134
132
|
|
|
133
|
+
Якщо в base у цього **Service** уже є **`spec.clusterIPs`**, той самий **patch** розшир **remove** на **`/spec/clusterIPs`** (див. **`check-abie.mjs`**).
|
|
134
|
+
|
|
135
135
|
## k8s: overlay **ru** і HealthCheckPolicy
|
|
136
136
|
|
|
137
137
|
Якщо в дереві **k8s** є **HealthCheckPolicy**, у **`ru/kustomization.yaml`** має бути patch **`$patch: delete`** для політики (узгоджено з **k8s.mdc**; перевірка в **`check-k8s.mjs`**, **`ruKustomizationHasHealthCheckDeletePatch`**). Підстав реальне ім’я замість **`СЕРВІС`**:
|
package/package.json
CHANGED
package/scripts/check-abie.mjs
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* Вибір **`op`** — **k8s.mdc**.
|
|
35
35
|
*
|
|
36
36
|
* **Service (overlay ru):** для кожного **Service**, оголошеного в YAML під **`…/k8s/…`**, де шлях **не** проходить через **`k8s/ua/`** чи **`k8s/ru/`** (маніфести base / спільного шару, у т. ч. **headless** з **`clusterIP: None`** і **`-hl`**), якщо ще не **NodePort** / **LoadBalancer** / **ExternalName**,
|
|
37
|
-
* у файлі **`k8s/ru/kustomization.yaml`** того ж пакета (overlay середовища **ru**) — inline **JSON6902** на **`kind: Service`** з тим самим **`target.name`**: **`path: /spec/type`**, **`value: NodePort`**; якщо в base було **`spec.clusterIP: None`**
|
|
37
|
+
* у файлі **`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`**).
|
|
38
38
|
*/
|
|
39
39
|
import { existsSync } from 'node:fs'
|
|
40
40
|
import { readFile } from 'node:fs/promises'
|
|
@@ -370,9 +370,9 @@ export function serviceDocumentRequiresAbieRuNodePortOverlay(obj) {
|
|
|
370
370
|
}
|
|
371
371
|
|
|
372
372
|
/**
|
|
373
|
-
* Чи в base-**Service**
|
|
373
|
+
* Чи в base-**Service** задано **headless** через **`spec.clusterIP: None`**, який треба прибрати в **ru** перед **NodePort**.
|
|
374
374
|
* @param {unknown} obj корінь YAML (**Service**)
|
|
375
|
-
* @returns {boolean} **true**, якщо **`spec.clusterIP === 'None'`**
|
|
375
|
+
* @returns {boolean} **true**, якщо **`spec.clusterIP === 'None'`**
|
|
376
376
|
*/
|
|
377
377
|
export function serviceDocumentRequiresRuClusterIPNoneRemoval(obj) {
|
|
378
378
|
if (!isServiceDoc(obj)) {
|
|
@@ -384,20 +384,31 @@ export function serviceDocumentRequiresRuClusterIPNoneRemoval(obj) {
|
|
|
384
384
|
return false
|
|
385
385
|
}
|
|
386
386
|
const sp = /** @type {Record<string, unknown>} */ (spec)
|
|
387
|
-
|
|
388
|
-
|
|
387
|
+
return sp.clusterIP === 'None'
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Чи в base-**Service** у **`spec`** явно задано поле **`clusterIPs`** (тоді **`remove`** на **`/spec/clusterIPs`** безпечний для **`kubectl kustomize`**).
|
|
392
|
+
* @param {unknown} obj корінь YAML (**Service**)
|
|
393
|
+
* @returns {boolean} **true**, якщо **`Object.hasOwn(spec, 'clusterIPs')`**
|
|
394
|
+
*/
|
|
395
|
+
export function serviceDocumentBaseDeclaresClusterIPsField(obj) {
|
|
396
|
+
if (!isServiceDoc(obj)) {
|
|
397
|
+
return false
|
|
389
398
|
}
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
399
|
+
const rec = /** @type {Record<string, unknown>} */ (obj)
|
|
400
|
+
const spec = rec.spec
|
|
401
|
+
if (spec === null || typeof spec !== 'object' || Array.isArray(spec)) {
|
|
402
|
+
return false
|
|
393
403
|
}
|
|
394
|
-
|
|
404
|
+
const sp = /** @type {Record<string, unknown>} */ (spec)
|
|
405
|
+
return Object.hasOwn(sp, 'clusterIPs')
|
|
395
406
|
}
|
|
396
407
|
|
|
397
408
|
/**
|
|
398
409
|
* Чи **JSON6902**-текст містить **`op: remove`** для заданого **`path`** (порядок ключів **op** / **path** неважливий).
|
|
399
410
|
* @param {string} patchText поле **patch** у kustomization
|
|
400
|
-
* @param {string} posixPath
|
|
411
|
+
* @param {string} posixPath **`/spec/clusterIP`** або **`/spec/clusterIPs`**
|
|
401
412
|
* @returns {boolean} true, якщо знайдено пару **remove** + **path**
|
|
402
413
|
*/
|
|
403
414
|
export function jsonPatchRemovesPath(patchText, posixPath) {
|
|
@@ -416,12 +427,12 @@ export function jsonPatchRemovesPath(patchText, posixPath) {
|
|
|
416
427
|
}
|
|
417
428
|
|
|
418
429
|
/**
|
|
419
|
-
* Чи patch
|
|
430
|
+
* Чи patch містить **`op: remove`** для **`/spec/clusterIP`**, щоб прибрати **headless** перед **NodePort**.
|
|
420
431
|
* @param {string} patchText поле **patch** у kustomization
|
|
421
|
-
* @returns {boolean} true, якщо є **remove**
|
|
432
|
+
* @returns {boolean} true, якщо є **remove** для **`/spec/clusterIP`**
|
|
422
433
|
*/
|
|
423
434
|
export function jsonPatchTextClearsHeadlessServiceClusterIPNone(patchText) {
|
|
424
|
-
return jsonPatchRemovesPath(patchText, '/spec/clusterIP')
|
|
435
|
+
return jsonPatchRemovesPath(patchText, '/spec/clusterIP')
|
|
425
436
|
}
|
|
426
437
|
|
|
427
438
|
/**
|
|
@@ -516,7 +527,7 @@ function collectAbieRuServicePatchTextByTargetNameFromRaw(raw) {
|
|
|
516
527
|
/**
|
|
517
528
|
* Повідомлення про порушення patch **Service** у **ru/kustomization.yaml** (abie.mdc).
|
|
518
529
|
* @param {string} raw повний текст **kustomization.yaml**
|
|
519
|
-
* @param {Map<string, { requiresClusterIPNoneClear: boolean }>} targetsByName ім’я **Service** →
|
|
530
|
+
* @param {Map<string, { requiresClusterIPNoneClear: boolean, requiresClusterIPsRemove?: boolean }>} targetsByName ім’я **Service** → прапорці patch
|
|
520
531
|
* @returns {string[]} порожньо, якщо все OK
|
|
521
532
|
*/
|
|
522
533
|
export function getAbieRuServiceNodePortPatchErrors(raw, targetsByName) {
|
|
@@ -528,7 +539,8 @@ export function getAbieRuServiceNodePortPatchErrors(raw, targetsByName) {
|
|
|
528
539
|
const errors = []
|
|
529
540
|
for (const name of [...targetsByName.keys()].toSorted((a, b) => a.localeCompare(b))) {
|
|
530
541
|
const flags = targetsByName.get(name)
|
|
531
|
-
const
|
|
542
|
+
const requiresClusterIPRemove = flags?.requiresClusterIPNoneClear === true
|
|
543
|
+
const requiresClusterIPsRemove = flags?.requiresClusterIPsRemove === true
|
|
532
544
|
const pt = byName.get(name)
|
|
533
545
|
if (pt === undefined || String(pt).trim() === '') {
|
|
534
546
|
errors.push(`${name}: немає inline patch для kind: Service`)
|
|
@@ -536,9 +548,14 @@ export function getAbieRuServiceNodePortPatchErrors(raw, targetsByName) {
|
|
|
536
548
|
if (!jsonPatchTextSetsServiceTypeNodePort(pt)) {
|
|
537
549
|
errors.push(`${name}: потрібен JSON6902 path /spec/type та value NodePort`)
|
|
538
550
|
}
|
|
539
|
-
if (
|
|
551
|
+
if (requiresClusterIPRemove && !jsonPatchTextClearsHeadlessServiceClusterIPNone(pt)) {
|
|
552
|
+
errors.push(
|
|
553
|
+
`${name}: для spec.clusterIP: None додай у той самий patch op: remove для path /spec/clusterIP (abie.mdc)`
|
|
554
|
+
)
|
|
555
|
+
}
|
|
556
|
+
if (requiresClusterIPsRemove && !jsonPatchRemovesPath(pt, '/spec/clusterIPs')) {
|
|
540
557
|
errors.push(
|
|
541
|
-
`${name}:
|
|
558
|
+
`${name}: у base задано spec.clusterIPs — додай op: remove для path /spec/clusterIPs (інакше NodePort з None у clusterIPs; abie.mdc)`
|
|
542
559
|
)
|
|
543
560
|
}
|
|
544
561
|
}
|
|
@@ -551,10 +568,10 @@ export function getAbieRuServiceNodePortPatchErrors(raw, targetsByName) {
|
|
|
551
568
|
* @param {string} root корінь репозиторію
|
|
552
569
|
* @param {string[]} yamlAbs абсолютні шляхи yaml під **k8s**
|
|
553
570
|
* @param {(msg: string) => void} fail реєстрація помилки читання/парсингу
|
|
554
|
-
* @returns {Promise<Map<string, Map<string, { requiresClusterIPNoneClear: boolean }>>>} **pkgAbs** → (**ім’я** → прапорці)
|
|
571
|
+
* @returns {Promise<Map<string, Map<string, { requiresClusterIPNoneClear: boolean, requiresClusterIPsRemove: boolean }>>>} **pkgAbs** → (**ім’я** → прапорці)
|
|
555
572
|
*/
|
|
556
573
|
async function collectAbieRuNodePortServiceTargetsByPackage(root, yamlAbs, fail) {
|
|
557
|
-
/** @type {Map<string, Map<string, { requiresClusterIPNoneClear: boolean }>>} */
|
|
574
|
+
/** @type {Map<string, Map<string, { requiresClusterIPNoneClear: boolean, requiresClusterIPsRemove: boolean }>>} */
|
|
558
575
|
const map = new Map()
|
|
559
576
|
for (const abs of yamlAbs) {
|
|
560
577
|
const rel = relative(root, abs).replaceAll('\\', '/') || abs
|
|
@@ -600,9 +617,11 @@ async function collectAbieRuNodePortServiceTargetsByPackage(root, yamlAbs, fail)
|
|
|
600
617
|
map.set(pkgAbs, inner)
|
|
601
618
|
}
|
|
602
619
|
const needClear = serviceDocumentRequiresRuClusterIPNoneRemoval(obj)
|
|
620
|
+
const needClusterIPsRemove = serviceDocumentBaseDeclaresClusterIPsField(obj)
|
|
603
621
|
const prev = inner.get(n)
|
|
604
622
|
inner.set(n, {
|
|
605
|
-
requiresClusterIPNoneClear: (prev?.requiresClusterIPNoneClear === true) || needClear
|
|
623
|
+
requiresClusterIPNoneClear: (prev?.requiresClusterIPNoneClear === true) || needClear,
|
|
624
|
+
requiresClusterIPsRemove: (prev?.requiresClusterIPsRemove === true) || needClusterIPsRemove
|
|
606
625
|
})
|
|
607
626
|
}
|
|
608
627
|
}
|
|
@@ -617,7 +636,7 @@ async function collectAbieRuNodePortServiceTargetsByPackage(root, yamlAbs, fail)
|
|
|
617
636
|
}
|
|
618
637
|
|
|
619
638
|
/**
|
|
620
|
-
* У **`k8s/ru/kustomization.yaml`** для кожного **Service** з YAML **`k8s`**, шлях якого без сегментів **`k8s/ua/`** та **`k8s/ru/`** (у т. ч. **headless** / **`-hl`**) — **JSON6902** **`/spec/type` → NodePort**;
|
|
639
|
+
* У **`k8s/ru/kustomization.yaml`** для кожного **Service** з YAML **`k8s`**, шлях якого без сегментів **`k8s/ua/`** та **`k8s/ru/`** (у т. ч. **headless** / **`-hl`**) — **JSON6902** **`/spec/type` → NodePort**; при **`clusterIP: None`** — **`op: remove`** на **`/spec/clusterIP`**; якщо в base є **`spec.clusterIPs`** — ще **`remove`** на **`/spec/clusterIPs`** (abie.mdc).
|
|
621
640
|
* @param {string} root корінь
|
|
622
641
|
* @param {string[]} yamlFilesAbs yaml під **k8s**
|
|
623
642
|
* @param {(msg: string) => void} fail callback
|
|
@@ -637,7 +656,7 @@ async function ensureRuAbieServiceNodePortPatches(root, yamlFilesAbs, fail, pass
|
|
|
637
656
|
const nameList = [...targetsByName.keys()].toSorted((a, b) => a.localeCompare(b))
|
|
638
657
|
if (!existsSync(ruAbs)) {
|
|
639
658
|
fail(
|
|
640
|
-
`${relPkg}/k8s: є Service, для overlay ru потрібен patch Service (NodePort; для headless — ще remove clusterIP
|
|
659
|
+
`${relPkg}/k8s: є Service, для overlay ru потрібен patch Service (NodePort; для headless — ще remove /spec/clusterIP): ${nameList.join(', ')} — додай ru/kustomization.yaml (abie.mdc)`
|
|
641
660
|
)
|
|
642
661
|
return
|
|
643
662
|
}
|
package/skills/lint/SKILL.md
CHANGED
|
@@ -25,7 +25,7 @@ bun run lint
|
|
|
25
25
|
|
|
26
26
|
2. **Якщо exit code не 0** — проаналізуй вивід (останній упавший крок у ланцюжку **`lint`** часто видно з stderr / логів):
|
|
27
27
|
- Де скрипт уже робить **auto-fix** (**`--fix`**, **`markdownlint-cli2 --fix`**, **`oxfmt`** тощо) — перезапусти **`bun run lint`** після змін файлів.
|
|
28
|
-
- Де auto-fix **немає** (наприклад, **jscpd**, **cspell**, **zizmor**, перевірки без прапорця fix) —
|
|
28
|
+
- Де auto-fix **немає** (наприклад, **jscpd**, **cspell**, **zizmor**, перевірки без прапорця fix) — **рефактори код проєкту**, щоб усунути порушення: перейменуй ідентифікатори, перепиши логіку, видали дублікати тощо. **Заборонено** додавати `eslint-disable`, `// @ts-ignore`, розширювати `ignores`, **`.cspellignore`**, **`.jscpdignore`** або інші винятки **лише** щоб приховати порушення без обґрунтованої причини — політика узгоджена з **`.cursor/rules/`** (зокрема **n-js-lint** для **jscpd**, **n-text** для **cspell** тощо).
|
|
29
29
|
- Якщо спрацьовує **`sonarjs/cognitive-complexity`** — див. окремий блок нижче.
|
|
30
30
|
|
|
31
31
|
3. **Цикл** — повторюй кроки 1–2, доки **`bun run lint`** не завершиться успішно. Після суттєвих правок за потреби ще раз **`bun run lint`**, щоб переконатися, що не зламав наступний крок у скрипті **`lint`**.
|