@nitra/cursor 1.13.50 → 1.13.51

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,12 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.13.51] - 2026-05-19
8
+
9
+ ### Fixed
10
+
11
+ - `lint-k8s`: `kubescape scan -` (stdin), доданий у 1.13.49 і збережений у 1.13.50, **не працює в kubescape v4.x** — `-` трактується як шлях до файлу й сканер виходить з `no resources found to scan` (fatal), тож `bun run lint` падав на `lint-k8s` навіть на чистих маніфестах. Прапорця `--input`/`--stdin` у CLI також немає. Тепер `runKubescapeManifest` пише зібраний kustomize-маніфест у тимчасовий файл під `os.tmpdir()` (через `fs.mkdtempSync`) і запускає **`kubescape scan <tmp-file>`**; тимчасова директорія прибирається у `finally`. Bump `k8s.mdc` `1.38` → `1.39`.
12
+
7
13
  ## [1.13.50] - 2026-05-19
8
14
 
9
15
  ### Changed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.13.50",
3
+ "version": "1.13.51",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
package/rules/k8s/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.38'
3
+ version: '1.39'
4
4
  globs: "**/k8s/**/*.yaml"
5
5
  alwaysApply: false
6
6
  ---
@@ -33,7 +33,7 @@ alwaysApply: false
33
33
 
