@nitra/cursor 1.8.170 → 1.8.173

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 CHANGED
@@ -4,6 +4,30 @@
4
4
 
5
5
  Формат — [Keep a Changelog](https://keepachangelog.com/uk/1.1.0/), нумерація — [SemVer](https://semver.org/lang/uk/).
6
6
 
7
+ ## [1.8.173] - 2026-05-04
8
+
9
+ ### Added
10
+
11
+ - `.n-cursor.json` поле `ignore` (`schemas/n-cursor.json`): тепер не лише сигнал для AI, а й керує обходом усіх `check-*.mjs` / `run-*.mjs` — перелічені каталоги повністю виключаються з `walkDir`, як `node_modules` чи `.git`. Дозволяє безпечно тримати vendored Helm-чарти, генеровані маніфести, legacy-дерева у репо без false-positive’ів від check-скриптів. Розширено опис у схемі (стандартні виключення додавати не треба) і README отримав секцію «Виключення цілих дерев».
12
+ - `scripts/utils/load-cursor-config.mjs`: нова утиліта `loadCursorIgnorePaths(root)` — читає поле `ignore` з `.n-cursor.json` і нормалізує до абсолютних posix-шляхів без trailing-slash; пропускає не-рядки та порожні елементи; повертає `[]`, якщо файлу/поля нема або JSON невалідний.
13
+ - `scripts/utils/walkDir.mjs`: третій аргумент `ignorePaths` (за замовчуванням `[]`) — каталоги, які пропускаються разом з усім вмістом. Збіг — за повним шляхом (точний або з префіксом `/`), а не за basename, тож `postgres-master-test/` не пропускається коли в ignore лише `postgres-master/`. Стандартні пропуски (`node_modules`, `.git`, `dist`, `coverage`, `.turbo`, `.next`) працюють як раніше.
14
+
15
+ ### Changed
16
+
17
+ - Усі скрипти, що обходять FS через `walkDir`, тепер на початку `check()` зчитують `loadCursorIgnorePaths(root)` і передають третім аргументом: `check-abie`, `check-docker`, `check-graphql`, `check-hasura`, `check-image`, `check-js-bun-db`, `check-js-mssql`, `check-js-run`, `check-k8s`, `check-nginx-default-tpl`, `check-npm-module`, `check-vue`, плюс `run-docker`, `run-k8s` і `rename-yaml-extensions`. Wrapper-функції (`findDockerfilePaths`, `findK8sYamlFiles`, `findLintDockerfilePaths`, `findK8sRoots`, `findDefaultConfTemplatePaths`, `migrateDefaultTplConfFiles`) отримали опційний параметр `ignorePaths` для прозорого пробросу.
18
+
19
+ ## [1.8.172] - 2026-05-04
20
+
21
+ ### Changed
22
+
23
+ - `auto-rules.md` / `auto-rules.mjs`: правило `php` тепер автоувімкається за наявністю `composer.json` у корені, а не за будь-яким `*.php` файлом у дереві. Прибрано константу `PHP_RE`, факт `hasPhpSource` і його збір у `updateFileFacts`/`collectAutoRuleFacts`; натомість у `detectAutoRulesAndSkills` додано прапорець `composerJsonExists` (за аналогією з `packageJsonExists` / `npmDirExists`).
24
+
25
+ ## [1.8.171] - 2026-05-04
26
+
27
+ ### Removed
28
+
29
+ - `abie.mdc` (v1.17) / `check-abie.mjs`: прибрано перевірку `.github/actionlint.yaml` (мітки `self-hosted-runner` `ua` / `dev` / `ru`). Видалено константи `ABIE_REQUIRED_ACTIONLINT_LABELS`, шаблон файлу та функції `parseActionlintSelfHostedLabels`, `abieMissingActionlintLabels`, `ensureAbieActionlintConfig`; знято відповідні юніт- та інтеграційні тести. Файл `.github/actionlint.yaml` більше не створюється і не валідовується правилом abie.
30
+
7
31
  ## [1.8.170] - 2026-05-03
8
32
 
9
33
  ### Changed
package/README.md CHANGED
@@ -30,6 +30,20 @@
30
30
 
31
31
  Щоб використовувати конкретну версію правил, оновіть залежність `@nitra/cursor` у проєкті (`bun add -d @nitra/cursor@<версія>` тощо). Поле `version` у `.n-cursor.json`, якщо воно лишилось у старих конфігах, **ігнорується**.
32
32
 
33
+ ### Виключення цілих дерев — поле `ignore`
34
+
35
+ Поле `ignore` у `.n-cursor.json` — список директорій (posix-шляхи відносно кореня репозиторію), які CLI повністю пропускає під час обходу: жоден `check-*.mjs` не сканує і не валідує файли всередині них, а агент не редагує/не створює/не видаляє там файли. Стандартні виключення (`node_modules`, `.git`, `dist`, `coverage`, `.turbo`, `.next`) працюють завжди — додавати їх у `ignore` не потрібно.
36
+
37
+ Типові кандидати: vendored Helm-чарти, генеровані маніфести, legacy-дерева, які не підтягуються під поточні правила:
38
+
39
+ ```json
40
+ {
41
+ "$schema": "https://unpkg.com/@nitra/cursor/schemas/n-cursor.json",
42
+ "rules": ["k8s"],
43
+ "ignore": ["dremio/dev/dremio_v2", "postgres-master"]
44
+ }
45
+ ```
46
+
33
47
  ### Правило `k8s` і Kustomize
34
48
 
35
49
  У цільовому репозиторії з маніфестами під **`**/k8s`** дотримуйтесь **`mdc/k8s.mdc`** з пакету (після синку — `.cursor/rules/n-k8s.mdc`або копія з`node_modules/@nitra/cursor/mdc/k8s.mdc`).
package/bin/auto-rules.md CHANGED
@@ -36,7 +36,7 @@ nginx-default-tpl - якщо присутній хоч один файл з пе
36
36
 
37
37
  npm-module - якщо в корені присутня директорія npm
38
38
 
39
- php - якщо присутній хоч один php файл
39
+ php - якщо в корені є composer.json
40
40
 
41
41
  style-lint - якщо присутній хоч один vue або css файл
42
42
 
package/mdc/abie.mdc CHANGED
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  description: Правила для проєктів AbInBev Efes
3
3
  alwaysApply: true
4
- version: '1.16'
4
+ version: '1.17'
5
5
  ---
6
6
 
7
- Правило **abie** для споживачів **@nitra/cursor**: **k8s** (Deployment + **HealthCheckPolicy** у **`hc.yaml`**, overlay **ua** / **ru** — **nodeSelector**, **HTTPRoute** (будь-який непорожній **`target.name`**, для спільних сервісів **`auth-run-hl`** / **`file-link-hl`** — **`namespace: dev`** у base та patch **`…/backendRefs/…/namespace`** у **ua** / **ru**), у overlay **ru** — кожен **Service** (у т. ч. **headless** / **`-hl`**) → **`spec.type: NodePort`** через **JSON6902** у **`kustomization.yaml`**, видалення **HealthCheckPolicy** у **ru**), гілки **dev**, **ua**, **ru** у **clean-merged-branch**, **`.github/actionlint.yaml`** із мітками **`self-hosted-runner`** **ua** / **dev** / **ru** (створюється автоматично за відсутності), а також заборона тримати артефакти **Firebase Hosting** у **підкаталогах першого рівня** (безпосередні діти кореня репозиторію; у самому корені ці імена не вимагаються до видалення).
7
+ Правило **abie** для споживачів **@nitra/cursor**: **k8s** (Deployment + **HealthCheckPolicy** у **`hc.yaml`**, overlay **ua** / **ru** — **nodeSelector**, **HTTPRoute** (будь-який непорожній **`target.name`**, для спільних сервісів **`auth-run-hl`** / **`file-link-hl`** — **`namespace: dev`** у base та patch **`…/backendRefs/…/namespace`** у **ua** / **ru**), у overlay **ru** — кожен **Service** (у т. ч. **headless** / **`-hl`**) → **`spec.type: NodePort`** через **JSON6902** у **`kustomization.yaml`**, видалення **HealthCheckPolicy** у **ru**), гілки **dev**, **ua**, **ru** у **clean-merged-branch**, а також заборона тримати артефакти **Firebase Hosting** у **підкаталогах першого рівня** (безпосередні діти кореня репозиторію; у самому корені ці імена не вимагаються до видалення).
8
8
 
9
9
  **`npx @nitra/cursor check abie`** виконується лише якщо в **`.n-cursor.json`** у **`rules`** є **`abie`** — інакше вихід **0** без зауважень.
10
10
 
@@ -336,18 +336,6 @@ spec:
336
336
 
337
337
  У **кожному** підкаталозі, що лежить **безпосередньо** в корені репозиторію, не тримати конфіг і кеш **Firebase Hosting**: у таких каталогах не повинно бути **`.firebaserc`**, **`firebase.json`** та каталогу **`.firebase/`** (у **самому** корені репозиторію ці імена перевіркою abie **не** розглядаються; `node_modules` / `.git` зі скану вилучаються).
338
338
 
339
- ## actionlint: self-hosted-runner labels
340
-
341
- У **`.github/actionlint.yaml`** має бути блок **`self-hosted-runner.labels`** з присутніми мітками **`ua`**, **`dev`**, **`ru`**. Якщо файлу немає — **`npx @nitra/cursor check abie`** створює його з канонічним вмістом. Інші мітки, інший порядок та формат лапок дозволені — перевіряється лише наявність трьох обов'язкових міток (деталі — **`check-abie.mjs`**).
342
-
343
- ```yaml title=".github/actionlint.yaml"
344
- self-hosted-runner:
345
- labels:
346
- - 'ua'
347
- - 'dev'
348
- - 'ru'
349
- ```
350
-
351
339
  ## Git branches
352
340
 
353
341
  У **`.github/workflows/clean-merged-branch.yml`** у кроці **`phpdocker-io/github-actions-delete-abandoned-branches`** значення **`with.ignore_branches`** має містити **dev**, **ua** та **ru** (разом з іншими гілками, якщо потрібно):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitra/cursor",
3
- "version": "1.8.170",
3
+ "version": "1.8.173",
4
4
  "description": "CLI для завантаження cursor-правил (префікс n-) у локальний репозиторій",
5
5
  "keywords": [
6
6
  "cli",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "ignore": {
47
47
  "type": "array",
48
- "description": "Директорії, у яких заборонено будь-які модифікації файлів (AI не редагує, не видаляє, не створює файли). Шляхи відносно кореня репозиторію. Наприклад: [\"packages/vendor\", \"packages/generated\"].",
48
+ "description": "Директорії, що повністю виключаються з обходу check-скриптів CLI (npx @nitra/cursor check ...) і AI-модифікацій (AI не редагує, не видаляє, не створює файли). Шляхи відносно кореня репозиторію (posix). Приклади: vendored Helm-чарти, генеровані маніфести, legacy-дерева. Стандартні виключення (node_modules, .git, dist, coverage, .turbo, .next) застосовуються завжди — додавати їх не треба.",
49
49
  "items": {
50
50
  "type": "string",
51
51
  "minLength": 1
@@ -63,7 +63,6 @@ const HASURA_CONFIG_MARKER = 'metadata_directory: metadata'
63
63
  const JS_LIKE_RE = /\.(?:mjs|cjs|js|jsx|ts|tsx)$/iu
64
64
  const STYLE_RE = /\.(?:css|vue)$/iu
65
65
  const VUE_RE = /\.vue$/iu
66
- const PHP_RE = /\.php$/iu
67
66
  const NGINX_DEFAULT_FILES = new Set(['default.conf.template', 'default.conf', 'nginx.conf'])
68
67
  const IGNORED_DIR_NAMES = new Set(['node_modules', '.git', '.next', '.turbo'])
69
68
  const DEFAULT_DISABLED_LIST = Object.freeze([])
@@ -245,7 +244,6 @@ function updateDirFacts(dirName, facts) {
245
244
  * hasDockerfile: boolean,
246
245
  * hasJsLikeSource: boolean,
247
246
  * hasNginxDefaultTplFile: boolean,
248
- * hasPhpSource: boolean,
249
247
  * hasVueOrCssSource: boolean,
250
248
  * hasVueSource: boolean
251
249
  * }} facts агреговані факти
@@ -267,9 +265,6 @@ function updateFileFacts(fileName, relPath, facts) {
267
265
  if (VUE_RE.test(relPath)) {
268
266
  facts.hasVueSource = true
269
267
  }
270
- if (PHP_RE.test(relPath)) {
271
- facts.hasPhpSource = true
272
- }
273
268
  if (STYLE_RE.test(relPath)) {
274
269
  facts.hasVueOrCssSource = true
275
270
  }
@@ -452,7 +447,6 @@ export function isMonorepoPackage(packageJson) {
452
447
  * hasK8sDir: boolean,
453
448
  * hasNginxDefaultTplFile: boolean,
454
449
  * hasTempoDir: boolean,
455
- * hasPhpSource: boolean,
456
450
  * hasVueSource: boolean,
457
451
  * hasVueOrCssSource: boolean
458
452
  * }>} агреговані факти
@@ -469,7 +463,6 @@ export async function collectAutoRuleFacts(root) {
469
463
  hasK8sDir: false,
470
464
  hasNginxDefaultTplFile: false,
471
465
  hasTempoDir: false,
472
- hasPhpSource: false,
473
466
  hasVueSource: false,
474
467
  hasVueOrCssSource: false
475
468
  }
@@ -556,6 +549,7 @@ export async function detectAutoRulesAndSkills({
556
549
 
557
550
  const packageJsonExists = existsSync(join(root, 'package.json'))
558
551
  const npmDirExists = existsSync(join(root, 'npm'))
552
+ const composerJsonExists = existsSync(join(root, 'composer.json'))
559
553
  const repositoryUrl = getRepositoryUrl(
560
554
  packageJsonParsed && typeof packageJsonParsed === 'object' && !Array.isArray(packageJsonParsed)
561
555
  ? /** @type {Record<string, unknown>} */ (packageJsonParsed).repository
@@ -612,7 +606,7 @@ export async function detectAutoRulesAndSkills({
612
606
  { enabled: facts.hasK8sDir, id: 'k8s' },
613
607
  { enabled: facts.hasNginxDefaultTplFile, id: 'nginx-default-tpl' },
614
608
  { enabled: npmDirExists, id: 'npm-module' },
615
- { enabled: facts.hasPhpSource, id: 'php' },
609
+ { enabled: composerJsonExists, id: 'php' },
616
610
  { enabled: facts.hasVueOrCssSource, id: 'style-lint' }
617
611
  ]
618
612
  for (const item of autoRuleChecks) {
@@ -39,7 +39,7 @@
39
39
  * у файлі **`k8s/ru/kustomization.yaml`** того ж пакета (overlay середовища **ru**) — inline **JSON6902** на **`kind: Service`** з тим самим **`target.name`**: **`path: /spec/type`**, **`value: NodePort`**; якщо в base було **`spec.clusterIP: None`** — **`op: remove`** для **`/spec/clusterIP`**; якщо в base **явно** задано **`spec.clusterIPs`** — також **`remove`** для **`/spec/clusterIPs`** (інакше **API** може залишити **`None`** для **NodePort**; без ключа **`clusterIPs`** у base **`remove`** на **`/spec/clusterIPs`** ламає **`kubectl kustomize`**).
40
40
  */
41
41
  import { existsSync } from 'node:fs'
42
- import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises'
42
+ import { readdir, readFile } from 'node:fs/promises'
43
43
  import { dirname, join, relative } from 'node:path'
44
44
 
45
45
  import { parseAllDocuments } from 'yaml'
@@ -47,6 +47,7 @@ import { parseAllDocuments } from 'yaml'
47
47
  import { pathHasK8sSegment, ruKustomizationHasHealthCheckDeletePatch } from './check-k8s.mjs'
48
48
  import { createCheckReporter } from './utils/check-reporter.mjs'
49
49
  import { flattenWorkflowSteps, getStepUses, parseWorkflowYaml } from './utils/gha-workflow.mjs'
50
+ import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
50
51
  import { walkDir } from './utils/walkDir.mjs'
51
52
 
52
53
  const CONFIG_FILE = '.n-cursor.json'
@@ -118,20 +119,6 @@ const HTTPROUTE_BACKENDREF_PORT_8081_VALUE_FIRST_RE =
118
119
  /** Гілки, які мають бути в **`ignore_branches`** за abie.mdc. */
119
120
  export const ABIE_REQUIRED_IGNORE_BRANCHES = ['dev', 'ua', 'ru']
120
121
 
121
- /** Канонічний шлях до конфігу actionlint у репо (abie.mdc). */
122
- const ABIE_ACTIONLINT_PATH = '.github/actionlint.yaml'
123
-
124
- /** Канонічний вміст **`.github/actionlint.yaml`**, який ми створюємо за відсутності файлу (abie.mdc). */
125
- const ABIE_ACTIONLINT_TEMPLATE = `self-hosted-runner:
126
- labels:
127
- - 'ua'
128
- - 'dev'
129
- - 'ru'
130
- `
131
-
132
- /** Мітки **`self-hosted-runner.labels`**, які мають бути присутні в **`.github/actionlint.yaml`** (abie.mdc). */
133
- export const ABIE_REQUIRED_ACTIONLINT_LABELS = Object.freeze(['ua', 'dev', 'ru'])
134
-
135
122
  /**
136
123
  * Чи відносний шлях вказує на **`ru/kustomization.yaml`** (сегмент **`ru`** перед ім'ям файлу) — специфіка abie overlay.
137
124
  * @param {string} rel шлях від кореня репозиторію
@@ -399,18 +386,22 @@ export function ignoreBranchesIncludesRequired(ignoreBranches, required) {
399
386
  * @param {string} root корінь репозиторію
400
387
  * @returns {Promise<string[]>} відсортовані шляхи
401
388
  */
402
- async function findK8sYamlFiles(root) {
389
+ async function findK8sYamlFiles(root, ignorePaths = []) {
403
390
  /** @type {string[]} */
404
391
  const out = []
405
- await walkDir(root, p => {
406
- if (!pathHasK8sSegment(p)) {
407
- return
408
- }
409
- if (!YAML_EXTENSION_RE.test(p)) {
410
- return
411
- }
412
- out.push(p)
413
- })
392
+ await walkDir(
393
+ root,
394
+ p => {
395
+ if (!pathHasK8sSegment(p)) {
396
+ return
397
+ }
398
+ if (!YAML_EXTENSION_RE.test(p)) {
399
+ return
400
+ }
401
+ out.push(p)
402
+ },
403
+ ignorePaths
404
+ )
414
405
  return [...out].toSorted((a, b) => a.localeCompare(b))
415
406
  }
416
407
 
@@ -1730,83 +1721,6 @@ async function ensureNoFirebaseHostingArtifacts(root, passFn, failFn) {
1730
1721
  passFn('Підкаталоги кореня (1-й рівень, без .git/node_modules): артефактів Firebase Hosting не знайдено (abie.mdc)')
1731
1722
  }
1732
1723
 
1733
- /**
1734
- * Витягує мітки **`self-hosted-runner.labels`** з тексту `.github/actionlint.yaml`.
1735
- * @param {string} raw повний вміст файлу (YAML)
1736
- * @returns {string[] | null} масив рядків-міток або null, якщо ключа/масиву не знайдено
1737
- */
1738
- export function parseActionlintSelfHostedLabels(raw) {
1739
- let docs
1740
- try {
1741
- docs = parseAllDocuments(raw)
1742
- } catch {
1743
- return null
1744
- }
1745
- for (const doc of docs) {
1746
- if (doc.errors.length > 0) continue
1747
- const root = doc.toJSON()
1748
- if (root === null || typeof root !== 'object' || Array.isArray(root)) continue
1749
- const block = /** @type {Record<string, unknown>} */ (root)['self-hosted-runner']
1750
- if (block === null || typeof block !== 'object' || Array.isArray(block)) continue
1751
- const labels = /** @type {Record<string, unknown>} */ (block).labels
1752
- if (!Array.isArray(labels)) continue
1753
- return labels.filter(l => typeof l === 'string')
1754
- }
1755
- return null
1756
- }
1757
-
1758
- /**
1759
- * Які з **`ABIE_REQUIRED_ACTIONLINT_LABELS`** відсутні в наданому списку міток (abie.mdc).
1760
- * @param {string[]} labels мітки **`self-hosted-runner.labels`**
1761
- * @returns {string[]} відсутні мітки (порожньо — все ок)
1762
- */
1763
- export function abieMissingActionlintLabels(labels) {
1764
- const present = new Set(labels.map(l => l.trim()))
1765
- return ABIE_REQUIRED_ACTIONLINT_LABELS.filter(r => !present.has(r))
1766
- }
1767
-
1768
- /**
1769
- * Гарантує наявність **`.github/actionlint.yaml`** із потрібними мітками **`self-hosted-runner`** (abie.mdc):
1770
- * створює файл із канонічним вмістом, якщо його немає; якщо є — звіряє мітки.
1771
- * @param {string} root корінь репозиторію
1772
- * @param {(msg: string) => void} pass callback при успішній перевірці
1773
- * @param {(msg: string) => void} fail callback при помилці
1774
- */
1775
- async function ensureAbieActionlintConfig(root, pass, fail) {
1776
- const abs = join(root, ABIE_ACTIONLINT_PATH)
1777
- if (!existsSync(abs)) {
1778
- try {
1779
- await mkdir(dirname(abs), { recursive: true })
1780
- await writeFile(abs, ABIE_ACTIONLINT_TEMPLATE, 'utf8')
1781
- } catch (error) {
1782
- const msg = error instanceof Error ? error.message : String(error)
1783
- fail(`${ABIE_ACTIONLINT_PATH}: не вдалося створити (${msg}) — abie.mdc`)
1784
- return
1785
- }
1786
- pass(`${ABIE_ACTIONLINT_PATH}: створено з self-hosted-runner.labels [ua, dev, ru] (abie.mdc)`)
1787
- return
1788
- }
1789
- let raw
1790
- try {
1791
- raw = await readFile(abs, 'utf8')
1792
- } catch (error) {
1793
- const msg = error instanceof Error ? error.message : String(error)
1794
- fail(`${ABIE_ACTIONLINT_PATH}: не вдалося прочитати (${msg}) — abie.mdc`)
1795
- return
1796
- }
1797
- const labels = parseActionlintSelfHostedLabels(raw)
1798
- if (labels === null) {
1799
- fail(`${ABIE_ACTIONLINT_PATH}: не знайдено self-hosted-runner.labels — додай мітки ua, dev, ru (abie.mdc)`)
1800
- return
1801
- }
1802
- const missing = abieMissingActionlintLabels(labels)
1803
- if (missing.length > 0) {
1804
- fail(`${ABIE_ACTIONLINT_PATH}: у self-hosted-runner.labels бракує ${missing.join(', ')} (abie.mdc)`)
1805
- return
1806
- }
1807
- pass(`${ABIE_ACTIONLINT_PATH}: self-hosted-runner.labels містить ua, dev, ru (abie.mdc)`)
1808
- }
1809
-
1810
1724
  /**
1811
1725
  * Перевіряє clean-merged-branch.yml на ignore_branches.
1812
1726
  * @param {string} root корінь репозиторію
@@ -2178,9 +2092,9 @@ export async function check() {
2178
2092
  pass('Правило abie увімкнено — виконуємо перевірки')
2179
2093
  await ensureNoFirebaseHostingArtifacts(root, pass, fail)
2180
2094
  await checkCleanMergedBranch(root, pass, fail)
2181
- await ensureAbieActionlintConfig(root, pass, fail)
2182
2095
 
2183
- const yamlFiles = await findK8sYamlFiles(root)
2096
+ const ignorePaths = await loadCursorIgnorePaths(root)
2097
+ const yamlFiles = await findK8sYamlFiles(root, ignorePaths)
2184
2098
  const deploymentDirs = await collectDeploymentDirs(root, yamlFiles, fail)
2185
2099
 
2186
2100
  if (deploymentDirs.size > 0) {
@@ -33,6 +33,7 @@ import { basename } from 'node:path'
33
33
  import { getMirrorGcrHint, getFromImageToken } from './utils/docker-mirror.mjs'
34
34
  import { lintDockerfileWithHadolint, posixRel } from './utils/docker-hadolint.mjs'
35
35
  import { createCheckReporter } from './utils/check-reporter.mjs'
36
+ import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
36
37
  import { walkDir } from './utils/walkDir.mjs'
37
38
 
38
39
  const NEWLINE_RE = /\r?\n/
@@ -65,14 +66,19 @@ export function isDockerfileName(name) {
65
66
  /**
66
67
  * Збирає абсолютні шляхи до Dockerfile / Containerfile від кореня cwd.
67
68
  * @param {string} root корінь репозиторію
69
+ * @param {string[]} [ignorePaths=[]] шляхи каталогів, повністю виключених з обходу
68
70
  * @returns {Promise<string[]>} відсортовані абсолютні шляхи
69
71
  */
70
- export async function findDockerfilePaths(root) {
72
+ export async function findDockerfilePaths(root, ignorePaths = []) {
71
73
  /** @type {string[]} */
72
74
  const out = []
73
- await walkDir(root, p => {
74
- if (isDockerfileName(basename(p))) out.push(p)
75
- })
75
+ await walkDir(
76
+ root,
77
+ p => {
78
+ if (isDockerfileName(basename(p))) out.push(p)
79
+ },
80
+ ignorePaths
81
+ )
76
82
  return out.toSorted((a, b) => a.localeCompare(b))
77
83
  }
78
84
 
@@ -285,7 +291,8 @@ export async function check() {
285
291
  const { pass } = reporter
286
292
 
287
293
  const root = process.cwd()
288
- const files = await findDockerfilePaths(root)
294
+ const ignorePaths = await loadCursorIgnorePaths(root)
295
+ const files = await findDockerfilePaths(root, ignorePaths)
289
296
 
290
297
  if (files.length === 0) {
291
298
  pass('Немає Dockerfile / Containerfile — перевірку hadolint пропущено')
@@ -17,6 +17,7 @@ import {
17
17
  shouldSkipFileForGqlScan,
18
18
  sourceFileHasGqlTaggedTemplate
19
19
  } from './utils/graphql-gql-scan.mjs'
20
+ import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
20
21
  import { walkDir } from './utils/walkDir.mjs'
21
22
 
22
23
  /** Очікуваний файл GraphQL Config у корені (graphql.mdc). */
@@ -33,16 +34,20 @@ export const REQUIRED_DUMP_SCHEMA_SCRIPT =
33
34
  * @param {string} root абсолютний шлях кореня
34
35
  * @returns {Promise<string[]>} список кандидатів
35
36
  */
36
- async function collectScanCandidates(root) {
37
+ async function collectScanCandidates(root, ignorePaths) {
37
38
  /** @type {string[]} */
38
39
  const candidates = []
39
- await walkDir(root, absPath => {
40
- const rel = relative(root, absPath).split('\\').join('/')
41
- if (shouldSkipFileForGqlScan(rel) || !isGqlScanSourceFile(rel)) {
42
- return
43
- }
44
- candidates.push(absPath)
45
- })
40
+ await walkDir(
41
+ root,
42
+ absPath => {
43
+ const rel = relative(root, absPath).split('\\').join('/')
44
+ if (shouldSkipFileForGqlScan(rel) || !isGqlScanSourceFile(rel)) {
45
+ return
46
+ }
47
+ candidates.push(absPath)
48
+ },
49
+ ignorePaths
50
+ )
46
51
  return candidates
47
52
  }
48
53
 
@@ -148,7 +153,8 @@ export async function check() {
148
153
  const { pass, fail } = reporter
149
154
 
150
155
  const root = process.cwd()
151
- const candidates = await collectScanCandidates(root)
156
+ const ignorePaths = await loadCursorIgnorePaths(root)
157
+ const candidates = await collectScanCandidates(root, ignorePaths)
152
158
  const hits = await collectGqlHits(root, candidates)
153
159
 
154
160
  if (hits.length === 0) {
@@ -30,6 +30,7 @@ import { parseAllDocuments } from 'yaml'
30
30
 
31
31
  import { getRepositoryUrl } from './auto-rules.mjs'
32
32
  import { createCheckReporter } from './utils/check-reporter.mjs'
33
+ import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
33
34
  import { walkDir } from './utils/walkDir.mjs'
34
35
 
35
36
  const NITRA_REPOSITORY_URL_MARKER = 'https://github.com/nitra/'
@@ -103,15 +104,19 @@ export function isEnvFile(relPath) {
103
104
  * @param {string} root абсолютний шлях кореня
104
105
  * @returns {Promise<string[]>} відсортовані posix-шляхи відносно кореня
105
106
  */
106
- async function collectEnvFiles(root) {
107
+ async function collectEnvFiles(root, ignorePaths) {
107
108
  /** @type {string[]} */
108
109
  const out = []
109
- await walkDir(root, absPath => {
110
- const rel = relative(root, absPath).split('\\').join('/')
111
- if (isEnvFile(rel)) {
112
- out.push(rel)
113
- }
114
- })
110
+ await walkDir(
111
+ root,
112
+ absPath => {
113
+ const rel = relative(root, absPath).split('\\').join('/')
114
+ if (isEnvFile(rel)) {
115
+ out.push(rel)
116
+ }
117
+ },
118
+ ignorePaths
119
+ )
115
120
  return out.toSorted((a, b) => a.localeCompare(b))
116
121
  }
117
122
 
@@ -206,7 +211,8 @@ export async function check() {
206
211
  namespace: await readYamlMetadataName(join(root, HASURA_NAMESPACE_FILE), 'Namespace')
207
212
  }
208
213
 
209
- const envFiles = await collectEnvFiles(root)
214
+ const ignorePaths = await loadCursorIgnorePaths(root)
215
+ const envFiles = await collectEnvFiles(root, ignorePaths)
210
216
  if (envFiles.length === 0) {
211
217
  pass('Не знайдено жодного *.env файла — нічого перевіряти')
212
218
  return reporter.getExitCode()
@@ -25,6 +25,7 @@ import { readFile } from 'node:fs/promises'
25
25
  import { join, relative } from 'node:path'
26
26
 
27
27
  import { createCheckReporter } from './utils/check-reporter.mjs'
28
+ import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
28
29
  import { walkDir } from './utils/walkDir.mjs'
29
30
  import { getMonorepoPackageRootDirs } from './utils/workspaces.mjs'
30
31
 
@@ -216,16 +217,20 @@ function packageHasAvifDisabled(pkg) {
216
217
  * @param {(msg: string) => void} fail callback при помилці
217
218
  * @returns {Promise<void>}
218
219
  */
219
- async function checkVueAvifImportsInPackage(packageRoot, otherRootsAbs, pass, fail) {
220
+ async function checkVueAvifImportsInPackage(packageRoot, otherRootsAbs, ignorePaths, pass, fail) {
220
221
  const absRoot = join(process.cwd(), packageRoot)
221
222
  const label = packageRoot === '.' ? 'корінь' : packageRoot
222
223
  /** @type {string[]} */
223
224
  const vueFiles = []
224
- await walkDir(absRoot, absPath => {
225
- if (!absPath.endsWith('.vue')) return
226
- if (otherRootsAbs.some(other => absPath.startsWith(`${other}/`))) return
227
- vueFiles.push(absPath)
228
- })
225
+ await walkDir(
226
+ absRoot,
227
+ absPath => {
228
+ if (!absPath.endsWith('.vue')) return
229
+ if (otherRootsAbs.some(other => absPath.startsWith(`${other}/`))) return
230
+ vueFiles.push(absPath)
231
+ },
232
+ ignorePaths
233
+ )
229
234
  if (vueFiles.length === 0) return
230
235
 
231
236
  let violations = 0
@@ -262,7 +267,7 @@ async function checkVueAvifImportsInPackage(packageRoot, otherRootsAbs, pass, fa
262
267
  * @param {(msg: string) => void} fail callback при помилці
263
268
  * @returns {Promise<void>}
264
269
  */
265
- async function checkVueAvifImports(pass, fail) {
270
+ async function checkVueAvifImports(ignorePaths, pass, fail) {
266
271
  const roots = await getMonorepoPackageRootDirs()
267
272
  const absRootsByRel = new Map(roots.map(r => [r, join(process.cwd(), r)]))
268
273
  for (const root of roots) {
@@ -274,7 +279,7 @@ async function checkVueAvifImports(pass, fail) {
274
279
  continue
275
280
  }
276
281
  const otherRootsAbs = roots.filter(r => r !== root && r !== '.').map(r => absRootsByRel.get(r) ?? '')
277
- await checkVueAvifImportsInPackage(root, otherRootsAbs, pass, fail)
282
+ await checkVueAvifImportsInPackage(root, otherRootsAbs, ignorePaths, pass, fail)
278
283
  }
279
284
  }
280
285
 
@@ -314,7 +319,8 @@ export async function check() {
314
319
  await checkHashCacheNotIgnored(pass, fail)
315
320
  await checkLegacyCacheRemoved(pass, fail)
316
321
  }
317
- await checkVueAvifImports(pass, fail)
322
+ const ignorePaths = await loadCursorIgnorePaths(process.cwd())
323
+ await checkVueAvifImports(ignorePaths, pass, fail)
318
324
 
319
325
  return reporter.getExitCode()
320
326
  }
@@ -35,6 +35,7 @@ import {
35
35
  isBunSqlScanSourceFile,
36
36
  textHasBunSqlImport
37
37
  } from './utils/bun-sql-scan.mjs'
38
+ import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
38
39
  import { walkDir } from './utils/walkDir.mjs'
39
40
 
40
41
  /** Імена забороненої залежності у будь-якому `package.json`. */
@@ -54,14 +55,18 @@ function asObject(v) {
54
55
  * @param {string} repoRoot абсолютний шлях до кореня репозиторію
55
56
  * @returns {Promise<string[]>} абсолютні шляхи, відсортовані за відносним шляхом
56
57
  */
57
- async function findAllPackageJsonPaths(repoRoot) {
58
+ async function findAllPackageJsonPaths(repoRoot, ignorePaths) {
58
59
  /** @type {string[]} */
59
60
  const paths = []
60
- await walkDir(repoRoot, absPath => {
61
- if (absPath.endsWith(`${sep}package.json`)) {
62
- paths.push(absPath)
63
- }
64
- })
61
+ await walkDir(
62
+ repoRoot,
63
+ absPath => {
64
+ if (absPath.endsWith(`${sep}package.json`)) {
65
+ paths.push(absPath)
66
+ }
67
+ },
68
+ ignorePaths
69
+ )
65
70
  paths.sort((a, b) => relative(repoRoot, a).localeCompare(relative(repoRoot, b)))
66
71
  return paths
67
72
  }
@@ -71,15 +76,19 @@ async function findAllPackageJsonPaths(repoRoot) {
71
76
  * @param {string} repoRoot абсолютний шлях до кореня репозиторію
72
77
  * @returns {Promise<string[]>} абсолютні шляхи, відсортовані за відносним шляхом
73
78
  */
74
- async function findAllSourcePathsForBunSqlScan(repoRoot) {
79
+ async function findAllSourcePathsForBunSqlScan(repoRoot, ignorePaths) {
75
80
  /** @type {string[]} */
76
81
  const paths = []
77
- await walkDir(repoRoot, absPath => {
78
- const rel = relative(repoRoot, absPath).split('\\').join('/')
79
- if (isBunSqlScanSourceFile(rel)) {
80
- paths.push(absPath)
81
- }
82
- })
82
+ await walkDir(
83
+ repoRoot,
84
+ absPath => {
85
+ const rel = relative(repoRoot, absPath).split('\\').join('/')
86
+ if (isBunSqlScanSourceFile(rel)) {
87
+ paths.push(absPath)
88
+ }
89
+ },
90
+ ignorePaths
91
+ )
83
92
  paths.sort((a, b) => relative(repoRoot, a).localeCompare(relative(repoRoot, b)))
84
93
  return paths
85
94
  }
@@ -236,7 +245,8 @@ export async function check() {
236
245
  return reporter.getExitCode()
237
246
  }
238
247
 
239
- const pkgJsonPaths = await findAllPackageJsonPaths(repoRoot)
248
+ const ignorePaths = await loadCursorIgnorePaths(repoRoot)
249
+ const pkgJsonPaths = await findAllPackageJsonPaths(repoRoot, ignorePaths)
240
250
  if (pkgJsonPaths.length === 0) {
241
251
  pass('js-bun-db: package.json не знайдено — перевірку пропущено')
242
252
  return reporter.getExitCode()
@@ -244,7 +254,7 @@ export async function check() {
244
254
 
245
255
  await checkForbiddenDependencies(pkgJsonPaths, repoRoot, reporter)
246
256
 
247
- const sourcePaths = await findAllSourcePathsForBunSqlScan(repoRoot)
257
+ const sourcePaths = await findAllSourcePathsForBunSqlScan(repoRoot, ignorePaths)
248
258
  if (sourcePaths.length === 0) {
249
259
  pass('js-bun-db: немає JS/TS файлів для скану патернів Bun SQL')
250
260
  return reporter.getExitCode()