@nitra/cursor 1.9.19 → 1.9.20
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 +29 -0
- package/package.json +1 -1
- package/policy/rego/package_json/package_json.rego +35 -0
- package/policy/rego/package_json/package_json_test.rego +42 -0
- package/policy/rego/vscode_extensions/vscode_extensions.rego +19 -0
- package/policy/rego/vscode_extensions/vscode_extensions_test.rego +34 -0
- package/policy/rego/vscode_settings/vscode_settings.rego +38 -0
- package/policy/rego/vscode_settings/vscode_settings_test.rego +55 -0
- package/policy/tauri/vscode_extensions/vscode_extensions.rego +28 -0
- package/policy/tauri/vscode_extensions/vscode_extensions_test.rego +44 -0
- package/scripts/check-abie.mjs +0 -1
- package/scripts/check-hasura.mjs +1 -1
- package/scripts/check-rego.mjs +106 -0
- package/scripts/check-tauri.mjs +88 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,35 @@
|
|
|
4
4
|
|
|
5
5
|
Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
|
|
6
6
|
|
|
7
|
+
## [1.9.20] - 2026-05-14
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`check-rego.mjs` orchestrator + 3 rego-полісі для `rego.mdc`:**
|
|
12
|
+
- JS gate у `npm/scripts/check-rego.mjs`: walk дерева від `cwd` (з типовими skip-ами і `.n-cursor.json:ignore`); якщо немає жодного `.rego` файла — `pass` (skip) ("rego-tooling не вимагається"). Інакше — FS-existence + content-валідація 3 файлів через `runConftestBatch`.
|
|
13
|
+
- `rego.vscode_extensions` — `recommendations` ∋ `tsandall.opa`.
|
|
14
|
+
- `rego.vscode_settings` — `[rego]` блок з `editor.defaultFormatter: "tsandall.opa"` + `editor.formatOnSave: true`; окремі deny на «не object», «неправильний defaultFormatter», «formatOnSave не true / відсутній».
|
|
15
|
+
- `rego.package_json` — `scripts.lint-rego` присутній і дорівнює `"bun ./npm/scripts/lint-rego.mjs"` (точне значення, з підтримкою whitespace через `trim_space`).
|
|
16
|
+
- +20 rego-тестів (5 + 7 + 8). Глобально у `lint-conftest` НЕ реєструються — це conditional правило, gating через JS.
|
|
17
|
+
- **`check-tauri.mjs` orchestrator + 1 rego-полісі для `tauri.mdc`:**
|
|
18
|
+
- JS detector маркера Tauri-проєкту: `src-tauri/` каталог, `tauri.conf.json` у корені, або `@tauri-apps/*` у `dependencies`/`devDependencies` кореневого `package.json`. Якщо немає — `pass` (skip).
|
|
19
|
+
- `tauri.vscode_extensions` — `recommendations` ∋ обидва: `tauri-apps.tauri-vscode` і `rust-lang.rust-analyzer`. Один deny з шаблоном повідомлень + `recommendations_set` поза deny (performance hint).
|
|
20
|
+
- +6 rego-тестів (canonical, додаткові розширення, кожний відсутній маркер окремо, empty, no field).
|
|
21
|
+
- **`conftest verify`** — **293/293 pass** (+26).
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **CLI auto-discovery** підхоплює `check-rego.mjs` і `check-tauri.mjs` через `discoverCheckScripts()` у `bin/n-cursor.js`. Окрема реєстрація не потрібна — будь-який `check-*.mjs` стає доступним через `npx @nitra/cursor check <rule>`.
|
|
26
|
+
|
|
27
|
+
### Verified
|
|
28
|
+
|
|
29
|
+
- **На цьому репо `npx @nitra/cursor check rego` детектує реальні гепи у `.vscode/extensions.json` (немає `tsandall.opa`) і `.vscode/settings.json` (немає `[rego]` блока).** Це true-positive: репо має `rego` у `.n-cursor.json:rules` і містить `.rego` файли, тож канонічний tooling-набір вимагається. Фікс — додати entries у `.vscode/*` згідно `rego.mdc`.
|
|
30
|
+
|
|
31
|
+
### Not migrated (explained)
|
|
32
|
+
|
|
33
|
+
- **`changelog.mdc` format-валідація** — пропущено: `conftest` не парсить markdown без pre-processing. Структурна валідація формату `## [version] - YYYY-MM-DD` лишається в `check-changelog.mjs` (JS), яке через regex розбирає текст. Перенесення вимагало б pre-processing markdown → JSON у JS перед викликом conftest — додаткова складність без виграшу.
|
|
34
|
+
- **`image-compress.mdc`** — вже має повне покриття rego через `image_compress.package_json` (8 deny, тести у [npm/policy/image_compress/package_json/](npm/policy/image_compress/package_json/)). `.gitignore` cross-file checks залишаються в `check-image-compress.mjs` як FS-логіка (rego не вміє).
|
|
35
|
+
|
|
7
36
|
## [1.9.19] - 2026-05-14
|
|
8
37
|
|
|
9
38
|
### Removed
|
package/package.json
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Перевірка `package.json` для rego (rego.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Викликається з `check-rego.mjs` через `runConftestBatch` лише ПІСЛЯ виявлення
|
|
4
|
+
# `.rego` файлів у дереві. Глобально у `lint-conftest` НЕ реєструється.
|
|
5
|
+
#
|
|
6
|
+
# Canonical (rego.mdc): scripts.lint-rego має бути "bun ./npm/scripts/lint-rego.mjs".
|
|
7
|
+
#
|
|
8
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
9
|
+
package rego.package_json
|
|
10
|
+
|
|
11
|
+
import rego.v1
|
|
12
|
+
|
|
13
|
+
canonical_lint_rego := "bun ./npm/scripts/lint-rego.mjs"
|
|
14
|
+
|
|
15
|
+
lint_rego_template := concat(" ", [
|
|
16
|
+
"package.json: scripts.lint-rego має бути %q",
|
|
17
|
+
"(зараз: %q) (rego.mdc)",
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
deny contains msg if {
|
|
21
|
+
scripts := object.get(input, "scripts", {})
|
|
22
|
+
not "lint-rego" in object.keys(scripts)
|
|
23
|
+
msg := concat(" ", [
|
|
24
|
+
"package.json: відсутній scripts.lint-rego — додай",
|
|
25
|
+
"\"lint-rego\": \"bun ./npm/scripts/lint-rego.mjs\" (rego.mdc)",
|
|
26
|
+
])
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
deny contains msg if {
|
|
30
|
+
scripts := object.get(input, "scripts", {})
|
|
31
|
+
lint_rego := object.get(scripts, "lint-rego", "")
|
|
32
|
+
lint_rego != ""
|
|
33
|
+
trim_space(lint_rego) != canonical_lint_rego
|
|
34
|
+
msg := sprintf(lint_rego_template, [canonical_lint_rego, lint_rego])
|
|
35
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Тести для `rego.package_json`. Запуск:
|
|
2
|
+
# conftest verify -p npm/policy/rego/package_json
|
|
3
|
+
package rego.package_json_test
|
|
4
|
+
|
|
5
|
+
import rego.v1
|
|
6
|
+
|
|
7
|
+
import data.rego.package_json
|
|
8
|
+
|
|
9
|
+
canonical_lint_rego := "bun ./npm/scripts/lint-rego.mjs"
|
|
10
|
+
|
|
11
|
+
test_allow_canonical if {
|
|
12
|
+
pkg := {"scripts": {"lint-rego": canonical_lint_rego}}
|
|
13
|
+
count(package_json.deny) == 0 with input as pkg
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test_allow_with_other_scripts if {
|
|
17
|
+
pkg := {"scripts": {"lint-rego": canonical_lint_rego, "test": "bun test"}}
|
|
18
|
+
count(package_json.deny) == 0 with input as pkg
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test_allow_with_whitespace if {
|
|
22
|
+
pkg := {"scripts": {"lint-rego": concat("", [" ", canonical_lint_rego, " "])}}
|
|
23
|
+
count(package_json.deny) == 0 with input as pkg
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
test_deny_missing_lint_rego if {
|
|
27
|
+
count(package_json.deny) > 0 with input as {"scripts": {}}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test_deny_no_scripts if {
|
|
31
|
+
count(package_json.deny) > 0 with input as {"name": "x"}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
test_deny_wrong_value if {
|
|
35
|
+
pkg := {"scripts": {"lint-rego": "opa check ."}}
|
|
36
|
+
count(package_json.deny) > 0 with input as pkg
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
test_deny_npx_form if {
|
|
40
|
+
pkg := {"scripts": {"lint-rego": "npx opa check ."}}
|
|
41
|
+
count(package_json.deny) > 0 with input as pkg
|
|
42
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Перевірка `.vscode/extensions.json` для rego (rego.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Викликається з `check-rego.mjs` через `runConftestBatch` лише ПІСЛЯ того, як
|
|
4
|
+
# JS виявив `.rego` файли у дереві (умовне правило — проєкти без rego не
|
|
5
|
+
# зобовʼязані ставити tsandall.opa). Глобально у `lint-conftest` НЕ
|
|
6
|
+
# реєструється.
|
|
7
|
+
#
|
|
8
|
+
# Canonical (rego.mdc): `recommendations` має містити `tsandall.opa`.
|
|
9
|
+
#
|
|
10
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
11
|
+
package rego.vscode_extensions
|
|
12
|
+
|
|
13
|
+
import rego.v1
|
|
14
|
+
|
|
15
|
+
deny contains msg if {
|
|
16
|
+
recs := object.get(input, "recommendations", [])
|
|
17
|
+
not "tsandall.opa" in {r | some r in recs}
|
|
18
|
+
msg := ".vscode/extensions.json: recommendations має містити \"tsandall.opa\" (rego.mdc)"
|
|
19
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Тести для `rego.vscode_extensions`. Запуск:
|
|
2
|
+
# conftest verify -p npm/policy/rego/vscode_extensions
|
|
3
|
+
package rego.vscode_extensions_test
|
|
4
|
+
|
|
5
|
+
import rego.v1
|
|
6
|
+
|
|
7
|
+
import data.rego.vscode_extensions
|
|
8
|
+
|
|
9
|
+
test_allow_with_required_extension if {
|
|
10
|
+
cfg := {"recommendations": ["tsandall.opa"]}
|
|
11
|
+
count(vscode_extensions.deny) == 0 with input as cfg
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
test_allow_with_additional_extensions if {
|
|
15
|
+
cfg := {"recommendations": [
|
|
16
|
+
"dbaeumer.vscode-eslint",
|
|
17
|
+
"tsandall.opa",
|
|
18
|
+
"oxc.oxc-vscode",
|
|
19
|
+
]}
|
|
20
|
+
count(vscode_extensions.deny) == 0 with input as cfg
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
test_deny_missing_extension if {
|
|
24
|
+
cfg := {"recommendations": ["dbaeumer.vscode-eslint"]}
|
|
25
|
+
count(vscode_extensions.deny) > 0 with input as cfg
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test_deny_empty_recommendations if {
|
|
29
|
+
count(vscode_extensions.deny) > 0 with input as {"recommendations": []}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test_deny_no_recommendations_field if {
|
|
33
|
+
count(vscode_extensions.deny) > 0 with input as {}
|
|
34
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Перевірка `.vscode/settings.json` для rego (rego.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Викликається з `check-rego.mjs` через `runConftestBatch` лише ПІСЛЯ виявлення
|
|
4
|
+
# `.rego` файлів у дереві. Глобально у `lint-conftest` НЕ реєструється.
|
|
5
|
+
#
|
|
6
|
+
# Canonical (rego.mdc):
|
|
7
|
+
# { "[rego]": { "editor.defaultFormatter": "tsandall.opa",
|
|
8
|
+
# "editor.formatOnSave": true } }
|
|
9
|
+
#
|
|
10
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
11
|
+
package rego.vscode_settings
|
|
12
|
+
|
|
13
|
+
import rego.v1
|
|
14
|
+
|
|
15
|
+
# ── deny: [rego] block ──────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
deny contains msg if {
|
|
18
|
+
not is_object(object.get(input, "[rego]", null))
|
|
19
|
+
msg := concat(" ", [
|
|
20
|
+
".vscode/settings.json: \"[rego]\" має бути обʼєктом з",
|
|
21
|
+
"\"editor.defaultFormatter\": \"tsandall.opa\" і",
|
|
22
|
+
"\"editor.formatOnSave\": true (rego.mdc)",
|
|
23
|
+
])
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
deny contains msg if {
|
|
27
|
+
rego_block := object.get(input, "[rego]", {})
|
|
28
|
+
is_object(rego_block)
|
|
29
|
+
object.get(rego_block, "editor.defaultFormatter", null) != "tsandall.opa"
|
|
30
|
+
msg := ".vscode/settings.json: \"[rego].editor.defaultFormatter\" має бути \"tsandall.opa\" (rego.mdc)"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
deny contains msg if {
|
|
34
|
+
rego_block := object.get(input, "[rego]", {})
|
|
35
|
+
is_object(rego_block)
|
|
36
|
+
object.get(rego_block, "editor.formatOnSave", null) != true
|
|
37
|
+
msg := ".vscode/settings.json: \"[rego].editor.formatOnSave\" має бути true (rego.mdc)"
|
|
38
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Тести для `rego.vscode_settings`. Запуск:
|
|
2
|
+
# conftest verify -p npm/policy/rego/vscode_settings
|
|
3
|
+
package rego.vscode_settings_test
|
|
4
|
+
|
|
5
|
+
import rego.v1
|
|
6
|
+
|
|
7
|
+
import data.rego.vscode_settings
|
|
8
|
+
|
|
9
|
+
valid_cfg := {"[rego]": {
|
|
10
|
+
"editor.defaultFormatter": "tsandall.opa",
|
|
11
|
+
"editor.formatOnSave": true,
|
|
12
|
+
}}
|
|
13
|
+
|
|
14
|
+
test_allow_canonical if {
|
|
15
|
+
count(vscode_settings.deny) == 0 with input as valid_cfg
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test_allow_with_additional_lang_blocks if {
|
|
19
|
+
cfg := json.patch(
|
|
20
|
+
valid_cfg,
|
|
21
|
+
[{"op": "add", "path": "/[javascript]", "value": {"editor.defaultFormatter": "oxc.oxc-vscode"}}],
|
|
22
|
+
)
|
|
23
|
+
count(vscode_settings.deny) == 0 with input as cfg
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
test_deny_rego_block_missing if {
|
|
27
|
+
count(vscode_settings.deny) > 0 with input as {}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test_deny_rego_block_not_object if {
|
|
31
|
+
count(vscode_settings.deny) > 0 with input as {"[rego]": "tsandall.opa"}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
test_deny_wrong_default_formatter if {
|
|
35
|
+
cfg := json.patch(
|
|
36
|
+
valid_cfg,
|
|
37
|
+
[{"op": "replace", "path": "/[rego]/editor.defaultFormatter", "value": "prettier"}],
|
|
38
|
+
)
|
|
39
|
+
count(vscode_settings.deny) > 0 with input as cfg
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
test_deny_default_formatter_missing if {
|
|
43
|
+
cfg := json.patch(valid_cfg, [{"op": "remove", "path": "/[rego]/editor.defaultFormatter"}])
|
|
44
|
+
count(vscode_settings.deny) > 0 with input as cfg
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test_deny_format_on_save_false if {
|
|
48
|
+
cfg := json.patch(valid_cfg, [{"op": "replace", "path": "/[rego]/editor.formatOnSave", "value": false}])
|
|
49
|
+
count(vscode_settings.deny) > 0 with input as cfg
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
test_deny_format_on_save_missing if {
|
|
53
|
+
cfg := json.patch(valid_cfg, [{"op": "remove", "path": "/[rego]/editor.formatOnSave"}])
|
|
54
|
+
count(vscode_settings.deny) > 0 with input as cfg
|
|
55
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Перевірка `.vscode/extensions.json` для tauri (tauri.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Викликається з `check-tauri.mjs` через `runConftestBatch` лише ПІСЛЯ того,
|
|
4
|
+
# як JS виявив маркер Tauri-проєкту (`src-tauri/` каталог, `tauri.conf.json`
|
|
5
|
+
# у будь-якому пакеті, або залежність `@tauri-apps/*`). Глобально у
|
|
6
|
+
# `lint-conftest` НЕ реєструється — інакше false-positive порушення на не-Tauri проєктах.
|
|
7
|
+
#
|
|
8
|
+
# Canonical (tauri.mdc): `recommendations` має містити обидва записи —
|
|
9
|
+
# - tauri-apps.tauri-vscode
|
|
10
|
+
# - rust-lang.rust-analyzer
|
|
11
|
+
#
|
|
12
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
13
|
+
package tauri.vscode_extensions
|
|
14
|
+
|
|
15
|
+
import rego.v1
|
|
16
|
+
|
|
17
|
+
required_extensions := {"tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"}
|
|
18
|
+
|
|
19
|
+
missing_extension_template := ".vscode/extensions.json: recommendations має містити %q (tauri.mdc)"
|
|
20
|
+
|
|
21
|
+
# Множина усіх записів `recommendations` (поза deny — performance/non-loop-expression).
|
|
22
|
+
recommendations_set := {r | some r in object.get(input, "recommendations", [])}
|
|
23
|
+
|
|
24
|
+
deny contains msg if {
|
|
25
|
+
some required in required_extensions
|
|
26
|
+
not required in recommendations_set
|
|
27
|
+
msg := sprintf(missing_extension_template, [required])
|
|
28
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Тести для `tauri.vscode_extensions`. Запуск:
|
|
2
|
+
# conftest verify -p npm/policy/tauri/vscode_extensions
|
|
3
|
+
package tauri.vscode_extensions_test
|
|
4
|
+
|
|
5
|
+
import rego.v1
|
|
6
|
+
|
|
7
|
+
import data.tauri.vscode_extensions
|
|
8
|
+
|
|
9
|
+
canonical := {"recommendations": [
|
|
10
|
+
"tauri-apps.tauri-vscode",
|
|
11
|
+
"rust-lang.rust-analyzer",
|
|
12
|
+
]}
|
|
13
|
+
|
|
14
|
+
test_allow_canonical if {
|
|
15
|
+
count(vscode_extensions.deny) == 0 with input as canonical
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test_allow_with_additional_extensions if {
|
|
19
|
+
cfg := {"recommendations": [
|
|
20
|
+
"dbaeumer.vscode-eslint",
|
|
21
|
+
"tauri-apps.tauri-vscode",
|
|
22
|
+
"rust-lang.rust-analyzer",
|
|
23
|
+
"oxc.oxc-vscode",
|
|
24
|
+
]}
|
|
25
|
+
count(vscode_extensions.deny) == 0 with input as cfg
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test_deny_missing_tauri if {
|
|
29
|
+
cfg := {"recommendations": ["rust-lang.rust-analyzer"]}
|
|
30
|
+
count(vscode_extensions.deny) > 0 with input as cfg
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test_deny_missing_rust_analyzer if {
|
|
34
|
+
cfg := {"recommendations": ["tauri-apps.tauri-vscode"]}
|
|
35
|
+
count(vscode_extensions.deny) > 0 with input as cfg
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
test_deny_empty_recommendations if {
|
|
39
|
+
count(vscode_extensions.deny) > 0 with input as {"recommendations": []}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
test_deny_no_recommendations_field if {
|
|
43
|
+
count(vscode_extensions.deny) > 0 with input as {}
|
|
44
|
+
}
|
package/scripts/check-abie.mjs
CHANGED
|
@@ -76,7 +76,6 @@ const UA_KUSTOMIZATION_PATH_RE = /(^|\/)ua\/kustomization\.yaml$/u
|
|
|
76
76
|
const OVERLAY_PACKAGE_DIR_RE = /^(.+)\/k8s\/ua\/kustomization\.yaml$/u
|
|
77
77
|
const BASE_SEGMENT_RE = /(^|\/)base\//u
|
|
78
78
|
const YAML_EXTENSION_RE = /\.ya?ml$/iu
|
|
79
|
-
const K8S_PACKAGE_DIR_RE = /^(.+)\/k8s\//u
|
|
80
79
|
const PATCH_NODE_SELECTOR_PATH_RE = /path:\s*\/spec\/template\/spec\/nodeSelector\b/u
|
|
81
80
|
const PATCH_PREEM_FALSE_RE = /\bpreem:\s*['"]?false['"]?\b/u
|
|
82
81
|
const TRAILING_SLASH_RE = /\/$/u
|
package/scripts/check-hasura.mjs
CHANGED
|
@@ -143,7 +143,7 @@ async function checkEnvFile(relPath, expected, reporter) {
|
|
|
143
143
|
const value = m[1].trim()
|
|
144
144
|
const parsed = parseInternalHasuraEndpoint(value)
|
|
145
145
|
if (!parsed.ok) {
|
|
146
|
-
const example =
|
|
146
|
+
const example = "https://<service>.<namespace>.svc.<cluster>.internal:<port>"
|
|
147
147
|
fail(
|
|
148
148
|
`${relPath}: HASURA_GRAPHQL_ENDPOINT="${value}" — потрібен внутрішній кластерний URL виду ${example} (hasura.mdc)`
|
|
149
149
|
)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Перевіряє інструментарій rego (rego.mdc): VSCode та `package.json` для проєктів,
|
|
3
|
+
* які мають хоча б один `.rego` файл у дереві.
|
|
4
|
+
*
|
|
5
|
+
* Cross-file gating (JS):
|
|
6
|
+
* 1. Walk дерева від `cwd` (з типовими skip-ами і `.n-cursor.json:ignore`).
|
|
7
|
+
* 2. Якщо немає жодного `.rego` — пропустити перевірку (rego-tooling не вимагається).
|
|
8
|
+
* 3. Інакше — для кожного канонічного файла:
|
|
9
|
+
* - FS-existence (з повідомленням, якщо відсутній);
|
|
10
|
+
* - делегувати content-валідацію rego-пакетам через `runConftestBatch`:
|
|
11
|
+
* `rego.vscode_extensions` — `.vscode/extensions.json`: `tsandall.opa`
|
|
12
|
+
* у `recommendations`;
|
|
13
|
+
* `rego.vscode_settings` — `.vscode/settings.json`: `[rego]` з
|
|
14
|
+
* `editor.defaultFormatter: "tsandall.opa"` і `editor.formatOnSave: true`;
|
|
15
|
+
* `rego.package_json` — `package.json#scripts.lint-rego` має бути
|
|
16
|
+
* канонічним `"bun ./npm/scripts/lint-rego.mjs"`.
|
|
17
|
+
*
|
|
18
|
+
* Rego-полісі глобально у `lint-conftest` НЕ реєструються — це conditional
|
|
19
|
+
* правило (без `.rego` файлів вимоги не діють). Plan B: Rego-authoritative +
|
|
20
|
+
* JS-orchestrator з `runConftestBatch`.
|
|
21
|
+
*
|
|
22
|
+
* `bun run lint-rego` (`npm/scripts/lint-rego.mjs`) — окрема перевірка САМИХ
|
|
23
|
+
* rego-полісі (opa check / regal lint / conftest verify), не плутати з цим
|
|
24
|
+
* скриптом, який перевіряє ПРОЄКТНЕ оточення для роботи з rego.
|
|
25
|
+
*/
|
|
26
|
+
import { existsSync } from 'node:fs'
|
|
27
|
+
|
|
28
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
29
|
+
import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
|
|
30
|
+
import { runConftestBatch } from './utils/run-conftest-batch.mjs'
|
|
31
|
+
import { walkDir } from './utils/walkDir.mjs'
|
|
32
|
+
|
|
33
|
+
/** Список (path, namespace, policyDirRel) для трьох канонічних конфігів rego.mdc. */
|
|
34
|
+
const REGO_TARGETS = [
|
|
35
|
+
['.vscode/extensions.json', 'rego.vscode_extensions', 'rego/vscode_extensions'],
|
|
36
|
+
['.vscode/settings.json', 'rego.vscode_settings', 'rego/vscode_settings'],
|
|
37
|
+
['package.json', 'rego.package_json', 'rego/package_json']
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Чи є хоча б один `.rego` файл у дереві від `cwd`.
|
|
42
|
+
* @param {string} root абсолютний шлях кореня
|
|
43
|
+
* @param {string[]} ignorePaths шляхи каталогів, повністю виключених з обходу
|
|
44
|
+
* @returns {Promise<boolean>} `true`, якщо знайдено хоча б один `.rego`
|
|
45
|
+
*/
|
|
46
|
+
async function projectHasRegoFiles(root, ignorePaths) {
|
|
47
|
+
let found = false
|
|
48
|
+
await walkDir(
|
|
49
|
+
root,
|
|
50
|
+
p => {
|
|
51
|
+
if (p.endsWith('.rego')) {
|
|
52
|
+
found = true
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
ignorePaths
|
|
56
|
+
)
|
|
57
|
+
return found
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Делегує content-валідацію одного канонічного конфіга rego-пакету через
|
|
62
|
+
* `runConftestBatch`. FS-існування — попередньо перевірено.
|
|
63
|
+
* @param {string} path відносний шлях до файлу
|
|
64
|
+
* @param {string} namespace rego-пакет (наприклад `rego.vscode_extensions`)
|
|
65
|
+
* @param {string} policyDirRel піддиректорія у `npm/policy/`
|
|
66
|
+
* @param {(msg: string) => void} pass success-репортер
|
|
67
|
+
* @param {(msg: string) => void} fail fail-репортер
|
|
68
|
+
* @returns {void}
|
|
69
|
+
*/
|
|
70
|
+
function runRegoPolicyOnPath(path, namespace, policyDirRel, pass, fail) {
|
|
71
|
+
const violations = runConftestBatch({ policyDirRel, namespace, files: [path] })
|
|
72
|
+
if (violations.length === 0) {
|
|
73
|
+
pass(`${path} відповідає ${namespace} (rego)`)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
for (const v of violations) fail(v.message)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Перевіряє відповідність проєкту правилам rego.mdc.
|
|
81
|
+
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
82
|
+
*/
|
|
83
|
+
export async function check() {
|
|
84
|
+
const reporter = createCheckReporter()
|
|
85
|
+
const { pass, fail } = reporter
|
|
86
|
+
|
|
87
|
+
const root = process.cwd()
|
|
88
|
+
const ignorePaths = await loadCursorIgnorePaths(root)
|
|
89
|
+
const hasRego = await projectHasRegoFiles(root, ignorePaths)
|
|
90
|
+
if (!hasRego) {
|
|
91
|
+
pass('Немає *.rego у дереві — rego-tooling не вимагається (rego.mdc)')
|
|
92
|
+
return reporter.getExitCode()
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
pass('Знайдено *.rego у дереві — перевіряємо канонічні конфіги rego.mdc')
|
|
96
|
+
|
|
97
|
+
for (const [path, namespace, policyDirRel] of REGO_TARGETS) {
|
|
98
|
+
if (!existsSync(path)) {
|
|
99
|
+
fail(`${path} не існує — створи згідно rego.mdc (${namespace})`)
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
102
|
+
runRegoPolicyOnPath(path, namespace, policyDirRel, pass, fail)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return reporter.getExitCode()
|
|
106
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Перевіряє інструментарій Tauri (tauri.mdc): VSCode `extensions.json` для
|
|
3
|
+
* проєктів, у яких є маркер Tauri.
|
|
4
|
+
*
|
|
5
|
+
* Cross-file gating (JS):
|
|
6
|
+
* 1. Tauri-маркер визначаємо за **будь-яким** з:
|
|
7
|
+
* - існує каталог `src-tauri/` у `cwd`;
|
|
8
|
+
* - існує файл `tauri.conf.json` у `cwd` або в workspace-пакетах;
|
|
9
|
+
* - кореневий `package.json#devDependencies` або `dependencies` містить
|
|
10
|
+
* ключ з префіксом `@tauri-apps/`.
|
|
11
|
+
* 2. Якщо маркера немає — пропустити перевірку (tauri-tooling не вимагається).
|
|
12
|
+
* 3. Інакше — для `.vscode/extensions.json` зробити FS-existence + делегувати
|
|
13
|
+
* content `rego.tauri.vscode_extensions` через `runConftestBatch`.
|
|
14
|
+
*
|
|
15
|
+
* Rego-полісі глобально у `lint-conftest` НЕ реєструється — це conditional
|
|
16
|
+
* правило. Plan B: Rego-authoritative + JS-orchestrator з `runConftestBatch`.
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, statSync } from 'node:fs'
|
|
19
|
+
import { readFile } from 'node:fs/promises'
|
|
20
|
+
|
|
21
|
+
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
22
|
+
import { runConftestBatch } from './utils/run-conftest-batch.mjs'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Чи є префікс `@tauri-apps/` у ключах `dependencies` або `devDependencies`.
|
|
26
|
+
* @param {Record<string, unknown> | null | undefined} pkg розпарсений `package.json`
|
|
27
|
+
* @returns {boolean} true, якщо знайдено хоча б один `@tauri-apps/*`
|
|
28
|
+
*/
|
|
29
|
+
function packageHasTauriDep(pkg) {
|
|
30
|
+
if (!pkg || typeof pkg !== 'object') return false
|
|
31
|
+
for (const field of ['dependencies', 'devDependencies']) {
|
|
32
|
+
const deps = /** @type {Record<string, unknown> | undefined} */ (pkg[field])
|
|
33
|
+
if (!deps || typeof deps !== 'object') continue
|
|
34
|
+
for (const name of Object.keys(deps)) {
|
|
35
|
+
if (name.startsWith('@tauri-apps/')) return true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Чи є у проєкті маркер Tauri: `src-tauri/`, `tauri.conf.json` (root або
|
|
43
|
+
* workspace), або `@tauri-apps/*` у залежностях кореневого `package.json`.
|
|
44
|
+
* @returns {Promise<boolean>} true, якщо проєкт використовує Tauri
|
|
45
|
+
*/
|
|
46
|
+
async function projectHasTauriMarker() {
|
|
47
|
+
if (existsSync('src-tauri') && statSync('src-tauri').isDirectory()) return true
|
|
48
|
+
if (existsSync('tauri.conf.json')) return true
|
|
49
|
+
if (!existsSync('package.json')) return false
|
|
50
|
+
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
51
|
+
if (packageHasTauriDep(pkg)) return true
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Перевіряє відповідність проєкту правилам tauri.mdc.
|
|
57
|
+
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
58
|
+
*/
|
|
59
|
+
export async function check() {
|
|
60
|
+
const reporter = createCheckReporter()
|
|
61
|
+
const { pass, fail } = reporter
|
|
62
|
+
|
|
63
|
+
const hasTauri = await projectHasTauriMarker()
|
|
64
|
+
if (!hasTauri) {
|
|
65
|
+
pass('Немає маркера Tauri (src-tauri/, tauri.conf.json, @tauri-apps/*) — tauri-tooling не вимагається')
|
|
66
|
+
return reporter.getExitCode()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
pass('Знайдено маркер Tauri — перевіряємо канонічні конфіги tauri.mdc')
|
|
70
|
+
|
|
71
|
+
const extPath = '.vscode/extensions.json'
|
|
72
|
+
if (!existsSync(extPath)) {
|
|
73
|
+
fail(`${extPath} не існує — створи з recommendations "tauri-apps.tauri-vscode" і "rust-lang.rust-analyzer" (tauri.mdc)`)
|
|
74
|
+
return reporter.getExitCode()
|
|
75
|
+
}
|
|
76
|
+
const violations = runConftestBatch({
|
|
77
|
+
policyDirRel: 'tauri/vscode_extensions',
|
|
78
|
+
namespace: 'tauri.vscode_extensions',
|
|
79
|
+
files: [extPath]
|
|
80
|
+
})
|
|
81
|
+
if (violations.length === 0) {
|
|
82
|
+
pass(`${extPath} відповідає tauri.vscode_extensions (rego)`)
|
|
83
|
+
} else {
|
|
84
|
+
for (const v of violations) fail(v.message)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return reporter.getExitCode()
|
|
88
|
+
}
|