@nitra/cursor 1.8.171 → 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 +18 -0
- package/README.md +14 -0
- package/bin/auto-rules.md +1 -1
- package/package.json +1 -1
- package/schemas/n-cursor.json +1 -1
- package/scripts/auto-rules.mjs +2 -8
- package/scripts/check-abie.mjs +17 -11
- 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,24 @@
|
|
|
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
|
+
|
|
7
25
|
## [1.8.171] - 2026-05-04
|
|
8
26
|
|
|
9
27
|
### Removed
|
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/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
|
@@ -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'
|
|
@@ -385,18 +386,22 @@ export function ignoreBranchesIncludesRequired(ignoreBranches, required) {
|
|
|
385
386
|
* @param {string} root корінь репозиторію
|
|
386
387
|
* @returns {Promise<string[]>} відсортовані шляхи
|
|
387
388
|
*/
|
|
388
|
-
async function findK8sYamlFiles(root) {
|
|
389
|
+
async function findK8sYamlFiles(root, ignorePaths = []) {
|
|
389
390
|
/** @type {string[]} */
|
|
390
391
|
const out = []
|
|
391
|
-
await walkDir(
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
+
)
|
|
400
405
|
return [...out].toSorted((a, b) => a.localeCompare(b))
|
|
401
406
|
}
|
|
402
407
|
|
|
@@ -2088,7 +2093,8 @@ export async function check() {
|
|
|
2088
2093
|
await ensureNoFirebaseHostingArtifacts(root, pass, fail)
|
|
2089
2094
|
await checkCleanMergedBranch(root, pass, fail)
|
|
2090
2095
|
|
|
2091
|
-
const
|
|
2096
|
+
const ignorePaths = await loadCursorIgnorePaths(root)
|
|
2097
|
+
const yamlFiles = await findK8sYamlFiles(root, ignorePaths)
|
|
2092
2098
|
const deploymentDirs = await collectDeploymentDirs(root, yamlFiles, fail)
|
|
2093
2099
|
|
|
2094
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()
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
findUnsafeMssqlInListMissingEmptyGuardInText,
|
|
23
23
|
isMssqlScanSourceFile
|
|
24
24
|
} from './utils/mssql-pool-scan.mjs'
|
|
25
|
+
import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
|
|
25
26
|
import { walkDir } from './utils/walkDir.mjs'
|
|
26
27
|
|
|
27
28
|
const VERSION_PREFIX_RE = /^[\^~>=<]+\s*/u
|
|
@@ -35,14 +36,18 @@ const MIN_MSSQL_VERSION = { major: 12, minor: 5, patch: 0 }
|
|
|
35
36
|
* @param {string} repoRoot абсолютний шлях до кореня репозиторію
|
|
36
37
|
* @returns {Promise<string[]>} абсолютні шляхи, відсортовані за відносним шляхом
|
|
37
38
|
*/
|
|
38
|
-
async function findAllPackageJsonPaths(repoRoot) {
|
|
39
|
+
async function findAllPackageJsonPaths(repoRoot, ignorePaths) {
|
|
39
40
|
/** @type {string[]} */
|
|
40
41
|
const paths = []
|
|
41
|
-
await walkDir(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
await walkDir(
|
|
43
|
+
repoRoot,
|
|
44
|
+
absPath => {
|
|
45
|
+
if (absPath.endsWith(`${sep}package.json`)) {
|
|
46
|
+
paths.push(absPath)
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
ignorePaths
|
|
50
|
+
)
|
|
46
51
|
paths.sort((a, b) => relative(repoRoot, a).localeCompare(relative(repoRoot, b)))
|
|
47
52
|
return paths
|
|
48
53
|
}
|
|
@@ -52,15 +57,19 @@ async function findAllPackageJsonPaths(repoRoot) {
|
|
|
52
57
|
* @param {string} repoRoot абсолютний шлях до кореня репозиторію
|
|
53
58
|
* @returns {Promise<string[]>} абсолютні шляхи, відсортовані за відносним шляхом
|
|
54
59
|
*/
|
|
55
|
-
async function findAllSourcePathsForMssqlScan(repoRoot) {
|
|
60
|
+
async function findAllSourcePathsForMssqlScan(repoRoot, ignorePaths) {
|
|
56
61
|
/** @type {string[]} */
|
|
57
62
|
const paths = []
|
|
58
|
-
await walkDir(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
await walkDir(
|
|
64
|
+
repoRoot,
|
|
65
|
+
absPath => {
|
|
66
|
+
const rel = relative(repoRoot, absPath).split('\\').join('/')
|
|
67
|
+
if (isMssqlScanSourceFile(rel)) {
|
|
68
|
+
paths.push(absPath)
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
ignorePaths
|
|
72
|
+
)
|
|
64
73
|
paths.sort((a, b) => relative(repoRoot, a).localeCompare(relative(repoRoot, b)))
|
|
65
74
|
return paths
|
|
66
75
|
}
|
|
@@ -253,8 +262,8 @@ function reportZeroMssqlSourceViolations(counters, pass) {
|
|
|
253
262
|
* @param {(msg: string) => void} fail fail callback
|
|
254
263
|
* @returns {Promise<void>}
|
|
255
264
|
*/
|
|
256
|
-
async function auditMssqlSources(repoRoot, pass, fail) {
|
|
257
|
-
const sourcePaths = await findAllSourcePathsForMssqlScan(repoRoot)
|
|
265
|
+
async function auditMssqlSources(repoRoot, ignorePaths, pass, fail) {
|
|
266
|
+
const sourcePaths = await findAllSourcePathsForMssqlScan(repoRoot, ignorePaths)
|
|
258
267
|
if (sourcePaths.length === 0) {
|
|
259
268
|
pass('js-mssql: немає JS/TS файлів для скану singleton ConnectionPool')
|
|
260
269
|
return
|
|
@@ -291,7 +300,8 @@ export async function check() {
|
|
|
291
300
|
return reporter.getExitCode()
|
|
292
301
|
}
|
|
293
302
|
|
|
294
|
-
const
|
|
303
|
+
const ignorePaths = await loadCursorIgnorePaths(repoRoot)
|
|
304
|
+
const pkgJsonPaths = await findAllPackageJsonPaths(repoRoot, ignorePaths)
|
|
295
305
|
if (pkgJsonPaths.length === 0) {
|
|
296
306
|
pass('js-mssql: package.json не знайдено — перевірку пропущено')
|
|
297
307
|
return reporter.getExitCode()
|
|
@@ -307,7 +317,7 @@ export async function check() {
|
|
|
307
317
|
pass(`js-mssql: всі знайдені dependencies.mssql відповідають мінімальній версії 12.5.0 (${found})`)
|
|
308
318
|
}
|
|
309
319
|
|
|
310
|
-
await auditMssqlSources(repoRoot, pass, fail)
|
|
320
|
+
await auditMssqlSources(repoRoot, ignorePaths, pass, fail)
|
|
311
321
|
|
|
312
322
|
return reporter.getExitCode()
|
|
313
323
|
}
|
package/scripts/check-js-run.mjs
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
isInsideConnDir,
|
|
37
37
|
resolveConnDirFromPackageJson
|
|
38
38
|
} from './utils/conn-imports-scan.mjs'
|
|
39
|
+
import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
|
|
39
40
|
import { walkDir } from './utils/walkDir.mjs'
|
|
40
41
|
import { getMonorepoPackageRootDirs } from './utils/workspaces.mjs'
|
|
41
42
|
|
|
@@ -56,15 +57,19 @@ function relPosix(absPackageRoot, absPath) {
|
|
|
56
57
|
* @param {(msg: string) => void} fail callback при помилці
|
|
57
58
|
* @returns {Promise<number>} кількість знайдених порушень
|
|
58
59
|
*/
|
|
59
|
-
async function checkBunyanImports(absPackageRoot, label, fail) {
|
|
60
|
+
async function checkBunyanImports(absPackageRoot, ignorePaths, label, fail) {
|
|
60
61
|
/** @type {string[]} */
|
|
61
62
|
const sourcePaths = []
|
|
62
|
-
await walkDir(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
await walkDir(
|
|
64
|
+
absPackageRoot,
|
|
65
|
+
absPath => {
|
|
66
|
+
const rel = relPosix(absPackageRoot, absPath)
|
|
67
|
+
if (!shouldSkipFileForBunyanScan(rel) && isBunyanScanSourceFile(rel)) {
|
|
68
|
+
sourcePaths.push(absPath)
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
ignorePaths
|
|
72
|
+
)
|
|
68
73
|
|
|
69
74
|
let violations = 0
|
|
70
75
|
for (const absPath of sourcePaths) {
|
|
@@ -83,13 +88,17 @@ async function checkBunyanImports(absPackageRoot, label, fail) {
|
|
|
83
88
|
* @param {string} absPackageRoot абсолютний шлях до кореня пакета
|
|
84
89
|
* @returns {Promise<string[]>} абсолютні шляхи до файлів
|
|
85
90
|
*/
|
|
86
|
-
async function collectSourceFiles(absPackageRoot) {
|
|
91
|
+
async function collectSourceFiles(absPackageRoot, ignorePaths) {
|
|
87
92
|
/** @type {string[]} */
|
|
88
93
|
const out = []
|
|
89
|
-
await walkDir(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
await walkDir(
|
|
95
|
+
absPackageRoot,
|
|
96
|
+
absPath => {
|
|
97
|
+
const rel = relPosix(absPackageRoot, absPath)
|
|
98
|
+
if (isCheckEnvScanSourceFile(rel)) out.push(absPath)
|
|
99
|
+
},
|
|
100
|
+
ignorePaths
|
|
101
|
+
)
|
|
93
102
|
return out
|
|
94
103
|
}
|
|
95
104
|
|
|
@@ -153,17 +162,17 @@ async function checkProcessEnvUsage(absPackageRoot, sourcePaths, label, fail) {
|
|
|
153
162
|
* @param {(msg: string) => void} passFn успішне повідомлення (як у check-reporter)
|
|
154
163
|
* @returns {Promise<void>} завершується після перевірок цього пакета
|
|
155
164
|
*/
|
|
156
|
-
async function checkWorkspacePackage(rootDir, fail, passFn) {
|
|
165
|
+
async function checkWorkspacePackage(rootDir, ignorePaths, fail, passFn) {
|
|
157
166
|
const label = `[${rootDir}] `
|
|
158
167
|
const absPackageRoot = join(process.cwd(), rootDir)
|
|
159
168
|
const pkgJson = await loadPackageJsonAndCheckBunyanDeps(rootDir, label, fail)
|
|
160
169
|
|
|
161
|
-
const importViolations = await checkBunyanImports(absPackageRoot, label, fail)
|
|
170
|
+
const importViolations = await checkBunyanImports(absPackageRoot, ignorePaths, label, fail)
|
|
162
171
|
if (importViolations === 0) {
|
|
163
172
|
passFn(`${label}немає імпортів '@nitra/bunyan' / 'bunyan' у джерелах`)
|
|
164
173
|
}
|
|
165
174
|
|
|
166
|
-
const sourcePaths = await collectSourceFiles(absPackageRoot)
|
|
175
|
+
const sourcePaths = await collectSourceFiles(absPackageRoot, ignorePaths)
|
|
167
176
|
|
|
168
177
|
const connViolations = await checkConnImports(absPackageRoot, sourcePaths, pkgJson, label, fail)
|
|
169
178
|
if (connViolations === 0) {
|
|
@@ -245,8 +254,9 @@ export async function check() {
|
|
|
245
254
|
return reporter.getExitCode()
|
|
246
255
|
}
|
|
247
256
|
|
|
257
|
+
const ignorePaths = await loadCursorIgnorePaths(process.cwd())
|
|
248
258
|
for (const r of workspaceRoots) {
|
|
249
|
-
await checkWorkspacePackage(r, fail, pass)
|
|
259
|
+
await checkWorkspacePackage(r, ignorePaths, fail, pass)
|
|
250
260
|
}
|
|
251
261
|
|
|
252
262
|
return reporter.getExitCode()
|
package/scripts/check-k8s.mjs
CHANGED
|
@@ -109,6 +109,7 @@ import { basename, dirname, join, relative, resolve } from 'node:path'
|
|
|
109
109
|
import { isSeq, parseAllDocuments, parseDocument } from 'yaml'
|
|
110
110
|
|
|
111
111
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
112
|
+
import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
|
|
112
113
|
import { walkDir } from './utils/walkDir.mjs'
|
|
113
114
|
|
|
114
115
|
/** Версія набору схем yannh — узгоджено з k8s.mdc */
|
|
@@ -1581,16 +1582,21 @@ export function baseKustomizationNamespaceViolation(obj) {
|
|
|
1581
1582
|
/**
|
|
1582
1583
|
* Збирає всі `*.yaml` та `*.yml` під деревом від кореня cwd, якщо шлях містить сегмент `k8s` (для `.yml` далі — помилка перейменування).
|
|
1583
1584
|
* @param {string} root корінь репозиторію (cwd)
|
|
1585
|
+
* @param {string[]} [ignorePaths=[]] шляхи каталогів, повністю виключених з обходу
|
|
1584
1586
|
* @returns {Promise<string[]>} відсортовані абсолютні шляхи до файлів
|
|
1585
1587
|
*/
|
|
1586
|
-
async function findK8sYamlFiles(root) {
|
|
1588
|
+
async function findK8sYamlFiles(root, ignorePaths = []) {
|
|
1587
1589
|
/** @type {string[]} */
|
|
1588
1590
|
const out = []
|
|
1589
|
-
await walkDir(
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1591
|
+
await walkDir(
|
|
1592
|
+
root,
|
|
1593
|
+
p => {
|
|
1594
|
+
if (!pathHasK8sSegment(p)) return
|
|
1595
|
+
if (!YAML_EXTENSION_RE.test(p)) return
|
|
1596
|
+
out.push(p)
|
|
1597
|
+
},
|
|
1598
|
+
ignorePaths
|
|
1599
|
+
)
|
|
1594
1600
|
|
|
1595
1601
|
return out.toSorted((a, b) => a.localeCompare(b))
|
|
1596
1602
|
}
|
|
@@ -1659,12 +1665,13 @@ export function classifyBackendConfigManifestPresence(body) {
|
|
|
1659
1665
|
/**
|
|
1660
1666
|
* Видаляє під **`k8s`** YAML-файли, що містять **лише** ресурси **BackendConfig**; змішані файли — `fail`.
|
|
1661
1667
|
* @param {string} root корінь репозиторію
|
|
1668
|
+
* @param {string[]} ignorePaths шляхи каталогів, повністю виключених з обходу
|
|
1662
1669
|
* @param {(msg: string) => void} fail реєстрація порушення
|
|
1663
1670
|
* @param {(msg: string) => void} pass реєстрація успіху
|
|
1664
1671
|
* @returns {Promise<void>}
|
|
1665
1672
|
*/
|
|
1666
|
-
async function removeBackendConfigOnlyK8sYamlFiles(root, fail, pass) {
|
|
1667
|
-
const yamlFiles = await findK8sYamlFiles(root)
|
|
1673
|
+
async function removeBackendConfigOnlyK8sYamlFiles(root, ignorePaths, fail, pass) {
|
|
1674
|
+
const yamlFiles = await findK8sYamlFiles(root, ignorePaths)
|
|
1668
1675
|
for (const abs of yamlFiles) {
|
|
1669
1676
|
const rel = (relative(root, abs) || abs).replaceAll('\\', '/')
|
|
1670
1677
|
try {
|
|
@@ -1736,12 +1743,13 @@ export function replaceBatchV1beta1ApiVersionInYamlText(raw) {
|
|
|
1736
1743
|
/**
|
|
1737
1744
|
* Проходить усі `*.yaml` / `*.yml` під сегментом `k8s` і на диску застосовує **`replaceBatchV1beta1ApiVersionInYamlText`**.
|
|
1738
1745
|
* @param {string} root корінь репозиторію
|
|
1746
|
+
* @param {string[]} ignorePaths шляхи каталогів, повністю виключених з обходу
|
|
1739
1747
|
* @param {(msg: string) => void} fail колбек повідомлення про помилку
|
|
1740
1748
|
* @param {(msg: string) => void} pass колбек успішного повідомлення
|
|
1741
1749
|
* @returns {Promise<void>}
|
|
1742
1750
|
*/
|
|
1743
|
-
async function rewriteBatchV1beta1ApiVersionInK8sYamlFiles(root, fail, pass) {
|
|
1744
|
-
const yamlFiles = await findK8sYamlFiles(root)
|
|
1751
|
+
async function rewriteBatchV1beta1ApiVersionInK8sYamlFiles(root, ignorePaths, fail, pass) {
|
|
1752
|
+
const yamlFiles = await findK8sYamlFiles(root, ignorePaths)
|
|
1745
1753
|
for (const abs of yamlFiles) {
|
|
1746
1754
|
const rel = (relative(root, abs) || abs).replaceAll('\\', '/')
|
|
1747
1755
|
try {
|
|
@@ -5762,12 +5770,13 @@ export async function check() {
|
|
|
5762
5770
|
const { pass, fail } = reporter
|
|
5763
5771
|
|
|
5764
5772
|
const root = process.cwd()
|
|
5773
|
+
const ignorePaths = await loadCursorIgnorePaths(root)
|
|
5765
5774
|
|
|
5766
|
-
await rewriteBatchV1beta1ApiVersionInK8sYamlFiles(root, fail, pass)
|
|
5775
|
+
await rewriteBatchV1beta1ApiVersionInK8sYamlFiles(root, ignorePaths, fail, pass)
|
|
5767
5776
|
|
|
5768
|
-
await removeBackendConfigOnlyK8sYamlFiles(root, fail, pass)
|
|
5777
|
+
await removeBackendConfigOnlyK8sYamlFiles(root, ignorePaths, fail, pass)
|
|
5769
5778
|
|
|
5770
|
-
const yamlFiles = await findK8sYamlFiles(root)
|
|
5779
|
+
const yamlFiles = await findK8sYamlFiles(root, ignorePaths)
|
|
5771
5780
|
|
|
5772
5781
|
if (yamlFiles.length === 0) {
|
|
5773
5782
|
pass('Немає *.yaml під k8s — перевірку $schema пропущено')
|
|
@@ -19,6 +19,7 @@ import { basename, dirname, join, relative } from 'node:path'
|
|
|
19
19
|
|
|
20
20
|
import { findDockerfilePaths } from './check-docker.mjs'
|
|
21
21
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
22
|
+
import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
|
|
22
23
|
import { walkDir } from './utils/walkDir.mjs'
|
|
23
24
|
|
|
24
25
|
const LINE_SPLIT_RE = /\r?\n/u
|
|
@@ -36,15 +37,19 @@ const GZIP_EXTENSION_RE = /\*\.(?:js|css)/u
|
|
|
36
37
|
* @param {string} root корінь cwd
|
|
37
38
|
* @returns {Promise<string[]>} відсортовані абсолютні шляхи до шаблонів
|
|
38
39
|
*/
|
|
39
|
-
export async function findDefaultConfTemplatePaths(root) {
|
|
40
|
+
export async function findDefaultConfTemplatePaths(root, ignorePaths = []) {
|
|
40
41
|
/** @type {string[]} */
|
|
41
42
|
const out = []
|
|
42
|
-
await walkDir(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
await walkDir(
|
|
44
|
+
root,
|
|
45
|
+
p => {
|
|
46
|
+
if (basename(p) !== 'default.conf.template') return
|
|
47
|
+
const rel = relative(root, p).replaceAll('\\', '/')
|
|
48
|
+
if (rel.includes('tests/fixtures/')) return
|
|
49
|
+
out.push(p)
|
|
50
|
+
},
|
|
51
|
+
ignorePaths
|
|
52
|
+
)
|
|
48
53
|
return out.toSorted((a, b) => a.localeCompare(b))
|
|
49
54
|
}
|
|
50
55
|
|
|
@@ -54,12 +59,16 @@ export async function findDefaultConfTemplatePaths(root) {
|
|
|
54
59
|
* @param {string} root корінь обходу (зазвичай cwd репозиторію)
|
|
55
60
|
* @returns {Promise<{ renamed: string[], overwritten: string[] }>} відносні шляхи до обробленого **default.tpl.conf** (для звіту)
|
|
56
61
|
*/
|
|
57
|
-
export async function migrateDefaultTplConfFiles(root) {
|
|
62
|
+
export async function migrateDefaultTplConfFiles(root, ignorePaths = []) {
|
|
58
63
|
/** @type {string[]} */
|
|
59
64
|
const oldPaths = []
|
|
60
|
-
await walkDir(
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
await walkDir(
|
|
66
|
+
root,
|
|
67
|
+
p => {
|
|
68
|
+
if (basename(p) === 'default.tpl.conf') oldPaths.push(p)
|
|
69
|
+
},
|
|
70
|
+
ignorePaths
|
|
71
|
+
)
|
|
63
72
|
oldPaths.sort((a, b) => a.localeCompare(b))
|
|
64
73
|
|
|
65
74
|
/** @type {string[]} */
|
|
@@ -316,8 +325,8 @@ async function checkTemplateFile(abs, root, passFn, failFn) {
|
|
|
316
325
|
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
317
326
|
* @param {(msg: string) => void} failFn callback при помилці
|
|
318
327
|
*/
|
|
319
|
-
async function checkDockerfiles(root, passFn, failFn) {
|
|
320
|
-
const dockerPaths = await findDockerfilePaths(root)
|
|
328
|
+
async function checkDockerfiles(root, ignorePaths, passFn, failFn) {
|
|
329
|
+
const dockerPaths = await findDockerfilePaths(root, ignorePaths)
|
|
321
330
|
if (dockerPaths.length === 0) {
|
|
322
331
|
failFn(
|
|
323
332
|
'Є default.conf.template, але немає Dockerfile / Containerfile — додай gzip для статики та envsubst (див. nginx-default-tpl.mdc)'
|
|
@@ -380,8 +389,9 @@ export async function check() {
|
|
|
380
389
|
const { pass, fail } = reporter
|
|
381
390
|
|
|
382
391
|
const root = process.cwd()
|
|
392
|
+
const ignorePaths = await loadCursorIgnorePaths(root)
|
|
383
393
|
|
|
384
|
-
const { renamed: tplRenamed, overwritten: tplOverwritten } = await migrateDefaultTplConfFiles(root)
|
|
394
|
+
const { renamed: tplRenamed, overwritten: tplOverwritten } = await migrateDefaultTplConfFiles(root, ignorePaths)
|
|
385
395
|
for (const rel of tplRenamed) {
|
|
386
396
|
pass(`Перейменовано default.tpl.conf → default.conf.template: ${rel}`)
|
|
387
397
|
}
|
|
@@ -389,7 +399,7 @@ export async function check() {
|
|
|
389
399
|
pass(`Перезаписано default.conf.template змістом default.tpl.conf: ${rel}`)
|
|
390
400
|
}
|
|
391
401
|
|
|
392
|
-
const templates = await findDefaultConfTemplatePaths(root)
|
|
402
|
+
const templates = await findDefaultConfTemplatePaths(root, ignorePaths)
|
|
393
403
|
|
|
394
404
|
if (templates.length === 0) {
|
|
395
405
|
pass('Немає default.conf.template — перевірку nginx-default-tpl пропущено')
|
|
@@ -402,7 +412,7 @@ export async function check() {
|
|
|
402
412
|
await checkTemplateFile(abs, root, pass, fail)
|
|
403
413
|
}
|
|
404
414
|
|
|
405
|
-
await checkDockerfiles(root, pass, fail)
|
|
415
|
+
await checkDockerfiles(root, ignorePaths, pass, fail)
|
|
406
416
|
await checkVscodeNginx(pass, fail)
|
|
407
417
|
|
|
408
418
|
return reporter.getExitCode()
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
pushHasMainBranch,
|
|
27
27
|
pushPathsIncludeNpmGlob
|
|
28
28
|
} from './utils/gha-workflow.mjs'
|
|
29
|
+
import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
|
|
29
30
|
import { walkDir } from './utils/walkDir.mjs'
|
|
30
31
|
|
|
31
32
|
const TYPES_FILE_RE = /^\.\/types\/.+\.d\.(ts|mts)$/
|
|
@@ -43,17 +44,21 @@ const CHANGELOG_PATH = 'npm/CHANGELOG.md'
|
|
|
43
44
|
* Чи є під `npm/src` хоча б один `.js` (рекурсивно).
|
|
44
45
|
* @returns {Promise<boolean>} `true`, якщо знайдено хоча б один `.js`
|
|
45
46
|
*/
|
|
46
|
-
async function npmSrcTreeHasJsFile() {
|
|
47
|
+
async function npmSrcTreeHasJsFile(ignorePaths = []) {
|
|
47
48
|
const root = 'npm/src'
|
|
48
49
|
if (!existsSync(root)) {
|
|
49
50
|
return false
|
|
50
51
|
}
|
|
51
52
|
let found = false
|
|
52
|
-
await walkDir(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
await walkDir(
|
|
54
|
+
root,
|
|
55
|
+
p => {
|
|
56
|
+
if (p.endsWith('.js')) {
|
|
57
|
+
found = true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
ignorePaths
|
|
61
|
+
)
|
|
57
62
|
return found
|
|
58
63
|
}
|
|
59
64
|
|
|
@@ -387,7 +392,8 @@ export async function check() {
|
|
|
387
392
|
|
|
388
393
|
await checkNpmModuleBasicStructure(pass, fail)
|
|
389
394
|
|
|
390
|
-
const
|
|
395
|
+
const ignorePaths = await loadCursorIgnorePaths(process.cwd())
|
|
396
|
+
const useSrcJsLayout = await npmSrcTreeHasJsFile(ignorePaths)
|
|
391
397
|
|
|
392
398
|
await checkNpmPackageJson(useSrcJsLayout, pass, fail)
|
|
393
399
|
|
package/scripts/check-vue.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
isVueImportScanSourceFile,
|
|
21
21
|
shouldSkipFileForVueImportScan
|
|
22
22
|
} from './utils/vue-forbidden-imports.mjs'
|
|
23
|
+
import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
|
|
23
24
|
import { walkDir } from './utils/walkDir.mjs'
|
|
24
25
|
import { getMonorepoPackageRootDirs } from './utils/workspaces.mjs'
|
|
25
26
|
|
|
@@ -113,14 +114,18 @@ async function collectEsbuildMatchesInFiles(absPackageRoot, files, maxMatches) {
|
|
|
113
114
|
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
114
115
|
* @param {(msg: string) => void} fail callback при помилці
|
|
115
116
|
*/
|
|
116
|
-
async function checkEsbuildMentions(rootDir, absPackageRoot, prefix, passFn, fail) {
|
|
117
|
+
async function checkEsbuildMentions(rootDir, absPackageRoot, ignorePaths, prefix, passFn, fail) {
|
|
117
118
|
/** @type {{ rel: string }[]} */
|
|
118
119
|
const candidates = []
|
|
119
|
-
await walkDir(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
await walkDir(
|
|
121
|
+
absPackageRoot,
|
|
122
|
+
absPath => {
|
|
123
|
+
const rel = relative(absPackageRoot, absPath).split('\\').join('/')
|
|
124
|
+
if (!isEsbuildScanFile(rel)) return
|
|
125
|
+
candidates.push({ rel })
|
|
126
|
+
},
|
|
127
|
+
ignorePaths
|
|
128
|
+
)
|
|
124
129
|
|
|
125
130
|
const maxMatches = 30
|
|
126
131
|
const matches = await collectEsbuildMatchesInFiles(absPackageRoot, candidates, maxMatches)
|
|
@@ -308,7 +313,7 @@ async function checkViteConfig(rootDir, prefix, passFn, fail) {
|
|
|
308
313
|
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
309
314
|
* @param {(msg: string) => void} fail callback при помилці
|
|
310
315
|
*/
|
|
311
|
-
async function checkVueImportViolations(rootDir, absPackageRoot, hasVueAutoImport, prefix, passFn, fail) {
|
|
316
|
+
async function checkVueImportViolations(rootDir, absPackageRoot, ignorePaths, hasVueAutoImport, prefix, passFn, fail) {
|
|
312
317
|
if (!hasVueAutoImport) {
|
|
313
318
|
passFn(
|
|
314
319
|
`${prefix}value-імпорти з 'vue' не заборонені — спершу додай 'vue' до AutoImport.imports у vite.config`
|
|
@@ -317,12 +322,16 @@ async function checkVueImportViolations(rootDir, absPackageRoot, hasVueAutoImpor
|
|
|
317
322
|
}
|
|
318
323
|
/** @type {string[]} */
|
|
319
324
|
const sourcePaths = []
|
|
320
|
-
await walkDir(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
325
|
+
await walkDir(
|
|
326
|
+
absPackageRoot,
|
|
327
|
+
absPath => {
|
|
328
|
+
const rel = relative(absPackageRoot, absPath).split('\\').join('/')
|
|
329
|
+
if (!shouldSkipFileForVueImportScan(rel) && isVueImportScanSourceFile(rel)) {
|
|
330
|
+
sourcePaths.push(absPath)
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
ignorePaths
|
|
334
|
+
)
|
|
326
335
|
|
|
327
336
|
let importViolations = 0
|
|
328
337
|
for (const absPath of sourcePaths) {
|
|
@@ -347,7 +356,7 @@ async function checkVueImportViolations(rootDir, absPackageRoot, hasVueAutoImpor
|
|
|
347
356
|
* @param {(msg: string) => void} passFn успішне повідомлення (як у check-reporter)
|
|
348
357
|
* @returns {Promise<void>} завершується після перевірок залежностей, `vite.config` і сканування джерел на імпорти з `vue`
|
|
349
358
|
*/
|
|
350
|
-
async function checkVuePackage(rootDir, fail, passFn) {
|
|
359
|
+
async function checkVuePackage(rootDir, ignorePaths, fail, passFn) {
|
|
351
360
|
const prefix = `[${packageLabel(rootDir)}] `
|
|
352
361
|
const pkg = JSON.parse(await readFile(join(rootDir, 'package.json'), 'utf8'))
|
|
353
362
|
const deps = pkg.dependencies || {}
|
|
@@ -387,8 +396,8 @@ async function checkVuePackage(rootDir, fail, passFn) {
|
|
|
387
396
|
)
|
|
388
397
|
|
|
389
398
|
const { hasVueAutoImport } = await checkViteConfig(rootDir, prefix, passFn, fail)
|
|
390
|
-
await checkVueImportViolations(rootDir, join(process.cwd(), rootDir), hasVueAutoImport, prefix, passFn, fail)
|
|
391
|
-
await checkEsbuildMentions(rootDir, join(process.cwd(), rootDir), prefix, passFn, fail)
|
|
399
|
+
await checkVueImportViolations(rootDir, join(process.cwd(), rootDir), ignorePaths, hasVueAutoImport, prefix, passFn, fail)
|
|
400
|
+
await checkEsbuildMentions(rootDir, join(process.cwd(), rootDir), ignorePaths, prefix, passFn, fail)
|
|
392
401
|
}
|
|
393
402
|
|
|
394
403
|
/**
|
|
@@ -446,8 +455,9 @@ export async function check() {
|
|
|
446
455
|
|
|
447
456
|
await checkVueVolarRecommendation(pass, fail)
|
|
448
457
|
|
|
458
|
+
const ignorePaths = await loadCursorIgnorePaths(process.cwd())
|
|
449
459
|
for (const r of vueRoots) {
|
|
450
|
-
await checkVuePackage(r, fail, pass)
|
|
460
|
+
await checkVuePackage(r, ignorePaths, fail, pass)
|
|
451
461
|
}
|
|
452
462
|
|
|
453
463
|
return reporter.getExitCode()
|
|
@@ -14,6 +14,7 @@ import { rename } from 'node:fs/promises'
|
|
|
14
14
|
import { cwd } from 'node:process'
|
|
15
15
|
import { relative, resolve } from 'node:path'
|
|
16
16
|
|
|
17
|
+
import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
|
|
17
18
|
import { walkDir } from './utils/walkDir.mjs'
|
|
18
19
|
|
|
19
20
|
const K8S_YML_RE = /\.yml$/iu
|
|
@@ -69,37 +70,41 @@ export function replaceExtension(relPosix, newExt) {
|
|
|
69
70
|
* @param {string} rootAbs абсолютний корінь репозиторію
|
|
70
71
|
* @returns {Promise<Array<{ kind: 'k8s' | 'github', fromAbs: string, toAbs: string, relFrom: string, relTo: string }>>} відсортовані операції перейменування без запису на диск
|
|
71
72
|
*/
|
|
72
|
-
async function collectRenameOps(rootAbs) {
|
|
73
|
+
async function collectRenameOps(rootAbs, ignorePaths) {
|
|
73
74
|
/** @type {Array<{ kind: 'k8s' | 'github', fromAbs: string, toAbs: string, relFrom: string, relTo: string }>} */
|
|
74
75
|
const ops = []
|
|
75
76
|
|
|
76
|
-
await walkDir(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
77
|
+
await walkDir(
|
|
78
|
+
rootAbs,
|
|
79
|
+
fileAbs => {
|
|
80
|
+
const rel = posixRelFromRoot(rootAbs, fileAbs)
|
|
81
|
+
if (rel === null) return
|
|
82
|
+
if (pathMatchesK8sYml(rel)) {
|
|
83
|
+
const relTo = replaceExtension(rel, '.yaml')
|
|
84
|
+
if (relTo === rel) return
|
|
85
|
+
ops.push({
|
|
86
|
+
kind: 'k8s',
|
|
87
|
+
fromAbs: resolve(rootAbs, rel),
|
|
88
|
+
toAbs: resolve(rootAbs, relTo),
|
|
89
|
+
relFrom: rel,
|
|
90
|
+
relTo
|
|
91
|
+
})
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
if (pathMatchesGithubYaml(rel)) {
|
|
95
|
+
const relTo = replaceExtension(rel, '.yml')
|
|
96
|
+
if (relTo === rel) return
|
|
97
|
+
ops.push({
|
|
98
|
+
kind: 'github',
|
|
99
|
+
fromAbs: resolve(rootAbs, rel),
|
|
100
|
+
toAbs: resolve(rootAbs, relTo),
|
|
101
|
+
relFrom: rel,
|
|
102
|
+
relTo
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
ignorePaths
|
|
107
|
+
)
|
|
103
108
|
|
|
104
109
|
ops.sort((a, b) => {
|
|
105
110
|
const ko = (a.kind === 'k8s' ? 0 : 1) - (b.kind === 'k8s' ? 0 : 1)
|
|
@@ -119,7 +124,8 @@ async function collectRenameOps(rootAbs) {
|
|
|
119
124
|
export async function renameYamlExtensions(root, options = {}) {
|
|
120
125
|
const dryRun = options.dryRun === true
|
|
121
126
|
const rootAbs = resolve(root)
|
|
122
|
-
const
|
|
127
|
+
const ignorePaths = await loadCursorIgnorePaths(rootAbs)
|
|
128
|
+
const ops = await collectRenameOps(rootAbs, ignorePaths)
|
|
123
129
|
|
|
124
130
|
/** @type { { relFrom: string, relTo: string }[]} */
|
|
125
131
|
const renamed = []
|
package/scripts/run-docker.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import { basename } from 'node:path'
|
|
|
12
12
|
import { isRunAsCli } from './cli-entry.mjs'
|
|
13
13
|
import { lintDockerfileWithHadolint, posixRel } from './utils/docker-hadolint.mjs'
|
|
14
14
|
import { createCheckReporter } from './utils/check-reporter.mjs'
|
|
15
|
+
import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
|
|
15
16
|
import { walkDir } from './utils/walkDir.mjs'
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -30,12 +31,16 @@ export function isLintDockerfileName(name) {
|
|
|
30
31
|
* @param {string} root корінь репозиторію
|
|
31
32
|
* @returns {Promise<string[]>} відсортовані абсолютні шляхи
|
|
32
33
|
*/
|
|
33
|
-
export async function findLintDockerfilePaths(root) {
|
|
34
|
+
export async function findLintDockerfilePaths(root, ignorePaths = []) {
|
|
34
35
|
/** @type {string[]} */
|
|
35
36
|
const out = []
|
|
36
|
-
await walkDir(
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
await walkDir(
|
|
38
|
+
root,
|
|
39
|
+
p => {
|
|
40
|
+
if (isLintDockerfileName(basename(p))) out.push(p)
|
|
41
|
+
},
|
|
42
|
+
ignorePaths
|
|
43
|
+
)
|
|
39
44
|
return out.toSorted((a, b) => a.localeCompare(b))
|
|
40
45
|
}
|
|
41
46
|
|
|
@@ -48,7 +53,8 @@ async function main() {
|
|
|
48
53
|
const { pass, fail } = reporter
|
|
49
54
|
|
|
50
55
|
const root = process.cwd()
|
|
51
|
-
const
|
|
56
|
+
const ignorePaths = await loadCursorIgnorePaths(root)
|
|
57
|
+
const files = await findLintDockerfilePaths(root, ignorePaths)
|
|
52
58
|
|
|
53
59
|
if (files.length === 0) {
|
|
54
60
|
pass('lint-docker: немає Dockerfile / *.Dockerfile — hadolint пропущено')
|
package/scripts/run-k8s.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import { spawnSync } from 'node:child_process'
|
|
|
16
16
|
import { basename, dirname } from 'node:path'
|
|
17
17
|
|
|
18
18
|
import { isRunAsCli } from './cli-entry.mjs'
|
|
19
|
+
import { loadCursorIgnorePaths } from './utils/load-cursor-config.mjs'
|
|
19
20
|
import { resolveCmd } from './utils/resolve-cmd.mjs'
|
|
20
21
|
import { walkDir } from './utils/walkDir.mjs'
|
|
21
22
|
|
|
@@ -60,15 +61,19 @@ export function k8sRootFromFile(absFile) {
|
|
|
60
61
|
* @param {string} root корінь репозиторію
|
|
61
62
|
* @returns {Promise<string[]>} відсортовані абсолютні шляхи до каталогів `k8s`
|
|
62
63
|
*/
|
|
63
|
-
export async function findK8sRoots(root) {
|
|
64
|
+
export async function findK8sRoots(root, ignorePaths = []) {
|
|
64
65
|
/** @type {Set<string>} */
|
|
65
66
|
const roots = new Set()
|
|
66
|
-
await walkDir(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
await walkDir(
|
|
68
|
+
root,
|
|
69
|
+
p => {
|
|
70
|
+
if (!pathHasK8sSegment(p)) return
|
|
71
|
+
if (!YAML_EXT_RE.test(p)) return
|
|
72
|
+
const k8sRoot = k8sRootFromFile(p)
|
|
73
|
+
if (k8sRoot) roots.add(k8sRoot)
|
|
74
|
+
},
|
|
75
|
+
ignorePaths
|
|
76
|
+
)
|
|
72
77
|
return [...roots].toSorted((a, b) => a.localeCompare(b))
|
|
73
78
|
}
|
|
74
79
|
|
|
@@ -134,7 +139,8 @@ function runKubescape(dirs) {
|
|
|
134
139
|
*/
|
|
135
140
|
async function main() {
|
|
136
141
|
const root = process.cwd()
|
|
137
|
-
const
|
|
142
|
+
const ignorePaths = await loadCursorIgnorePaths(root)
|
|
143
|
+
const dirs = await findK8sRoots(root, ignorePaths)
|
|
138
144
|
|
|
139
145
|
if (dirs.length === 0) {
|
|
140
146
|
console.log('run-k8s: немає *.yaml під k8s — kubeconform і kubescape пропущено')
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Утиліта читання `.n-cursor.json` у корені репозиторію.
|
|
3
|
+
*
|
|
4
|
+
* Зараз експортує `loadCursorIgnorePaths(root)` — список абсолютних posix-шляхів каталогів,
|
|
5
|
+
* які check-скрипти повністю виключають з обходу (поле `ignore` у конфізі).
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync } from 'node:fs'
|
|
8
|
+
import { readFile } from 'node:fs/promises'
|
|
9
|
+
import { isAbsolute, join, resolve, sep } from 'node:path'
|
|
10
|
+
|
|
11
|
+
const CONFIG_FILE = '.n-cursor.json'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Нормалізує шлях до абсолютного posix-формату без trailing-slash.
|
|
15
|
+
* Відносні шляхи розв'язуються від `root`.
|
|
16
|
+
* @param {string} root абсолютний корінь репозиторію
|
|
17
|
+
* @param {string} p шлях з конфігу (відносний або абсолютний)
|
|
18
|
+
* @returns {string} абсолютний posix-шлях
|
|
19
|
+
*/
|
|
20
|
+
function toAbsPosix(root, p) {
|
|
21
|
+
const trimmed = String(p).trim()
|
|
22
|
+
const abs = isAbsolute(trimmed) ? trimmed : resolve(root, trimmed)
|
|
23
|
+
return abs.split(sep).join('/').replace(/\/+$/, '')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Читає `.n-cursor.json` з кореня та повертає нормалізовані ignore-шляхи.
|
|
28
|
+
* Якщо файлу нема, поле `ignore` відсутнє чи має невалідний формат — повертає порожній масив.
|
|
29
|
+
* Сам конфіг не валідується (це робить v8r/окрема перевірка) — лише поле `ignore`.
|
|
30
|
+
* @param {string} root абсолютний корінь репозиторію
|
|
31
|
+
* @returns {Promise<string[]>} абсолютні posix-шляхи без trailing-slash
|
|
32
|
+
*/
|
|
33
|
+
export async function loadCursorIgnorePaths(root) {
|
|
34
|
+
const file = join(root, CONFIG_FILE)
|
|
35
|
+
if (!existsSync(file)) return []
|
|
36
|
+
let raw
|
|
37
|
+
try {
|
|
38
|
+
raw = JSON.parse(await readFile(file, 'utf8'))
|
|
39
|
+
} catch {
|
|
40
|
+
return []
|
|
41
|
+
}
|
|
42
|
+
const list = raw?.ignore
|
|
43
|
+
if (!Array.isArray(list)) return []
|
|
44
|
+
/** @type {string[]} */
|
|
45
|
+
const out = []
|
|
46
|
+
for (const item of list) {
|
|
47
|
+
if (typeof item !== 'string') continue
|
|
48
|
+
const v = item.trim()
|
|
49
|
+
if (v.length === 0) continue
|
|
50
|
+
out.push(toAbsPosix(root, v))
|
|
51
|
+
}
|
|
52
|
+
return out
|
|
53
|
+
}
|
|
@@ -2,19 +2,60 @@
|
|
|
2
2
|
* Рекурсивний обхід каталогів для скриптів перевірки (Dockerfile, k8s YAML тощо).
|
|
3
3
|
*
|
|
4
4
|
* Обходить дерево від заданого кореня; для кожного звичайного файлу викликає переданий callback.
|
|
5
|
-
* Каталоги node_modules, .git, dist, coverage, .turbo, .next не заходяться.
|
|
6
|
-
*
|
|
5
|
+
* Каталоги node_modules, .git, dist, coverage, .turbo, .next не заходяться.
|
|
6
|
+
* Додатково можна передати `ignorePaths` — повні шляхи каталогів (абсолютні posix), які слід
|
|
7
|
+
* пропускати разом з усім вмістом (поле `ignore` у `.n-cursor.json`). Якщо readdir для каталогу
|
|
8
|
+
* не вдається — тихо виходить без throw.
|
|
7
9
|
*/
|
|
8
10
|
import { readdir } from 'node:fs/promises'
|
|
9
|
-
import { join } from 'node:path'
|
|
11
|
+
import { isAbsolute, join, resolve, sep } from 'node:path'
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
14
|
+
* Перетворює довільний шлях у абсолютний posix-формат без trailing-slash.
|
|
15
|
+
* @param {string} p шлях
|
|
16
|
+
* @returns {string} абсолютний posix-шлях
|
|
17
|
+
*/
|
|
18
|
+
function toAbsPosix(p) {
|
|
19
|
+
const abs = isAbsolute(p) ? p : resolve(p)
|
|
20
|
+
return abs.split(sep).join('/').replace(/\/+$/, '')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Чи каталог `dirAbsPosix` входить у список ignore (точний збіг або префікс з '/').
|
|
25
|
+
* Часткові збіги басенейму не враховуються (postgres-master-test ≠ postgres-master).
|
|
26
|
+
* @param {string} dirAbsPosix абсолютний posix-шлях каталогу
|
|
27
|
+
* @param {string[]} ignorePosix вже нормалізовані ignore-шляхи
|
|
28
|
+
* @returns {boolean}
|
|
29
|
+
*/
|
|
30
|
+
function isIgnoredDir(dirAbsPosix, ignorePosix) {
|
|
31
|
+
for (const ig of ignorePosix) {
|
|
32
|
+
if (dirAbsPosix === ig) return true
|
|
33
|
+
if (dirAbsPosix.startsWith(`${ig}/`)) return true
|
|
34
|
+
}
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Рекурсивно обходить каталог, пропускає типові артефакти збірки/залежностей та `ignorePaths`.
|
|
13
40
|
* @param {string} dir абсолютний шлях
|
|
14
41
|
* @param {(filePath: string) => void} onFile виклик для кожного файлу
|
|
42
|
+
* @param {string[]} [ignorePaths=[]] шляхи каталогів (відносні від cwd або абсолютні), що повністю виключаються з обходу
|
|
43
|
+
* @returns {Promise<void>}
|
|
44
|
+
*/
|
|
45
|
+
export async function walkDir(dir, onFile, ignorePaths = []) {
|
|
46
|
+
const ignorePosix = ignorePaths.map(toAbsPosix)
|
|
47
|
+
await walkDirInner(dir, onFile, ignorePosix)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Внутрішній рекурсор. ignorePosix вже нормалізовано — не нормалізуємо повторно на кожному рівні.
|
|
52
|
+
* @param {string} dir
|
|
53
|
+
* @param {(filePath: string) => void} onFile
|
|
54
|
+
* @param {string[]} ignorePosix
|
|
15
55
|
* @returns {Promise<void>}
|
|
16
56
|
*/
|
|
17
|
-
|
|
57
|
+
async function walkDirInner(dir, onFile, ignorePosix) {
|
|
58
|
+
if (ignorePosix.length > 0 && isIgnoredDir(toAbsPosix(dir), ignorePosix)) return
|
|
18
59
|
let entries
|
|
19
60
|
try {
|
|
20
61
|
entries = await readdir(dir, { withFileTypes: true })
|
|
@@ -31,9 +72,9 @@ export async function walkDir(dir, onFile) {
|
|
|
31
72
|
e.name === 'coverage' ||
|
|
32
73
|
e.name === '.turbo' ||
|
|
33
74
|
e.name === '.next'
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
75
|
+
if (skipDir) continue
|
|
76
|
+
if (ignorePosix.length > 0 && isIgnoredDir(toAbsPosix(p), ignorePosix)) continue
|
|
77
|
+
await walkDirInner(p, onFile, ignorePosix)
|
|
37
78
|
} else if (e.isFile()) {
|
|
38
79
|
onFile(p)
|
|
39
80
|
}
|