@nitra/cursor 1.10.0 → 1.11.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 +34 -1
- package/bin/n-cursor.js +29 -29
- package/package.json +2 -1
- package/rules/abie/js/applies/check.mjs +24 -0
- package/rules/abie/js/env_dns/check.mjs +53 -0
- package/rules/abie/js/firebase_hosting/check.mjs +49 -0
- package/rules/abie/js/hc_pairing/check.mjs +58 -0
- package/rules/abie/js/ua_http_route/check.mjs +86 -0
- package/rules/abie/js/ua_node_selector/check.mjs +65 -0
- package/rules/abie/policy/base_deployment_preem/target.json +10 -0
- package/rules/abie/policy/clean_merged_ignore_branches/target.json +4 -0
- package/rules/abie/policy/health_check_policy/target.json +4 -0
- package/rules/abie/policy/http_route_base/target.json +4 -0
- package/rules/abie/utils/enabled.mjs +35 -0
- package/rules/abie/utils/env-dns.mjs +81 -0
- package/rules/abie/utils/hc-yaml.mjs +27 -0
- package/rules/abie/utils/http-route.mjs +93 -0
- package/rules/abie/utils/k8s-tree.mjs +102 -0
- package/rules/abie/utils/kustomization-patches.mjs +224 -0
- package/rules/abie/utils/overlay-paths.mjs +97 -0
- package/rules/abie/utils/yaml.mjs +72 -0
- package/rules/adr/policy/settings_json/target.json +4 -0
- package/rules/adr/policy/settings_local_json/target.json +4 -0
- package/rules/bun/policy/bunfig/target.json +4 -0
- package/rules/bun/policy/package_json/target.json +4 -0
- package/rules/capacitor/policy/package_json/target.json +4 -0
- package/rules/docker/policy/lint_docker_yml/target.json +4 -0
- package/rules/docker/policy/package_json/target.json +4 -0
- package/rules/hasura/policy/svc_hl/target.json +4 -0
- package/rules/image-avif/policy/package_json/target.json +4 -0
- package/rules/image-compress/policy/package_json/target.json +4 -0
- package/rules/js-bun-db/policy/package_json/target.json +4 -0
- package/rules/js-bun-redis/policy/package_json/target.json +4 -0
- package/rules/js-lint/policy/lint_js_yml/target.json +4 -0
- package/rules/js-lint/policy/package_json/target.json +4 -0
- package/rules/js-mssql/policy/package_json/target.json +4 -0
- package/rules/js-run/policy/configmap/target.json +4 -0
- package/rules/js-run/policy/package_json/target.json +4 -0
- package/rules/k8s/policy/base_kustomization/target.json +4 -0
- package/rules/k8s/policy/base_manifest/target.json +10 -0
- package/rules/k8s/policy/gateway/target.json +4 -0
- package/rules/k8s/policy/hpa_pdb/target.json +4 -0
- package/rules/k8s/policy/kustomization/target.json +4 -0
- package/rules/k8s/policy/manifest/target.json +4 -0
- package/rules/k8s/policy/svc_hl_yaml/target.json +4 -0
- package/rules/k8s/policy/svc_yaml/target.json +4 -0
- package/rules/npm-module/policy/emit_types_config/target.json +4 -0
- package/rules/npm-module/policy/npm_package_json/target.json +4 -0
- package/rules/npm-module/policy/npm_publish_yml/target.json +4 -0
- package/rules/npm-module/policy/root_package_json/target.json +4 -0
- package/rules/php/policy/lint_php_yml/target.json +4 -0
- package/rules/php/policy/package_json/target.json +4 -0
- package/rules/rego/js/applies/check.mjs +54 -0
- package/rules/rego/policy/package_json/target.json +5 -0
- package/rules/rego/policy/vscode_extensions/target.json +5 -0
- package/rules/rego/policy/vscode_settings/target.json +5 -0
- package/rules/style-lint/policy/lint_style_yml/target.json +4 -0
- package/rules/style-lint/policy/package_json/target.json +4 -0
- package/rules/style-lint/policy/vscode_extensions/target.json +4 -0
- package/rules/style-lint/policy/vscode_settings/target.json +4 -0
- package/rules/text/policy/cspell/target.json +4 -0
- package/rules/text/policy/markdownlint/target.json +4 -0
- package/rules/text/policy/oxfmtrc/target.json +4 -0
- package/rules/text/policy/package_json/target.json +4 -0
- package/rules/text/policy/vscode_extensions/target.json +4 -0
- package/rules/text/policy/vscode_settings/target.json +4 -0
- package/rules/vue/policy/package_json/target.json +4 -0
- package/schemas/target.json +58 -0
- package/scripts/lint-conftest.mjs +65 -414
- package/scripts/utils/discover-checkable-rules.mjs +123 -0
- package/scripts/utils/resolve-target-files.mjs +109 -0
- package/scripts/utils/run-rule.mjs +131 -0
- package/rules/abie/js/check.mjs +0 -1152
- package/rules/rego/js/check.mjs +0 -106
|
@@ -1,59 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Прогоняє `conftest test` по всіх Rego-полісі з `npm/rules/<rule>/policy
|
|
3
|
-
* які вже виконуються через `npm/rules/ga/js/lint.mjs`).
|
|
2
|
+
* Прогоняє `conftest test` по всіх Rego-полісі з `npm/rules/<rule>/policy/<concern>/`.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Джерело правди — `target.json` поруч із кожним `<concern>.rego`. Маніфест декларує,
|
|
5
|
+
* які файли проєкту фідити в conftest (`files.single` або `files.walkGlob`). Resolver
|
|
6
|
+
* і walk-кеш — спільні з CLI `check` (`scripts/utils/resolve-target-files.mjs`),
|
|
7
|
+
* discovery — `scripts/utils/discover-checkable-rules.mjs`.
|
|
8
|
+
*
|
|
9
|
+
* Фільтрація за `.n-cursor.json:rules` — не перевіряємо полісі правил, які проєкт
|
|
10
|
+
* не активує (як було у попередній hardcoded TARGETS-таблиці).
|
|
9
11
|
*
|
|
10
12
|
* Поведінка fallback:
|
|
11
|
-
* - якщо `conftest` не в
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* - якщо `npm/rules/` не існує (нетипова інсталяція) — також `ℹ` skip.
|
|
13
|
+
* - якщо `conftest` не в PATH — `ℹ` install-hint, повертаємо 0 (структурні JS-перевірки
|
|
14
|
+
* в `check-*.mjs` лишаються паралельно). Те саме рішення — у `rules/ga/js/lint.mjs`.
|
|
15
|
+
* - якщо `rules/` каталог відсутній (нетипова інсталяція) — також `ℹ` skip.
|
|
15
16
|
*
|
|
16
|
-
* Перший ненульовий exit-код conftest — повертаємо як результат, але всі
|
|
17
|
-
*
|
|
18
|
-
* порушень (а не виправляти по одному).
|
|
17
|
+
* Перший ненульовий exit-код conftest — повертаємо як результат, але всі наступні цілі
|
|
18
|
+
* все одно виконуємо, щоб одразу побачити повний список порушень.
|
|
19
19
|
*
|
|
20
|
-
* Експортовано
|
|
21
|
-
* `
|
|
20
|
+
* Експортовано `runLintConftestCli` — використовується з `bin/n-cursor.js` як підкоманда
|
|
21
|
+
* `lint-conftest`, а також виконується напряму через `bun ./npm/scripts/lint-conftest.mjs`.
|
|
22
22
|
*/
|
|
23
|
-
import { existsSync,
|
|
23
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
24
|
+
import { readFile } from 'node:fs/promises'
|
|
24
25
|
import { spawnSync } from 'node:child_process'
|
|
25
|
-
import { dirname, join
|
|
26
|
+
import { dirname, join } from 'node:path'
|
|
26
27
|
import { fileURLToPath } from 'node:url'
|
|
27
28
|
|
|
29
|
+
import { discoverCheckableRules } from './utils/discover-checkable-rules.mjs'
|
|
28
30
|
import { resolveCmd } from './utils/resolve-cmd.mjs'
|
|
31
|
+
import { resolveTargetFiles } from './utils/resolve-target-files.mjs'
|
|
29
32
|
|
|
30
33
|
/** Каталог пакету `@nitra/cursor`, від якого ресолвимо вшиті директорії правил. */
|
|
31
34
|
const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)))
|
|
32
35
|
|
|
33
|
-
/** Шлях до кореня правил. У npm-tarball публікується через `files: ["rules"]`.
|
|
36
|
+
/** Шлях до кореня правил. У npm-tarball публікується через `files: ["rules"]`. */
|
|
34
37
|
const RULES_DIR = join(PACKAGE_ROOT, 'rules')
|
|
35
38
|
|
|
36
|
-
/**
|
|
37
|
-
* Опис одного таргета: namespace + спосіб розвʼязати цільові файли.
|
|
38
|
-
*
|
|
39
|
-
* `single` — конкретний файл відносно cwd, перевіряється `existsSync`-ом.
|
|
40
|
-
* `walk` — рекурсивний обхід від cwd із простим суфікс-предикатом
|
|
41
|
-
* (наприклад `name === 'package.json'`). Глибокі ігнори — як у `walkDir`
|
|
42
|
-
* в інших скриптах: `node_modules`, `.git`, `dist`, `coverage`, `build`,
|
|
43
|
-
* `.turbo`, `.next`. Не використовуємо bun Glob, щоб не плодити залежності
|
|
44
|
-
* за межами `node:fs`.
|
|
45
|
-
* @typedef {{
|
|
46
|
-
* namespace: string,
|
|
47
|
-
* policyDir: string,
|
|
48
|
-
* rule?: string,
|
|
49
|
-
* single?: string,
|
|
50
|
-
* walk?: { match: (relPosix: string) => boolean }
|
|
51
|
-
* }} ConftestTarget
|
|
52
|
-
*/
|
|
53
|
-
|
|
54
39
|
/**
|
|
55
40
|
* Зчитує `rules` з `.n-cursor.json` у cwd. Повертає множину рядків — або `null`,
|
|
56
|
-
* якщо файлу немає чи поле некоректне (тоді гейтинг вимикаємо — як
|
|
41
|
+
* якщо файлу немає чи поле некоректне (тоді гейтинг вимикаємо — як було в попередній версії).
|
|
57
42
|
* @param {string} cwd корінь репо
|
|
58
43
|
* @returns {Set<string> | null} множина активних правил або null
|
|
59
44
|
*/
|
|
@@ -69,381 +54,37 @@ function loadActiveCursorRules(cwd) {
|
|
|
69
54
|
}
|
|
70
55
|
}
|
|
71
56
|
|
|
72
|
-
const SKIP_DIR_NAMES = new Set(['node_modules', '.git', 'dist', 'coverage', 'build', '.turbo', '.next'])
|
|
73
|
-
|
|
74
|
-
/** `…/k8s/<env>/configmap.yaml` (configmap безпосередньо у directory `k8s/<…>`). */
|
|
75
|
-
const K8S_CONFIGMAP_PATH_RE = /(^|\/)k8s\/[^/]+\/configmap\.yaml$/u
|
|
76
|
-
/** Будь-який шлях під сегментом `k8s/`. */
|
|
77
|
-
const K8S_DIR_PATH_RE = /(^|\/)k8s\//u
|
|
78
|
-
/** `…/k8s/<…>/hc.yaml` (HealthCheckPolicy будь-де під k8s). */
|
|
79
|
-
const K8S_HC_YAML_PATH_RE = /(^|\/)k8s\/.+\/hc\.yaml$/u
|
|
80
|
-
/** `…/k8s/…/base/…/hr.yaml` (HTTPRoute у base-шарі). */
|
|
81
|
-
const K8S_BASE_HR_YAML_PATH_RE = /(^|\/)k8s\/.*base\/.*hr\.yaml$/u
|
|
82
|
-
/** Будь-який ресурсний YAML під `…/k8s/.../base/...` (для abie.base_deployment_preem). */
|
|
83
|
-
const K8S_BASE_RESOURCE_PATH_RE = /(^|\/)k8s\/.*base\//u
|
|
84
|
-
/** `kustomization.yaml` будь-де під сегментом `k8s/`. */
|
|
85
|
-
const K8S_KUSTOMIZATION_PATH_RE = /(^|\/)k8s\/.*\/kustomization\.yaml$/u
|
|
86
|
-
/** `…/k8s/.../base/.../kustomization.yaml`. */
|
|
87
|
-
const K8S_BASE_KUSTOMIZATION_PATH_RE = /(^|\/)k8s\/.*base\/(?:.*\/)?kustomization\.yaml$/u
|
|
88
|
-
/** Будь-який ресурсний `*.yaml` під сегментом `…/k8s/.../base/...`, окрім `kustomization.yaml`. */
|
|
89
|
-
const K8S_BASE_MANIFEST_PATH_RE = /(^|\/)k8s\/.*base\//u
|
|
90
|
-
/** `…/k8s/.../svc.yaml` (cluster-IP Service). */
|
|
91
|
-
const K8S_SVC_YAML_PATH_RE = /(^|\/)k8s\/.+\/svc\.yaml$/u
|
|
92
|
-
/** `…/k8s/.../svc-hl.yaml` (headless Service). */
|
|
93
|
-
const K8S_SVC_HL_YAML_PATH_RE = /(^|\/)k8s\/.+\/svc-hl\.yaml$/u
|
|
94
|
-
|
|
95
|
-
/** @type {ConftestTarget[]} */
|
|
96
|
-
const TARGETS = [
|
|
97
|
-
// ── bun ─────────────────────────────────────────────────────────────────
|
|
98
|
-
{ namespace: 'bun.bunfig', policyDir: 'bun', rule: 'bun', single: 'bunfig.toml' },
|
|
99
|
-
{ namespace: 'bun.package_json', policyDir: 'bun', rule: 'bun', single: 'package.json' },
|
|
100
|
-
|
|
101
|
-
// ── text ────────────────────────────────────────────────────────────────
|
|
102
|
-
{ namespace: 'text.oxfmtrc', policyDir: 'text', rule: 'text', single: '.oxfmtrc.json' },
|
|
103
|
-
{ namespace: 'text.cspell', policyDir: 'text', rule: 'text', single: '.cspell.json' },
|
|
104
|
-
{ namespace: 'text.markdownlint', policyDir: 'text', rule: 'text', single: '.markdownlint-cli2.jsonc' },
|
|
105
|
-
{ namespace: 'text.package_json', policyDir: 'text', rule: 'text', single: 'package.json' },
|
|
106
|
-
{ namespace: 'text.vscode_extensions', policyDir: 'text', rule: 'text', single: '.vscode/extensions.json' },
|
|
107
|
-
{ namespace: 'text.vscode_settings', policyDir: 'text', rule: 'text', single: '.vscode/settings.json' },
|
|
108
|
-
|
|
109
|
-
// ── style-lint ──────────────────────────────────────────────────────────
|
|
110
|
-
{ namespace: 'style_lint.package_json', policyDir: 'style_lint', rule: 'style-lint', single: 'package.json' },
|
|
111
|
-
{
|
|
112
|
-
namespace: 'style_lint.lint_style_yml',
|
|
113
|
-
policyDir: 'style_lint',
|
|
114
|
-
rule: 'style-lint',
|
|
115
|
-
single: '.github/workflows/lint-style.yml'
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
namespace: 'style_lint.vscode_extensions',
|
|
119
|
-
policyDir: 'style_lint',
|
|
120
|
-
rule: 'style-lint',
|
|
121
|
-
single: '.vscode/extensions.json'
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
namespace: 'style_lint.vscode_settings',
|
|
125
|
-
policyDir: 'style_lint',
|
|
126
|
-
rule: 'style-lint',
|
|
127
|
-
single: '.vscode/settings.json'
|
|
128
|
-
},
|
|
129
|
-
|
|
130
|
-
// ── php ─────────────────────────────────────────────────────────────────
|
|
131
|
-
{ namespace: 'php.package_json', policyDir: 'php', rule: 'php', single: 'package.json' },
|
|
132
|
-
{
|
|
133
|
-
namespace: 'php.lint_php_yml',
|
|
134
|
-
policyDir: 'php',
|
|
135
|
-
rule: 'php',
|
|
136
|
-
single: '.github/workflows/lint-php.yml'
|
|
137
|
-
},
|
|
138
|
-
|
|
139
|
-
// ── docker ──────────────────────────────────────────────────────────────
|
|
140
|
-
{ namespace: 'docker.package_json', policyDir: 'docker', rule: 'docker', single: 'package.json' },
|
|
141
|
-
{
|
|
142
|
-
namespace: 'docker.lint_docker_yml',
|
|
143
|
-
policyDir: 'docker',
|
|
144
|
-
rule: 'docker',
|
|
145
|
-
single: '.github/workflows/lint-docker.yml'
|
|
146
|
-
},
|
|
147
|
-
|
|
148
|
-
// ── npm-module ──────────────────────────────────────────────────────────
|
|
149
|
-
{
|
|
150
|
-
namespace: 'npm_module.root_package_json',
|
|
151
|
-
policyDir: 'npm_module',
|
|
152
|
-
rule: 'npm-module',
|
|
153
|
-
single: 'package.json'
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
namespace: 'npm_module.npm_package_json',
|
|
157
|
-
policyDir: 'npm_module',
|
|
158
|
-
rule: 'npm-module',
|
|
159
|
-
single: 'npm/package.json'
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
namespace: 'npm_module.emit_types_config',
|
|
163
|
-
policyDir: 'npm_module',
|
|
164
|
-
rule: 'npm-module',
|
|
165
|
-
single: 'npm/tsconfig.emit-types.json'
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
namespace: 'npm_module.npm_publish_yml',
|
|
169
|
-
policyDir: 'npm_module',
|
|
170
|
-
rule: 'npm-module',
|
|
171
|
-
single: '.github/workflows/npm-publish.yml'
|
|
172
|
-
},
|
|
173
|
-
|
|
174
|
-
// ── js-lint ─────────────────────────────────────────────────────────────
|
|
175
|
-
{ namespace: 'js_lint.package_json', policyDir: 'js_lint', rule: 'js-lint', single: 'package.json' },
|
|
176
|
-
{
|
|
177
|
-
namespace: 'js_lint.lint_js_yml',
|
|
178
|
-
policyDir: 'js_lint',
|
|
179
|
-
rule: 'js-lint',
|
|
180
|
-
single: '.github/workflows/lint-js.yml'
|
|
181
|
-
},
|
|
182
|
-
|
|
183
|
-
// ── image-compress / image-avif / capacitor ─────────────────────────────
|
|
184
|
-
{
|
|
185
|
-
namespace: 'image_compress.package_json',
|
|
186
|
-
policyDir: 'image_compress',
|
|
187
|
-
rule: 'image-compress',
|
|
188
|
-
single: 'package.json'
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
namespace: 'image_avif.package_json',
|
|
192
|
-
policyDir: 'image_avif',
|
|
193
|
-
rule: 'image-avif',
|
|
194
|
-
walk: { match: rel => rel.endsWith('/package.json') || rel === 'package.json' }
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
namespace: 'capacitor.package_json',
|
|
198
|
-
policyDir: 'capacitor',
|
|
199
|
-
rule: 'capacitor',
|
|
200
|
-
single: 'package.json'
|
|
201
|
-
},
|
|
202
|
-
|
|
203
|
-
// ── hasura ──────────────────────────────────────────────────────────────
|
|
204
|
-
{
|
|
205
|
-
namespace: 'hasura.svc_hl',
|
|
206
|
-
policyDir: 'hasura',
|
|
207
|
-
rule: 'hasura',
|
|
208
|
-
single: 'hasura/k8s/base/svc-hl.yaml'
|
|
209
|
-
},
|
|
210
|
-
|
|
211
|
-
// ── adr ─────────────────────────────────────────────────────────────────
|
|
212
|
-
{ namespace: 'adr.settings_json', policyDir: 'adr', rule: 'adr', single: '.claude/settings.json' },
|
|
213
|
-
{
|
|
214
|
-
namespace: 'adr.settings_local_json',
|
|
215
|
-
policyDir: 'adr',
|
|
216
|
-
rule: 'adr',
|
|
217
|
-
single: '.claude/settings.local.json'
|
|
218
|
-
},
|
|
219
|
-
|
|
220
|
-
// ── multi-file (walk) ───────────────────────────────────────────────────
|
|
221
|
-
// Усі `package.json` у дереві (включно з workspace-пакетами).
|
|
222
|
-
{
|
|
223
|
-
namespace: 'js_mssql.package_json',
|
|
224
|
-
policyDir: 'js_mssql',
|
|
225
|
-
rule: 'js-mssql',
|
|
226
|
-
walk: { match: rel => rel.endsWith('/package.json') || rel === 'package.json' }
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
namespace: 'js_bun_db.package_json',
|
|
230
|
-
policyDir: 'js_bun_db',
|
|
231
|
-
rule: 'js-bun-db',
|
|
232
|
-
walk: { match: rel => rel.endsWith('/package.json') || rel === 'package.json' }
|
|
233
|
-
},
|
|
234
|
-
{
|
|
235
|
-
namespace: 'js_bun_redis.package_json',
|
|
236
|
-
policyDir: 'js_bun_redis',
|
|
237
|
-
rule: 'js-bun-redis',
|
|
238
|
-
walk: { match: rel => rel.endsWith('/package.json') || rel === 'package.json' }
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
namespace: 'js_run.package_json',
|
|
242
|
-
policyDir: 'js_run',
|
|
243
|
-
rule: 'js-run',
|
|
244
|
-
walk: { match: rel => rel.endsWith('/package.json') || rel === 'package.json' }
|
|
245
|
-
},
|
|
246
|
-
// `js_run.jsconfig` НЕ реєструємо тут — `jsconfig.json` має канонічну структуру
|
|
247
|
-
// лише для backend-пакетів (без `vite` у `devDependencies`) з каталогом `src/`,
|
|
248
|
-
// а lint-conftest фільтрує лише по `activeRules` на рівні репозиторію — не
|
|
249
|
-
// вміє пропустити окремий workspace-пакет за наявністю `vite`. Тому валідація
|
|
250
|
-
// структури делегується з `check-js-run.mjs` через `runConftestBatch` після
|
|
251
|
-
// того, як JS визначить, що пакет — backend з `src/`.
|
|
252
|
-
{
|
|
253
|
-
namespace: 'vue.package_json',
|
|
254
|
-
policyDir: 'vue',
|
|
255
|
-
rule: 'vue',
|
|
256
|
-
walk: { match: rel => rel.endsWith('/package.json') || rel === 'package.json' }
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
// ConfigMap у `…/k8s/base/configmap.yaml` будь-де у дереві.
|
|
260
|
-
{
|
|
261
|
-
namespace: 'js_run.configmap',
|
|
262
|
-
policyDir: 'js_run',
|
|
263
|
-
rule: 'js-run',
|
|
264
|
-
walk: { match: rel => K8S_CONFIGMAP_PATH_RE.test(rel) }
|
|
265
|
-
},
|
|
266
|
-
|
|
267
|
-
// Усі YAML у дереві з сегментом `k8s` — пер-документні структурні правила.
|
|
268
|
-
{
|
|
269
|
-
namespace: 'k8s.manifest',
|
|
270
|
-
policyDir: 'k8s/manifest',
|
|
271
|
-
rule: 'k8s',
|
|
272
|
-
walk: { match: rel => K8S_DIR_PATH_RE.test(rel) && (rel.endsWith('.yaml') || rel.endsWith('.yml')) }
|
|
273
|
-
},
|
|
274
|
-
|
|
275
|
-
// Gateway API + HealthCheckPolicy — застосовується до будь-якого YAML під k8s
|
|
276
|
-
// (правила перевіряють лише відповідні kind / apiVersion).
|
|
277
|
-
{
|
|
278
|
-
namespace: 'k8s.gateway',
|
|
279
|
-
policyDir: 'k8s/gateway',
|
|
280
|
-
rule: 'k8s',
|
|
281
|
-
walk: { match: rel => K8S_DIR_PATH_RE.test(rel) && (rel.endsWith('.yaml') || rel.endsWith('.yml')) }
|
|
282
|
-
},
|
|
283
|
-
|
|
284
|
-
// Структурні перевірки HPA / PDB (apiVersion / behavior / metrics / selector).
|
|
285
|
-
{
|
|
286
|
-
namespace: 'k8s.hpa_pdb',
|
|
287
|
-
policyDir: 'k8s/hpa_pdb',
|
|
288
|
-
rule: 'k8s',
|
|
289
|
-
walk: { match: rel => K8S_DIR_PATH_RE.test(rel) && (rel.endsWith('.yaml') || rel.endsWith('.yml')) }
|
|
290
|
-
},
|
|
291
|
-
|
|
292
|
-
// Kustomization-файли: resources sort, patches sort, JSON6902 conflicts.
|
|
293
|
-
{
|
|
294
|
-
namespace: 'k8s.kustomization',
|
|
295
|
-
policyDir: 'k8s/kustomization',
|
|
296
|
-
rule: 'k8s',
|
|
297
|
-
walk: { match: rel => K8S_KUSTOMIZATION_PATH_RE.test(rel) }
|
|
298
|
-
},
|
|
299
|
-
|
|
300
|
-
// svc.yaml — cluster-IP Service.
|
|
301
|
-
{
|
|
302
|
-
namespace: 'k8s.svc_yaml',
|
|
303
|
-
policyDir: 'k8s/svc_yaml',
|
|
304
|
-
rule: 'k8s',
|
|
305
|
-
walk: { match: rel => K8S_SVC_YAML_PATH_RE.test(rel) }
|
|
306
|
-
},
|
|
307
|
-
|
|
308
|
-
// svc-hl.yaml — headless Service з суфіксом `-hl`.
|
|
309
|
-
{
|
|
310
|
-
namespace: 'k8s.svc_hl_yaml',
|
|
311
|
-
policyDir: 'k8s/svc_hl_yaml',
|
|
312
|
-
rule: 'k8s',
|
|
313
|
-
walk: { match: rel => K8S_SVC_HL_YAML_PATH_RE.test(rel) }
|
|
314
|
-
},
|
|
315
|
-
|
|
316
|
-
// base/kustomization.yaml — обов'язкове непорожнє поле `namespace:`.
|
|
317
|
-
{
|
|
318
|
-
namespace: 'k8s.base_kustomization',
|
|
319
|
-
policyDir: 'k8s/base_kustomization',
|
|
320
|
-
rule: 'k8s',
|
|
321
|
-
walk: { match: rel => K8S_BASE_KUSTOMIZATION_PATH_RE.test(rel) }
|
|
322
|
-
},
|
|
323
|
-
|
|
324
|
-
// Ресурсні маніфести під `…/k8s/.../base/...` (окрім kustomization.yaml).
|
|
325
|
-
{
|
|
326
|
-
namespace: 'k8s.base_manifest',
|
|
327
|
-
policyDir: 'k8s/base_manifest',
|
|
328
|
-
rule: 'k8s',
|
|
329
|
-
walk: {
|
|
330
|
-
match: rel =>
|
|
331
|
-
K8S_BASE_MANIFEST_PATH_RE.test(rel) &&
|
|
332
|
-
!K8S_BASE_KUSTOMIZATION_PATH_RE.test(rel) &&
|
|
333
|
-
(rel.endsWith('.yaml') || rel.endsWith('.yml'))
|
|
334
|
-
}
|
|
335
|
-
},
|
|
336
|
-
|
|
337
|
-
// abie HealthCheckPolicy: `hc.yaml` у дереві k8s.
|
|
338
|
-
{
|
|
339
|
-
namespace: 'abie.health_check_policy',
|
|
340
|
-
policyDir: 'abie/health_check_policy',
|
|
341
|
-
rule: 'abie',
|
|
342
|
-
walk: { match: rel => K8S_HC_YAML_PATH_RE.test(rel) }
|
|
343
|
-
},
|
|
344
|
-
|
|
345
|
-
// abie HTTPRoute у `base/`.
|
|
346
|
-
{
|
|
347
|
-
namespace: 'abie.http_route_base',
|
|
348
|
-
policyDir: 'abie/http_route_base',
|
|
349
|
-
rule: 'abie',
|
|
350
|
-
walk: { match: rel => K8S_BASE_HR_YAML_PATH_RE.test(rel) }
|
|
351
|
-
},
|
|
352
|
-
|
|
353
|
-
// abie Deployment у `…/k8s/.../base/...` має preem nodeSelector.
|
|
354
|
-
{
|
|
355
|
-
namespace: 'abie.base_deployment_preem',
|
|
356
|
-
policyDir: 'abie/base_deployment_preem',
|
|
357
|
-
rule: 'abie',
|
|
358
|
-
walk: {
|
|
359
|
-
match: rel =>
|
|
360
|
-
K8S_BASE_RESOURCE_PATH_RE.test(rel) &&
|
|
361
|
-
!K8S_BASE_KUSTOMIZATION_PATH_RE.test(rel) &&
|
|
362
|
-
(rel.endsWith('.yaml') || rel.endsWith('.yml'))
|
|
363
|
-
}
|
|
364
|
-
},
|
|
365
|
-
|
|
366
|
-
// abie clean-merged-branch.yml: with.ignore_branches має містити dev/ua.
|
|
367
|
-
{
|
|
368
|
-
namespace: 'abie.clean_merged_ignore_branches',
|
|
369
|
-
policyDir: 'abie/clean_merged_ignore_branches',
|
|
370
|
-
rule: 'abie',
|
|
371
|
-
single: '.github/workflows/clean-merged-branch.yml'
|
|
372
|
-
}
|
|
373
|
-
]
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* Рекурсивно збирає відносні (posix) шляхи від cwd, які матчаться предикатом.
|
|
377
|
-
* Глибокі ігнори — `SKIP_DIR_NAMES`. Не йде у симлінки, помилки stat — мовчки skip.
|
|
378
|
-
* @param {string} root абсолютний корінь обходу
|
|
379
|
-
* @param {(relPosix: string) => boolean} match предикат на відносний posix-шлях
|
|
380
|
-
* @returns {string[]} список відносних posix-шляхів
|
|
381
|
-
*/
|
|
382
|
-
function collectFiles(root, match) {
|
|
383
|
-
/** @type {string[]} */
|
|
384
|
-
const out = []
|
|
385
|
-
/** @param {string} dirAbs абсолютний шлях каталогу для рекурсивного обходу */
|
|
386
|
-
function visit(dirAbs) {
|
|
387
|
-
/** @type {import('node:fs').Dirent[]} */
|
|
388
|
-
let entries
|
|
389
|
-
try {
|
|
390
|
-
entries = readdirSync(dirAbs, { withFileTypes: true })
|
|
391
|
-
} catch {
|
|
392
|
-
return
|
|
393
|
-
}
|
|
394
|
-
for (const e of entries) {
|
|
395
|
-
if (e.isSymbolicLink()) continue
|
|
396
|
-
const abs = join(dirAbs, e.name)
|
|
397
|
-
if (e.isDirectory()) {
|
|
398
|
-
if (SKIP_DIR_NAMES.has(e.name)) continue
|
|
399
|
-
visit(abs)
|
|
400
|
-
continue
|
|
401
|
-
}
|
|
402
|
-
if (!e.isFile()) continue
|
|
403
|
-
const rel = abs
|
|
404
|
-
.slice(root.length + 1)
|
|
405
|
-
.split(sep)
|
|
406
|
-
.join('/')
|
|
407
|
-
if (match(rel)) out.push(rel)
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
visit(root)
|
|
411
|
-
return out
|
|
412
|
-
}
|
|
413
|
-
|
|
414
57
|
/**
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
* @
|
|
58
|
+
* Обчислює namespace rego-полісі за id правила і ім'ям концерну.
|
|
59
|
+
* Rego не дозволяє '-' в імені пакета, тож kebab-id у `.n-cursor.json:rules`
|
|
60
|
+
* мапиться на snake у namespace; ім'я концерну йде як є (вже snake у `policy/<concern>/`).
|
|
61
|
+
* @param {string} ruleId id правила (kebab)
|
|
62
|
+
* @param {string} concernName ім'я concern (підкаталог у `policy/`)
|
|
63
|
+
* @returns {string} namespace для `conftest --namespace`
|
|
419
64
|
*/
|
|
420
|
-
function
|
|
421
|
-
|
|
422
|
-
return existsSync(join(cwd, target.single)) ? [target.single] : []
|
|
423
|
-
}
|
|
424
|
-
if (target.walk) {
|
|
425
|
-
return collectFiles(cwd, target.walk.match)
|
|
426
|
-
}
|
|
427
|
-
return []
|
|
65
|
+
function computeNamespace(ruleId, concernName) {
|
|
66
|
+
return `${ruleId.replaceAll('-', '_')}.${concernName}`
|
|
428
67
|
}
|
|
429
68
|
|
|
430
69
|
/**
|
|
431
|
-
* Запускає conftest на одному
|
|
70
|
+
* Запускає conftest на одному policy-концерні. Повертає exit-код (0 — OK, 1+ — порушення).
|
|
432
71
|
*
|
|
433
|
-
*
|
|
434
|
-
*
|
|
72
|
+
* stdio: 'inherit' — щоб користувач бачив рідну форматовану табличку conftest у виводі
|
|
73
|
+
* `bun run lint` (відрізняється від структурованого JSON-варіанта в `check`-команді).
|
|
435
74
|
* @param {string} conftestBin абсолютний шлях до бінарника conftest
|
|
436
|
-
* @param {
|
|
437
|
-
* @param {string
|
|
75
|
+
* @param {string} ruleId id правила
|
|
76
|
+
* @param {string} concernName ім'я concern
|
|
77
|
+
* @param {string} namespace rego-пакет
|
|
78
|
+
* @param {string[]} files список файлів (відносні/абсолютні шляхи)
|
|
438
79
|
* @returns {number} exit-код
|
|
439
80
|
*/
|
|
440
|
-
function
|
|
441
|
-
const policyAbs = join(RULES_DIR,
|
|
81
|
+
function runConftestForConcern(conftestBin, ruleId, concernName, namespace, files) {
|
|
82
|
+
const policyAbs = join(RULES_DIR, ruleId, 'policy', concernName)
|
|
442
83
|
if (!existsSync(policyAbs)) {
|
|
443
84
|
return 0
|
|
444
85
|
}
|
|
445
|
-
console.log(`\n▶ conftest (${
|
|
446
|
-
const r = spawnSync(conftestBin, ['test', ...files, '-p', policyAbs, '--namespace',
|
|
86
|
+
console.log(`\n▶ conftest (${namespace} — ${files.length} файл(ів))`)
|
|
87
|
+
const r = spawnSync(conftestBin, ['test', ...files, '-p', policyAbs, '--namespace', namespace, '--no-color'], {
|
|
447
88
|
stdio: 'inherit',
|
|
448
89
|
env: process.env
|
|
449
90
|
})
|
|
@@ -455,14 +96,14 @@ function runConftestForTarget(conftestBin, target, files) {
|
|
|
455
96
|
}
|
|
456
97
|
|
|
457
98
|
/**
|
|
458
|
-
* Запускає `conftest test` по всіх
|
|
459
|
-
*
|
|
99
|
+
* Запускає `conftest test` по всіх policy-концернах із `target.json`-маніфестів.
|
|
100
|
+
* Фільтрація — за `activeRules` (поле `rules` у `.n-cursor.json`). Перший ненульовий
|
|
101
|
+
* exit-код запамʼятовується, але цикл йде до кінця.
|
|
460
102
|
*
|
|
461
|
-
* Якщо `conftest` не знайдено в PATH — друкує `ℹ` повідомлення і повертає 0
|
|
462
|
-
*
|
|
463
|
-
* @returns {number} 0 — все OK або skip; інакше — перший ненульовий exit-код
|
|
103
|
+
* Якщо `conftest` не знайдено в PATH — друкує `ℹ` повідомлення і повертає 0.
|
|
104
|
+
* @returns {Promise<number>} 0 — все OK або skip; інакше — перший ненульовий exit-код
|
|
464
105
|
*/
|
|
465
|
-
export function runLintConftestCli() {
|
|
106
|
+
export async function runLintConftestCli() {
|
|
466
107
|
const conftestBin = resolveCmd('conftest')
|
|
467
108
|
if (!conftestBin) {
|
|
468
109
|
console.log(
|
|
@@ -478,19 +119,29 @@ export function runLintConftestCli() {
|
|
|
478
119
|
|
|
479
120
|
const cwd = process.cwd()
|
|
480
121
|
const activeRules = loadActiveCursorRules(cwd)
|
|
122
|
+
const rules = await discoverCheckableRules(RULES_DIR)
|
|
123
|
+
/** @type {Map<string, Promise<string[]>>} */
|
|
124
|
+
const walkCache = new Map()
|
|
481
125
|
let firstFailureCode = 0
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
126
|
+
|
|
127
|
+
for (const rule of rules) {
|
|
128
|
+
if (activeRules && !activeRules.has(rule.id)) continue
|
|
129
|
+
for (const concern of rule.policyConcerns) {
|
|
130
|
+
const targetPath = join(RULES_DIR, rule.id, 'policy', concern.name, 'target.json')
|
|
131
|
+
/** @type {{ files: { single?: string, walkGlob?: string|string[], required?: boolean }, missingMessage?: string }} */
|
|
132
|
+
const target = JSON.parse(await readFile(targetPath, 'utf8'))
|
|
133
|
+
const files = await resolveTargetFiles(target.files, cwd, walkCache)
|
|
134
|
+
if (files.length === 0) continue
|
|
135
|
+
const namespace = computeNamespace(rule.id, concern.name)
|
|
136
|
+
const code = runConftestForConcern(conftestBin, rule.id, concern.name, namespace, files)
|
|
137
|
+
if (code !== 0 && firstFailureCode === 0) {
|
|
138
|
+
firstFailureCode = code
|
|
139
|
+
}
|
|
489
140
|
}
|
|
490
141
|
}
|
|
491
142
|
return firstFailureCode
|
|
492
143
|
}
|
|
493
144
|
|
|
494
145
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
495
|
-
process.exitCode = runLintConftestCli()
|
|
146
|
+
process.exitCode = (await runLintConftestCli()) ?? 0
|
|
496
147
|
}
|