@nitra/cursor 1.13.50 → 1.13.52
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 +12 -0
- package/package.json +1 -1
- package/rules/bun/bun.mdc +4 -2
- package/rules/bun/fix/layout/check.mjs +98 -26
- package/rules/k8s/k8s.mdc +2 -2
- package/rules/k8s/lint/lint.mjs +30 -17
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@
|
|
|
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.52] - 2026-05-19
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `check bun`: **зворотній інваріант** для `lint-<id>`-скриптів. Раніше `checkCursorRuleScripts` ([npm/rules/bun/fix/layout/check.mjs](rules/bun/fix/layout/check.mjs)) перевіряв лише пряму імплікацію — «правило в `.n-cursor.json:rules` → скрипт у `package.json`». Тепер також fail-имо, коли правило **відсутнє** в `rules` (або явно перенесене в **`disable-rules`**), але в кореневому `package.json` залишилися: (а) сам скрипт `lint-<id>`, або (б) виклик `bun run lint-<id>` у агрегованому `scripts.lint`. Причина: `n-cursor lint-<id>` запускається напряму й **ігнорує** `.n-cursor.json`, тож `bun run lint` падає на вимкненому правилі (як було з `disable-rules: ["k8s"]` у cursor-репо, де `lint-k8s` обходив template-сорці власного правила). Покриті скрипти і їхні правила-власники: `lint-docker` ← `docker`, `lint-k8s` ← `k8s`, `lint-image` ← `image-avif`/`image-compress` (multi-owner — скрипт лишається дозволеним, поки активний **хоч один** власник). Розпізнавання згадки `bun run lint-<id>` у chain'і — через токен-границі (regex `\\bbun run <script>\\b`), щоб не матчити префікси (`lint-k8s-foo` ≠ `lint-k8s`). Bump `bun.mdc` `1.8` → `1.9`.
|
|
12
|
+
|
|
13
|
+
## [1.13.51] - 2026-05-19
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- `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`.
|
|
18
|
+
|
|
7
19
|
## [1.13.50] - 2026-05-19
|
|
8
20
|
|
|
9
21
|
### Changed
|
package/package.json
CHANGED
package/rules/bun/bun.mdc
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
description: Bun як єдиний package manager у монорепо
|
|
3
3
|
globs: "**/package.json,**/bunfig.toml,**/bun.lock,**/bun.lockb"
|
|
4
4
|
alwaysApply: false
|
|
5
|
-
version: '1.
|
|
5
|
+
version: '1.9'
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
Проект використовує тільки Bun для керування залежностями та запуску скриптів.
|
|
@@ -70,4 +70,6 @@ FROM oven/bun:alpine AS build-env
|
|
|
70
70
|
|
|
71
71
|
У кінці скрипта `lint` додай `&& oxfmt .`.
|
|
72
72
|
|
|
73
|
-
Якщо в **`.n-cursor.json`** у масиві **`rules`** є **`docker`**, у кореневому `package.json` **обов'язково** скрипт **`lint-docker`** (див. **`docker.mdc`**) і рядок **`bun run lint-docker`** у **`lint`**. Якщо є **`k8s`** — **обов'язково** **`lint-k8s`** і **`bun run lint-k8s`** у **`lint`** (див. **`k8s.mdc`**).
|
|
73
|
+
Якщо в **`.n-cursor.json`** у масиві **`rules`** є **`docker`**, у кореневому `package.json` **обов'язково** скрипт **`lint-docker`** (див. **`docker.mdc`**) і рядок **`bun run lint-docker`** у **`lint`**. Якщо є **`k8s`** — **обов'язково** **`lint-k8s`** і **`bun run lint-k8s`** у **`lint`** (див. **`k8s.mdc`**).
|
|
74
|
+
|
|
75
|
+
**Зворотній інваріант:** якщо правила **немає** в `rules` (або воно явно перенесене в **`disable-rules`**), скрипту **`lint-<id>`** у кореневому `package.json` бути **не може**, і ланцюжок агрегованого **`scripts.lint`** не має містити **`bun run lint-<id>`**. Інакше `bun run lint` падатиме на вимкненому правилі — `n-cursor lint-<id>` ігнорує `.n-cursor.json` і обходить дерево незалежно від `rules`/`disable-rules`. Для скриптів із кількома власниками (як **`lint-image`** — обслуговує і **`image-avif`**, і **`image-compress`**) скрипт лишається дозволеним, поки активний **хоч один** власник; зворотній інваріант тригериться лише коли в `rules` немає **жодного** з них. Перевірка — **`npx @nitra/cursor check bun`**.
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* - наявність `bun.lock`, `bunfig.toml`, `package.json` у корені (FS-existence);
|
|
6
6
|
* - заборонені lockfile та артефакти yarn/pnpm (`package-lock.json`, `yarn.lock`,
|
|
7
7
|
* `pnpm-lock.yaml`, `.yarnrc.yml`, директорія `.yarn/`);
|
|
8
|
-
* -
|
|
9
|
-
* `
|
|
10
|
-
* (
|
|
8
|
+
* - двосторонній зв'язок `.n-cursor.json:rules` ↔ `package.json:scripts` для правил із
|
|
9
|
+
* `lint-<id>` (`docker`, `k8s`): rule увімкнено → скрипт мусить існувати; rule
|
|
10
|
+
* відсутнє (або в `disable-rules`) → скрипту та згадки `bun run lint-<id>` у
|
|
11
|
+
* агрегованому `scripts.lint` бути **не може** (інакше `bun run lint` падатиме
|
|
12
|
+
* на правилі, яке у конфізі вимкнено).
|
|
11
13
|
*
|
|
12
14
|
* **Що покрила Rego** (`npx \@nitra/cursor check`):
|
|
13
15
|
* - `npm/policy/bun/bunfig/` — `[install].linker == "hoisted"` у `bunfig.toml`;
|
|
@@ -25,46 +27,116 @@ import { createCheckReporter } from '../../../../scripts/utils/check-reporter.mj
|
|
|
25
27
|
// видалено, щоб не було двох джерел істини.
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
|
-
* Зчитує
|
|
29
|
-
* @returns {Promise<Set<string
|
|
30
|
+
* Зчитує `rules` та `disable-rules` з `.n-cursor.json`.
|
|
31
|
+
* @returns {Promise<{ rules: Set<string>, disabled: Set<string> }>} активні правила і явно вимкнені
|
|
30
32
|
*/
|
|
31
33
|
async function loadNCursorRules() {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
34
|
+
const empty = { rules: new Set(), disabled: new Set() }
|
|
35
|
+
if (!existsSync('.n-cursor.json')) return empty
|
|
35
36
|
try {
|
|
36
37
|
const raw = JSON.parse(await readFile('.n-cursor.json', 'utf8'))
|
|
37
|
-
const list = raw?.rules
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
return new Set(list.map(String))
|
|
38
|
+
const list = Array.isArray(raw?.rules) ? raw.rules.map(String) : []
|
|
39
|
+
const disabled = Array.isArray(raw?.['disable-rules']) ? raw['disable-rules'].map(String) : []
|
|
40
|
+
return { rules: new Set(list), disabled: new Set(disabled) }
|
|
42
41
|
} catch {
|
|
43
|
-
return
|
|
42
|
+
return empty
|
|
44
43
|
}
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
/**
|
|
48
|
-
*
|
|
47
|
+
* Чи містить `scripts.lint` виклик `bun run <script>` у chain'і. Шукаємо саме `bun run <script>`
|
|
48
|
+
* як окремий токен (між пробілами/`&&`), щоб уникнути false-positive на префіксах
|
|
49
|
+
* (`bun run lint-k8s-foo` не матчиться як `bun run lint-k8s`).
|
|
50
|
+
* @param {string} lintScript значення `scripts.lint` (порожній рядок — якщо нема)
|
|
51
|
+
* @param {string} target ім'я скрипта (без префіксів)
|
|
52
|
+
* @returns {boolean} true, якщо chain згадує `bun run <target>`
|
|
53
|
+
*/
|
|
54
|
+
function lintChainHasScript(lintScript, target) {
|
|
55
|
+
if (!lintScript) return false
|
|
56
|
+
const escaped = target.replaceAll(/[.*+?^${}()|[\]\\]/gu, '\\$&')
|
|
57
|
+
return new RegExp(`(?:^|\\s)bun\\s+run\\s+${escaped}(?:$|\\s)`, 'u').test(lintScript)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Описує `lint-<id>`-обгортку та правила, що нею володіють. Один скрипт може мати кілька
|
|
62
|
+
* власників (`lint-image` — обслуговує і `image-avif`, і `image-compress`); скрипт вважається
|
|
63
|
+
* «потрібним», якщо **хоч одне** з власних правил активне у `.n-cursor.json:rules`.
|
|
64
|
+
* @typedef {object} RuleScript
|
|
65
|
+
* @property {string[]} rules id правил-власників (>=1); скрипт зобов'язаний існувати, поки активне хоч одне з них
|
|
66
|
+
* @property {string} script ім'я скрипта в `package.json:scripts`
|
|
67
|
+
* @property {string} doc `.mdc`-файл (або кома-список), на який посилається повідомлення check-у
|
|
68
|
+
*/
|
|
69
|
+
|
|
70
|
+
/** @type {RuleScript[]} */
|
|
71
|
+
const RULE_SCRIPTS = [
|
|
72
|
+
{ rules: ['docker'], script: 'lint-docker', doc: 'docker.mdc' },
|
|
73
|
+
{ rules: ['k8s'], script: 'lint-k8s', doc: 'k8s.mdc' },
|
|
74
|
+
{ rules: ['image-avif', 'image-compress'], script: 'lint-image', doc: 'image-avif.mdc / image-compress.mdc' }
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Описує стан правил-власників скрипта для повідомлень про reason. Повертає або список увімкнених
|
|
79
|
+
* правил (для passing-кейсу «правило є»), або компактний опис, чому всі вимкнені (для inverse-fail).
|
|
80
|
+
* @param {string[]} owners id правил-власників (>=1)
|
|
81
|
+
* @param {{ rules: Set<string>, disabled: Set<string> }} cursorRules `rules` та `disable-rules`
|
|
82
|
+
* @returns {{ enabled: string[], reason: string }} `enabled` — список з `cursorRules.rules`; `reason` — текст для лога
|
|
83
|
+
*/
|
|
84
|
+
function ownerStatus(owners, cursorRules) {
|
|
85
|
+
const enabled = owners.filter(r => cursorRules.rules.has(r))
|
|
86
|
+
if (enabled.length > 0) {
|
|
87
|
+
return { enabled, reason: `правил${enabled.length === 1 ? 'о' : 'а'} ${enabled.map(r => `\`${r}\``).join(', ')}` }
|
|
88
|
+
}
|
|
89
|
+
if (owners.length === 1) {
|
|
90
|
+
const [only] = owners
|
|
91
|
+
const where = cursorRules.disabled.has(only) ? 'в disable-rules' : 'відсутнє в rules'
|
|
92
|
+
return { enabled, reason: `правило \`${only}\` ${where}` }
|
|
93
|
+
}
|
|
94
|
+
const disabledCount = owners.filter(r => cursorRules.disabled.has(r)).length
|
|
95
|
+
const note = disabledCount === owners.length ? 'усі власники в disable-rules' : 'жоден власник не активний у rules'
|
|
96
|
+
return { enabled, reason: `${owners.map(r => `\`${r}\``).join('/')} — ${note}` }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Перевіряє двосторонній зв'язок `rules` ↔ `scripts.lint-<id>` для правил із `lint-<id>`-обгорткою
|
|
101
|
+
* (див. `RULE_SCRIPTS`). Якщо активне хоч одне правило-власник — скрипт мусить існувати; якщо
|
|
102
|
+
* жодне з власників не активне (відсутнє у `rules` або є в `disable-rules`), скрипту і згадки
|
|
103
|
+
* `bun run <script>` у `scripts.lint` бути **не може**. Інакше `bun run lint` падатиме на
|
|
104
|
+
* вимкненому правилі: `n-cursor lint-<id>` ігнорує `.n-cursor.json` і обходить дерево
|
|
105
|
+
* незалежно від конфігу (як було в cursor-репо: `disable-rules: ["k8s"]` + залишений `lint-k8s`
|
|
106
|
+
* ламав chain на template-сорцях власного правила).
|
|
107
|
+
* @param {{ pass: (msg: string) => void, fail: (msg: string) => void }} reporter
|
|
49
108
|
* @param {Record<string, string>} scripts scripts з package.json
|
|
50
|
-
* @param {Set<string>} cursorRules
|
|
109
|
+
* @param {{ rules: Set<string>, disabled: Set<string> }} cursorRules `rules` та `disable-rules`
|
|
51
110
|
*/
|
|
52
111
|
function checkCursorRuleScripts(reporter, scripts, cursorRules) {
|
|
53
112
|
const { pass, fail } = reporter
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
pass(`package.json: є \`${script}\` (правило ${rule} у .n-cursor.json)`)
|
|
113
|
+
const lintScript = typeof scripts.lint === 'string' ? scripts.lint : ''
|
|
114
|
+
for (const { rules: owners, script, doc } of RULE_SCRIPTS) {
|
|
115
|
+
const status = ownerStatus(owners, cursorRules)
|
|
116
|
+
const present = Boolean(scripts[script])
|
|
117
|
+
const inChain = lintChainHasScript(lintScript, script)
|
|
118
|
+
if (status.enabled.length > 0) {
|
|
119
|
+
if (present) {
|
|
120
|
+
pass(`package.json: є \`${script}\` (${status.reason} у .n-cursor.json)`)
|
|
63
121
|
} else {
|
|
64
122
|
fail(
|
|
65
|
-
`У .n-cursor.json
|
|
123
|
+
`У .n-cursor.json увімкнено ${status.reason} — додай скрипт \`${script}\` у кореневий package.json (див. ${doc})`
|
|
66
124
|
)
|
|
67
125
|
}
|
|
126
|
+
continue
|
|
127
|
+
}
|
|
128
|
+
if (present) {
|
|
129
|
+
fail(
|
|
130
|
+
`У .n-cursor.json немає активних власників ${owners.map(r => `\`${r}\``).join('/')} — прибери скрипт \`${script}\` з кореневого package.json (див. ${doc})`
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
if (inChain) {
|
|
134
|
+
fail(
|
|
135
|
+
`У \`scripts.lint\` є \`bun run ${script}\`, але серед \`${owners.join('/')}\` жоден не активний у .n-cursor.json — прибери з ланцюжка lint (див. ${doc})`
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
if (!present && !inChain) {
|
|
139
|
+
pass(`package.json: \`${script}\` відсутній (${status.reason})`)
|
|
68
140
|
}
|
|
69
141
|
}
|
|
70
142
|
}
|
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.
|
|
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
|
|
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
|
|
package/rules/k8s/lint/lint.mjs
CHANGED
|
@@ -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
|
|
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
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* `namespace`
|
|
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 =
|
|
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
|