34
34
  **Версія Kubernetes для kubeconform** має відповідати PIN yannh у цьому правилі та в **`check-k8s.mjs`** (зараз **`-kubernetes-version 1.33.9`** — semver без префікса `v`, еквівалент релізу **v1.33.9**; набір схем **`v1.33.9-standalone-strict`**). Для CRD додатково підключай реєстр [datreeio/CRDs-catalog](https://github.com/datreeio/CRDs-catalog) другим **`-schema-location`**, як у [прикладах kubeconform](https://github.com/yannh/kubeconform#readme). За потреби **`-ignore-missing-schemas`**, якщо частина CRD ще без публічної схеми.
35
35
 
36
- **kubescape — вхід через зібраний kustomize-маніфест:** для кожного dir-у з `kustomization.yaml` (`kind: Kustomization`; **`kind: Component`** пропускається — він не білдиться окремо) `lint-k8s` виконує **`kubectl kustomize <dir> | kubescape scan -`** з порогом **`--severity-threshold high`** (вбудована в kubectl підкоманда `kustomize` — окремий бінарник `kustomize` не потрібен; рендеринг локальний і не потребує доступу до кластера). Це усуває false-positive **C-0260** (`Missing network policy`) у каноні з sibling **`components/networkpolicy.yaml`** без `metadata.namespace`: сирий dir-скан не виконує kustomize-збірку й бачить порожній namespace у NetworkPolicy проти непорожнього у Deployment з `base/`, тож `podSelector` не матчиться. Якщо в дереві **`…/k8s`** немає жодного `kustomization.yaml` (проєкт без Kustomize) — fallback на старий dir-скан **`kubescape scan <каталог-k8s>`**. Перший запуск kubescape може завантажувати артефакти — у CI потрібна мережа або [offline](https://github.com/kubescape/kubescape#readme). На відміну від kubeconform, у **kubescape scan** немає прапорця **`-kubernetes-version`**: перевірка йде за **framework/control** (NSA, MITRE, CIS тощо), а не проти OpenAPI-схеми конкретного релізу Kubernetes. **Орієнтир** для репозиторію той самий, що й для kubeconform — кластер **v1.33.9** (див. **`-kubernetes-version 1.33.9`** вище); для CIS і подібних наближень обирай актуальний framework під політику команди (**`kubescape list frameworks`**, див. [CLI reference](https://github.com/kubescape/kubescape/blob/master/docs/cli-reference.md)).
36
+ **kubescape — вхід через зібраний kustomize-маніфест:** для кожного dir-у з `kustomization.yaml` (`kind: Kustomization`; **`kind: Component`** пропускається — він не білдиться окремо) `lint-k8s` виконує **`kubectl kustomize <dir>`** і передає stdout у **`kubescape scan <tmp-file>`** з порогом **`--severity-threshold high`** (вбудована в kubectl підкоманда `kustomize` — окремий бінарник `kustomize` не потрібен; рендеринг локальний і не потребує доступу до кластера). Маніфест проходить через тимчасовий файл, бо **`kubescape scan` у v4.x не читає stdin** (`-` як шлях → `no resources found to scan`; прапорця `--input`/`--stdin` немає); тимчасова директорія створюється під `os.tmpdir()` і прибирається після скану. Це усуває false-positive **C-0260** (`Missing network policy`) у каноні з sibling **`components/networkpolicy.yaml`** без `metadata.namespace`: сирий dir-скан не виконує kustomize-збірку й бачить порожній namespace у NetworkPolicy проти непорожнього у Deployment з `base/`, тож `podSelector` не матчиться. Якщо в дереві **`…/k8s`** немає жодного `kustomization.yaml` (проєкт без Kustomize) — fallback на старий dir-скан **`kubescape scan <каталог-k8s>`**. Перший запуск kubescape може завантажувати артефакти — у CI потрібна мережа або [offline](https://github.com/kubescape/kubescape#readme). На відміну від kubeconform, у **kubescape scan** немає прапорця **`-kubernetes-version`**: перевірка йде за **framework/control** (NSA, MITRE, CIS тощо), а не проти OpenAPI-схеми конкретного релізу Kubernetes. **Орієнтир** для репозиторію той самий, що й для kubeconform — кластер **v1.33.9** (див. **`-kubernetes-version 1.33.9`** вище); для CIS і подібних наближень обирай актуальний framework під політику команди (**`kubescape list frameworks`**, див. [CLI reference](https://github.com/kubescape/kubescape/blob/master/docs/cli-reference.md)).
37
37
 
38
38
  ### Винятки kubescape: `.kubescape-exceptions.json`
39
39
 
@@ -13,8 +13,9 @@
13
13
  * Kubescape не має аналога цього прапорця; орієнтир цільового кластера — та сама лінія релізу (див. k8s.mdc).
14
14
  */
15
15
  import { spawnSync } from 'node:child_process'
16
- import { existsSync } from 'node:fs'
16
+ import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
17
17
  import { readFile } from 'node:fs/promises'
18
+ import { tmpdir } from 'node:os'
18
19
  import { basename, dirname, join, relative } from 'node:path'
19
20
 
20
21
  import { parse } from 'yaml'
@@ -195,29 +196,41 @@ function runKustomizeBuild(kubectlPath, dir) {
195
196
  }
196
197
 
197
198
  /**
198
- * Запускає `kubescape scan -` зі stdin (буфер `manifest`). stdout/stderr інхерит у термінал.
199
+ * Запускає `kubescape scan <file>` для зібраного kustomize-маніфесту. stdout/stderr інхерит у термінал.
200
+ *
201
+ * Маніфест пишемо в тимчасовий файл, бо `kubescape scan` у v4.x **не читає stdin**: `-` як шлях
202
+ * не розпізнається (`no resources found to scan`), а прапорця типу `--input`/`--stdin` у CLI немає
203
+ * (`kubescape scan --help` показує лише шляхи й кластер). Тимчасова директорія створюється
204
+ * через `mkdtempSync` під `os.tmpdir()` і прибирається в `finally`.
199
205
  * @param {string} kubescapePath абсолютний шлях до бінарника kubescape
200
206
  * @param {Buffer} manifest зібраний kustomize-маніфест
201
207
  * @param {string[]} exceptionsArgs `['--exceptions', '<file>']` або `[]`
202
208
  * @returns {{ status: number, enoent: boolean }} статус процесу і прапор ENOENT
203
209
  */
204
- function runKubescapeStdin(kubescapePath, manifest, exceptionsArgs) {
205
- const r = spawnSync(kubescapePath, ['scan', '-', '--severity-threshold', 'high', ...exceptionsArgs], {
206
- input: manifest,
207
- stdio: ['pipe', 'inherit', 'inherit'],
208
- shell: false
209
- })
210
- const enoent = Boolean(r.error && 'code' in r.error && r.error.code === 'ENOENT')
211
- return { status: r.status ?? 1, enoent }
210
+ function runKubescapeManifest(kubescapePath, manifest, exceptionsArgs) {
211
+ const dir = mkdtempSync(join(tmpdir(), 'nitra-cursor-k8s-'))
212
+ const file = join(dir, 'manifest.yaml')
213
+ try {
214
+ writeFileSync(file, manifest)
215
+ const r = spawnSync(kubescapePath, ['scan', file, '--severity-threshold', 'high', ...exceptionsArgs], {
216
+ stdio: 'inherit',
217
+ shell: false
218
+ })
219
+ const enoent = Boolean(r.error && 'code' in r.error && r.error.code === 'ENOENT')
220
+ return { status: r.status ?? 1, enoent }
221
+ } finally {
222
+ rmSync(dir, { recursive: true, force: true })
223
+ }
212
224
  }
213
225
 
214
226
  /**
215
227
  * Запускає kubescape по зібраному kustomize-маніфесту для кожного `…/k8s`-кореня. Для кожного
216
- * dir-у з `kustomization.yaml` (крім `kind: Component`) робимо `kubectl kustomize <dir>` і піпимо
217
- * stdout у `kubescape scan -`. Це усуває false-positive C-0260 (`Missing network policy`) у випадках,
218
- * коли NetworkPolicy живе у sibling `components/` без `metadata.namespace` (намспейс інжектить
219
- * overlay через `kustomization.namespace`); сирий dir-скан не виконує kustomize й бачить порожній
220
- * `namespace` у NetworkPolicy проти непорожнього у Deployment, через що `podSelector` не матчиться.
228
+ * dir-у з `kustomization.yaml` (крім `kind: Component`) робимо `kubectl kustomize <dir>` і
229
+ * передаємо stdout у `kubescape scan <tmp-file>` через тимчасовий файл (kubescape v4.x не читає
230
+ * stdin див. `runKubescapeManifest`). Це усуває false-positive C-0260 (`Missing network policy`)
231
+ * у випадках, коли NetworkPolicy живе у sibling `components/` без `metadata.namespace` (намспейс
232
+ * інжектить overlay через `kustomization.namespace`); сирий dir-скан не виконує kustomize й бачить
233
+ * порожній `namespace` у NetworkPolicy проти непорожнього у Deployment, через що `podSelector` не матчиться.
221
234
  *
222
235
  * Якщо в `…/k8s`-корені немає жодного білдабельного kustomization.yaml (проєкт без Kustomize) —
223
236
  * fallback на старий dir-скан, щоб не блокувати чистий YAML-only набір маніфестів.
@@ -262,10 +275,10 @@ async function runKubescape(dirs, root) {
262
275
  }
263
276
  }
264
277
  for (const kdir of kdirs) {
265
- console.log(`run-k8s: kubectl kustomize ${kdir} | kubescape scan -`)
278
+ console.log(`run-k8s: kubectl kustomize ${kdir} | kubescape scan <tmp>`)
266
279
  const build = runKustomizeBuild(kubectlPath, kdir)
267
280
  if (build.status !== 0) return build.status
268
- const ks = runKubescapeStdin(kubescapePath, build.stdout, exceptionsArgs)
281
+ const ks = runKubescapeManifest(kubescapePath, build.stdout, exceptionsArgs)
269
282
  if (ks.enoent) {
270
283
  console.error('kubescape не знайдено в PATH. Встанови з https://github.com/kubescape/kubescape#readme')
271
284
  return 127