@nitra/cursor 1.9.14 → 1.9.16
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 +38 -0
- package/package.json +1 -1
- package/policy/graphql/vscode_extensions/vscode_extensions.rego +20 -0
- package/policy/graphql/vscode_extensions/vscode_extensions_test.rego +34 -0
- package/policy/image_avif/package_json/package_json.rego +61 -0
- package/policy/image_avif/package_json/package_json_test.rego +69 -0
- package/policy/js_run/jsconfig/jsconfig_test.rego +88 -0
- package/policy/nginx_default_tpl/vscode_extensions/vscode_extensions.rego +16 -0
- package/policy/nginx_default_tpl/vscode_extensions/vscode_extensions_test.rego +30 -0
- package/policy/nginx_default_tpl/vscode_settings/vscode_settings.rego +36 -0
- package/policy/nginx_default_tpl/vscode_settings/vscode_settings_test.rego +53 -0
- package/policy/style_lint/vscode_extensions/vscode_extensions.rego +23 -0
- package/policy/style_lint/vscode_extensions/vscode_extensions_test.rego +39 -0
- package/policy/style_lint/vscode_settings/vscode_settings.rego +24 -0
- package/policy/style_lint/vscode_settings/vscode_settings_test.rego +49 -0
- package/scripts/check-graphql.mjs +18 -26
- package/scripts/check-js-run.mjs +18 -7
- package/scripts/check-nginx-default-tpl.mjs +28 -18
- package/scripts/check-style-lint.mjs +11 -33
- package/scripts/lint-conftest.mjs +25 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,44 @@
|
|
|
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.16] - 2026-05-13
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **5 нових rego-полісі для `.vscode/extensions.json` / `.vscode/settings.json`** (мігровано канон з .mdc у rego, прибрано JS-дублі):
|
|
12
|
+
- `style_lint.vscode_extensions` — `recommendations` має містити `stylelint.vscode-stylelint` (style-lint.mdc).
|
|
13
|
+
- `style_lint.vscode_settings` — `css.validate` / `scss.validate` / `less.validate: false`; `editor.codeActionsOnSave` свідомо не enforced (smell-test, мдс показує як рекомендацію).
|
|
14
|
+
- `graphql.vscode_extensions` — `recommendations` має містити `graphql.vscode-graphql` (graphql.mdc). НЕ реєструється глобально у `lint-conftest` TARGETS — правило conditional на наявність `gql\`…\``у джерелах; викликається з`check-graphql.mjs`через`runConftestBatch` після gql-scan.
|
|
15
|
+
- `nginx_default_tpl.vscode_extensions` — `recommendations` має містити `ahmadalli.vscode-nginx-conf` (nginx-default-tpl.mdc).
|
|
16
|
+
- `nginx_default_tpl.vscode_settings` — `editor.formatOnSave: true` і `[nginx].editor.defaultFormatter: "ahmadalli.vscode-nginx-conf"`. Обидва nginx-полісі викликаються з `check-nginx-default-tpl.mjs` через `runConftestBatch` лише після виявлення `default.conf.template`.
|
|
17
|
+
- **28 нових тестів** до пʼяти полісі: 5 (style_lint.vscode_extensions) + 6 (style_lint.vscode_settings) + 5 (graphql.vscode_extensions) + 5 (nginx_default_tpl.vscode_extensions) + 7 (nginx_default_tpl.vscode_settings). `conftest verify` — **234/234 pass** (+28).
|
|
18
|
+
|
|
19
|
+
### Removed
|
|
20
|
+
|
|
21
|
+
- **`check-style-lint.mjs::checkVscodeStylelint`** — функція повністю видалена; зміст delegated у `style_lint.vscode_extensions` і `style_lint.vscode_settings`. JSDoc-преамбулу оновлено.
|
|
22
|
+
- **`check-graphql.mjs::checkExtensionsRecommendation` — JS-копія тіла перевірки видалена:** функція тепер є тонкою обгорткою над `runConftestBatch`, делегує `graphql.vscode_extensions`. Зник дубль JSON-парсингу й порівняння `recommendations`.
|
|
23
|
+
- **`check-nginx-default-tpl.mjs::checkVscodeNginx` — JS-копія тіла перевірки видалена:** функція тепер делегує `nginx_default_tpl.vscode_extensions` і `nginx_default_tpl.vscode_settings` через `runConftestBatch`. Зник дубль перевірок `editor.formatOnSave` і `[nginx].editor.defaultFormatter` у JS.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **`lint-conftest.mjs` TARGETS — два нові глобальні entry для style-lint:** `style_lint.vscode_extensions` (`single: .vscode/extensions.json`) і `style_lint.vscode_settings` (`single: .vscode/settings.json`), обидва з `rule: 'style-lint'`. Не-style-lint проєкти не зачіпають (filter по `activeRules` з `.n-cursor.json`).
|
|
28
|
+
- **graphql/nginx — НЕ реєструються глобально у `lint-conftest`:** правила conditional на per-package умовах, які lint-conftest не вміє виразити (`gql\`…\``у джерелах для graphql; наявність`default.conf.template`для nginx). Plan B: rego-authoritative + JS-orchestrator з`runConftestBatch`.
|
|
29
|
+
|
|
30
|
+
## [1.9.15] - 2026-05-13
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **`npm/policy/js_run/jsconfig/jsconfig_test.rego` — 12 нових тестів для канону `jsconfig.json`:** rego-полісі `js_run.jsconfig` (canonical compilerOptions — `lib: ["esnext"]`, `module/moduleResolution: NodeNext`, `target: esnext`, `checkJs: false`, `include: ["src/**/*"]`) існувала, але не мала тестів і не запускалась на реальних файлах. Додано happy path + 11 негативних кейсів через `json.patch`-фікстури.
|
|
35
|
+
- **`npm/policy/image_avif/package_json/` — структурна валідація опт-аут конфігу:** новий rego-пакет `image_avif.package_json` з 3 deny-правилами для `package.json`: значення `"@nitra/minify-image"` має бути обʼєктом (якщо присутнє), `disable-avif` має бути boolean (якщо присутнє), захист від typo `disabled-avif`. Поле опційне — більшість проєктів його не мають, deny спрацьовує лише на нелегітимну форму (typo або wrong type, що тихо ламає опт-аут). +11 тестів. Зареєстровано у `lint-conftest.mjs` TARGETS з `walk` по всіх `package.json` (з фільтром `rule: "image-avif"`).
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- **`check-js-run.mjs::checkBackendJsconfigWhenSrcPresent` — структуру `jsconfig.json` тепер валідує rego через `runConftestBatch`:** замість FS-existence-only + посилання на `lint-conftest` (яке насправді не запускалось — rego не була зареєстрована глобально), JS тепер викликає rego-пакет `js_run.jsconfig` через `runConftestBatch` після того, як визначить, що пакет — backend (без `vite` у `devDependencies`) з каталогом `src/`. Це Plan B: Rego-authoritative + JS-orchestrator. Глобальна реєстрація `js_run.jsconfig` у `lint-conftest.mjs` свідомо не додавалась — rule стосується лише workspace-пакетів певної форми, що lint-conftest filter (`activeRules` на рівні репо) не вміє виразити.
|
|
40
|
+
|
|
41
|
+
### Not done (Phase 1.5 — пізніше)
|
|
42
|
+
|
|
43
|
+
- **`rego.mdc`, `tauri.mdc`** — rego-полісі для канонічних `.vscode/extensions.json` / `.vscode/settings.json` потрібен JS-orchestrator. Ці правила conditional (rego — glob `**/*.rego`, tauri — лише Tauri-проєкти), тож запускати rego безумовно на кожний `.vscode/extensions.json` дало б false-positive порушення для всіх не-rego/не-tauri проєктів. Чисте розширення rego-полісі без `check-<rule>.mjs`-orchestrator-а тут не закриває правило.
|
|
44
|
+
|
|
7
45
|
## [1.9.14] - 2026-05-13
|
|
8
46
|
|
|
9
47
|
### Added
|
package/package.json
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Перевірка `.vscode/extensions.json` для graphql (graphql.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Викликається з `check-graphql.mjs` через `runConftestBatch` лише ПІСЛЯ того,
|
|
4
|
+
# як JS виявив `gql\`…\`` tagged template literal у джерелах (умовне правило).
|
|
5
|
+
# Тому в `lint-conftest.mjs` TARGETS глобально не реєструється — інакше були б
|
|
6
|
+
# false-positive порушення на проєктах без gql.
|
|
7
|
+
#
|
|
8
|
+
# Canonical (graphql.mdc):
|
|
9
|
+
# { "recommendations": ["graphql.vscode-graphql"] }
|
|
10
|
+
#
|
|
11
|
+
# Канон задає мінімум; інші записи (від markdownlint/oxc/...) дозволені.
|
|
12
|
+
package graphql.vscode_extensions
|
|
13
|
+
|
|
14
|
+
import rego.v1
|
|
15
|
+
|
|
16
|
+
deny contains msg if {
|
|
17
|
+
recs := object.get(input, "recommendations", [])
|
|
18
|
+
not "graphql.vscode-graphql" in {r | some r in recs}
|
|
19
|
+
msg := ".vscode/extensions.json: додай у recommendations \"graphql.vscode-graphql\" (graphql.mdc)"
|
|
20
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Тести для `graphql.vscode_extensions`. Запуск:
|
|
2
|
+
# conftest verify -p npm/policy/graphql/vscode_extensions
|
|
3
|
+
package graphql.vscode_extensions_test
|
|
4
|
+
|
|
5
|
+
import rego.v1
|
|
6
|
+
|
|
7
|
+
import data.graphql.vscode_extensions
|
|
8
|
+
|
|
9
|
+
test_allow_with_required_extension if {
|
|
10
|
+
cfg := {"recommendations": ["graphql.vscode-graphql"]}
|
|
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
|
+
"graphql.vscode-graphql",
|
|
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,61 @@
|
|
|
1
|
+
# Структурна перевірка опт-аут конфігу для image-avif у `package.json` (image-avif.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Запуск (локально):
|
|
4
|
+
# conftest test <pkg>/package.json -p npm/policy/image_avif/package_json \
|
|
5
|
+
# --namespace image_avif.package_json
|
|
6
|
+
#
|
|
7
|
+
# Канонічна форма опт-ауту з image-avif.mdc:
|
|
8
|
+
# { "@nitra/minify-image": { "disable-avif": true } }
|
|
9
|
+
#
|
|
10
|
+
# Поле опційне — більшість проєктів його не мають. Полісі deny лише, якщо поле
|
|
11
|
+
# присутнє, але має нелегітимну форму: типовий typo (`disabled-avif`) або
|
|
12
|
+
# неправильний тип (`"disable-avif": "yes"`). Без цієї перевірки помилкове
|
|
13
|
+
# написання тихо повертає AVIF-генерацію всередину пакета, де її хотіли вимкнути.
|
|
14
|
+
#
|
|
15
|
+
# FS / behavior (запуск `npx @nitra/minify-image`, walk `.vue`, видалення AVIF-сиріт) — у JS.
|
|
16
|
+
#
|
|
17
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
18
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`.
|
|
19
|
+
package image_avif.package_json
|
|
20
|
+
|
|
21
|
+
import rego.v1
|
|
22
|
+
|
|
23
|
+
minify_image_field := "@nitra/minify-image"
|
|
24
|
+
|
|
25
|
+
# ── deny: значення поля має бути обʼєктом, якщо присутнє ──────────────────
|
|
26
|
+
|
|
27
|
+
deny contains msg if {
|
|
28
|
+
value := object.get(input, minify_image_field, null)
|
|
29
|
+
value != null
|
|
30
|
+
not is_object(value)
|
|
31
|
+
msg := sprintf(
|
|
32
|
+
"package.json: поле \"@nitra/minify-image\" має бути обʼєктом (зараз: %v) (image-avif.mdc)",
|
|
33
|
+
[value],
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# ── deny: відомі ключі мають правильний тип ──────────────────────────────
|
|
38
|
+
|
|
39
|
+
deny contains msg if {
|
|
40
|
+
cfg := object.get(input, minify_image_field, {})
|
|
41
|
+
is_object(cfg)
|
|
42
|
+
value := object.get(cfg, "disable-avif", null)
|
|
43
|
+
value != null
|
|
44
|
+
not is_boolean(value)
|
|
45
|
+
msg := sprintf(
|
|
46
|
+
"package.json: \"@nitra/minify-image.disable-avif\" має бути boolean (зараз: %v) (image-avif.mdc)",
|
|
47
|
+
[value],
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# ── deny: захист від typo `disabled-avif` ────────────────────────────────
|
|
52
|
+
|
|
53
|
+
deny contains msg if {
|
|
54
|
+
cfg := object.get(input, minify_image_field, {})
|
|
55
|
+
is_object(cfg)
|
|
56
|
+
"disabled-avif" in object.keys(cfg)
|
|
57
|
+
msg := concat(" ", [
|
|
58
|
+
"package.json: ключ \"@nitra/minify-image.disabled-avif\" виглядає як typo —",
|
|
59
|
+
"канонічна назва \"disable-avif\" (image-avif.mdc)",
|
|
60
|
+
])
|
|
61
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Тести для `image_avif.package_json`. Запуск:
|
|
2
|
+
# conftest verify -p npm/policy/image_avif/package_json
|
|
3
|
+
package image_avif.package_json_test
|
|
4
|
+
|
|
5
|
+
import rego.v1
|
|
6
|
+
|
|
7
|
+
import data.image_avif.package_json
|
|
8
|
+
|
|
9
|
+
# ── happy path ────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
test_allow_no_field if {
|
|
12
|
+
count(package_json.deny) == 0 with input as {"name": "x"}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
test_allow_canonical_opt_out if {
|
|
16
|
+
pkg := {"name": "x", "@nitra/minify-image": {"disable-avif": true}}
|
|
17
|
+
count(package_json.deny) == 0 with input as pkg
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
test_allow_disable_avif_false if {
|
|
21
|
+
pkg := {"name": "x", "@nitra/minify-image": {"disable-avif": false}}
|
|
22
|
+
count(package_json.deny) == 0 with input as pkg
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test_allow_empty_config if {
|
|
26
|
+
pkg := {"name": "x", "@nitra/minify-image": {}}
|
|
27
|
+
count(package_json.deny) == 0 with input as pkg
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test_allow_other_keys_inside if {
|
|
31
|
+
pkg := {"name": "x", "@nitra/minify-image": {"disable-avif": true, "future-flag": "y"}}
|
|
32
|
+
count(package_json.deny) == 0 with input as pkg
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# ── deny: тип поля ───────────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
test_deny_field_is_string if {
|
|
38
|
+
pkg := {"name": "x", "@nitra/minify-image": "disable-avif"}
|
|
39
|
+
count(package_json.deny) > 0 with input as pkg
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
test_deny_field_is_array if {
|
|
43
|
+
pkg := {"name": "x", "@nitra/minify-image": ["disable-avif"]}
|
|
44
|
+
count(package_json.deny) > 0 with input as pkg
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test_deny_field_is_boolean if {
|
|
48
|
+
pkg := {"name": "x", "@nitra/minify-image": true}
|
|
49
|
+
count(package_json.deny) > 0 with input as pkg
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# ── deny: тип disable-avif ──────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
test_deny_disable_avif_string if {
|
|
55
|
+
pkg := {"name": "x", "@nitra/minify-image": {"disable-avif": "yes"}}
|
|
56
|
+
count(package_json.deny) > 0 with input as pkg
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
test_deny_disable_avif_number if {
|
|
60
|
+
pkg := {"name": "x", "@nitra/minify-image": {"disable-avif": 1}}
|
|
61
|
+
count(package_json.deny) > 0 with input as pkg
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# ── deny: typo disabled-avif ────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
test_deny_typo_disabled_avif if {
|
|
67
|
+
pkg := {"name": "x", "@nitra/minify-image": {"disabled-avif": true}}
|
|
68
|
+
count(package_json.deny) > 0 with input as pkg
|
|
69
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Тести для `js_run.jsconfig`. Запуск:
|
|
2
|
+
# conftest verify -p npm/policy/js_run/jsconfig
|
|
3
|
+
package js_run.jsconfig_test
|
|
4
|
+
|
|
5
|
+
import rego.v1
|
|
6
|
+
|
|
7
|
+
import data.js_run.jsconfig
|
|
8
|
+
|
|
9
|
+
valid_cfg := {
|
|
10
|
+
"compilerOptions": {
|
|
11
|
+
"lib": ["esnext"],
|
|
12
|
+
"module": "NodeNext",
|
|
13
|
+
"moduleResolution": "NodeNext",
|
|
14
|
+
"target": "esnext",
|
|
15
|
+
"checkJs": false,
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*"],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
# ── happy path ────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
test_allow_canonical if {
|
|
23
|
+
count(jsconfig.deny) == 0 with input as valid_cfg
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# ── compilerOptions.lib ───────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
test_deny_lib_not_array if {
|
|
29
|
+
cfg := json.patch(valid_cfg, [{"op": "replace", "path": "/compilerOptions/lib", "value": "esnext"}])
|
|
30
|
+
count(jsconfig.deny) > 0 with input as cfg
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test_deny_lib_wrong_value if {
|
|
34
|
+
cfg := json.patch(valid_cfg, [{"op": "replace", "path": "/compilerOptions/lib", "value": ["es2022"]}])
|
|
35
|
+
count(jsconfig.deny) > 0 with input as cfg
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
test_deny_lib_missing if {
|
|
39
|
+
cfg := json.patch(valid_cfg, [{"op": "remove", "path": "/compilerOptions/lib"}])
|
|
40
|
+
count(jsconfig.deny) > 0 with input as cfg
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
# ── compilerOptions.module / moduleResolution / target / checkJs ──────────
|
|
44
|
+
|
|
45
|
+
test_deny_module_not_nodenext if {
|
|
46
|
+
cfg := json.patch(valid_cfg, [{"op": "replace", "path": "/compilerOptions/module", "value": "esnext"}])
|
|
47
|
+
count(jsconfig.deny) > 0 with input as cfg
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
test_deny_module_resolution_not_nodenext if {
|
|
51
|
+
cfg := json.patch(
|
|
52
|
+
valid_cfg,
|
|
53
|
+
[{"op": "replace", "path": "/compilerOptions/moduleResolution", "value": "node"}],
|
|
54
|
+
)
|
|
55
|
+
count(jsconfig.deny) > 0 with input as cfg
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
test_deny_target_not_esnext if {
|
|
59
|
+
cfg := json.patch(valid_cfg, [{"op": "replace", "path": "/compilerOptions/target", "value": "es2022"}])
|
|
60
|
+
count(jsconfig.deny) > 0 with input as cfg
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
test_deny_check_js_true if {
|
|
64
|
+
cfg := json.patch(valid_cfg, [{"op": "replace", "path": "/compilerOptions/checkJs", "value": true}])
|
|
65
|
+
count(jsconfig.deny) > 0 with input as cfg
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
test_deny_check_js_missing if {
|
|
69
|
+
cfg := json.patch(valid_cfg, [{"op": "remove", "path": "/compilerOptions/checkJs"}])
|
|
70
|
+
count(jsconfig.deny) > 0 with input as cfg
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# ── include ──────────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
test_deny_include_not_array if {
|
|
76
|
+
cfg := json.patch(valid_cfg, [{"op": "replace", "path": "/include", "value": "src/**/*"}])
|
|
77
|
+
count(jsconfig.deny) > 0 with input as cfg
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
test_deny_include_wrong_value if {
|
|
81
|
+
cfg := json.patch(valid_cfg, [{"op": "replace", "path": "/include", "value": ["lib/**/*"]}])
|
|
82
|
+
count(jsconfig.deny) > 0 with input as cfg
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
test_deny_include_missing if {
|
|
86
|
+
cfg := json.patch(valid_cfg, [{"op": "remove", "path": "/include"}])
|
|
87
|
+
count(jsconfig.deny) > 0 with input as cfg
|
|
88
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Перевірка `.vscode/extensions.json` для nginx-default-tpl (nginx-default-tpl.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Викликається з `check-nginx-default-tpl.mjs` через `runConftestBatch` лише
|
|
4
|
+
# ПІСЛЯ того, як JS виявив `default.conf.template` у дереві (умовне правило).
|
|
5
|
+
# Глобально у `lint-conftest.mjs` TARGETS не реєструється.
|
|
6
|
+
#
|
|
7
|
+
# Canonical: `recommendations` має містити `ahmadalli.vscode-nginx-conf`.
|
|
8
|
+
package nginx_default_tpl.vscode_extensions
|
|
9
|
+
|
|
10
|
+
import rego.v1
|
|
11
|
+
|
|
12
|
+
deny contains msg if {
|
|
13
|
+
recs := object.get(input, "recommendations", [])
|
|
14
|
+
not "ahmadalli.vscode-nginx-conf" in {r | some r in recs}
|
|
15
|
+
msg := ".vscode/extensions.json: recommendations має містити \"ahmadalli.vscode-nginx-conf\" (nginx-default-tpl.mdc)"
|
|
16
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Тести для `nginx_default_tpl.vscode_extensions`. Запуск:
|
|
2
|
+
# conftest verify -p npm/policy/nginx_default_tpl/vscode_extensions
|
|
3
|
+
package nginx_default_tpl.vscode_extensions_test
|
|
4
|
+
|
|
5
|
+
import rego.v1
|
|
6
|
+
|
|
7
|
+
import data.nginx_default_tpl.vscode_extensions
|
|
8
|
+
|
|
9
|
+
test_allow_with_required_extension if {
|
|
10
|
+
cfg := {"recommendations": ["ahmadalli.vscode-nginx-conf"]}
|
|
11
|
+
count(vscode_extensions.deny) == 0 with input as cfg
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
test_allow_with_additional_extensions if {
|
|
15
|
+
cfg := {"recommendations": ["dbaeumer.vscode-eslint", "ahmadalli.vscode-nginx-conf"]}
|
|
16
|
+
count(vscode_extensions.deny) == 0 with input as cfg
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
test_deny_missing_extension if {
|
|
20
|
+
cfg := {"recommendations": ["dbaeumer.vscode-eslint"]}
|
|
21
|
+
count(vscode_extensions.deny) > 0 with input as cfg
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
test_deny_empty_recommendations if {
|
|
25
|
+
count(vscode_extensions.deny) > 0 with input as {"recommendations": []}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test_deny_no_recommendations_field if {
|
|
29
|
+
count(vscode_extensions.deny) > 0 with input as {}
|
|
30
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Перевірка `.vscode/settings.json` для nginx-default-tpl (nginx-default-tpl.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Викликається з `check-nginx-default-tpl.mjs` через `runConftestBatch` лише
|
|
4
|
+
# ПІСЛЯ того, як JS виявив `default.conf.template`. Глобально у `lint-conftest`
|
|
5
|
+
# не реєструється.
|
|
6
|
+
#
|
|
7
|
+
# Canonical:
|
|
8
|
+
# { "editor.formatOnSave": true,
|
|
9
|
+
# "[nginx]": { "editor.defaultFormatter": "ahmadalli.vscode-nginx-conf" } }
|
|
10
|
+
package nginx_default_tpl.vscode_settings
|
|
11
|
+
|
|
12
|
+
import rego.v1
|
|
13
|
+
|
|
14
|
+
deny contains msg if {
|
|
15
|
+
object.get(input, "editor.formatOnSave", null) != true
|
|
16
|
+
msg := ".vscode/settings.json: \"editor.formatOnSave\" має бути true (nginx-default-tpl.mdc)"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
deny contains msg if {
|
|
20
|
+
nginx_block := object.get(input, "[nginx]", {})
|
|
21
|
+
not is_object(nginx_block)
|
|
22
|
+
msg := concat(" ", [
|
|
23
|
+
".vscode/settings.json: \"[nginx]\" має бути обʼєктом з",
|
|
24
|
+
"\"editor.defaultFormatter\": \"ahmadalli.vscode-nginx-conf\" (nginx-default-tpl.mdc)",
|
|
25
|
+
])
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
deny contains msg if {
|
|
29
|
+
nginx_block := object.get(input, "[nginx]", {})
|
|
30
|
+
is_object(nginx_block)
|
|
31
|
+
object.get(nginx_block, "editor.defaultFormatter", null) != "ahmadalli.vscode-nginx-conf"
|
|
32
|
+
msg := concat(" ", [
|
|
33
|
+
".vscode/settings.json: \"[nginx].editor.defaultFormatter\" має бути",
|
|
34
|
+
"\"ahmadalli.vscode-nginx-conf\" (nginx-default-tpl.mdc)",
|
|
35
|
+
])
|
|
36
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Тести для `nginx_default_tpl.vscode_settings`. Запуск:
|
|
2
|
+
# conftest verify -p npm/policy/nginx_default_tpl/vscode_settings
|
|
3
|
+
package nginx_default_tpl.vscode_settings_test
|
|
4
|
+
|
|
5
|
+
import rego.v1
|
|
6
|
+
|
|
7
|
+
import data.nginx_default_tpl.vscode_settings
|
|
8
|
+
|
|
9
|
+
valid_cfg := {
|
|
10
|
+
"editor.formatOnSave": true,
|
|
11
|
+
"[nginx]": {"editor.defaultFormatter": "ahmadalli.vscode-nginx-conf"},
|
|
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_keys if {
|
|
19
|
+
cfg := json.patch(valid_cfg, [{
|
|
20
|
+
"op": "add",
|
|
21
|
+
"path": "/[javascript]",
|
|
22
|
+
"value": {"editor.defaultFormatter": "oxc.oxc-vscode"},
|
|
23
|
+
}])
|
|
24
|
+
count(vscode_settings.deny) == 0 with input as cfg
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test_deny_format_on_save_false if {
|
|
28
|
+
cfg := json.patch(valid_cfg, [{"op": "replace", "path": "/editor.formatOnSave", "value": false}])
|
|
29
|
+
count(vscode_settings.deny) > 0 with input as cfg
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
test_deny_format_on_save_missing if {
|
|
33
|
+
cfg := json.patch(valid_cfg, [{"op": "remove", "path": "/editor.formatOnSave"}])
|
|
34
|
+
count(vscode_settings.deny) > 0 with input as cfg
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
test_deny_nginx_block_missing if {
|
|
38
|
+
cfg := json.patch(valid_cfg, [{"op": "remove", "path": "/[nginx]"}])
|
|
39
|
+
count(vscode_settings.deny) > 0 with input as cfg
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
test_deny_nginx_block_wrong_type if {
|
|
43
|
+
cfg := json.patch(valid_cfg, [{"op": "replace", "path": "/[nginx]", "value": "ahmadalli.vscode-nginx-conf"}])
|
|
44
|
+
count(vscode_settings.deny) > 0 with input as cfg
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test_deny_nginx_wrong_formatter if {
|
|
48
|
+
cfg := json.patch(
|
|
49
|
+
valid_cfg,
|
|
50
|
+
[{"op": "replace", "path": "/[nginx]/editor.defaultFormatter", "value": "ms-vscode.cpptools"}],
|
|
51
|
+
)
|
|
52
|
+
count(vscode_settings.deny) > 0 with input as cfg
|
|
53
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Перевірка `.vscode/extensions.json` для style-lint (style-lint.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Запуск (локально):
|
|
4
|
+
# conftest test .vscode/extensions.json -p npm/policy/style_lint/vscode_extensions \
|
|
5
|
+
# --namespace style_lint.vscode_extensions
|
|
6
|
+
#
|
|
7
|
+
# Canonical (style-lint.mdc):
|
|
8
|
+
# { "recommendations": ["stylelint.vscode-stylelint"] }
|
|
9
|
+
#
|
|
10
|
+
# Канон задає мінімум — `recommendations` має МІСТИТИ `stylelint.vscode-stylelint`;
|
|
11
|
+
# додаткові записи (від інших правил — markdownlint, oxc тощо) дозволені.
|
|
12
|
+
#
|
|
13
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
14
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`.
|
|
15
|
+
package style_lint.vscode_extensions
|
|
16
|
+
|
|
17
|
+
import rego.v1
|
|
18
|
+
|
|
19
|
+
deny contains msg if {
|
|
20
|
+
recs := object.get(input, "recommendations", [])
|
|
21
|
+
not "stylelint.vscode-stylelint" in {r | some r in recs}
|
|
22
|
+
msg := ".vscode/extensions.json: recommendations має містити \"stylelint.vscode-stylelint\" (style-lint.mdc)"
|
|
23
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Тести для `style_lint.vscode_extensions`. Запуск:
|
|
2
|
+
# conftest verify -p npm/policy/style_lint/vscode_extensions
|
|
3
|
+
package style_lint.vscode_extensions_test
|
|
4
|
+
|
|
5
|
+
import rego.v1
|
|
6
|
+
|
|
7
|
+
import data.style_lint.vscode_extensions
|
|
8
|
+
|
|
9
|
+
# ── happy path ────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
test_allow_with_required_extension if {
|
|
12
|
+
cfg := {"recommendations": ["stylelint.vscode-stylelint"]}
|
|
13
|
+
count(vscode_extensions.deny) == 0 with input as cfg
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test_allow_with_additional_extensions if {
|
|
17
|
+
cfg := {"recommendations": [
|
|
18
|
+
"dbaeumer.vscode-eslint",
|
|
19
|
+
"stylelint.vscode-stylelint",
|
|
20
|
+
"oxc.oxc-vscode",
|
|
21
|
+
"DavidAnson.vscode-markdownlint",
|
|
22
|
+
]}
|
|
23
|
+
count(vscode_extensions.deny) == 0 with input as cfg
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# ── deny ──────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
test_deny_missing_extension if {
|
|
29
|
+
cfg := {"recommendations": ["dbaeumer.vscode-eslint"]}
|
|
30
|
+
count(vscode_extensions.deny) > 0 with input as cfg
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test_deny_empty_recommendations if {
|
|
34
|
+
count(vscode_extensions.deny) > 0 with input as {"recommendations": []}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
test_deny_no_recommendations_field if {
|
|
38
|
+
count(vscode_extensions.deny) > 0 with input as {}
|
|
39
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Перевірка `.vscode/settings.json` для style-lint (style-lint.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Запуск (локально):
|
|
4
|
+
# conftest test .vscode/settings.json -p npm/policy/style_lint/vscode_settings \
|
|
5
|
+
# --namespace style_lint.vscode_settings
|
|
6
|
+
#
|
|
7
|
+
# Canonical (style-lint.mdc): вимкнути вбудовану валідацію CSS/SCSS/Less, щоб
|
|
8
|
+
# stylelint був єдиним джерелом діагностики.
|
|
9
|
+
# { "css.validate": false, "less.validate": false, "scss.validate": false }
|
|
10
|
+
#
|
|
11
|
+
# `editor.codeActionsOnSave` у каноні є, але це smell-test — навмисно не deny,
|
|
12
|
+
# щоб не падати на пакетах, які мають свій codeActionsOnSave-конфіг.
|
|
13
|
+
#
|
|
14
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
15
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`.
|
|
16
|
+
package style_lint.vscode_settings
|
|
17
|
+
|
|
18
|
+
import rego.v1
|
|
19
|
+
|
|
20
|
+
deny contains msg if {
|
|
21
|
+
some key in {"css.validate", "less.validate", "scss.validate"}
|
|
22
|
+
object.get(input, key, null) != false
|
|
23
|
+
msg := sprintf(".vscode/settings.json: \"%s\" має бути false (style-lint.mdc)", [key])
|
|
24
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Тести для `style_lint.vscode_settings`. Запуск:
|
|
2
|
+
# conftest verify -p npm/policy/style_lint/vscode_settings
|
|
3
|
+
package style_lint.vscode_settings_test
|
|
4
|
+
|
|
5
|
+
import rego.v1
|
|
6
|
+
|
|
7
|
+
import data.style_lint.vscode_settings
|
|
8
|
+
|
|
9
|
+
valid_cfg := {
|
|
10
|
+
"css.validate": false,
|
|
11
|
+
"less.validate": false,
|
|
12
|
+
"scss.validate": false,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
# ── happy path ────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
test_allow_canonical if {
|
|
18
|
+
count(vscode_settings.deny) == 0 with input as valid_cfg
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
test_allow_with_additional_keys if {
|
|
22
|
+
cfg := json.patch(valid_cfg, [{
|
|
23
|
+
"op": "add",
|
|
24
|
+
"path": "/editor.codeActionsOnSave",
|
|
25
|
+
"value": {"source.fixAll": "explicit"},
|
|
26
|
+
}])
|
|
27
|
+
count(vscode_settings.deny) == 0 with input as cfg
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# ── deny ──────────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
test_deny_css_validate_true if {
|
|
33
|
+
cfg := json.patch(valid_cfg, [{"op": "replace", "path": "/css.validate", "value": true}])
|
|
34
|
+
count(vscode_settings.deny) > 0 with input as cfg
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
test_deny_scss_validate_missing if {
|
|
38
|
+
cfg := json.patch(valid_cfg, [{"op": "remove", "path": "/scss.validate"}])
|
|
39
|
+
count(vscode_settings.deny) > 0 with input as cfg
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
test_deny_less_validate_string if {
|
|
43
|
+
cfg := json.patch(valid_cfg, [{"op": "replace", "path": "/less.validate", "value": "off"}])
|
|
44
|
+
count(vscode_settings.deny) > 0 with input as cfg
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test_deny_empty_object if {
|
|
48
|
+
count(vscode_settings.deny) > 0 with input as {}
|
|
49
|
+
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
sourceFileHasGqlTaggedTemplate
|
|
18
18
|
} from './utils/graphql-gql-scan.mjs'
|
|
19
19
|
import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
|
|
20
|
+
import { runConftestBatch } from './utils/run-conftest-batch.mjs'
|
|
20
21
|
import { walkDir } from './utils/walkDir.mjs'
|
|
21
22
|
|
|
22
23
|
/** Очікуваний файл GraphQL Config у корені (graphql.mdc). */
|
|
@@ -68,38 +69,29 @@ async function collectGqlHits(root, candidates) {
|
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
/**
|
|
71
|
-
*
|
|
72
|
+
* Делегує валідацію `.vscode/extensions.json` rego-пакету `graphql.vscode_extensions`
|
|
73
|
+
* через `runConftestBatch`. Викликається лише після того, як JS виявив `gql` у дереві
|
|
74
|
+
* (умовне правило — без gql цей крок не запускається).
|
|
72
75
|
* @param {(msg: string) => void} pass success-репортер
|
|
73
76
|
* @param {(msg: string) => void} fail fail-репортер
|
|
74
|
-
* @returns {
|
|
77
|
+
* @returns {void}
|
|
75
78
|
*/
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
79
|
+
function checkExtensionsRecommendation(pass, fail) {
|
|
80
|
+
const path = '.vscode/extensions.json'
|
|
81
|
+
if (!existsSync(path)) {
|
|
82
|
+
fail(`${path} не існує — створи файл і додай у recommendations ${REQUIRED_GRAPHQL_VSCODE_EXTENSION} (graphql.mdc)`)
|
|
81
83
|
return
|
|
82
84
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
85
|
+
const violations = runConftestBatch({
|
|
86
|
+
policyDirRel: 'graphql/vscode_extensions',
|
|
87
|
+
namespace: 'graphql.vscode_extensions',
|
|
88
|
+
files: [path]
|
|
89
|
+
})
|
|
90
|
+
if (violations.length === 0) {
|
|
91
|
+
pass(`${path} відповідає graphql.vscode_extensions (rego)`)
|
|
89
92
|
return
|
|
90
93
|
}
|
|
91
|
-
|
|
92
|
-
const rec = ext.recommendations
|
|
93
|
-
if (!Array.isArray(rec)) {
|
|
94
|
-
fail('.vscode/extensions.json: поле recommendations має бути масивом')
|
|
95
|
-
return
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (rec.includes(REQUIRED_GRAPHQL_VSCODE_EXTENSION)) {
|
|
99
|
-
pass(`.vscode/extensions.json: є ${REQUIRED_GRAPHQL_VSCODE_EXTENSION}`)
|
|
100
|
-
} else {
|
|
101
|
-
fail(`.vscode/extensions.json: додай у recommendations "${REQUIRED_GRAPHQL_VSCODE_EXTENSION}" (graphql.mdc)`)
|
|
102
|
-
}
|
|
94
|
+
for (const v of violations) fail(v.message)
|
|
103
95
|
}
|
|
104
96
|
|
|
105
97
|
/**
|
|
@@ -133,7 +125,7 @@ export async function check() {
|
|
|
133
125
|
)
|
|
134
126
|
}
|
|
135
127
|
|
|
136
|
-
|
|
128
|
+
checkExtensionsRecommendation(pass, fail)
|
|
137
129
|
|
|
138
130
|
return reporter.getExitCode()
|
|
139
131
|
}
|
package/scripts/check-js-run.mjs
CHANGED
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
} from './utils/bunyan-imports.mjs'
|
|
41
41
|
import { findUncheckedProcessEnvInText, isCheckEnvScanSourceFile } from './utils/check-env-scan.mjs'
|
|
42
42
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
43
|
+
import { runConftestBatch } from './utils/run-conftest-batch.mjs'
|
|
43
44
|
import { findConnFileRuleViolations, isConnFileRulesSourceFile } from './utils/conn-file-rules.mjs'
|
|
44
45
|
import {
|
|
45
46
|
findConnFactoryImportsInText,
|
|
@@ -67,10 +68,11 @@ function backendPackageHasSrcDir(absPackageRoot) {
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
/**
|
|
70
|
-
* FS-existence
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
71
|
+
* FS-existence + структурна валідація `jsconfig.json` у backend-пакеті з
|
|
72
|
+
* каталогом `src/`. Структуру (canonical `compilerOptions` і `include`)
|
|
73
|
+
* делегуємо у rego-пакет `js_run.jsconfig` через `runConftestBatch` — Plan B:
|
|
74
|
+
* Rego-authoritative, JS оркеструє per-package gate (frontend з `vite` сюди
|
|
75
|
+
* взагалі не доходить, бо викликається лише з backend-гілки).
|
|
74
76
|
* @param {string} rootDir відносний шлях workspace
|
|
75
77
|
* @param {string} absPackageRoot абсолютний корінь пакета
|
|
76
78
|
* @param {string} label префікс `[pkg] `
|
|
@@ -82,14 +84,23 @@ function checkBackendJsconfigWhenSrcPresent(rootDir, absPackageRoot, label, fail
|
|
|
82
84
|
if (!backendPackageHasSrcDir(absPackageRoot)) return
|
|
83
85
|
|
|
84
86
|
const jcPath = join(rootDir, 'jsconfig.json')
|
|
85
|
-
if (existsSync(jcPath)) {
|
|
86
|
-
passFn(`${label}jsconfig.json є (структуру перевіряє bun run lint-conftest → js_run.jsconfig)`)
|
|
87
|
-
} else {
|
|
87
|
+
if (!existsSync(jcPath)) {
|
|
88
88
|
fail(
|
|
89
89
|
`${label}є каталог src/, але немає jsconfig.json — додай канонічний файл з js-run.mdc ` +
|
|
90
90
|
`(NodeNext, include: src/**/*).`
|
|
91
91
|
)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
const violations = runConftestBatch({
|
|
95
|
+
policyDirRel: 'js_run/jsconfig',
|
|
96
|
+
namespace: 'js_run.jsconfig',
|
|
97
|
+
files: [jcPath]
|
|
98
|
+
})
|
|
99
|
+
if (violations.length === 0) {
|
|
100
|
+
passFn(`${label}jsconfig.json відповідає js_run.jsconfig (rego)`)
|
|
101
|
+
return
|
|
92
102
|
}
|
|
103
|
+
for (const v of violations) fail(`${label}${v.message}`)
|
|
93
104
|
}
|
|
94
105
|
|
|
95
106
|
/**
|
|
@@ -20,6 +20,7 @@ import { basename, dirname, join, relative } from 'node:path'
|
|
|
20
20
|
import { findDockerfilePaths } from './check-docker.mjs'
|
|
21
21
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
22
22
|
import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
|
|
23
|
+
import { runConftestBatch } from './utils/run-conftest-batch.mjs'
|
|
23
24
|
import { walkDir } from './utils/walkDir.mjs'
|
|
24
25
|
|
|
25
26
|
const LINE_SPLIT_RE = /\r?\n/u
|
|
@@ -350,36 +351,45 @@ async function checkDockerfiles(root, ignorePaths, passFn, failFn) {
|
|
|
350
351
|
}
|
|
351
352
|
|
|
352
353
|
/**
|
|
353
|
-
*
|
|
354
|
+
* Делегує валідацію `.vscode/extensions.json` і `.vscode/settings.json` rego-пакетам
|
|
355
|
+
* `nginx_default_tpl.vscode_extensions` і `nginx_default_tpl.vscode_settings`
|
|
356
|
+
* через `runConftestBatch`. Викликається лише після того, як JS виявив
|
|
357
|
+
* `default.conf.template` (умовне правило — без шаблона цей крок не запускається).
|
|
354
358
|
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
355
359
|
* @param {(msg: string) => void} failFn callback при помилці
|
|
360
|
+
* @returns {void}
|
|
356
361
|
*/
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
+
function checkVscodeNginx(passFn, failFn) {
|
|
363
|
+
const extPath = '.vscode/extensions.json'
|
|
364
|
+
if (existsSync(extPath)) {
|
|
365
|
+
const violations = runConftestBatch({
|
|
366
|
+
policyDirRel: 'nginx_default_tpl/vscode_extensions',
|
|
367
|
+
namespace: 'nginx_default_tpl.vscode_extensions',
|
|
368
|
+
files: [extPath]
|
|
369
|
+
})
|
|
370
|
+
if (violations.length === 0) {
|
|
371
|
+
passFn(`${extPath} відповідає nginx_default_tpl.vscode_extensions (rego)`)
|
|
362
372
|
} else {
|
|
363
|
-
|
|
373
|
+
for (const v of violations) failFn(v.message)
|
|
364
374
|
}
|
|
365
375
|
} else {
|
|
366
376
|
failFn('Очікується .vscode/extensions.json з ahmadalli.vscode-nginx-conf (див. nginx-default-tpl.mdc)')
|
|
367
377
|
}
|
|
368
378
|
|
|
369
|
-
|
|
379
|
+
const setPath = '.vscode/settings.json'
|
|
380
|
+
if (!existsSync(setPath)) {
|
|
370
381
|
failFn('Очікується .vscode/settings.json з форматером nginx і formatOnSave (див. nginx-default-tpl.mdc)')
|
|
371
382
|
return
|
|
372
383
|
}
|
|
373
|
-
const
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
passFn('settings.json: [nginx] defaultFormatter налаштовано')
|
|
384
|
+
const violations = runConftestBatch({
|
|
385
|
+
policyDirRel: 'nginx_default_tpl/vscode_settings',
|
|
386
|
+
namespace: 'nginx_default_tpl.vscode_settings',
|
|
387
|
+
files: [setPath]
|
|
388
|
+
})
|
|
389
|
+
if (violations.length === 0) {
|
|
390
|
+
passFn(`${setPath} відповідає nginx_default_tpl.vscode_settings (rego)`)
|
|
381
391
|
} else {
|
|
382
|
-
|
|
392
|
+
for (const v of violations) failFn(v.message)
|
|
383
393
|
}
|
|
384
394
|
}
|
|
385
395
|
|
|
@@ -416,7 +426,7 @@ export async function check() {
|
|
|
416
426
|
}
|
|
417
427
|
|
|
418
428
|
await checkDockerfiles(root, ignorePaths, pass, fail)
|
|
419
|
-
|
|
429
|
+
checkVscodeNginx(pass, fail)
|
|
420
430
|
|
|
421
431
|
return reporter.getExitCode()
|
|
422
432
|
}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Перевіряє CSS/SCSS лінт за правилом style-lint.mdc.
|
|
3
3
|
*
|
|
4
|
-
* **Що тут лишилося** (FS /
|
|
4
|
+
* **Що тут лишилося** (FS / cross-file — не покривається conftest):
|
|
5
5
|
* - наявність зовнішнього файлу конфігу stylelint (`.stylelintrc.*`,
|
|
6
6
|
* `stylelint.config.js`) як альтернатива полю `stylelint` у `package.json`
|
|
7
7
|
* (cross-file: треба знати, чи є поле, чи немає);
|
|
8
|
-
* - `.stylelintignore` у
|
|
9
|
-
* - `.vscode/extensions.json` recommendation `stylelint.vscode-stylelint`;
|
|
10
|
-
* - `.vscode/settings.json` `css.validate` / `scss.validate` / `less.validate: false`.
|
|
8
|
+
* - `.stylelintignore` у корені.
|
|
11
9
|
*
|
|
12
10
|
* **Що покрила Rego** (`bun run lint-conftest`):
|
|
13
11
|
* - `npm/policy/style_lint/package_json/` — скрипт `lint-style` через `npx stylelint`,
|
|
14
12
|
* `@nitra/stylelint-config` у `devDependencies`, поле `stylelint.extends`;
|
|
15
|
-
* - `npm/policy/style_lint/lint_style_yml/` — `npx stylelint` у `run` workflow
|
|
13
|
+
* - `npm/policy/style_lint/lint_style_yml/` — `npx stylelint` у `run` workflow;
|
|
14
|
+
* - `npm/policy/style_lint/vscode_extensions/` — `stylelint.vscode-stylelint`
|
|
15
|
+
* у `recommendations` `.vscode/extensions.json`;
|
|
16
|
+
* - `npm/policy/style_lint/vscode_settings/` — `css.validate`/`scss.validate`/
|
|
17
|
+
* `less.validate: false` у `.vscode/settings.json`.
|
|
16
18
|
*/
|
|
17
19
|
import { existsSync } from 'node:fs'
|
|
18
20
|
import { readFile } from 'node:fs/promises'
|
|
@@ -39,32 +41,10 @@ async function checkStylelintConfigPresence(reporter) {
|
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const { pass, fail } = reporter
|
|
47
|
-
if (existsSync('.vscode/extensions.json')) {
|
|
48
|
-
const ext = JSON.parse(await readFile('.vscode/extensions.json', 'utf8'))
|
|
49
|
-
if (ext.recommendations?.includes('stylelint.vscode-stylelint')) {
|
|
50
|
-
pass('extensions.json містить stylelint.vscode-stylelint')
|
|
51
|
-
} else {
|
|
52
|
-
fail('extensions.json не містить stylelint.vscode-stylelint')
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
fail('.vscode/extensions.json не існує')
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (!existsSync('.vscode/settings.json')) return
|
|
59
|
-
const s = JSON.parse(await readFile('.vscode/settings.json', 'utf8'))
|
|
60
|
-
for (const key of ['css.validate', 'scss.validate', 'less.validate']) {
|
|
61
|
-
if (s[key] === false) {
|
|
62
|
-
pass(`${key} вимкнено`)
|
|
63
|
-
} else {
|
|
64
|
-
fail(`settings.json: ${key} має бути false`)
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
44
|
+
// `.vscode/extensions.json` (`stylelint.vscode-stylelint`) і `.vscode/settings.json`
|
|
45
|
+
// (`css.validate`/`scss.validate`/`less.validate: false`) — у rego-пакетах
|
|
46
|
+
// `style_lint.vscode_extensions` і `style_lint.vscode_settings`, прогоняє
|
|
47
|
+
// `bun run lint-conftest`. JS-копії видалено, щоб не було двох джерел істини.
|
|
68
48
|
|
|
69
49
|
/**
|
|
70
50
|
* Перевіряє відповідність проєкту правилам style-lint.mdc
|
|
@@ -89,7 +69,5 @@ export async function check() {
|
|
|
89
69
|
fail(`${wfPath} не існує — створи його`)
|
|
90
70
|
}
|
|
91
71
|
|
|
92
|
-
await checkVscodeStylelint(reporter)
|
|
93
|
-
|
|
94
72
|
return reporter.getExitCode()
|
|
95
73
|
}
|
|
@@ -112,6 +112,18 @@ const TARGETS = [
|
|
|
112
112
|
rule: 'style-lint',
|
|
113
113
|
single: '.github/workflows/lint-style.yml'
|
|
114
114
|
},
|
|
115
|
+
{
|
|
116
|
+
namespace: 'style_lint.vscode_extensions',
|
|
117
|
+
policyDir: 'style_lint',
|
|
118
|
+
rule: 'style-lint',
|
|
119
|
+
single: '.vscode/extensions.json'
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
namespace: 'style_lint.vscode_settings',
|
|
123
|
+
policyDir: 'style_lint',
|
|
124
|
+
rule: 'style-lint',
|
|
125
|
+
single: '.vscode/settings.json'
|
|
126
|
+
},
|
|
115
127
|
|
|
116
128
|
// ── php ─────────────────────────────────────────────────────────────────
|
|
117
129
|
{ namespace: 'php.package_json', policyDir: 'php', rule: 'php', single: 'package.json' },
|
|
@@ -157,13 +169,19 @@ const TARGETS = [
|
|
|
157
169
|
single: '.github/workflows/lint-js.yml'
|
|
158
170
|
},
|
|
159
171
|
|
|
160
|
-
// ── image-compress / capacitor
|
|
172
|
+
// ── image-compress / image-avif / capacitor ─────────────────────────────
|
|
161
173
|
{
|
|
162
174
|
namespace: 'image_compress.package_json',
|
|
163
175
|
policyDir: 'image_compress',
|
|
164
176
|
rule: 'image-compress',
|
|
165
177
|
single: 'package.json'
|
|
166
178
|
},
|
|
179
|
+
{
|
|
180
|
+
namespace: 'image_avif.package_json',
|
|
181
|
+
policyDir: 'image_avif',
|
|
182
|
+
rule: 'image-avif',
|
|
183
|
+
walk: { match: rel => rel.endsWith('/package.json') || rel === 'package.json' }
|
|
184
|
+
},
|
|
167
185
|
{
|
|
168
186
|
namespace: 'capacitor.package_json',
|
|
169
187
|
policyDir: 'capacitor',
|
|
@@ -214,6 +232,12 @@ const TARGETS = [
|
|
|
214
232
|
rule: 'js-run',
|
|
215
233
|
walk: { match: rel => rel.endsWith('/package.json') || rel === 'package.json' }
|
|
216
234
|
},
|
|
235
|
+
// `js_run.jsconfig` НЕ реєструємо тут — `jsconfig.json` має канонічну структуру
|
|
236
|
+
// лише для backend-пакетів (без `vite` у `devDependencies`) з каталогом `src/`,
|
|
237
|
+
// а lint-conftest фільтрує лише по `activeRules` на рівні репозиторію — не
|
|
238
|
+
// вміє пропустити окремий workspace-пакет за наявністю `vite`. Тому валідація
|
|
239
|
+
// структури делегується з `check-js-run.mjs` через `runConftestBatch` після
|
|
240
|
+
// того, як JS визначить, що пакет — backend з `src/`.
|
|
217
241
|
{
|
|
218
242
|
namespace: 'vue.package_json',
|
|
219
243
|
policyDir: 'vue',
|