@nitra/cursor 1.8.206 → 1.8.207
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/abie/health_check_policy/health_check_policy.rego +73 -0
- package/policy/abie/http_route_base/http_route_base.rego +45 -0
- package/policy/adr/settings_json/settings_json.rego +31 -0
- package/policy/adr/settings_local_json/settings_local_json.rego +28 -0
- package/policy/bun/bunfig/bunfig.rego +33 -0
- package/policy/bun/package_json/package_json.rego +94 -0
- package/policy/capacitor/package_json/package_json.rego +45 -0
- package/policy/ga/clean_ga_workflows/clean_ga_workflows.rego +0 -26
- package/policy/ga/clean_merged_branch/clean_merged_branch.rego +0 -25
- package/policy/ga/git_ai/git_ai.rego +0 -26
- package/policy/ga/lint_ga/lint_ga.rego +0 -26
- package/policy/ga/workflow_common/workflow_common.rego +161 -0
- package/policy/graphql/package_json/package_json.rego +35 -0
- package/policy/hasura/svc_hl/svc_hl.rego +27 -0
- package/policy/image_compress/package_json/package_json.rego +94 -0
- package/policy/js_bun_db/package_json/package_json.rego +28 -0
- package/policy/js_lint/lint_js_yml/lint_js_yml.rego +98 -0
- package/policy/js_lint/package_json/package_json.rego +137 -0
- package/policy/js_mssql/package_json/package_json.rego +57 -0
- package/policy/js_run/configmap/configmap.rego +45 -0
- package/policy/js_run/jsconfig/jsconfig.rego +66 -0
- package/policy/js_run/package_json/package_json.rego +31 -0
- package/policy/k8s/manifest/manifest.rego +130 -0
- package/policy/npm_module/emit_types_config/emit_types_config.rego +37 -0
- package/policy/npm_module/npm_package_json/npm_package_json.rego +55 -0
- package/policy/npm_module/npm_publish_yml/npm_publish_yml.rego +79 -0
- package/policy/npm_module/root_package_json/root_package_json.rego +28 -0
- package/policy/php/lint_php_yml/lint_php_yml.rego +32 -0
- package/policy/php/package_json/package_json.rego +19 -0
- package/policy/style_lint/lint_style_yml/lint_style_yml.rego +35 -0
- package/policy/style_lint/package_json/package_json.rego +49 -0
- package/policy/text/cspell/cspell.rego +91 -0
- package/policy/text/markdownlint/markdownlint.rego +21 -0
- package/policy/text/oxfmtrc/oxfmtrc.rego +90 -0
- package/policy/text/package_json/package_json.rego +88 -0
- package/policy/vue/package_json/package_json.rego +54 -0
- package/scripts/check-adr.mjs +3 -2
- package/scripts/check-bun.mjs +21 -117
- package/scripts/check-graphql.mjs +6 -45
- package/scripts/check-hasura.mjs +2 -3
- package/scripts/check-image-avif.mjs +3 -3
- package/scripts/check-image-compress.mjs +25 -132
- package/scripts/check-js-bun-db.mjs +3 -50
- package/scripts/check-js-run.mjs +8 -8
- package/scripts/check-k8s.mjs +4 -4
- package/scripts/check-npm-module.mjs +17 -8
- package/scripts/check-php.mjs +16 -51
- package/scripts/check-style-lint.mjs +28 -52
- package/scripts/check-text.mjs +47 -219
- package/scripts/check-vue.mjs +3 -16
- package/scripts/lint-conftest.mjs +351 -0
- package/scripts/lint-ga.mjs +39 -2
- package/scripts/run-shellcheck-text.mjs +2 -2
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Порт перевірок `.cspell.json` з `npm/scripts/check-text.mjs` (text.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Запуск (локально):
|
|
4
|
+
# conftest test .cspell.json -p npm/policy/text --namespace text.cspell
|
|
5
|
+
#
|
|
6
|
+
# Перевіряє: `version: "0.2"`, наявність `language`, імпорт `@nitra/cspell-dict`,
|
|
7
|
+
# відсутність прямих імпортів `@cspell/dict-*`, обовʼязкові glob-и в `ignorePaths`
|
|
8
|
+
# (text.mdc).
|
|
9
|
+
#
|
|
10
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
11
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
12
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
13
|
+
package text.cspell
|
|
14
|
+
|
|
15
|
+
import rego.v1
|
|
16
|
+
|
|
17
|
+
# ── Очікувані значення ─────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
# Канонічні `ignorePaths` з text.mdc — кожен має бути присутнім.
|
|
20
|
+
required_ignore_paths := {
|
|
21
|
+
"**/node_modules/**",
|
|
22
|
+
"**/vscode-extension/**",
|
|
23
|
+
"**/.git/**",
|
|
24
|
+
".vscode",
|
|
25
|
+
"report",
|
|
26
|
+
"*.svg",
|
|
27
|
+
"**/k8s/**/*.yaml",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
nitra_cspell_dict_marker := "@nitra/cspell-dict"
|
|
31
|
+
|
|
32
|
+
legacy_dict_marker := "@cspell/dict-"
|
|
33
|
+
|
|
34
|
+
# Шаблон повідомлення про заборонений імпорт `@cspell/dict-*` — через `concat`
|
|
35
|
+
# для regal style/line-length.
|
|
36
|
+
legacy_dict_import_template := concat(" ", [
|
|
37
|
+
".cspell.json не має імпортувати @cspell/dict-* —",
|
|
38
|
+
"використовуй лише @nitra/cspell-dict (знайдено: %s) (text.mdc)",
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
# ── deny: version / language ──────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
deny contains msg if {
|
|
44
|
+
object.get(input, "version", null) != "0.2"
|
|
45
|
+
msg := ".cspell.json: version має бути \"0.2\" (text.mdc)"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
deny contains msg if {
|
|
49
|
+
not object.get(input, "language", false)
|
|
50
|
+
msg := ".cspell.json: відсутнє поле language (text.mdc)"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# ── deny: imports ─────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
deny contains msg if {
|
|
56
|
+
imports := object.get(input, "import", [])
|
|
57
|
+
is_array(imports)
|
|
58
|
+
not has_nitra_dict_import(imports)
|
|
59
|
+
msg := ".cspell.json не імпортує @nitra/cspell-dict/cspell-ext.json (text.mdc)"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
deny contains msg if {
|
|
63
|
+
imports := object.get(input, "import", [])
|
|
64
|
+
is_array(imports)
|
|
65
|
+
some imp in imports
|
|
66
|
+
is_string(imp)
|
|
67
|
+
contains(imp, legacy_dict_marker)
|
|
68
|
+
msg := sprintf(legacy_dict_import_template, [imp])
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# ── deny: ignorePaths ─────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
deny contains msg if {
|
|
74
|
+
not is_array(object.get(input, "ignorePaths", null))
|
|
75
|
+
msg := ".cspell.json: додай масив ignorePaths з канонічними glob-ами (text.mdc)"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
deny contains msg if {
|
|
79
|
+
is_array(input.ignorePaths)
|
|
80
|
+
some path in required_ignore_paths
|
|
81
|
+
not path in {p | some p in input.ignorePaths}
|
|
82
|
+
msg := sprintf(".cspell.json ignorePaths: додай %q (text.mdc)", [path])
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# ── helpers ────────────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
has_nitra_dict_import(imports) if {
|
|
88
|
+
some imp in imports
|
|
89
|
+
is_string(imp)
|
|
90
|
+
contains(imp, nitra_cspell_dict_marker)
|
|
91
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Порт перевірки `.markdownlint-cli2.jsonc` з `npm/scripts/check-text.mjs` (text.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Запуск (локально):
|
|
4
|
+
# conftest test .markdownlint-cli2.jsonc -p npm/policy/text \
|
|
5
|
+
# --namespace text.markdownlint --parser json
|
|
6
|
+
#
|
|
7
|
+
# Конфтест парсить `.jsonc` як JSON лише якщо файл — валідний JSON (без коментарів).
|
|
8
|
+
# У випадку справжнього JSONC з `//` коментарями цей крок мовчки ігноруватиметься
|
|
9
|
+
# (conftest skip). FS-перевірка (наявність файлу) живе у JS.
|
|
10
|
+
#
|
|
11
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
12
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
13
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
14
|
+
package text.markdownlint
|
|
15
|
+
|
|
16
|
+
import rego.v1
|
|
17
|
+
|
|
18
|
+
deny contains msg if {
|
|
19
|
+
object.get(input, "gitignore", null) != true
|
|
20
|
+
msg := ".markdownlint-cli2.jsonc: додай на верхньому рівні \"gitignore\": true (text.mdc)"
|
|
21
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Порт перевірок `.oxfmtrc.json` з `npm/scripts/check-text.mjs` (text.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Запуск (локально):
|
|
4
|
+
# conftest test .oxfmtrc.json -p npm/policy/text --namespace text.oxfmtrc
|
|
5
|
+
#
|
|
6
|
+
# Перевіряє: обовʼязкові ключі, канонічні значення (`semi=false`, `singleQuote=true`,
|
|
7
|
+
# `tabWidth=2`, `useTabs=false`, `printWidth=120`), масив `ignorePatterns` з
|
|
8
|
+
# канонічними glob-ами (hasura/metadata, schema.graphql, auto-imports.d.ts).
|
|
9
|
+
#
|
|
10
|
+
# FS-перевірки (наявність самого `.oxfmtrc.json`, `.prettierrc.*` файлів) живуть
|
|
11
|
+
# у `check-text.mjs`. Тут — лише про вже завантажений input.
|
|
12
|
+
#
|
|
13
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
14
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
15
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
16
|
+
package text.oxfmtrc
|
|
17
|
+
|
|
18
|
+
import rego.v1
|
|
19
|
+
|
|
20
|
+
# ── Очікувані значення ─────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
required_keys := [
|
|
23
|
+
"arrowParens",
|
|
24
|
+
"printWidth",
|
|
25
|
+
"bracketSpacing",
|
|
26
|
+
"bracketSameLine",
|
|
27
|
+
"semi",
|
|
28
|
+
"singleQuote",
|
|
29
|
+
"tabWidth",
|
|
30
|
+
"trailingComma",
|
|
31
|
+
"useTabs",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
required_ignore_patterns := {
|
|
35
|
+
"**/hasura/metadata/**",
|
|
36
|
+
"**/schema.graphql",
|
|
37
|
+
"**/auto-imports.d.ts",
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
# ── deny: обовʼязкові ключі ────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
deny contains msg if {
|
|
43
|
+
some key in required_keys
|
|
44
|
+
not key in object.keys(input)
|
|
45
|
+
msg := sprintf(".oxfmtrc.json: відсутній обовʼязковий ключ %q (text.mdc)", [key])
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# ── deny: канонічні значення ───────────────────────────────────────────────
|
|
49
|
+
#
|
|
50
|
+
# `object.get(…, sentinel)` робить значення визначеним — інакше при відсутньому
|
|
51
|
+
# ключі порівняння дало б `undefined`, не `true`, і правило мовчки не спрацювало б.
|
|
52
|
+
|
|
53
|
+
deny contains msg if {
|
|
54
|
+
object.get(input, "semi", null) != false
|
|
55
|
+
msg := ".oxfmtrc.json: semi має бути false (text.mdc)"
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
deny contains msg if {
|
|
59
|
+
object.get(input, "singleQuote", null) != true
|
|
60
|
+
msg := ".oxfmtrc.json: singleQuote має бути true (text.mdc)"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
deny contains msg if {
|
|
64
|
+
object.get(input, "tabWidth", null) != 2
|
|
65
|
+
msg := ".oxfmtrc.json: tabWidth має бути 2 (text.mdc)"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
deny contains msg if {
|
|
69
|
+
object.get(input, "useTabs", null) != false
|
|
70
|
+
msg := ".oxfmtrc.json: useTabs має бути false (text.mdc)"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
deny contains msg if {
|
|
74
|
+
object.get(input, "printWidth", null) != 120
|
|
75
|
+
msg := ".oxfmtrc.json: printWidth має бути 120 (text.mdc)"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# ── deny: ignorePatterns ───────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
deny contains msg if {
|
|
81
|
+
not is_array(object.get(input, "ignorePatterns", null))
|
|
82
|
+
msg := ".oxfmtrc.json: додай масив ignorePatterns з канонічними glob-ами (text.mdc)"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
deny contains msg if {
|
|
86
|
+
is_array(input.ignorePatterns)
|
|
87
|
+
some pattern in required_ignore_patterns
|
|
88
|
+
not pattern in {p | some p in input.ignorePatterns}
|
|
89
|
+
msg := sprintf(".oxfmtrc.json ignorePatterns: додай %q (text.mdc)", [pattern])
|
|
90
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Порт текст-специфічних перевірок `package.json` з `npm/scripts/check-text.mjs` (text.mdc).
|
|
2
|
+
#
|
|
3
|
+
# Запуск (локально):
|
|
4
|
+
# conftest test package.json -p npm/policy/text --namespace text.package_json
|
|
5
|
+
#
|
|
6
|
+
# Перевіряє: відсутність Prettier (поле + конфіги в deps), `@nitra/cspell-dict ^2.0.0+`
|
|
7
|
+
# у `devDependencies`, заборона `markdownlint-cli2` у dependencies/devDependencies.
|
|
8
|
+
#
|
|
9
|
+
# Перевірка скрипта `lint-text` (cspell, run-shellcheck-text.mjs, markdownlint, v8r,
|
|
10
|
+
# обовʼязкові glob-и для v8r) — у JS-частині (`check-text.mjs`): занадто варіативна
|
|
11
|
+
# для декларативної політики (3 режими v8r з різними вимогами до глобів).
|
|
12
|
+
#
|
|
13
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
14
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
15
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
16
|
+
package text.package_json
|
|
17
|
+
|
|
18
|
+
import rego.v1
|
|
19
|
+
|
|
20
|
+
# ── Заборонені пакети у dependencies/devDependencies ──────────────────────
|
|
21
|
+
|
|
22
|
+
forbidden_packages := {
|
|
23
|
+
"prettier": "Prettier заборонено — використовуй oxfmt (text.mdc)",
|
|
24
|
+
"@nitra/prettier-config": "Prettier-конфіг заборонено — використовуй oxfmt (text.mdc)",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# ── deny: заборонене поле `prettier` у package.json ───────────────────────
|
|
28
|
+
|
|
29
|
+
deny contains msg if {
|
|
30
|
+
object.get(input, "prettier", null) != null
|
|
31
|
+
msg := "package.json містить поле \"prettier\" — видали його (text.mdc)"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# ── deny: prettier у dependencies/devDependencies ─────────────────────────
|
|
35
|
+
|
|
36
|
+
deny contains msg if {
|
|
37
|
+
some pkg, hint in forbidden_packages
|
|
38
|
+
pkg in object.keys(object.get(input, "dependencies", {}))
|
|
39
|
+
msg := sprintf("package.json: dependencies містить %q — %s", [pkg, hint])
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
deny contains msg if {
|
|
43
|
+
some pkg, hint in forbidden_packages
|
|
44
|
+
pkg in object.keys(object.get(input, "devDependencies", {}))
|
|
45
|
+
msg := sprintf("package.json: devDependencies містить %q — %s", [pkg, hint])
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# ── deny: markdownlint-cli2 не повинен бути у залежностях ─────────────────
|
|
49
|
+
#
|
|
50
|
+
# Канонічний виклик — `bunx markdownlint-cli2` у `lint-text`, без оголошення пакета.
|
|
51
|
+
|
|
52
|
+
deny contains msg if {
|
|
53
|
+
"markdownlint-cli2" in object.keys(object.get(input, "dependencies", {}))
|
|
54
|
+
msg := "package.json: dependencies містить markdownlint-cli2 — використовуй bunx у lint-text (text.mdc)"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
deny contains msg if {
|
|
58
|
+
"markdownlint-cli2" in object.keys(object.get(input, "devDependencies", {}))
|
|
59
|
+
msg := "package.json: devDependencies містить markdownlint-cli2 — використовуй bunx у lint-text (text.mdc)"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
# ── deny: @nitra/cspell-dict ^2.0.0+ обовʼязковий ─────────────────────────
|
|
63
|
+
|
|
64
|
+
deny contains msg if {
|
|
65
|
+
dev := object.get(input, "devDependencies", {})
|
|
66
|
+
not "@nitra/cspell-dict" in object.keys(dev)
|
|
67
|
+
msg := "@nitra/cspell-dict у devDependencies обовʼязковий — bun add -d @nitra/cspell-dict@^2.0.0 (text.mdc)"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
deny contains msg if {
|
|
71
|
+
range := object.get(object.get(input, "devDependencies", {}), "@nitra/cspell-dict", "")
|
|
72
|
+
range != ""
|
|
73
|
+
not cspell_dict_major_at_least_2(range)
|
|
74
|
+
msg := sprintf("@nitra/cspell-dict має бути ^2.0.0 або новіший (зараз %q) (text.mdc)", [range])
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# ── helpers ────────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
# Чи мажорна версія cspell-dict ≥ 2. Підтримує `^2.0.0`, `~2.x`, `2.5.0`,
|
|
80
|
+
# `>=2.0.0`, `workspace:*` (тоді fallback false), із префіксом і без.
|
|
81
|
+
# Regex `^[\^~>=<]*\s*(\d+)` дістає першу цифру після опціональних range-операторів.
|
|
82
|
+
cspell_dict_major_at_least_2(range) if {
|
|
83
|
+
# `regex.find_n` повертає масив збігів; беремо перший і дивимось на перше число.
|
|
84
|
+
match := regex.find_n(`^[\^~>=<]*\s*(\d+)`, range, 1)
|
|
85
|
+
count(match) > 0
|
|
86
|
+
major := to_number(regex.replace(match[0], `^[\^~>=<]*\s*`, ""))
|
|
87
|
+
major >= 2
|
|
88
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Порт перевірки версій з `package.json` для Vue+Vite пакетів з
|
|
2
|
+
# `npm/scripts/check-vue.mjs` (vue.mdc).
|
|
3
|
+
#
|
|
4
|
+
# Запуск (локально, у Vue+Vite-пакеті):
|
|
5
|
+
# conftest test path/to/package.json -p npm/policy/vue \
|
|
6
|
+
# --namespace vue.package_json
|
|
7
|
+
#
|
|
8
|
+
# Перевіряє: якщо в `dependencies` є `vue`, то у `devDependencies.vite` має бути
|
|
9
|
+
# мажорна версія ≥ 8.
|
|
10
|
+
#
|
|
11
|
+
# AST-сканування коду (заборона явних value-імпортів `from 'vue'`, заборона
|
|
12
|
+
# Node-нативних модулів у `.vue` SFC, перевірка `vite.config` на
|
|
13
|
+
# `process.env.npm_lifecycle_event`, vue-macros, auto-import тощо), а також
|
|
14
|
+
# FS-перевірки (`src/vite-env.d.ts`, `jsconfig.json` у корені пакета) — у JS.
|
|
15
|
+
#
|
|
16
|
+
# Структура каталогу збігається зі шляхом пакету (regal: directory-package-mismatch).
|
|
17
|
+
# Конвенція проєкту — `import rego.v1` + multi-value `deny contains msg if { … }`
|
|
18
|
+
# (.cursor/rules/conftest.mdc). Лінт — `bun run lint-rego` (regal).
|
|
19
|
+
package vue.package_json
|
|
20
|
+
|
|
21
|
+
import rego.v1
|
|
22
|
+
|
|
23
|
+
deny contains msg if {
|
|
24
|
+
uses_vue
|
|
25
|
+
not vite_in_dev_dependencies
|
|
26
|
+
msg := "Vue-пакет: відсутня залежність `vite` у devDependencies (vue.mdc)"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
deny contains msg if {
|
|
30
|
+
uses_vue
|
|
31
|
+
vite_in_dev_dependencies
|
|
32
|
+
not vite_major_at_least_8
|
|
33
|
+
vite_range := input.devDependencies.vite
|
|
34
|
+
msg := sprintf("Vue-пакет: vite має бути >= 8 (зараз %q) (vue.mdc)", [vite_range])
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# ── helpers ────────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
uses_vue if {
|
|
40
|
+
"vue" in object.keys(object.get(input, "dependencies", {}))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
vite_in_dev_dependencies if {
|
|
44
|
+
"vite" in object.keys(object.get(input, "devDependencies", {}))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
vite_major_at_least_8 if {
|
|
48
|
+
range := input.devDependencies.vite
|
|
49
|
+
|
|
50
|
+
# Перша мажорна цифра з рядка: `^8`, `>=8.0.0`, `8.x` → 8.
|
|
51
|
+
match := regex.find_n(`\d+`, range, 1)
|
|
52
|
+
count(match) > 0
|
|
53
|
+
to_number(match[0]) >= 8
|
|
54
|
+
}
|
package/scripts/check-adr.mjs
CHANGED
|
@@ -26,6 +26,7 @@ const PROJECT_SETTINGS_PATH = '.claude/settings.json'
|
|
|
26
26
|
const PROJECT_LOCAL_SETTINGS_PATH = '.claude/settings.local.json'
|
|
27
27
|
const PROJECT_LOG_PATH = '.claude/hooks/capture-decisions.log'
|
|
28
28
|
const HOOK_COMMAND_MARKER = '.claude/hooks/capture-decisions.sh'
|
|
29
|
+
const EOL_RE = /\r?\n/u
|
|
29
30
|
|
|
30
31
|
const here = dirname(fileURLToPath(import.meta.url))
|
|
31
32
|
/** Канонічний bundled-скрипт у пакеті — джерело правди для звірки з проєктним. */
|
|
@@ -183,9 +184,9 @@ async function checkGitignore(reporter) {
|
|
|
183
184
|
}
|
|
184
185
|
const content = await readFile('.gitignore', 'utf8')
|
|
185
186
|
const covers = content
|
|
186
|
-
.split(
|
|
187
|
+
.split(EOL_RE)
|
|
187
188
|
.map(l => l.trim())
|
|
188
|
-
.some(gitignoreLineCoversHookLog)
|
|
189
|
+
.some(line => gitignoreLineCoversHookLog(line))
|
|
189
190
|
if (covers) {
|
|
190
191
|
pass(`.gitignore покриває ${PROJECT_LOG_PATH}`)
|
|
191
192
|
} else {
|
package/scripts/check-bun.mjs
CHANGED
|
@@ -1,54 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Перевіряє відповідність репозиторію правилам Bun (bun.mdc).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* **Що тут лишилося** (FS / cross-file — не покривається conftest):
|
|
5
|
+
* - наявність `bun.lock`, `bunfig.toml`, `package.json` у корені (FS-existence);
|
|
6
|
+
* - заборонені lockfile та артефакти yarn/pnpm (`package-lock.json`, `yarn.lock`,
|
|
7
|
+
* `pnpm-lock.yaml`, `.yarnrc.yml`, директорія `.yarn/`);
|
|
8
|
+
* - якщо в `.n-cursor.json` у `rules` є `docker` або `k8s`, у кореневому
|
|
9
|
+
* `package.json` має бути відповідний скрипт `lint-docker` / `lint-k8s`
|
|
10
|
+
* (cross-file: два JSON-файли).
|
|
7
11
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* Якщо в кореневому `package.json` є скрипти з префіксом `lint-`, перевіряє наявність агрегованого
|
|
15
|
-
* скрипта `lint`, у якому через `bun run <ім’я>` викликаються всі такі скрипти, і що рядок `lint`
|
|
16
|
-
* закінчується на `&& oxfmt .`.
|
|
12
|
+
* **Що покрила Rego** (`bun run lint-conftest`):
|
|
13
|
+
* - `npm/policy/bun/bunfig/` — `[install].linker == "hoisted"` у `bunfig.toml`;
|
|
14
|
+
* - `npm/policy/bun/package_json/` — відсутність `packageManager` / `dependencies`
|
|
15
|
+
* у кореневому `package.json`, у `devDependencies` лише `@nitra/*`, агрегований
|
|
16
|
+
* `lint`-скрипт покриває всі `lint-*` через `bun run` і завершується `&& oxfmt .`.
|
|
17
17
|
*/
|
|
18
18
|
import { existsSync } from 'node:fs'
|
|
19
19
|
import { readFile } from 'node:fs/promises'
|
|
20
20
|
|
|
21
21
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
22
22
|
|
|
23
|
-
const OXFMT_END_RE = /&&[ \t]+oxfmt[ \t]+\.[ \t]*$/
|
|
24
|
-
/** Пробіли/таби без `\s` (уникаємо super-linear backtracking у sonarjs/slow-regex). */
|
|
25
|
-
const HOISTED_LINKER_RE = /^[ \t]*linker[ \t]*=[ \t]*"hoisted"[ \t]*$/m
|
|
26
|
-
const INSTALL_SECTION_RE = /^[ \t]*\[install\][ \t]*$/m
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Перевіряє `bunfig.toml` на секцію `[install]` з `linker = "hoisted"`.
|
|
30
|
-
* @param {{ pass: (msg: string) => void, fail: (msg: string) => void }} reporter репортер
|
|
31
|
-
*/
|
|
32
|
-
async function checkBunfigHoisted(reporter) {
|
|
33
|
-
const { pass, fail } = reporter
|
|
34
|
-
if (!existsSync('bunfig.toml')) {
|
|
35
|
-
fail('Відсутній bunfig.toml — створи з [install] linker = "hoisted" (bun.mdc)')
|
|
36
|
-
return
|
|
37
|
-
}
|
|
38
|
-
const content = await readFile('bunfig.toml', 'utf8')
|
|
39
|
-
if (!INSTALL_SECTION_RE.test(content)) {
|
|
40
|
-
fail('bunfig.toml: відсутня секція [install] (bun.mdc)')
|
|
41
|
-
return
|
|
42
|
-
}
|
|
43
|
-
if (HOISTED_LINKER_RE.test(content)) {
|
|
44
|
-
pass('bunfig.toml: [install] linker = "hoisted"')
|
|
45
|
-
} else {
|
|
46
|
-
fail('bunfig.toml: у секції [install] має бути linker = "hoisted" (bun.mdc)')
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
23
|
/**
|
|
51
24
|
* Чи ім'я пакета дозволене в кореневих `devDependencies` за bun.mdc (лише **`@nitra/*`**).
|
|
25
|
+
*
|
|
26
|
+
* Залишилася як експорт для `check-text.mjs` і тестів — `bun.package_json` Rego
|
|
27
|
+
* робить ту саму перевірку для check-runner-а.
|
|
52
28
|
* @param {string} name ключ з поля `devDependencies`
|
|
53
29
|
* @returns {boolean} true, якщо префікс дозволений
|
|
54
30
|
*/
|
|
@@ -76,66 +52,6 @@ async function loadNCursorRules() {
|
|
|
76
52
|
}
|
|
77
53
|
}
|
|
78
54
|
|
|
79
|
-
/**
|
|
80
|
-
* @param {{ pass: (msg: string) => void, fail: (msg: string) => void }} reporter репортер для збору результатів
|
|
81
|
-
* @param {Record<string, unknown>} pkg розібраний package.json
|
|
82
|
-
*/
|
|
83
|
-
function checkDevDependencies(reporter, pkg) {
|
|
84
|
-
const { pass, fail } = reporter
|
|
85
|
-
const dev = pkg.devDependencies
|
|
86
|
-
if (dev === undefined) {
|
|
87
|
-
pass('Кореневий package.json без devDependencies')
|
|
88
|
-
return
|
|
89
|
-
}
|
|
90
|
-
if (dev === null || typeof dev !== 'object' || Array.isArray(dev)) {
|
|
91
|
-
fail(
|
|
92
|
-
'Кореневий package.json: `devDependencies` має бути object з ключами пакетів і діапазонами версій (не null, не масив)'
|
|
93
|
-
)
|
|
94
|
-
return
|
|
95
|
-
}
|
|
96
|
-
const bad = Object.keys(/** @type {object} */ (dev)).filter(n => !isAllowedRootDevDependency(n))
|
|
97
|
-
if (bad.length > 0) {
|
|
98
|
-
fail(`Кореневі devDependencies: дозволені лише @nitra/* — прибери або перенеси: ${bad.join(', ')} (bun.mdc)`)
|
|
99
|
-
return
|
|
100
|
-
}
|
|
101
|
-
const n = Object.keys(/** @type {object} */ (dev)).length
|
|
102
|
-
pass(
|
|
103
|
-
n === 0
|
|
104
|
-
? 'Кореневі devDependencies порожні або відсутні (лише @nitra/*)'
|
|
105
|
-
: `Кореневі devDependencies: лише @nitra/* (${n} пак.)`
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* @param {{ pass: (msg: string) => void, fail: (msg: string) => void }} reporter репортер для збору результатів
|
|
111
|
-
* @param {Record<string, string>} scripts scripts з package.json
|
|
112
|
-
*/
|
|
113
|
-
function checkLintAggregate(reporter, scripts) {
|
|
114
|
-
const { pass, fail } = reporter
|
|
115
|
-
const lintPrefixed = Object.keys(scripts).filter(name => name.startsWith('lint-'))
|
|
116
|
-
if (lintPrefixed.length === 0) return
|
|
117
|
-
const aggregate = typeof scripts.lint === 'string' ? scripts.lint : ''
|
|
118
|
-
if (!aggregate.trim()) {
|
|
119
|
-
const scriptList = lintPrefixed.map(s => `\`${s}\``).join(', ')
|
|
120
|
-
fail(
|
|
121
|
-
`У package.json є скрипти ${scriptList}, але немає агрегованого \`lint\` — додай скрипт, який запускає їх через \`bun run\``
|
|
122
|
-
)
|
|
123
|
-
return
|
|
124
|
-
}
|
|
125
|
-
const missing = lintPrefixed.filter(name => !aggregate.includes(`bun run ${name}`))
|
|
126
|
-
if (missing.length > 0) {
|
|
127
|
-
const missingList = missing.map(s => '`' + s + '`').join(', ')
|
|
128
|
-
fail(`Скрипт \`lint\` має викликати всі lint-* через bun run; відсутньо: ${missingList}`)
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
pass('package.json: агрегований `lint` покриває всі `lint-*` скрипти')
|
|
132
|
-
if (OXFMT_END_RE.test(aggregate.trim())) {
|
|
133
|
-
pass('package.json: `lint` завершується `&& oxfmt .`')
|
|
134
|
-
} else {
|
|
135
|
-
fail('Скрипт `lint` має закінчуватися на `&& oxfmt .`')
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
55
|
/**
|
|
140
56
|
* @param {{ pass: (msg: string) => void, fail: (msg: string) => void }} reporter репортер для збору результатів
|
|
141
57
|
* @param {Record<string, string>} scripts scripts з package.json
|
|
@@ -188,34 +104,22 @@ export async function check() {
|
|
|
188
104
|
fail('Відсутній bun.lock — запусти bun i')
|
|
189
105
|
}
|
|
190
106
|
|
|
191
|
-
|
|
107
|
+
if (!existsSync('bunfig.toml')) {
|
|
108
|
+
fail('Відсутній bunfig.toml — створи з [install] linker = "hoisted" (bun.mdc)')
|
|
109
|
+
} else {
|
|
110
|
+
pass('bunfig.toml є (структуру перевіряє bun run lint-conftest → bun.bunfig)')
|
|
111
|
+
}
|
|
192
112
|
|
|
193
113
|
const cursorRules = await loadNCursorRules()
|
|
194
114
|
|
|
195
115
|
if (!existsSync('package.json')) {
|
|
116
|
+
fail('Відсутній package.json у корені')
|
|
196
117
|
return reporter.getExitCode()
|
|
197
118
|
}
|
|
198
119
|
|
|
199
120
|
const pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
200
|
-
if (pkg.packageManager) {
|
|
201
|
-
fail(`package.json містить поле packageManager: "${pkg.packageManager}" — видали його`)
|
|
202
|
-
} else {
|
|
203
|
-
pass('package.json не містить packageManager')
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (pkg.dependencies === undefined) {
|
|
207
|
-
pass('Кореневий package.json без поля `dependencies`')
|
|
208
|
-
} else {
|
|
209
|
-
fail(
|
|
210
|
-
'Кореневий package.json не повинен містити поле `dependencies` — додай залежності в workspace-пакети (bun.mdc)'
|
|
211
|
-
)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
checkDevDependencies(reporter, pkg)
|
|
215
|
-
|
|
216
121
|
const scripts = pkg.scripts && typeof pkg.scripts === 'object' ? pkg.scripts : {}
|
|
217
122
|
checkCursorRuleScripts(reporter, scripts, cursorRules)
|
|
218
|
-
checkLintAggregate(reporter, scripts)
|
|
219
123
|
|
|
220
124
|
return reporter.getExitCode()
|
|
221
125
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Перевіряє правило graphql.mdc: наявність **`.graphqlrc.yml
|
|
3
|
-
* **`graphql.vscode-graphql
|
|
4
|
-
* **`package.json`**, якщо у дереві є **`gql\`…\``**.
|
|
2
|
+
* Перевіряє правило graphql.mdc: наявність **`.graphqlrc.yml`** і рекомендації
|
|
3
|
+
* **`graphql.vscode-graphql`**, якщо у дереві є **`gql\`…\``**.
|
|
5
4
|
*
|
|
6
5
|
* Обхід репозиторію — **`walkDir`** від **`process.cwd()`** (пропуски як у інших check). Кандидати — **`.vue`** та **`.js`/`.ts`/`.jsx`/`.tsx`** тощо; пропуск **`.d.ts`**, **auto-imports.d.ts** тощо — **`shouldSkipFileForGqlScan`**.
|
|
7
6
|
*
|
|
8
7
|
* Виявлення **`gql`** — **oxc-parser** після витягування `<script>` з SFC (**`graphql-gql-scan.mjs`**). Якщо збігів немає — перевірка завершується успішно без вимог до конфігів.
|
|
8
|
+
*
|
|
9
|
+
* Перевірку `scripts.dump-schema == REQUIRED_DUMP_SCHEMA_SCRIPT` у `package.json`
|
|
10
|
+
* перенесено в Rego (`npm/policy/graphql/package_json/`); `bun run lint-conftest`
|
|
11
|
+
* запускає її окремо.
|
|
9
12
|
*/
|
|
10
13
|
import { existsSync } from 'node:fs'
|
|
11
14
|
import { readFile } from 'node:fs/promises'
|
|
@@ -25,9 +28,6 @@ export const GRAPHQL_RC_FILENAME = '.graphqlrc.yml'
|
|
|
25
28
|
|
|
26
29
|
/** Розширення VS Code з graphql.mdc. */
|
|
27
30
|
export const REQUIRED_GRAPHQL_VSCODE_EXTENSION = 'graphql.vscode-graphql'
|
|
28
|
-
/** Команда dump-schema з graphql.mdc. */
|
|
29
|
-
export const REQUIRED_DUMP_SCHEMA_SCRIPT =
|
|
30
|
-
"bunx graphqurl http://localhost:4040/v1/graphql -H 'X-Hasura-Admin-Secret: secret' --introspect > schema.graphql"
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Збирає абсолютні шляхи source-файлів, які підлягають скануванню на gql templates.
|
|
@@ -106,44 +106,6 @@ async function checkExtensionsRecommendation(pass, fail) {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
/**
|
|
110
|
-
* Перевіряє `package.json` і значення scripts.dump-schema.
|
|
111
|
-
* @param {(msg: string) => void} pass success-репортер
|
|
112
|
-
* @param {(msg: string) => void} fail fail-репортер
|
|
113
|
-
* @returns {Promise<void>}
|
|
114
|
-
*/
|
|
115
|
-
async function checkPackageDumpSchemaScript(pass, fail) {
|
|
116
|
-
if (!existsSync('package.json')) {
|
|
117
|
-
fail('Відсутній package.json у корені репозиторію')
|
|
118
|
-
return
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
let pkg
|
|
122
|
-
try {
|
|
123
|
-
pkg = JSON.parse(await readFile('package.json', 'utf8'))
|
|
124
|
-
} catch {
|
|
125
|
-
fail('package.json не є валідним JSON')
|
|
126
|
-
return
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const scripts = pkg.scripts
|
|
130
|
-
if (!scripts || typeof scripts !== 'object' || Array.isArray(scripts)) {
|
|
131
|
-
fail('package.json: поле scripts має бути обʼєктом')
|
|
132
|
-
return
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (!Object.hasOwn(scripts, 'dump-schema')) {
|
|
136
|
-
fail('package.json: відсутній scripts.dump-schema (graphql.mdc)')
|
|
137
|
-
return
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (scripts['dump-schema'] === REQUIRED_DUMP_SCHEMA_SCRIPT) {
|
|
141
|
-
pass('package.json: scripts.dump-schema відповідає graphql.mdc')
|
|
142
|
-
} else {
|
|
143
|
-
fail(`package.json: scripts.dump-schema має бути "${REQUIRED_DUMP_SCHEMA_SCRIPT}" (graphql.mdc)`)
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
109
|
/**
|
|
148
110
|
* Перевіряє graphql.mdc: умовна вимога .graphqlrc.yml, graphql.vscode-graphql
|
|
149
111
|
* і scripts.dump-schema за наявності gql tagged templates.
|
|
@@ -176,7 +138,6 @@ export async function check() {
|
|
|
176
138
|
}
|
|
177
139
|
|
|
178
140
|
await checkExtensionsRecommendation(pass, fail)
|
|
179
|
-
await checkPackageDumpSchemaScript(pass, fail)
|
|
180
141
|
|
|
181
142
|
return reporter.getExitCode()
|
|
182
143
|
}
|
package/scripts/check-hasura.mjs
CHANGED
|
@@ -46,7 +46,6 @@ const HASURA_ENDPOINT_LINE_RE = /^[ \t]*(?:export[ \t]+)?HASURA_GRAPHQL_ENDPOINT
|
|
|
46
46
|
// Дозволяємо два DNS-суфікси кластера: `<name>.internal` (GKE/GCP) і `cluster.local`
|
|
47
47
|
// (стандартний k8s / Yandex Cloud). У YC namespace.yaml + cluster mode дають коротший суфікс.
|
|
48
48
|
const INTERNAL_HASURA_URL_RE = /^http:\/\/([^./]+)\.([^./]+)\.svc\.((?:[^./:]+\.internal)|cluster\.local):(\d+)\/?$/u
|
|
49
|
-
const CLUSTER_LOCAL_SUFFIX = 'cluster.local'
|
|
50
49
|
const INTERNAL_DNS_SUFFIX = '.internal'
|
|
51
50
|
|
|
52
51
|
/**
|
|
@@ -149,9 +148,9 @@ async function checkEnvFile(relPath, expected, reporter) {
|
|
|
149
148
|
const value = m[1].trim()
|
|
150
149
|
const parsed = parseInternalHasuraEndpoint(value)
|
|
151
150
|
if (!parsed.ok) {
|
|
152
|
-
|
|
151
|
+
|
|
153
152
|
const example =
|
|
154
|
-
|
|
153
|
+
"https://<service>.<namespace>.svc.<cluster>.internal:<port> або http://<service>.<namespace>.svc.cluster.local:<port>"
|
|
155
154
|
fail(
|
|
156
155
|
`${relPath}: HASURA_GRAPHQL_ENDPOINT="${value}" — потрібен внутрішній кластерний URL виду ${example} (hasura.mdc)`
|
|
157
156
|
)
|