@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 +24 -0
- package/README.md +14 -0
- package/bin/auto-rules.md +1 -1
- package/mdc/abie.mdc +2 -14
- package/package.json +1 -1
- package/schemas/n-cursor.json +1 -1
- package/scripts/auto-rules.mjs +2 -8
- package/scripts/check-abie.mjs +18 -104
- package/scripts/check-docker.mjs +12 -5
- package/scripts/check-graphql.mjs +15 -9
- package/scripts/check-hasura.mjs +14 -8
- package/scripts/check-image.mjs +15 -9
- package/scripts/check-js-bun-db.mjs +25 -15
- package/scripts/check-js-mssql.mjs +27 -17
- package/scripts/check-js-run.mjs +26 -16
- package/scripts/check-k8s.mjs +22 -13
- package/scripts/check-nginx-default-tpl.mjs +26 -16
- package/scripts/check-npm-module.mjs +13 -7
- package/scripts/check-vue.mjs +27 -17
- package/scripts/rename-yaml-extensions.mjs +35 -29
- package/scripts/run-docker.mjs +11 -5
- package/scripts/run-k8s.mjs +14 -8
- package/scripts/utils/load-cursor-config.mjs +53 -0
- package/scripts/utils/walkDir.mjs +49 -8
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
package/mdc/abie.mdc
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Правила для проєктів AbInBev Efes
|
|
3
3
|
alwaysApply: true
|
|
4
|
-
version: '1.
|
|
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**,
|
|
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
package/schemas/n-cursor.json
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"ignore": {
|
|
47
47
|
"type": "array",
|
|
48
|
-
"description": "Директорії,
|
|
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
|
package/scripts/auto-rules.mjs
CHANGED
|
@@ -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:
|
|
609
|
+
{ enabled: composerJsonExists, id: 'php' },
|
|
616
610
|
{ enabled: facts.hasVueOrCssSource, id: 'style-lint' }
|
|
617
611
|
]
|
|
618
612
|
for (const item of autoRuleChecks) {
|
package/scripts/check-abie.mjs
CHANGED
|
@@ -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 {
|
|
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(
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
|
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) {
|
package/scripts/check-docker.mjs
CHANGED
|
@@ -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(
|
|
74
|
-
|
|
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
|
|
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(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
|
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) {
|
package/scripts/check-hasura.mjs
CHANGED
|
@@ -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(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
|
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()
|
package/scripts/check-image.mjs
CHANGED
|
@@ -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(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
|
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(
|
|
61
|
-
|
|
62
|
-
|
|
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(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
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()
|