@nitra/cursor 1.11.4 → 1.11.6
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 +24 -1
- package/bin/n-cursor.js +37 -4
- package/package.json +2 -1
- package/rules/abie/utils/http-route.mjs +1 -1
- package/rules/abie/utils/k8s-tree.mjs +9 -10
- package/rules/abie/utils/overlay-paths.mjs +1 -1
- package/rules/adr/adr.mdc +2 -2
- package/rules/adr/js/hooks/check.mjs +5 -5
- package/rules/docker/docker.mdc +2 -2
- package/rules/docker/js/run.mjs +3 -2
- package/rules/docker/policy/package_json/package_json.rego +1 -1
- package/rules/ga/js/lint.mjs +3 -26
- package/rules/k8s/js/run.mjs +3 -2
- package/rules/k8s/k8s.mdc +2 -4
- package/rules/npm-module/js/package_structure/check.mjs +2 -2
- package/rules/npm-module/npm-module.mdc +3 -3
- package/rules/rego/js/lint.mjs +4 -1
- package/rules/rego/policy/package_json/package_json.rego +5 -3
- package/rules/rego/rego.mdc +3 -3
- package/rules/style-lint/js/tooling/check.mjs +1 -1
- package/rules/style-lint/style-lint.mdc +1 -1
- package/rules/text/js/formatting/check.mjs +8 -24
- package/rules/text/js/lint.mjs +34 -0
- package/rules/text/js/run-shellcheck.mjs +2 -2
- package/rules/text/js/run-v8r.mjs +2 -2
- package/rules/text/text.mdc +5 -5
- package/schemas/v8r-catalog.json +6 -0
- package/scripts/auto-skills.mjs +1 -3
- package/scripts/utils/resolve-target-files.mjs +1 -1
- package/scripts/utils/run-lint-step.mjs +33 -0
- package/scripts/utils/run-rule.mjs +2 -1
- package/skills/abie-clean/SKILL.md +9 -5
- package/skills/fix/SKILL.md +3 -7
- package/rules/abie/policy/base_deployment_preem/base_deployment_preem_test.rego +0 -60
- package/rules/abie/policy/clean_merged_ignore_branches/clean_merged_ignore_branches_test.rego +0 -48
- package/rules/abie/policy/health_check_policy/health_check_policy_test.rego +0 -99
- package/rules/abie/policy/http_route_base/http_route_base_test.rego +0 -64
- package/rules/bun/policy/package_json/package_json_test.rego +0 -109
- package/rules/docker/policy/lint_docker_yml/lint_docker_yml_test.rego +0 -104
- package/rules/docker/policy/package_json/package_json_test.rego +0 -42
- package/rules/graphql/policy/vscode_extensions/vscode_extensions_test.rego +0 -34
- package/rules/image-avif/policy/package_json/package_json_test.rego +0 -69
- package/rules/js-lint/policy/package_json/package_json_test.rego +0 -130
- package/rules/js-run/policy/jsconfig/jsconfig_test.rego +0 -88
- package/rules/k8s/policy/base_kustomization/base_kustomization_test.rego +0 -73
- package/rules/k8s/policy/base_manifest/base_manifest_test.rego +0 -94
- package/rules/k8s/policy/gateway/gateway_test.rego +0 -122
- package/rules/k8s/policy/hasura_configmap/hasura_configmap_test.rego +0 -49
- package/rules/k8s/policy/hasura_httproute/hasura_httproute_test.rego +0 -148
- package/rules/k8s/policy/hpa_pdb/hpa_pdb_test.rego +0 -101
- package/rules/k8s/policy/kustomization/kustomization_test.rego +0 -128
- package/rules/k8s/policy/manifest/manifest_test.rego +0 -309
- package/rules/k8s/policy/svc_hl_yaml/svc_hl_yaml_test.rego +0 -42
- package/rules/k8s/policy/svc_yaml/svc_yaml_test.rego +0 -41
- package/rules/nginx-default-tpl/policy/vscode_extensions/vscode_extensions_test.rego +0 -30
- package/rules/nginx-default-tpl/policy/vscode_settings/vscode_settings_test.rego +0 -53
- package/rules/npm-module/policy/npm_package_json/npm_package_json_test.rego +0 -81
- package/rules/rego/policy/package_json/package_json_test.rego +0 -42
- package/rules/rego/policy/vscode_extensions/vscode_extensions_test.rego +0 -34
- package/rules/rego/policy/vscode_settings/vscode_settings_test.rego +0 -55
- package/rules/style-lint/policy/vscode_extensions/vscode_extensions_test.rego +0 -39
- package/rules/style-lint/policy/vscode_settings/vscode_settings_test.rego +0 -49
- package/rules/tauri/policy/vscode_extensions/vscode_extensions_test.rego +0 -44
- package/rules/text/policy/markdownlint/markdownlint_test.rego +0 -98
- package/rules/text/policy/vscode_extensions/vscode_extensions_test.rego +0 -51
- package/rules/text/policy/vscode_settings/vscode_settings_test.rego +0 -85
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI-обгортка над канонічним `lint-text` (text.mdc): послідовно
|
|
3
|
+
* 1) `cspell .` — перевірка правопису з `@nitra/cspell-dict`;
|
|
4
|
+
* 2) `runShellcheckText()` — авто-фікс і фінальна перевірка `*.sh` через `shellcheck`;
|
|
5
|
+
* 3) `bunx markdownlint-cli2 --fix "**\/*.md" "**\/*.mdc"` — авто-фікс Markdown;
|
|
6
|
+
* 4) `runV8rWithGlobs()` — schema-валідація json/json5/yaml/yml/toml через v8r з каталогом `@nitra/cursor`.
|
|
7
|
+
*
|
|
8
|
+
* Перший ненульовий код з ланцюжка повертається як код виходу; наступні кроки не запускаються.
|
|
9
|
+
* Експортовано як `runLintTextCli` — використовується з `bin/n-cursor.js` як підкоманда `lint-text`.
|
|
10
|
+
*/
|
|
11
|
+
import { runLintStep } from '../../../scripts/utils/run-lint-step.mjs'
|
|
12
|
+
import { runShellcheckText } from './run-shellcheck.mjs'
|
|
13
|
+
import { runV8rWithGlobs } from './run-v8r.mjs'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Виконує канонічний `lint-text`: cspell → run-shellcheck → markdownlint-cli2 → run-v8r.
|
|
17
|
+
* Першу помилку повертаємо як код виходу; наступні кроки не запускаються.
|
|
18
|
+
* Усі кроки синхронні (`spawnSync` + sync-ентрі з пакета), тому функція не async.
|
|
19
|
+
* @returns {number} 0 — все OK, інакше — код першого кроку, що впав
|
|
20
|
+
*/
|
|
21
|
+
export function runLintTextCli() {
|
|
22
|
+
const cspellCode = runLintStep('cspell', 'npx', ['cspell', '.'])
|
|
23
|
+
if (cspellCode !== 0) return cspellCode
|
|
24
|
+
|
|
25
|
+
console.log('\n▶ shellcheck (авто-фікс + фінальна перевірка *.sh)')
|
|
26
|
+
const shellcheckCode = runShellcheckText()
|
|
27
|
+
if (shellcheckCode !== 0) return shellcheckCode
|
|
28
|
+
|
|
29
|
+
const markdownlintCode = runLintStep('markdownlint', 'bunx', ['markdownlint-cli2', '--fix', '**/*.md', '**/*.mdc'])
|
|
30
|
+
if (markdownlintCode !== 0) return markdownlintCode
|
|
31
|
+
|
|
32
|
+
console.log('\n▶ v8r (schema-валідація json/json5/yaml/yml/toml)')
|
|
33
|
+
return runV8rWithGlobs()
|
|
34
|
+
}
|
|
@@ -82,7 +82,7 @@ export function listShellScriptPaths(cwd) {
|
|
|
82
82
|
return []
|
|
83
83
|
}
|
|
84
84
|
const files = ls.stdout.split('\0').filter(Boolean)
|
|
85
|
-
return new Set(files).toSorted()
|
|
85
|
+
return [...new Set(files)].toSorted()
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
@@ -90,7 +90,7 @@ export function listShellScriptPaths(cwd) {
|
|
|
90
90
|
cwd,
|
|
91
91
|
exclude: p => p.includes('node_modules') || p.startsWith(`node_modules/`) || p.split('/').includes('node_modules')
|
|
92
92
|
})
|
|
93
|
-
return new Set(fromGlob.map(p => p.replaceAll('\\', '/'))).toSorted()
|
|
93
|
+
return [...new Set(fromGlob.map(p => p.replaceAll('\\', '/')))].toSorted()
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/**
|
|
@@ -24,8 +24,8 @@ import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
|
24
24
|
/** Типові glob-и для форматів, які обробляє v8r (див. опис CLI v8r). */
|
|
25
25
|
export const DEFAULT_V8R_GLOBS = ['**/*.json', '**/*.json5', '**/*.yml', '**/*.yaml', '**/*.toml']
|
|
26
26
|
|
|
27
|
-
/** Абсолютний шлях до `schemas/v8r-catalog.json`
|
|
28
|
-
export const V8R_CATALOG_PATH = join(dirname(fileURLToPath(import.meta.url)), '
|
|
27
|
+
/** Абсолютний шлях до `schemas/v8r-catalog.json` у корені пакета `@nitra/cursor` (`npm/schemas/`). */
|
|
28
|
+
export const V8R_CATALOG_PATH = join(dirname(fileURLToPath(import.meta.url)), '../../../schemas/v8r-catalog.json')
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Повертає шлях до каталогу схем v8r для пакета (для тестів і діагностики).
|
package/rules/text/text.mdc
CHANGED
|
@@ -113,14 +113,14 @@ version: '1.26'
|
|
|
113
113
|
|
|
114
114
|
**`package.json`:** скрипт **`lint-text`** і devDependencies **`@nitra/cspell-dict`** (**`^2.0.0`** або новіший у лінії 2.x) — з **2.0.0** у пакет транзитивно входять типові **`@cspell/dict-*`**, тому **не** додавай їх окремо в корінь. **`markdownlint-cli2`** викликай у `lint-text` лише через **`bunx markdownlint-cli2`**, не додавай пакет до devDependencies. **`v8r`** лише через **`bunx v8r`** (зазвичай **`bunx v8r`**), не в devDependencies. Окремий пакет **`markdownlint`** не потрібний.
|
|
115
115
|
|
|
116
|
-
**shellcheck:** інструмент лише в **`PATH`** (як у **ga.mdc** для `lint-ga`), **не** додавай до `dependencies` / `devDependencies`. Якщо `shellcheck` відсутній — встанови локально (**macOS:** `brew install shellcheck`; **Debian/Ubuntu:** `sudo apt-get install -y shellcheck`; **Arch:** `sudo pacman -S shellcheck`).
|
|
116
|
+
**shellcheck:** інструмент лише в **`PATH`** (як у **ga.mdc** для `lint-ga`), **не** додавай до `dependencies` / `devDependencies`. Якщо `shellcheck` відсутній — встанови локально (**macOS:** `brew install shellcheck`; **Debian/Ubuntu:** `sudo apt-get install -y shellcheck`; **Arch:** `sudo pacman -S shellcheck`). Канонічний **`lint-text`** делегує до CLI **`n-cursor lint-text`** (бінарка з `node_modules/.bin/` пакету `@nitra/cursor`): після **`cspell .`** виконується **`runShellcheckText()`** з пакета — циклічно **`shellcheck -f diff`** і **`patch -p1`** для авто-виправлень, потім повний прогін **`shellcheck`** по всіх tracked `*.sh` (у git) або по `**/*.sh` без `node_modules`. Потрібен також **`patch`** у `PATH` (на macOS зазвичай уже є).
|
|
117
117
|
|
|
118
|
-
У v8r **немає** прапорця тихого режиму;
|
|
118
|
+
У v8r **немає** прапорця тихого режиму; CLI-обгортка **`n-cursor lint-text`** на четвертому кроці викликає **`runV8rWithGlobs()`** з пакета (реалізація — `npm/rules/text/js/run-v8r.mjs`): під капотом послідовні **`bunx v8r`** для кожного типу (**json**, **json5**, **yml**, **yaml**, **toml**), бо один процес v8r з кількома глобами падає з **98**, якщо хоч один glob порожній, і тоді інші розширення не перевіряються. Вивід при кодах **0** і **98** не показується. Каталог схем **`schemas/v8r-catalog.json`** пакета `@nitra/cursor` обгортка підставляє в v8r сама.
|
|
119
119
|
|
|
120
120
|
```json title="package.json"
|
|
121
121
|
{
|
|
122
122
|
"scripts": {
|
|
123
|
-
"lint-text": "
|
|
123
|
+
"lint-text": "n-cursor lint-text"
|
|
124
124
|
},
|
|
125
125
|
"devDependencies": {
|
|
126
126
|
"@nitra/cspell-dict": "^2.1.0"
|
|
@@ -239,7 +239,7 @@ jobs:
|
|
|
239
239
|
```json title="package.json"
|
|
240
240
|
{
|
|
241
241
|
"scripts": {
|
|
242
|
-
"lint-text": "
|
|
242
|
+
"lint-text": "n-cursor lint-text"
|
|
243
243
|
},
|
|
244
244
|
"devDependencies": {
|
|
245
245
|
"@nitra/cspell-dict": "^2.1.0"
|
|
@@ -256,7 +256,7 @@ jobs:
|
|
|
256
256
|
```json title="package.json"
|
|
257
257
|
{
|
|
258
258
|
"scripts": {
|
|
259
|
-
"lint-text": "
|
|
259
|
+
"lint-text": "n-cursor lint-text"
|
|
260
260
|
},
|
|
261
261
|
"devDependencies": {
|
|
262
262
|
"@nitra/cspell-dict": "^2.1.0"
|
package/schemas/v8r-catalog.json
CHANGED
|
@@ -8,6 +8,12 @@
|
|
|
8
8
|
"url": "https://unpkg.com/@nitra/cursor/schemas/n-cursor.json",
|
|
9
9
|
"fileMatch": [".n-cursor.json", "**/.n-cursor.json", ".n-cursor.example.json", "**/.n-cursor.example.json"]
|
|
10
10
|
},
|
|
11
|
+
{
|
|
12
|
+
"name": "rego-target.json",
|
|
13
|
+
"description": "Маніфест rego-полісі (npm/rules/<id>/policy/<name>/target.json) — які файли проєкту фідити в conftest",
|
|
14
|
+
"url": "https://unpkg.com/@nitra/cursor/schemas/target.json",
|
|
15
|
+
"fileMatch": ["npm/rules/*/policy/*/target.json", "rules/*/policy/*/target.json"]
|
|
16
|
+
},
|
|
11
17
|
{
|
|
12
18
|
"name": "schema-catalog",
|
|
13
19
|
"description": "Каталог схем Schema Store (v8r-catalog.json у пакеті)",
|
package/scripts/auto-skills.mjs
CHANGED
|
@@ -109,9 +109,7 @@ export function detectAutoSkills({ availableSkills, detectedRules, disableSkills
|
|
|
109
109
|
|
|
110
110
|
for (const [skillId, spec] of Object.entries(SKILL_AUTO_ACTIVATION)) {
|
|
111
111
|
if (!normalizedSkills.has(skillId) || disableSkillsSet.has(skillId)) continue
|
|
112
|
-
if ('always' in spec) {
|
|
113
|
-
detected.add(skillId)
|
|
114
|
-
} else if (spec.rules.every(d => detectedRulesSet.has(d))) {
|
|
112
|
+
if ('always' in spec || spec.rules.every(d => detectedRulesSet.has(d))) {
|
|
115
113
|
detected.add(skillId)
|
|
116
114
|
}
|
|
117
115
|
}
|
|
@@ -61,7 +61,7 @@ async function walkAllRelative(root, ignorePaths) {
|
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* Витягує (або обчислює і кешує) список усіх файлів у дереві для заданого набору ignore-шляхів.
|
|
64
|
-
* Кеш —
|
|
64
|
+
* Кеш — мапа `signature → Promise<string[]>`, тож паралельні виклики одного й того ж набору
|
|
65
65
|
* чекають один обхід.
|
|
66
66
|
* @param {string} root абсолютний корінь репозиторію
|
|
67
67
|
* @param {string[]} ignorePaths абсолютні posix-шляхи виключених каталогів
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Спільний хелпер для CLI-обгорток `lint-<rule>`: запускає один крок ланцюжка з логуванням
|
|
3
|
+
* команди і прокидає stdout/stderr на користувацькі stream-и (`stdio: 'inherit'`), щоб виглядало
|
|
4
|
+
* як прямий виклик у shell.
|
|
5
|
+
*
|
|
6
|
+
* Використовується з `n-cursor lint-ga`, `n-cursor lint-text` та інших підкоманд, щоб не дублювати
|
|
7
|
+
* одну й ту саму обгортку у кожному `rules/<id>/js/lint.mjs` (jscpd-clone).
|
|
8
|
+
*/
|
|
9
|
+
import { spawnSync } from 'node:child_process'
|
|
10
|
+
|
|
11
|
+
import { resolveCmd } from './resolve-cmd.mjs'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Запускає один крок lint-обгортки: резолвить `cmd` у PATH і `spawnSync` із успадкованим stdio.
|
|
15
|
+
* @param {string} title заголовок для логу (наприклад `actionlint`)
|
|
16
|
+
* @param {string} cmd ім'я команди (`bunx`, `uvx`, `npx`, …)
|
|
17
|
+
* @param {string[]} args аргументи команди
|
|
18
|
+
* @returns {number} код виходу дочірнього процесу: 0 — OK, 127 — команда відсутня в PATH, інше — помилка
|
|
19
|
+
*/
|
|
20
|
+
export function runLintStep(title, cmd, args) {
|
|
21
|
+
console.log(`\n▶ ${title}: ${cmd} ${args.join(' ')}`)
|
|
22
|
+
const resolved = resolveCmd(cmd)
|
|
23
|
+
if (!resolved) {
|
|
24
|
+
console.error(`❌ ${cmd} не знайдено в PATH (${title}).`)
|
|
25
|
+
return 127
|
|
26
|
+
}
|
|
27
|
+
const r = spawnSync(resolved, args, { stdio: 'inherit', env: process.env })
|
|
28
|
+
if (r.error) {
|
|
29
|
+
console.error(`❌ Не вдалося запустити ${cmd}: ${r.error.message}`)
|
|
30
|
+
return 1
|
|
31
|
+
}
|
|
32
|
+
return r.status ?? 1
|
|
33
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 2. **JS-концерни** — кожен `check*.mjs` у `js/<concern>/`. Concern `applies` теж може мати
|
|
8
8
|
* `check()` для друку контексту (його `applies()` уже відпрацював на кроці 1, він не повторюється).
|
|
9
9
|
* 3. **Policy-концерни** — кожен `policy/<concern>/target.json` через `runConftestBatch`.
|
|
10
|
-
*
|
|
10
|
+
* Резолвер `resolveTargetFiles` ділить cache (`walkCache`) між концернами.
|
|
11
11
|
*
|
|
12
12
|
* Кожен concern має власний `createCheckReporter` — їхні exit-коди OR-яться в один на рівні правила.
|
|
13
13
|
* Це дає той самий 0/1 контракт, що й попередня модель «один check.mjs на правило».
|
|
@@ -45,6 +45,7 @@ async function evaluateAppliesGate(bundledRulesDir, rule) {
|
|
|
45
45
|
const concern = rule.jsConcerns.find(c => c.name === APPLIES_CONCERN_NAME)
|
|
46
46
|
if (!concern || concern.files.length === 0) return true
|
|
47
47
|
const path = resolveJsCheckPath(bundledRulesDir, rule.id, concern, concern.files[0])
|
|
48
|
+
// eslint-disable-next-line no-unsanitized/method -- path будується з discovered concern/file, які пройшли regex CHECK_FILENAME_RE
|
|
48
49
|
const mod = await import(path)
|
|
49
50
|
if (typeof mod.applies !== 'function') return true
|
|
50
51
|
return Boolean(await mod.applies())
|
|
@@ -137,8 +137,9 @@ RUN bun install && bun vite build --mode "prod-$BRANCH" --base="$BASE"
|
|
|
137
137
|
|
|
138
138
|
Приклад БУЛО:
|
|
139
139
|
|
|
140
|
+
```vue
|
|
140
141
|
<template>
|
|
141
|
-
|
|
142
|
+
<h5>{{ t`Привіт` }}</h5>
|
|
142
143
|
</template>
|
|
143
144
|
|
|
144
145
|
<script setup>
|
|
@@ -146,16 +147,18 @@ import tf from '@nitra/tf/webpack'
|
|
|
146
147
|
|
|
147
148
|
// Translate
|
|
148
149
|
const tr = {
|
|
149
|
-
|
|
150
|
+
Привіт: 'Привет'
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
const t = tf.bind({ tr })
|
|
153
154
|
</script>
|
|
155
|
+
```
|
|
154
156
|
|
|
155
|
-
|
|
157
|
+
СТАЛО:
|
|
156
158
|
|
|
159
|
+
```vue
|
|
157
160
|
<template>
|
|
158
|
-
|
|
161
|
+
<h5>{{ t`Привіт` }}</h5>
|
|
159
162
|
</template>
|
|
160
163
|
|
|
161
164
|
<script setup>
|
|
@@ -163,11 +166,12 @@ import tf from '@nitra/tf/webpack'
|
|
|
163
166
|
|
|
164
167
|
// Translate
|
|
165
168
|
const tr = {
|
|
166
|
-
|
|
169
|
+
Привіт: 'Hello'
|
|
167
170
|
}
|
|
168
171
|
|
|
169
172
|
const t = tf.bind({ tr })
|
|
170
173
|
</script>
|
|
174
|
+
```
|
|
171
175
|
|
|
172
176
|
або
|
|
173
177
|
|
package/skills/fix/SKILL.md
CHANGED
|
@@ -36,17 +36,13 @@ bun i
|
|
|
36
36
|
oxfmt .
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
6.
|
|
39
|
+
6. **Лінт коду** — викликай скіл **`/n-lint`** (один канонічний прогон `bun run lint`):
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
bun run lint
|
|
43
|
-
bun run lint-text
|
|
44
|
-
bun run lint-style
|
|
42
|
+
bun run lint
|
|
45
43
|
```
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Підсумок: Перелічи запущені скрипти та зміни; опиши блокери, якщо лишились.
|
|
45
|
+
Лінт-логіку (auto-fix, рефакторинг під `sonarjs/cognitive-complexity`, обмеження `cspell`/`jscpd`/`knip`, заборона паралельних запусків ESLint) **не дублюй** тут — вона повністю у скілі **`/n-lint`**. Цей скіл (`/n-fix`) відповідає лише за **структуру** проєкту (правила `.cursor/rules/` + `npx @nitra/cursor check`); за **чистоту коду** відповідає **`/n-lint`**.
|
|
50
46
|
|
|
51
47
|
7. **Верифікація** — перевір що все виправлено:
|
|
52
48
|
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
# Тести для `abie.base_deployment_preem`. Запуск:
|
|
2
|
-
# conftest verify -p npm/policy/abie/base_deployment_preem
|
|
3
|
-
package abie.base_deployment_preem_test
|
|
4
|
-
|
|
5
|
-
import rego.v1
|
|
6
|
-
|
|
7
|
-
import data.abie.base_deployment_preem
|
|
8
|
-
|
|
9
|
-
mk_deployment(node_selector) := {
|
|
10
|
-
"apiVersion": "apps/v1",
|
|
11
|
-
"kind": "Deployment",
|
|
12
|
-
"metadata": {"name": "api", "namespace": "dev"},
|
|
13
|
-
"spec": {"template": {"spec": object.union(
|
|
14
|
-
{"containers": [{"name": "main", "image": "x"}]},
|
|
15
|
-
{"nodeSelector": node_selector},
|
|
16
|
-
)}},
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
test_deny_no_node_selector if {
|
|
20
|
-
input_doc := {
|
|
21
|
-
"apiVersion": "apps/v1",
|
|
22
|
-
"kind": "Deployment",
|
|
23
|
-
"metadata": {"name": "api"},
|
|
24
|
-
"spec": {"template": {"spec": {"containers": [{"name": "main", "image": "x"}]}}},
|
|
25
|
-
}
|
|
26
|
-
count(base_deployment_preem.deny) > 0 with input as input_doc
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
test_deny_node_selector_without_preem if {
|
|
30
|
-
count(base_deployment_preem.deny) > 0 with input as mk_deployment({"role": "worker"})
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
test_deny_preem_false if {
|
|
34
|
-
count(base_deployment_preem.deny) > 0 with input as mk_deployment({"preem": false})
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
test_deny_preem_string_false if {
|
|
38
|
-
count(base_deployment_preem.deny) > 0 with input as mk_deployment({"preem": "false"})
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
test_allow_preem_boolean_true if {
|
|
42
|
-
count(base_deployment_preem.deny) == 0 with input as mk_deployment({"preem": true})
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
test_allow_preem_string_true if {
|
|
46
|
-
count(base_deployment_preem.deny) == 0 with input as mk_deployment({"preem": "true"})
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
test_allow_preem_string_uppercase_true if {
|
|
50
|
-
count(base_deployment_preem.deny) == 0 with input as mk_deployment({"preem": "TRUE"})
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
# Не Deployment — пакет не діє (дзеркало JS-предиката).
|
|
54
|
-
test_allow_non_deployment if {
|
|
55
|
-
count(base_deployment_preem.deny) == 0 with input as {
|
|
56
|
-
"apiVersion": "v1",
|
|
57
|
-
"kind": "ConfigMap",
|
|
58
|
-
"metadata": {"name": "x"},
|
|
59
|
-
}
|
|
60
|
-
}
|
package/rules/abie/policy/clean_merged_ignore_branches/clean_merged_ignore_branches_test.rego
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
# Тести для `abie.clean_merged_ignore_branches`. Запуск:
|
|
2
|
-
# conftest verify -p npm/policy/abie/clean_merged_ignore_branches
|
|
3
|
-
package abie.clean_merged_ignore_branches_test
|
|
4
|
-
|
|
5
|
-
import rego.v1
|
|
6
|
-
|
|
7
|
-
import data.abie.clean_merged_ignore_branches
|
|
8
|
-
|
|
9
|
-
# Каркас workflow з одним job, що містить step із заданим with.
|
|
10
|
-
mk_workflow(step_with) := {"jobs": {"cleanup": {"steps": [{
|
|
11
|
-
"uses": "phpdocker-io/github-actions-delete-abandoned-branches@v2",
|
|
12
|
-
"with": step_with,
|
|
13
|
-
}]}}}
|
|
14
|
-
|
|
15
|
-
other_step_workflow := {"jobs": {"cleanup": {"steps": [{"uses": "actions/checkout@v6"}]}}}
|
|
16
|
-
|
|
17
|
-
# Workflow без потрібного кроку.
|
|
18
|
-
test_deny_step_missing if {
|
|
19
|
-
count(clean_merged_ignore_branches.deny) > 0 with input as other_step_workflow
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
test_deny_ignore_branches_missing if {
|
|
23
|
-
count(clean_merged_ignore_branches.deny) > 0 with input as mk_workflow({})
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
test_deny_missing_required_token if {
|
|
27
|
-
count(clean_merged_ignore_branches.deny) > 0 with input as mk_workflow({"ignore_branches": "dev"})
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
test_deny_completely_wrong_tokens if {
|
|
31
|
-
count(clean_merged_ignore_branches.deny) > 0 with input as mk_workflow({"ignore_branches": "main,develop"})
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
test_allow_required_tokens if {
|
|
35
|
-
count(clean_merged_ignore_branches.deny) == 0 with input as mk_workflow({"ignore_branches": "dev,ua"})
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
# Регістронезалежне порівняння і пропуск пробілів.
|
|
39
|
-
test_allow_uppercase_with_spaces if {
|
|
40
|
-
count(clean_merged_ignore_branches.deny) == 0 with input as mk_workflow({"ignore_branches": " DEV , UA "})
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
extra_branches_workflow := mk_workflow({"ignore_branches": "dev,ua,main,release/*"})
|
|
44
|
-
|
|
45
|
-
# Додаткові гілки після обов'язкових — дозволено.
|
|
46
|
-
test_allow_extra_branches if {
|
|
47
|
-
count(clean_merged_ignore_branches.deny) == 0 with input as extra_branches_workflow
|
|
48
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
# Тести для `abie.health_check_policy`. Запуск:
|
|
2
|
-
# conftest verify -p npm/policy/abie/health_check_policy
|
|
3
|
-
package abie.health_check_policy_test
|
|
4
|
-
|
|
5
|
-
import rego.v1
|
|
6
|
-
|
|
7
|
-
import data.abie.health_check_policy
|
|
8
|
-
|
|
9
|
-
valid_hcp := {
|
|
10
|
-
"apiVersion": "networking.gke.io/v1",
|
|
11
|
-
"kind": "HealthCheckPolicy",
|
|
12
|
-
"metadata": {"name": "api"},
|
|
13
|
-
"spec": {
|
|
14
|
-
"default": {"config": {
|
|
15
|
-
"type": "HTTP",
|
|
16
|
-
"httpHealthCheck": {"requestPath": "/healthz", "port": 8080},
|
|
17
|
-
}},
|
|
18
|
-
"targetRef": {"group": "", "kind": "Service", "name": "api-hl"},
|
|
19
|
-
},
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
# ── happy path ────────────────────────────────────────────────────────────
|
|
23
|
-
|
|
24
|
-
test_allow_canonical if {
|
|
25
|
-
count(health_check_policy.deny) == 0 with input as valid_hcp
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
# ── apiVersion ────────────────────────────────────────────────────────────
|
|
29
|
-
|
|
30
|
-
test_deny_wrong_api_version if {
|
|
31
|
-
bad := json.patch(valid_hcp, [{"op": "replace", "path": "/apiVersion", "value": "networking.gke.io/v1beta1"}])
|
|
32
|
-
count(health_check_policy.deny) > 0 with input as bad
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
# ── metadata.name ─────────────────────────────────────────────────────────
|
|
36
|
-
|
|
37
|
-
test_deny_empty_name if {
|
|
38
|
-
bad := json.patch(valid_hcp, [{"op": "replace", "path": "/metadata/name", "value": ""}])
|
|
39
|
-
count(health_check_policy.deny) > 0 with input as bad
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
# ── spec.default.config.type ──────────────────────────────────────────────
|
|
43
|
-
|
|
44
|
-
test_deny_config_type_not_http if {
|
|
45
|
-
bad := json.patch(valid_hcp, [{"op": "replace", "path": "/spec/default/config/type", "value": "TCP"}])
|
|
46
|
-
count(health_check_policy.deny) > 0 with input as bad
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
# ── requestPath ───────────────────────────────────────────────────────────
|
|
50
|
-
|
|
51
|
-
test_deny_empty_request_path if {
|
|
52
|
-
bad := json.patch(valid_hcp, [{
|
|
53
|
-
"op": "replace",
|
|
54
|
-
"path": "/spec/default/config/httpHealthCheck/requestPath",
|
|
55
|
-
"value": "",
|
|
56
|
-
}])
|
|
57
|
-
count(health_check_policy.deny) > 0 with input as bad
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
test_deny_request_path_without_slash if {
|
|
61
|
-
bad := json.patch(valid_hcp, [{
|
|
62
|
-
"op": "replace",
|
|
63
|
-
"path": "/spec/default/config/httpHealthCheck/requestPath",
|
|
64
|
-
"value": "healthz",
|
|
65
|
-
}])
|
|
66
|
-
count(health_check_policy.deny) > 0 with input as bad
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
# ── port ──────────────────────────────────────────────────────────────────
|
|
70
|
-
|
|
71
|
-
test_deny_port_not_8080 if {
|
|
72
|
-
bad := json.patch(valid_hcp, [{
|
|
73
|
-
"op": "replace",
|
|
74
|
-
"path": "/spec/default/config/httpHealthCheck/port",
|
|
75
|
-
"value": 9090,
|
|
76
|
-
}])
|
|
77
|
-
count(health_check_policy.deny) > 0 with input as bad
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
# ── targetRef ─────────────────────────────────────────────────────────────
|
|
81
|
-
|
|
82
|
-
test_deny_target_ref_kind_not_service if {
|
|
83
|
-
bad := json.patch(valid_hcp, [{"op": "replace", "path": "/spec/targetRef/kind", "value": "Gateway"}])
|
|
84
|
-
count(health_check_policy.deny) > 0 with input as bad
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
test_deny_target_ref_name_without_hl if {
|
|
88
|
-
bad := json.patch(valid_hcp, [{"op": "replace", "path": "/spec/targetRef/name", "value": "api"}])
|
|
89
|
-
count(health_check_policy.deny) > 0 with input as bad
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
# Не HCP — пакет не діє.
|
|
93
|
-
test_allow_other_kind if {
|
|
94
|
-
count(health_check_policy.deny) == 0 with input as {
|
|
95
|
-
"apiVersion": "v1",
|
|
96
|
-
"kind": "ConfigMap",
|
|
97
|
-
"metadata": {"name": "x"},
|
|
98
|
-
}
|
|
99
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
# Тести для `abie.http_route_base`. Запуск:
|
|
2
|
-
# conftest verify -p npm/policy/abie/http_route_base
|
|
3
|
-
package abie.http_route_base_test
|
|
4
|
-
|
|
5
|
-
import rego.v1
|
|
6
|
-
|
|
7
|
-
import data.abie.http_route_base
|
|
8
|
-
|
|
9
|
-
mk_route(hostnames) := {
|
|
10
|
-
"apiVersion": "gateway.networking.k8s.io/v1",
|
|
11
|
-
"kind": "HTTPRoute",
|
|
12
|
-
"metadata": {"name": "r", "namespace": "dev"},
|
|
13
|
-
"spec": {"hostnames": hostnames},
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
# ── allow ────────────────────────────────────────────────────────────────
|
|
17
|
-
|
|
18
|
-
test_allow_apex if {
|
|
19
|
-
count(http_route_base.deny) == 0 with input as mk_route(["aiml.live"])
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
test_allow_subdomain if {
|
|
23
|
-
count(http_route_base.deny) == 0 with input as mk_route(["api.aiml.live"])
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
test_allow_wildcard if {
|
|
27
|
-
count(http_route_base.deny) == 0 with input as mk_route(["*.aiml.live"])
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
test_allow_uppercase_apex if {
|
|
31
|
-
count(http_route_base.deny) == 0 with input as mk_route(["AIML.LIVE"])
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
test_allow_multiple_subdomains if {
|
|
35
|
-
count(http_route_base.deny) == 0 with input as mk_route(["api.aiml.live", "admin.aiml.live"])
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
# ── deny ─────────────────────────────────────────────────────────────────
|
|
39
|
-
|
|
40
|
-
test_deny_other_apex if {
|
|
41
|
-
count(http_route_base.deny) > 0 with input as mk_route(["example.com"])
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
test_deny_wrong_subdomain if {
|
|
45
|
-
count(http_route_base.deny) > 0 with input as mk_route(["api.example.com"])
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
test_deny_mixed_one_bad if {
|
|
49
|
-
count(http_route_base.deny) > 0 with input as mk_route(["api.aiml.live", "evil.com"])
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
test_deny_aiml_live_substring if {
|
|
53
|
-
# "aiml.live.example.com" не має закінчуватись на ".aiml.live" — це інший домен.
|
|
54
|
-
count(http_route_base.deny) > 0 with input as mk_route(["aiml.live.example.com"])
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
# Не HTTPRoute — пакет не діє.
|
|
58
|
-
test_allow_non_httproute if {
|
|
59
|
-
count(http_route_base.deny) == 0 with input as {
|
|
60
|
-
"apiVersion": "v1",
|
|
61
|
-
"kind": "Service",
|
|
62
|
-
"metadata": {"name": "x"},
|
|
63
|
-
}
|
|
64
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# Тести для `bun.package_json`. Запуск:
|
|
2
|
-
# conftest verify -p npm/policy/bun/package_json
|
|
3
|
-
package bun.package_json_test
|
|
4
|
-
|
|
5
|
-
import rego.v1
|
|
6
|
-
|
|
7
|
-
import data.bun.package_json
|
|
8
|
-
|
|
9
|
-
valid_pkg := {
|
|
10
|
-
"name": "n-cursor",
|
|
11
|
-
"devDependencies": {"@nitra/eslint-config": "^3.9.2"},
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
# ── happy path ────────────────────────────────────────────────────────────
|
|
15
|
-
|
|
16
|
-
test_allow_minimal if {
|
|
17
|
-
count(package_json.deny) == 0 with input as valid_pkg
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
test_allow_multiple_nitra_deps if {
|
|
21
|
-
pkg := json.patch(valid_pkg, [{
|
|
22
|
-
"op": "replace",
|
|
23
|
-
"path": "/devDependencies",
|
|
24
|
-
"value": {"@nitra/eslint-config": "^3.9.2", "@nitra/cspell-dict": "^2.0.0", "@nitra/stylelint-config": "^1.0.0"},
|
|
25
|
-
}])
|
|
26
|
-
count(package_json.deny) == 0 with input as pkg
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
test_allow_no_dev_dependencies if {
|
|
30
|
-
pkg := json.patch(valid_pkg, [{"op": "remove", "path": "/devDependencies"}])
|
|
31
|
-
count(package_json.deny) == 0 with input as pkg
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
# ── deny: devDependencies лише @nitra/* ──────────────────────────────────
|
|
35
|
-
|
|
36
|
-
test_deny_non_nitra_devdep if {
|
|
37
|
-
cases := [
|
|
38
|
-
{"@cspell/dict-uk-ua": "^2.0.0"},
|
|
39
|
-
{"@cspell/cspell-lib": "^9.0.0"},
|
|
40
|
-
{"lodash": "*"},
|
|
41
|
-
{"@types/node": "^24.0.0"},
|
|
42
|
-
]
|
|
43
|
-
some bad in cases
|
|
44
|
-
pkg := json.patch(valid_pkg, [{"op": "replace", "path": "/devDependencies", "value": bad}])
|
|
45
|
-
count(package_json.deny) > 0 with input as pkg
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
test_deny_mixed_dev_deps_only_flags_non_nitra if {
|
|
49
|
-
pkg := json.patch(valid_pkg, [{
|
|
50
|
-
"op": "replace",
|
|
51
|
-
"path": "/devDependencies",
|
|
52
|
-
"value": {"@nitra/eslint-config": "^3.9.2", "lodash": "*"},
|
|
53
|
-
}])
|
|
54
|
-
some msg in package_json.deny with input as pkg
|
|
55
|
-
contains(msg, "lodash")
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
# ── deny: packageManager ─────────────────────────────────────────────────
|
|
59
|
-
|
|
60
|
-
test_deny_package_manager_field if {
|
|
61
|
-
pkg := json.patch(valid_pkg, [{"op": "add", "path": "/packageManager", "value": "pnpm@9.0.0"}])
|
|
62
|
-
count(package_json.deny) > 0 with input as pkg
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
# ── deny: dependencies у кореневому ──────────────────────────────────────
|
|
66
|
-
|
|
67
|
-
test_deny_root_dependencies_present if {
|
|
68
|
-
pkg := json.patch(valid_pkg, [{"op": "add", "path": "/dependencies", "value": {"lodash": "*"}}])
|
|
69
|
-
count(package_json.deny) > 0 with input as pkg
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
test_deny_empty_dependencies_object if {
|
|
73
|
-
pkg := json.patch(valid_pkg, [{"op": "add", "path": "/dependencies", "value": {}}])
|
|
74
|
-
count(package_json.deny) > 0 with input as pkg
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
# ── deny: агрегований lint ───────────────────────────────────────────────
|
|
78
|
-
|
|
79
|
-
test_deny_lint_prefixed_without_aggregate if {
|
|
80
|
-
pkg := json.patch(valid_pkg, [{"op": "add", "path": "/scripts", "value": {"lint-js": "echo"}}])
|
|
81
|
-
count(package_json.deny) > 0 with input as pkg
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
test_allow_lint_aggregate_calls_subscript_and_oxfmt if {
|
|
85
|
-
pkg := json.patch(valid_pkg, [{
|
|
86
|
-
"op": "add",
|
|
87
|
-
"path": "/scripts",
|
|
88
|
-
"value": {"lint-js": "echo", "lint": "bun run lint-js && oxfmt ."},
|
|
89
|
-
}])
|
|
90
|
-
count(package_json.deny) == 0 with input as pkg
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
test_deny_lint_aggregate_missing_oxfmt if {
|
|
94
|
-
pkg := json.patch(valid_pkg, [{
|
|
95
|
-
"op": "add",
|
|
96
|
-
"path": "/scripts",
|
|
97
|
-
"value": {"lint-js": "echo", "lint": "bun run lint-js"},
|
|
98
|
-
}])
|
|
99
|
-
count(package_json.deny) > 0 with input as pkg
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
test_deny_lint_aggregate_missing_subscript_via_bun_run if {
|
|
103
|
-
pkg := json.patch(valid_pkg, [{
|
|
104
|
-
"op": "add",
|
|
105
|
-
"path": "/scripts",
|
|
106
|
-
"value": {"lint-js": "echo", "lint-text": "echo", "lint": "bun run lint-js && oxfmt ."},
|
|
107
|
-
}])
|
|
108
|
-
count(package_json.deny) > 0 with input as pkg
|
|
109
|
-
}
|