@nitra/cursor 1.8.22 → 1.8.24

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/README.md CHANGED
@@ -38,7 +38,6 @@
38
38
  Коротко:
39
39
 
40
40
  - **Структура Kustomize:** спільне виноситься в **`base`**; вміст **base** відповідає тому, як має виглядати середовище **dev**; окремої директорії **`dev/`** немає — за dev відповідає **`base`**. У інших середовищах — тонкі **overlays** (часто лише **`kustomization.yaml`** і patches / оверрайди).
41
- - У каталозі **`k8s`** має бути **`README.md`** з описом дерев і застосування.
42
41
  - **Namespace** задається в **`kustomization.yaml`** (`namespace:`), а не через **`metadata.namespace`** у кожному ресурсі; окремі patches лише на зміну **namespace** не потрібні.
43
42
  - У **Deployment** для кожного контейнера: **`resources`**, **`imagePullPolicy: Always`** (перевіряє **`npx @nitra/cursor check k8s`**).
44
43
  - Рядки в **base**, які змінюються в overlays, позначайте коментарем на рядку (узгоджено в команді), наприклад: `# буде замінено через kustomize`.
package/mdc/k8s.mdc CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  description: K8s YAML — $schema (yaml-language-server); lint-k8s (kubeconform, kubescape); check-k8s
3
- version: '1.16'
3
+ version: '1.18'
4
4
  globs: "**/k8s/**/*.{yaml,yml}"
5
5
  alwaysApply: false
6
6
  ---
@@ -145,7 +145,7 @@ resources: {}
145
145
  ### Namespace
146
146
 
147
147
  - У **`base/kustomization.yaml`** задай **`namespace: dev`** (або узгоджене ім’я для dev **namespace**), щоб **усі ресурси base** потрапляли в цей namespace через Kustomize.
148
- - У **маніфестах ресурсів** (Deployment, Service, …) **не вказуй** **`metadata.namespace`** — **namespace** задається **лише в файлах Kustomize** (`kustomization.yaml` / `kustomization.yml`), полем верхнього рівня **`namespace:`**.
148
+
149
149
  - **Не додавай** окремі **patches** Kustomize, які лише змінюють **namespace**: **namespace** визначає Kustomize; у overlays додаткові зміни — без дублювання логіки **namespace**.
150
150
 
151
151
  ### Рядки, що змінюються між середовищами
@@ -158,10 +158,6 @@ resources: {}
158
158
 
159
159
  Текст коментаря узгодь у команді; важливо, щоб було видно, що значення **навіть у base** може бути замінене overlay.
160
160
 
161
- ### Документація в дереві k8s
162
-
163
- - У каталозі **`k8s`** (корінь оголошеного дерева маніфестів) має бути **`README.md`**: коротко опиши структуру (`base`, overlays), як зібрати/застосувати середовища, посилання на внутрішні правила команди.
164
-
165
161
  ### Міграція зі старої структури
166
162
 
167
163
  - Після перенесення маніфестів у **`base`** та overlays і перевірки (**`check k8s`**, **`lint-k8s`**) **видали** застарілі файли та директорії, які замінені новою схемою (дубльовані копії, колишні шляхи без Kustomize), щоб у репозиторії не залишалося зайвих або суперечливих маніфестів.
@@ -199,7 +195,7 @@ resources: {}
199
195
 
200
196
  ## Перевірка
201
197
 
202
- **`npx @nitra/cursor check k8s`** — узгодженість першого рядка (`$schema`), один рядок `# yaml-language-server` на файл, правило для `.yml`, відповідність URL першому YAML-документу (до `---`) за логікою нижче; для кожного документа **`Deployment`** — **`containers[].resources`**, **`imagePullPolicy: Always`**; у файлах **не** `kustomization.yaml` / `kustomization.yml` — заборона **`metadata.namespace`** (**namespace** лише в Kustomize, див. **Kustomize: структура каталогів**); заборона **`kind: Ingress`**; наявність **`HealthCheckPolicy`** — вимога до **`ru/kustomization.yaml`** з **`$patch: delete`** (див. **Ingress → Gateway API**). Якщо під `k8s` немає yaml/yml — перевірку пропущено. Інший зміст маніфесту — вручну / **`lint-k8s`**.
198
+ **`npx @nitra/cursor check k8s`** — узгодженість першого рядка (`$schema`), один рядок `# yaml-language-server` на файл, правило для `.yml`, відповідність URL першому YAML-документу (до `---`) за логікою нижче; для кожного документа **`Deployment`** — **`containers[].resources`**, **`imagePullPolicy: Always`**; заборона **`kind: Ingress`**; наявність **`HealthCheckPolicy`** — вимога до **`ru/kustomization.yaml`** з **`$patch: delete`** (див. **Ingress → Gateway API**); заборона шляхів **`…/k8s/dev/…`**; якщо існує **`k8s/base/kustomization.yaml`** (або **`.yml`**) — непорожній **`namespace`** у першому документі. Якщо під `k8s` немає yaml/yml — перевірку пропущено. Інший зміст маніфесту — вручну / **`lint-k8s`**.
203
199
 
