@7n/rules 1.83.2 → 1.84.0
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
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.84.0] - 2026-08-05
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- `k8s/manifests` — перший зріз порту в `rules-core` плюс полагоджений дефект канону. Портовано два з чотирьох шарів `lint()`: rego-контур (`runAllK8sRego` — усі девʼять namespace-ів, класифікатор fix-підказок, гілка `--data`/`templateData` у `conftest`) і чотири самодостатні cross-file перевірки (`assertNoForbiddenK8sDevPaths`, `validateSvcYamlAndSvcHlPairs`, `validateKustomizationPathRefsExistOnDisk`, `validateKustomizationIncludesSvcHlWithSvc`, `validateConfigMapNameMatchesDeployment`). Сам концерн у `NATIVE_CONCERNS` **не** заведено — він неподільний для диспатчу і зайде туди разом із рештою шарів, тож поведінка лінту незмінна.
|
|
8
|
+
|
|
9
|
+
**Зміна поведінки:** звірка `metadata.name` ConfigMap із Deployment (`k8s/base/configmap.yaml`) більше не спирається на «перший `kind: Deployment` за порядком `readdir`». Обхід файлів каталогу став відсортованим (детермінований на будь-якій файловій системі — раніше macOS і Linux-раннер CI могли давати різний вислід), і серед **усіх** Deployment каталогу беруться лише ті, що посилаються рівно на один ConfigMap. Наслідок — fail-closed: каталог, де перший Deployment ConfigMap не вживає, раніше мовчки випадав із перевірки, тепер перевіряється. Репозиторії з одним Deployment на каталог змін не побачать.
|
|
10
|
+
|
|
11
|
+
## [1.83.3] - 2026-08-05
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- wasm-плагін lang-js: зріз невживаних feature-ів залежностей — артефакт 2 492 361 → 2 168 151 байт (запас до бюджету 2,5 MB зріс із 7,5 KiB до 324 KiB)
|
|
16
|
+
|
|
3
17
|
## [1.83.2] - 2026-08-05
|
|
4
18
|
|
|
5
19
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@7n/rules",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.84.0",
|
|
4
4
|
"description": "CLI еталонних правил і skills (префікс n-): синк у репозиторій, дельта-lint, конформність",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -83,9 +83,9 @@
|
|
|
83
83
|
"zod": "^4.4.3"
|
|
84
84
|
},
|
|
85
85
|
"optionalDependencies": {
|
|
86
|
-
"@7n/rules-darwin-arm64": "1.
|
|
87
|
-
"@7n/rules-linux-x64": "1.
|
|
88
|
-
"@7n/rules-win32-x64": "1.
|
|
86
|
+
"@7n/rules-darwin-arm64": "1.84.0",
|
|
87
|
+
"@7n/rules-linux-x64": "1.84.0",
|
|
88
|
+
"@7n/rules-win32-x64": "1.84.0",
|
|
89
89
|
"@earendil-works/pi-ai": "0.80.10",
|
|
90
90
|
"@earendil-works/pi-coding-agent": "0.80.10"
|
|
91
91
|
},
|
|
@@ -635,7 +635,7 @@ async function validateOneKustomizationPathRefsExist(root, kustAbs, rootNorm, fa
|
|
|
635
635
|
* @param {(msg: string) => void} fail callback для повідомлень про помилки
|
|
636
636
|
* @returns {Promise<void>} результат
|
|
637
637
|
*/
|
|
638
|
-
async function validateKustomizationPathRefsExistOnDisk(root, yamlFilesAbs, fail) {
|
|
638
|
+
export async function validateKustomizationPathRefsExistOnDisk(root, yamlFilesAbs, fail) {
|
|
639
639
|
const rootNorm = resolve(root)
|
|
640
640
|
const kustFiles = yamlFilesAbs.filter(p => basename(p).toLowerCase() === 'kustomization.yaml')
|
|
641
641
|
for (const kustAbs of kustFiles) {
|
|
@@ -724,7 +724,7 @@ async function validateOneKustomizationSvcHlWithSvc(root, kustAbs, fail) {
|
|
|
724
724
|
* @param {(msg: string) => void} fail callback помилки
|
|
725
725
|
* @returns {Promise<void>} результат
|
|
726
726
|
*/
|
|
727
|
-
async function validateKustomizationIncludesSvcHlWithSvc(root, yamlFiles, fail) {
|
|
727
|
+
export async function validateKustomizationIncludesSvcHlWithSvc(root, yamlFiles, fail) {
|
|
728
728
|
const kustFiles = yamlFiles.filter(p => basename(p).toLowerCase() === 'kustomization.yaml')
|
|
729
729
|
for (const kustAbs of kustFiles) {
|
|
730
730
|
await validateOneKustomizationSvcHlWithSvc(root, kustAbs, fail)
|
|
@@ -2372,35 +2372,27 @@ async function tryReaddir(dirPath) {
|
|
|
2372
2372
|
}
|
|
2373
2373
|
|
|
2374
2374
|
/**
|
|
2375
|
-
*
|
|
2376
|
-
*
|
|
2377
|
-
*
|
|
2378
|
-
* @returns {Promise<Record<string, unknown> | null>} знайдений об'єкт або null
|
|
2379
|
-
*/
|
|
2380
|
-
async function readFirstDocByKindFromFile(filePath, kind) {
|
|
2381
|
-
const raw = await tryReadFileUtf8(filePath)
|
|
2382
|
-
if (raw === undefined) return null
|
|
2383
|
-
const docs = tryParseAllYamlDocs(raw)
|
|
2384
|
-
if (docs === undefined) return null
|
|
2385
|
-
return findFirstDocByKind(docs, kind)
|
|
2386
|
-
}
|
|
2387
|
-
|
|
2388
|
-
/**
|
|
2389
|
-
* Знаходить перший документ **Deployment** серед YAML-файлів каталогу (для перевірки імені ConfigMap, js-run.mdc).
|
|
2375
|
+
* Усі документи **Deployment** каталогу, у **відсортованому** порядку імен файлів.
|
|
2376
|
+
* Сортування обов'язкове: без нього результат залежав би від порядку `readdir`,
|
|
2377
|
+
* тобто від файлової системи (APFS впорядковує, ext4 — hash-порядок).
|
|
2390
2378
|
* @param {string} dirPath абсолютний шлях до каталогу
|
|
2391
|
-
* @returns {Promise<Record<string, unknown>
|
|
2379
|
+
* @returns {Promise<Record<string, unknown>[]>} документи Deployment у детермінованому порядку
|
|
2392
2380
|
*/
|
|
2393
|
-
|
|
2394
|
-
const
|
|
2381
|
+
async function collectDeploymentDocsInDir(dirPath) {
|
|
2382
|
+
const dirEntries = await tryReaddir(dirPath)
|
|
2383
|
+
const entries = dirEntries.filter(e => K8S_YAML_EXT_RE.test(e)).toSorted()
|
|
2384
|
+
/**
|
|
2385
|
+
@type {Record<string, unknown>[]}
|
|
2386
|
+
*/
|
|
2387
|
+
const out = []
|
|
2395
2388
|
for (const entry of entries) {
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
if (found !== null) return found
|
|
2389
|
+
const raw = await tryReadFileUtf8(join(dirPath, entry))
|
|
2390
|
+
if (raw === undefined) continue
|
|
2391
|
+
const docs = tryParseAllYamlDocs(raw)
|
|
2392
|
+
if (docs === undefined) continue
|
|
2393
|
+
out.push(...collectDocsByKind(docs, 'Deployment'))
|
|
2402
2394
|
}
|
|
2403
|
-
return
|
|
2395
|
+
return out
|
|
2404
2396
|
}
|
|
2405
2397
|
|
|
2406
2398
|
/**
|
|
@@ -3064,7 +3056,7 @@ async function validateOneSvcYamlHlPair(root, absSet, svcAbs, fail) {
|
|
|
3064
3056
|
* @param {(msg: string) => void} fail callback помилки
|
|
3065
3057
|
* @returns {Promise<void>} результат
|
|
3066
3058
|
*/
|
|
3067
|
-
async function validateSvcYamlAndSvcHlPairs(root, yamlFiles, fail) {
|
|
3059
|
+
export async function validateSvcYamlAndSvcHlPairs(root, yamlFiles, fail) {
|
|
3068
3060
|
const absSet = new Set(yamlFiles)
|
|
3069
3061
|
failIfSvcHlWithoutSiblingSvc(root, yamlFiles, absSet, fail)
|
|
3070
3062
|
const svcFiles = yamlFiles.filter(p => basename(p).toLowerCase() === 'svc.yaml')
|
|
@@ -3374,7 +3366,7 @@ async function checkK8sYamlFile(abs, root, fail, pass) {
|
|
|
3374
3366
|
* @param {(msg: string) => void} fail callback для реєстрації порушення
|
|
3375
3367
|
* @returns {void} результат
|
|
3376
3368
|
*/
|
|
3377
|
-
function assertNoForbiddenK8sDevPaths(yamlFiles, root, fail) {
|
|
3369
|
+
export function assertNoForbiddenK8sDevPaths(yamlFiles, root, fail) {
|
|
3378
3370
|
for (const abs of yamlFiles) {
|
|
3379
3371
|
const rel = relative(root, abs).replaceAll('\\', '/')
|
|
3380
3372
|
if (isForbiddenK8sDevPath(rel)) {
|
|
@@ -3409,6 +3401,31 @@ function extractFirstConfigMapName(raw) {
|
|
|
3409
3401
|
return manifestMetadataName(cm)
|
|
3410
3402
|
}
|
|
3411
3403
|
|
|
3404
|
+
/**
|
|
3405
|
+
* Deployment каталогу, з яким має звірятись `metadata.name` ConfigMap.
|
|
3406
|
+
*
|
|
3407
|
+
* Раніше тут стояв `findDeploymentDocInDir` — «перший `kind: Deployment` за порядком
|
|
3408
|
+
* `readdir`». Це давало два дефекти: недетермінізм (порядок ФС) і false negative —
|
|
3409
|
+
* якщо першим траплявся Deployment без ConfigMap-рефа (або з двома), перевірка мовчки
|
|
3410
|
+
* закривалась, хоча поруч стояв Deployment рівно з одним рефом. Тепер обхід
|
|
3411
|
+
* детермінований (відсортований), і серед **усіх** Deployment каталогу беруться лише
|
|
3412
|
+
* ті, що посилаються рівно на один ConfigMap.
|
|
3413
|
+
* @param {string} dirPath каталог із `configmap.yaml`
|
|
3414
|
+
* @param {string} cmName `metadata.name` ConfigMap
|
|
3415
|
+
* @returns {Promise<{ matched: boolean, deployName: string } | null>} збіг, кандидат для повідомлення або null
|
|
3416
|
+
*/
|
|
3417
|
+
async function configMapOwnerDeployment(dirPath, cmName) {
|
|
3418
|
+
let firstCandidate = null
|
|
3419
|
+
for (const deployment of await collectDeploymentDocsInDir(dirPath)) {
|
|
3420
|
+
if (collectDeploymentConfigMapRefs(deployment).size !== 1) continue
|
|
3421
|
+
const deployName = manifestMetadataName(deployment)
|
|
3422
|
+
if (deployName === null) continue
|
|
3423
|
+
if (deployName === cmName) return { matched: true, deployName }
|
|
3424
|
+
firstCandidate ??= deployName
|
|
3425
|
+
}
|
|
3426
|
+
return firstCandidate === null ? null : { matched: false, deployName: firstCandidate }
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3412
3429
|
/**
|
|
3413
3430
|
* Перевіряє один файл `configmap.yaml`: якщо поруч є Deployment з рівно одним ConfigMap-рефом,
|
|
3414
3431
|
* `metadata.name` ConfigMap має збігатися з `metadata.name` Deployment.
|
|
@@ -3422,16 +3439,13 @@ async function validateSingleConfigMapNameMatch(cmAbs, rel, fail, passFn) {
|
|
|
3422
3439
|
if (raw === undefined) return
|
|
3423
3440
|
const cmName = extractFirstConfigMapName(raw)
|
|
3424
3441
|
if (cmName === null) return
|
|
3425
|
-
const
|
|
3426
|
-
if (
|
|
3427
|
-
|
|
3428
|
-
const cmRefs = collectDeploymentConfigMapRefs(deployment)
|
|
3429
|
-
if (cmRefs.size !== 1 || typeof deployName !== 'string') return
|
|
3430
|
-
if (cmName === deployName) {
|
|
3442
|
+
const owner = await configMapOwnerDeployment(dirname(cmAbs), cmName)
|
|
3443
|
+
if (owner === null) return
|
|
3444
|
+
if (owner.matched) {
|
|
3431
3445
|
passFn(`${rel}: metadata.name '${cmName}' збігається з Deployment (k8s.mdc)`)
|
|
3432
3446
|
} else {
|
|
3433
3447
|
fail(
|
|
3434
|
-
`${rel}: metadata.name '${cmName}' має збігатися з назвою Deployment '${deployName}' — Deployment посилається рівно на один ConfigMap (k8s.mdc)`
|
|
3448
|
+
`${rel}: metadata.name '${cmName}' має збігатися з назвою Deployment '${owner.deployName}' — Deployment посилається рівно на один ConfigMap (k8s.mdc)`
|
|
3435
3449
|
)
|
|
3436
3450
|
}
|
|
3437
3451
|
}
|
|
@@ -3444,7 +3458,7 @@ async function validateSingleConfigMapNameMatch(cmAbs, rel, fail, passFn) {
|
|
|
3444
3458
|
* @param {(msg: string) => void} fail callback при помилці
|
|
3445
3459
|
* @param {(msg: string) => void} passFn callback при успіху
|
|
3446
3460
|
*/
|
|
3447
|
-
async function validateConfigMapNameMatchesDeployment(root, yamlFilesAbs, fail, passFn) {
|
|
3461
|
+
export async function validateConfigMapNameMatchesDeployment(root, yamlFilesAbs, fail, passFn) {
|
|
3448
3462
|
const cmFiles = yamlFilesAbs.filter(abs => {
|
|
3449
3463
|
const rel = relative(root, abs).replaceAll('\\', '/')
|
|
3450
3464
|
return CONFIGMAP_BASE_PATH_RE.test(`/${rel}`) || rel === 'k8s/base/configmap.yaml'
|
|
@@ -6421,7 +6435,7 @@ function k8sRegoFixHint(ns, file, message) {
|
|
|
6421
6435
|
* @param {(msg: string, hint?: unknown) => void} fail callback реєстрації порушення.
|
|
6422
6436
|
* @returns {Promise<void>} результат
|
|
6423
6437
|
*/
|
|
6424
|
-
async function runAllK8sRego(root, yamlFiles, fail) {
|
|
6438
|
+
export async function runAllK8sRego(root, yamlFiles, fail) {
|
|
6425
6439
|
const relOf = abs => relative(root, abs).replaceAll('\\', '/') || abs
|
|
6426
6440
|
|
|
6427
6441
|
const allYaml = yamlFiles
|
|
Binary file
|