@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.
Files changed (55) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/package.json +1 -1
  3. package/policy/abie/health_check_policy/health_check_policy.rego +73 -0
  4. package/policy/abie/http_route_base/http_route_base.rego +45 -0
  5. package/policy/adr/settings_json/settings_json.rego +31 -0
  6. package/policy/adr/settings_local_json/settings_local_json.rego +28 -0
  7. package/policy/bun/bunfig/bunfig.rego +33 -0
  8. package/policy/bun/package_json/package_json.rego +94 -0
  9. package/policy/capacitor/package_json/package_json.rego +45 -0
  10. package/policy/ga/clean_ga_workflows/clean_ga_workflows.rego +0 -26
  11. package/policy/ga/clean_merged_branch/clean_merged_branch.rego +0 -25
  12. package/policy/ga/git_ai/git_ai.rego +0 -26
  13. package/policy/ga/lint_ga/lint_ga.rego +0 -26
  14. package/policy/ga/workflow_common/workflow_common.rego +161 -0
  15. package/policy/graphql/package_json/package_json.rego +35 -0
  16. package/policy/hasura/svc_hl/svc_hl.rego +27 -0
  17. package/policy/image_compress/package_json/package_json.rego +94 -0
  18. package/policy/js_bun_db/package_json/package_json.rego +28 -0
  19. package/policy/js_lint/lint_js_yml/lint_js_yml.rego +98 -0
  20. package/policy/js_lint/package_json/package_json.rego +137 -0
  21. package/policy/js_mssql/package_json/package_json.rego +57 -0
  22. package/policy/js_run/configmap/configmap.rego +45 -0
  23. package/policy/js_run/jsconfig/jsconfig.rego +66 -0
  24. package/policy/js_run/package_json/package_json.rego +31 -0
  25. package/policy/k8s/manifest/manifest.rego +130 -0
  26. package/policy/npm_module/emit_types_config/emit_types_config.rego +37 -0
  27. package/policy/npm_module/npm_package_json/npm_package_json.rego +55 -0
  28. package/policy/npm_module/npm_publish_yml/npm_publish_yml.rego +79 -0
  29. package/policy/npm_module/root_package_json/root_package_json.rego +28 -0
  30. package/policy/php/lint_php_yml/lint_php_yml.rego +32 -0
  31. package/policy/php/package_json/package_json.rego +19 -0
  32. package/policy/style_lint/lint_style_yml/lint_style_yml.rego +35 -0
  33. package/policy/style_lint/package_json/package_json.rego +49 -0
  34. package/policy/text/cspell/cspell.rego +91 -0
  35. package/policy/text/markdownlint/markdownlint.rego +21 -0
  36. package/policy/text/oxfmtrc/oxfmtrc.rego +90 -0
  37. package/policy/text/package_json/package_json.rego +88 -0
  38. package/policy/vue/package_json/package_json.rego +54 -0
  39. package/scripts/check-adr.mjs +3 -2
  40. package/scripts/check-bun.mjs +21 -117
  41. package/scripts/check-graphql.mjs +6 -45
  42. package/scripts/check-hasura.mjs +2 -3
  43. package/scripts/check-image-avif.mjs +3 -3
  44. package/scripts/check-image-compress.mjs +25 -132
  45. package/scripts/check-js-bun-db.mjs +3 -50
  46. package/scripts/check-js-run.mjs +8 -8
  47. package/scripts/check-k8s.mjs +4 -4
  48. package/scripts/check-npm-module.mjs +17 -8
  49. package/scripts/check-php.mjs +16 -51
  50. package/scripts/check-style-lint.mjs +28 -52
  51. package/scripts/check-text.mjs +47 -219
  52. package/scripts/check-vue.mjs +3 -16
  53. package/scripts/lint-conftest.mjs +351 -0
  54. package/scripts/lint-ga.mjs +39 -2
  55. 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
+ }
@@ -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(/\r?\n/u)
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 {
@@ -1,54 +1,30 @@
1
1
  /**
2
2
  * Перевіряє відповідність репозиторію правилам Bun (bun.mdc).
3
3
  *
4
- * Очікує наявність `bun.lock`, `bunfig.toml` з `linker = "hoisted"` у секції `[install]`,
5
- * забороняє lockfile та артефакти yarn/pnpm, директорію `.yarn` і поле `packageManager`
6
- * у кореневому `package.json`.
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
- * У кореневому `package.json` не має бути поля **`dependencies`**; у **`devDependencies`** дозволені лише
9
- * пакети **`@nitra/*`** (наприклад **`@nitra/cspell-dict`**, **`@nitra/eslint-config`**).
10
- *
11
- * Якщо в `.n-cursor.json` у `rules` є `docker` або `k8s`, вимагає у кореневому `package.json`
12
- * відповідно скриптів `lint-docker` / `lint-k8s` (див. docker.mdc, k8s.mdc).
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
- await checkBunfigHoisted(reporter)
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`** і скрипта **`dump-schema`** у кореневому
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
  }
@@ -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
- // eslint-disable-next-line @microsoft/sdl/no-insecure-url, sonarjs/no-clear-text-protocols -- hasura.mdc вимагає саме http:// для кластерного URL (TLS не використовується)
151
+
153
152
  const example =
154
- 'http://<service>.<namespace>.svc.<cluster>.internal:<port> або http://<service>.<namespace>.svc.cluster.local:<port>'
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
  )