204
200
  Після змін у маніфестах: **`bun run lint-k8s`** (kubeconform + kubescape; обов'язково для проєктів з правилом **`k8s`** у **`.n-cursor.json`**).
205
201
 
@@ -213,13 +209,15 @@ resources: {}
213
209
  - У файлах, ім’я яких **не** `kustomization.yaml` / `kustomization.yml`, у кожному документі — заборона поля **`metadata.namespace`** (**namespace** задається в Kustomize).
214
210
  - Документи з **`kind: Ingress`** — заборонені (потрібен перехід на Gateway API, див. розділ **Ingress → Gateway API**).
215
211
  - Якщо в будь-якому файлі під **`k8s`** є **`kind: HealthCheckPolicy`**, серед файлів має бути **`ru/kustomization.yaml`** (сегмент шляху **`ru`** перед іменем файлу), а його вміст — patch видалення **HealthCheckPolicy** з **`$patch: delete`** (див. той самий розділ).
212
+ - Заборона шляхів **`…/k8s/dev/…`** (окремої директорії **`dev`** під **`k8s`** не має бути).
213
+ - Якщо існує **`k8s/base/kustomization.yaml`** (або **`.yml`**), у першому документі має бути непорожнє поле **`namespace`** (типово **`dev`**; див. розділ **Namespace**).
216
214
 
217
215
  ## Коли застосовувати (агентам)
218
216
 
219
217
  - Зміни в k8s YAML — після правок **`check k8s`**.
220
218
  - Якщо перший рядок уже коректний і URL відповідає `apiVersion` / `kind` — не дублюй; змінився ресурс — онови лише `$schema`.
221
219
  - У **`Deployment`** без поля **`resources`** у контейнері — додай **`resources: {}`** (див. розділ **Deployment: `resources`**); додай **`imagePullPolicy: Always`** для кожного контейнера.
222
- - Дотримуйся структури **Kustomize** (`base` = dev, overlays без дублювання `base`, **`README.md`** у `k8s`, коментарі для рядків, що змінюються в overlay).
220
+ - Дотримуйся структури **Kustomize** (`base` = dev, overlays без дублювання `base`, коментарі для рядків, що змінюються в overlay).
223
221
  - Після міграції на нову структуру **видали** застарілі файли та каталоги, які вже замінені (див. **Міграція зі старої структури**).
224
222
 
225
223
  ## Визначення схеми YAML (канон)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.8.22",
3
+ "version": "1.8.24",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -15,6 +15,9 @@
15
15
  * **`kind: Ingress`** заборонено (потрібен перехід на Gateway API). Якщо є **`HealthCheckPolicy`**,
16
16
  * має існувати **`ru/kustomization.yaml`** з patch видалення цього kind (`$patch: delete`).
17
17
  *
18
+ * Структура **Kustomize** (див. k8s.mdc): заборона шляхів **`…/k8s/dev/…`**; якщо є **`…/k8s/base/kustomization.yaml`**
19
+ * (або **`.yml`**), у першому документі має бути непорожнє поле **`namespace`**.
20
+ *
18
21
  * Явні винятки до загальної логіки yannh/datree — таблиця **`EXPLICIT_K8S_SCHEMAS`** (`Map`): ключ
19
22
  * **`apiVersion`, `kind`, `type`** (для CRD без поля `type` у маніфесті — зірочка **`*`** як третій
20
23
  * компонент). Спочатку шукається збіг за фактичним `type`, потім за **`*`**.
@@ -150,6 +153,43 @@ export function pathHasK8sSegment(filePath) {
150
153
  return parts.includes('k8s')
151
154
  }
152
155
 
156
+ /**
157
+ * Чи заборонений шлях з окремою директорією **`dev`** під **`k8s`** (джерело правди — **`base`**).
158
+ * @param {string} rel шлях від кореня репозиторію
159
+ * @returns {boolean} true для `…/k8s/dev/…`
160
+ */
161
+ export function isForbiddenK8sDevPath(rel) {
162
+ const n = rel.replaceAll('\\', '/')
163
+ return n.includes('/k8s/dev/')
164
+ }
165
+
166
+ /**
167
+ * Чи це **`k8s/base/kustomization.yaml`** або **`kustomization.yml`** (перевірка поля **`namespace`**).
168
+ * @param {string} rel шлях від кореня репозиторію
169
+ * @returns {boolean} true, якщо це `…/k8s/base/kustomization.yaml` або `…/k8s/base/kustomization.yml`
170
+ */
171
+ export function isBaseKustomizationPath(rel) {
172
+ const n = rel.replaceAll('\\', '/')
173
+ return /(^|\/)k8s\/base\/kustomization\.yaml$/u.test(n) || /(^|\/)k8s\/base\/kustomization\.yml$/u.test(n)
174
+ }
175
+
176
+ /**
177
+ * Чи коректне поле **`namespace`** у розібраному Kustomization для **`base`**.
178
+ * @param {unknown} obj перший документ YAML
179
+ * @returns {string | null} текст порушення або null, якщо ок
180
+ */
181
+ export function baseKustomizationNamespaceViolation(obj) {
182
+ if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
183
+ return 'у base/kustomization.yaml має бути непорожній namespace (див. k8s.mdc)'
184
+ }
185
+ const rec = /** @type {Record<string, unknown>} */ (obj)
186
+ const ns = rec.namespace
187
+ if (typeof ns === 'string' && ns.trim() !== '') {
188
+ return null
189
+ }
190
+ return 'у base/kustomization.yaml має бути непорожній namespace (наприклад namespace: dev; див. k8s.mdc)'
191
+ }
192
+
153
193
  /**
154
194
  * Збирає всі yaml/yml під деревом від кореня cwd, якщо шлях містить сегмент `k8s`.
155
195
  * @param {string} root корінь репозиторію (cwd)
@@ -163,7 +203,8 @@ async function findK8sYamlFiles(root) {
163
203
  if (!/\.ya?ml$/iu.test(p)) return
164
204
  out.push(p)
165
205
  })
166
- return out.toSorted((a, b) => a.localeCompare(b))
206
+ // eslint-disable-next-line unicorn/no-array-sort -- toSorted потребує lib ES2023 у перевірці типів IDE
207
+ return [...out].sort((a, b) => a.localeCompare(b))
167
208
  }
168
209
 
169
210
  /**
@@ -601,6 +642,59 @@ async function checkK8sYamlFile(abs, root, fail, pass, healthCheckPolicyFiles) {
601
642
  validateK8sYamlPolicyDocuments(rel, baseLower, body, fail)
602
643
  }
603
644
 
645
+ /**
646
+ * Реєструє порушення для шляхів виду **`…/k8s/dev/…`** (окремої директорії **dev** не має бути).
647
+ * @param {string[]} yamlFiles абсолютні шляхи
648
+ * @param {string} root корінь репозиторію
649
+ * @param {(msg: string) => void} fail callback для реєстрації порушення
650
+ * @returns {void}
651
+ */
652
+ function assertNoForbiddenK8sDevPaths(yamlFiles, root, fail) {
653
+ for (const abs of yamlFiles) {
654
+ const rel = relative(root, abs).replaceAll('\\', '/')
655
+ if (isForbiddenK8sDevPath(rel)) {
656
+ fail(`${rel}: заборонена директорія k8s/dev/ — середовище dev відповідає base (див. k8s.mdc)`)
657
+ }
658
+ }
659
+ }
660
+
661
+ /**
662
+ * Якщо є **`k8s/base/kustomization.yaml`**, у ньому має бути непорожній **`namespace`**.
663
+ * @param {string} root корінь репозиторію
664
+ * @param {string[]} yamlFiles абсолютні шляхи
665
+ * @param {(msg: string) => void} fail callback для реєстрації порушення
666
+ * @returns {Promise<void>}
667
+ */
668
+ async function ensureBaseKustomizationHasNamespace(root, yamlFiles, fail) {
669
+ for (const abs of yamlFiles) {
670
+ const rel = relative(root, abs).replaceAll('\\', '/')
671
+ if (isBaseKustomizationPath(rel)) {
672
+ try {
673
+ const raw = await readFile(abs, 'utf8')
674
+ const lines = toLines(raw)
675
+ const body = yamlBodyAfterModeline(lines)
676
+ /** @type {import('yaml').Document[] | undefined} */
677
+ let docs
678
+ try {
679
+ docs = parseAllDocuments(body)
680
+ } catch {
681
+ fail(`${rel}: не вдалося розпарсити YAML для перевірки namespace у base (див. k8s.mdc)`)
682
+ }
683
+ if (docs !== undefined) {
684
+ const first = docs[0]?.toJSON()
685
+ const v = baseKustomizationNamespaceViolation(first)
686
+ if (v) {
687
+ fail(`${rel}: ${v}`)
688
+ }
689
+ }
690
+ } catch (error) {
691
+ const msg = error instanceof Error ? error.message : String(error)
692
+ fail(`${rel}: не вдалося прочитати (${msg})`)
693
+ }
694
+ }
695
+ }
696
+ }
697
+
604
698
  /**
605
699
  * Перевіряє відповідність проєкту правилам k8s.mdc.
606
700
  * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
@@ -622,6 +716,8 @@ export async function check() {
622
716
 
623
717
  pass(`YAML у k8s: ${yamlFiles.length} файл(ів)`)
624
718
 
719
+ assertNoForbiddenK8sDevPaths(yamlFiles, root, fail)
720
+
625
721
  /** @type {string[]} */
626
722
  const healthCheckPolicyFiles = []
627
723
 
@@ -631,5 +727,7 @@ export async function check() {
631
727
 
632
728
  await ensureRuKustomizationHealthCheckDelete(root, yamlFiles, healthCheckPolicyFiles, fail)
633
729
 
730
+ await ensureBaseKustomizationHasNamespace(root, yamlFiles, fail)
731
+
634
732
  return exitCode
635
733
  }