@nitra/cursor 1.27.9 → 1.28.0
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/package.json +1 -1
- package/rules/abie/js/applies.mjs +3 -2
- package/rules/abie/js/env_dns.mjs +4 -2
- package/rules/abie/js/firebase_hosting.mjs +3 -2
- package/rules/abie/js/hc_pairing.mjs +3 -2
- package/rules/abie/js/ua_http_route.mjs +4 -2
- package/rules/abie/js/ua_node_selector.mjs +4 -2
- package/rules/adr/js/hooks.mjs +36 -28
- package/rules/bun/js/layout.mjs +16 -11
- package/rules/capacitor/js/platforms.mjs +3 -2
- package/rules/changelog/js/consistency.mjs +85 -63
- package/rules/changelog/lib/package-manifest.mjs +5 -4
- package/rules/docker/js/lint.mjs +3 -2
- package/rules/ga/js/workflows.mjs +41 -32
- package/rules/graphql/js/tooling.mjs +15 -11
- package/rules/hasura/js/internal_urls.mjs +14 -10
- package/rules/image-avif/js/avif_generation.mjs +36 -23
- package/rules/image-compress/js/package_setup.mjs +18 -12
- package/rules/js-bun-db/js/safety.mjs +3 -2
- package/rules/js-lint/js/tooling.mjs +45 -32
- package/rules/js-run/js/runtime.mjs +21 -15
- package/rules/k8s/js/manifests.mjs +3 -2
- package/rules/nginx-default-tpl/js/template.mjs +3 -2
- package/rules/npm-module/js/package_structure.mjs +82 -57
- package/rules/rego/js/applies.mjs +4 -4
- package/rules/rust/js/applies.mjs +5 -3
- package/rules/security/js/sample_secret.mjs +2 -2
- package/rules/security/js/trufflehog.mjs +6 -4
- package/rules/style-lint/js/tooling.mjs +15 -8
- package/rules/test/coverage/coverage.mjs +1 -1
- package/rules/test/js/data/vitest_config/vitest.config.baseline.js +7 -0
- package/rules/test/js/location.mjs +3 -2
- package/rules/test/js/no-process-chdir.mjs +89 -0
- package/rules/test/js/vitest-config-pool-forks.mjs +52 -0
- package/rules/test/test.mdc +17 -0
- package/rules/text/js/forbidden-prettier.mjs +4 -2
- package/rules/text/js/formatting.mjs +25 -16
- package/rules/vue/js/packages.mjs +33 -25
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vitest.config.js` має ставити `pool: 'forks'` — defense-in-depth для
|
|
3
|
+
* race-bug у `process.cwd()` (test.mdc, секція "Заборона `process.chdir` у тестах").
|
|
4
|
+
*
|
|
5
|
+
* Чому не достатньо самої заборони `process.chdir(`: third-party код у залежностях
|
|
6
|
+
* може робити chdir всередині vitest worker'а. У `pool: 'threads'` (default) усі
|
|
7
|
+
* workers ділять один процес → race на `process.cwd()` між паралельними test
|
|
8
|
+
* files. `pool: 'forks'` ізолює кожен test file у власному child-процесі.
|
|
9
|
+
*
|
|
10
|
+
* Перевірка — substring у source-тексті `vitest.config.js`. Не парсимо JS AST,
|
|
11
|
+
* бо це може бути будь-який export-формат (ESM default, named, CommonJS).
|
|
12
|
+
* Достатньо знайти `pool:` із значенням `'forks'`/`"forks"` (whitespace дозволений).
|
|
13
|
+
*
|
|
14
|
+
* Скіпи: правило не застосовне, якщо `vitest.config.js` не існує (нема vitest
|
|
15
|
+
* у проєкті) — це не помилка, лише skip. Якщо файл є — `pool: 'forks'`
|
|
16
|
+
* обов'язковий.
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync } from 'node:fs'
|
|
19
|
+
import { readFile } from 'node:fs/promises'
|
|
20
|
+
import { join } from 'node:path'
|
|
21
|
+
|
|
22
|
+
import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
|
|
23
|
+
|
|
24
|
+
/** Subтring-pattern: `pool: 'forks'` або `pool: "forks"` (з опційним whitespace). */
|
|
25
|
+
const POOL_FORKS_RE = /pool\s*:\s*['"]forks['"]/u
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Перевіряє, що `vitest.config.js` (якщо існує) містить `pool: 'forks'`.
|
|
29
|
+
* @param {string} [cwdParam] корінь репозиторію
|
|
30
|
+
* @returns {Promise<number>} 0 — OK або skip, 1 — config без `pool: 'forks'`
|
|
31
|
+
*/
|
|
32
|
+
export async function check(cwdParam = process.cwd()) {
|
|
33
|
+
const reporter = createCheckReporter()
|
|
34
|
+
const { pass, fail } = reporter
|
|
35
|
+
|
|
36
|
+
const configPath = join(cwdParam, 'vitest.config.js')
|
|
37
|
+
if (!existsSync(configPath)) {
|
|
38
|
+
pass('vitest.config.js відсутній — pool-перевірку пропущено')
|
|
39
|
+
return reporter.getExitCode()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const body = await readFile(configPath, 'utf8')
|
|
43
|
+
if (POOL_FORKS_RE.test(body)) {
|
|
44
|
+
pass("vitest.config.js містить pool: 'forks' (test.mdc)")
|
|
45
|
+
} else {
|
|
46
|
+
fail(
|
|
47
|
+
"vitest.config.js має містити pool: 'forks' — defense-in-depth для race у process.cwd() між паралельними test files (test.mdc)"
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return reporter.getExitCode()
|
|
52
|
+
}
|
package/rules/test/test.mdc
CHANGED
|
@@ -60,6 +60,23 @@ Recursive globs ловлять файли всередині `tests/` так с
|
|
|
60
60
|
|
|
61
61
|
Пропускаються: `node_modules`, `.git`, `dist`, `build`, `.venv`, `venv`, шляхи з `.n-cursor.json:ignore`.
|
|
62
62
|
|
|
63
|
+
## Заборона `process.chdir` у тестах
|
|
64
|
+
|
|
65
|
+
`process.chdir(dir)` — **process-wide** мутація. Vitest за замовчуванням ставить `pool: 'threads'`, і всі workers ділять один процес: паралельний test file може перехопити cwd сусіда посеред FS- або `git`-операції. У реальному інциденті це призвело до того, що `git init`+`git commit` із tmp-фікстури потрапив у реальний робочий репозиторій і створив rogue commit з автором `test <test@test>`, знищивши `npm/package.json` / `CHANGELOG.md`.
|
|
66
|
+
|
|
67
|
+
Тому:
|
|
68
|
+
|
|
69
|
+
- **У тестах заборонено** `process.chdir(...)` напряму та будь-які хелпери, що його викликають (історичний `withTmpCwd` видалений у `1.28.0`).
|
|
70
|
+
- Канон: `withTmpDir(async dir => { ... })` зі `scripts/utils/test-helpers.mjs` — створює tmpdir і передає **абсолютний** `dir` у callback **без** `process.chdir`.
|
|
71
|
+
- Усі FS-операції у тесті — через `join(dir, …)` і `writeJson(join(dir, …), …)` / `ensureDir(join(dir, …))` (хелпери валідують `isAbsolute`).
|
|
72
|
+
- Усі child-процеси — `execFile(bin, args, { cwd: dir })`, `spawnSync(bin, args, { cwd: dir })`.
|
|
73
|
+
- Concern-функції правил — `await check(dir)`, `await applies(dir)`, `await fix(dir)`; усі production функції приймають перший параметр `cwd = process.cwd()` (default зберігає CLI-сумісність).
|
|
74
|
+
- `vitest.config.js` додатково ставить `pool: 'forks'` як defense-in-depth: навіть якщо хтось пропустить правило вище, fork-ізоляція не дасть race у production tree.
|
|
75
|
+
|
|
76
|
+
Це **обов'язково** і для тестів пакета `@nitra/cursor`, і для кожного проєкту-споживача. Перевіряє concern `no-process-chdir` (`rules/test/js/no-process-chdir.mjs`): сканує `**/*.test.{js,mjs}` і падає з ❌ на будь-яке вживання `process.chdir(`.
|
|
77
|
+
|
|
78
|
+
Канон `vitest.config.js` (substring requirement `pool: 'forks'`): [vitest.config.snippet.js](./policy/vitest_config/template/vitest.config.snippet.js)
|
|
79
|
+
|
|
63
80
|
## Покриття + мутаційне тестування
|
|
64
81
|
|
|
65
82
|
Канонічна команда — `n-cursor coverage`: збирає метрики покриття (`vitest run --coverage`, `cargo llvm-cov` тощо) і мутаційного тестування (Stryker з vitest-runner + `coverageAnalysis: 'perTest'`, `cargo-mutants`) з усіх активних провайдерів у `.n-cursor.json#rules` і пише `COVERAGE.md` у корінь проєкту. Лок і дедуп — `withLock('coverage', ...)`.
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* (https://prettier.io/docs/configuration). Якщо Prettier додасть новий формат — додай рядок.
|
|
10
10
|
*/
|
|
11
11
|
import { existsSync } from 'node:fs'
|
|
12
|
+
import { join } from 'node:path'
|
|
12
13
|
|
|
13
14
|
import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
|
|
14
15
|
|
|
@@ -38,15 +39,16 @@ const FORBIDDEN_PRETTIER_FILES = [
|
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
41
|
* Перевіряє, що жоден Prettier-конфіг чи ignore-файл не лежить у корені проєкту.
|
|
42
|
+
* @param {string} [cwd] корінь репозиторію
|
|
41
43
|
* @returns {number} 0 — все OK, 1 — знайдено заборонений файл
|
|
42
44
|
*/
|
|
43
|
-
export function check() {
|
|
45
|
+
export function check(cwd = process.cwd()) {
|
|
44
46
|
const reporter = createCheckReporter()
|
|
45
47
|
const { pass, fail } = reporter
|
|
46
48
|
|
|
47
49
|
let anyFound = false
|
|
48
50
|
for (const file of FORBIDDEN_PRETTIER_FILES) {
|
|
49
|
-
if (existsSync(file)) {
|
|
51
|
+
if (existsSync(join(cwd, file))) {
|
|
50
52
|
fail(`${file} заборонено — Prettier не використовуємо, перейди на oxfmt (text.mdc)`)
|
|
51
53
|
anyFound = true
|
|
52
54
|
}
|
|
@@ -27,9 +27,11 @@
|
|
|
27
27
|
* у `devDependencies`, заборона `markdownlint-cli2` у залежностях.
|
|
28
28
|
* - `npm/policy/bun/package_json/` — у `devDependencies` лише `@nitra/*`
|
|
29
29
|
* (раніше дублювалося тут).
|
|
30
|
+
* @param {string} cwd корінь репозиторію
|
|
30
31
|
*/
|
|
31
32
|
import { existsSync } from 'node:fs'
|
|
32
33
|
import { readFile } from 'node:fs/promises'
|
|
34
|
+
import { join } from 'node:path'
|
|
33
35
|
|
|
34
36
|
import { createCheckReporter } from '../../../scripts/lib/check-reporter.mjs'
|
|
35
37
|
import { anyRunStepIncludes, parseWorkflowYaml } from '../../../scripts/lib/gha-workflow.mjs'
|
|
@@ -65,14 +67,16 @@ function verifyUkApostropheRuleParagraph(filePath, body, failFn, passFn) {
|
|
|
65
67
|
* Перевіряє .v8rignore.
|
|
66
68
|
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
67
69
|
* @param {(msg: string) => void} failFn callback при помилці
|
|
70
|
+
* @param {string} cwd корінь репозиторію
|
|
68
71
|
*/
|
|
69
|
-
async function checkV8rIgnore(passFn, failFn) {
|
|
72
|
+
async function checkV8rIgnore(passFn, failFn, cwd) {
|
|
70
73
|
const required = ['.vscode/extensions.json', '.vscode/settings.json']
|
|
71
|
-
|
|
74
|
+
const v8rPath = join(cwd, '.v8rignore')
|
|
75
|
+
if (!existsSync(v8rPath)) {
|
|
72
76
|
failFn('.v8rignore не існує — створи згідно n-text.mdc (мінімум .vscode/extensions.json і .vscode/settings.json)')
|
|
73
77
|
return
|
|
74
78
|
}
|
|
75
|
-
const raw = await readFile(
|
|
79
|
+
const raw = await readFile(v8rPath, 'utf8')
|
|
76
80
|
const lines = new Set(
|
|
77
81
|
raw
|
|
78
82
|
.split('\n')
|
|
@@ -100,8 +104,9 @@ async function checkV8rIgnore(passFn, failFn) {
|
|
|
100
104
|
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
101
105
|
* @param {(msg: string) => void} failFn callback при помилці
|
|
102
106
|
* @returns {Promise<void>}
|
|
107
|
+
* @param {string} cwd корінь репозиторію
|
|
103
108
|
*/
|
|
104
|
-
function checkTextConfigsExistence(passFn, failFn) {
|
|
109
|
+
function checkTextConfigsExistence(passFn, failFn, cwd) {
|
|
105
110
|
for (const [path, mdcRef] of [
|
|
106
111
|
['.oxfmtrc.json', 'text.oxfmtrc'],
|
|
107
112
|
['.cspell.json', 'text.cspell'],
|
|
@@ -109,7 +114,7 @@ function checkTextConfigsExistence(passFn, failFn) {
|
|
|
109
114
|
['.vscode/extensions.json', 'text.vscode_extensions'],
|
|
110
115
|
['.vscode/settings.json', 'text.vscode_settings']
|
|
111
116
|
]) {
|
|
112
|
-
if (existsSync(path)) {
|
|
117
|
+
if (existsSync(join(cwd, path))) {
|
|
113
118
|
passFn(`${path} є (структуру перевіряє npx @nitra/cursor fix → ${mdcRef})`)
|
|
114
119
|
} else {
|
|
115
120
|
failFn(`${path} не існує — створи згідно n-text.mdc`)
|
|
@@ -125,14 +130,17 @@ function checkTextConfigsExistence(passFn, failFn) {
|
|
|
125
130
|
* `@nitra/*` гейт) — у Rego (`text.package_json`, `bun.package_json`).
|
|
126
131
|
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
127
132
|
* @param {(msg: string) => void} failFn callback при помилці
|
|
133
|
+
* @param {string} cwd корінь репозиторію
|
|
128
134
|
*/
|
|
129
|
-
async function checkPackageJsonText(passFn, failFn) {
|
|
130
|
-
|
|
131
|
-
|
|
135
|
+
async function checkPackageJsonText(passFn, failFn, cwd) {
|
|
136
|
+
const pkgPath = join(cwd, 'package.json')
|
|
137
|
+
if (!existsSync(pkgPath)) return
|
|
138
|
+
const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
|
|
132
139
|
checkLintTextScript(pkg.scripts?.['lint-text'], passFn, failFn)
|
|
133
140
|
|
|
134
|
-
|
|
135
|
-
|
|
141
|
+
const lintTextWf = join(cwd, '.github/workflows/lint-text.yml')
|
|
142
|
+
if (existsSync(lintTextWf)) {
|
|
143
|
+
const wf = await readFile(lintTextWf, 'utf8')
|
|
136
144
|
const root = parseWorkflowYaml(wf)
|
|
137
145
|
const ok = root ? anyRunStepIncludes(root, 'bun run lint-text') : wf.includes('bun run lint-text')
|
|
138
146
|
if (ok) {
|
|
@@ -166,27 +174,28 @@ function checkLintTextScript(lintText, passFn, failFn) {
|
|
|
166
174
|
|
|
167
175
|
/**
|
|
168
176
|
* Перевіряє відповідність проєкту правилам text.mdc.
|
|
177
|
+
* @param {string} [cwd] корінь репозиторію
|
|
169
178
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
170
179
|
*/
|
|
171
|
-
export async function check() {
|
|
180
|
+
export async function check(cwd = process.cwd()) {
|
|
172
181
|
const reporter = createCheckReporter()
|
|
173
182
|
const { pass, fail } = reporter
|
|
174
183
|
|
|
175
|
-
await checkV8rIgnore(pass, fail)
|
|
176
|
-
await checkTextConfigsExistence(pass, fail)
|
|
184
|
+
await checkV8rIgnore(pass, fail, cwd)
|
|
185
|
+
await checkTextConfigsExistence(pass, fail, cwd)
|
|
177
186
|
|
|
178
187
|
// Prettier-конфіги/ignore — окремий concern `text.forbidden-prettier` (rules/text/js/forbidden-prettier.mjs).
|
|
179
188
|
|
|
180
|
-
const textRulePaths = ['.cursor/rules/n-text.mdc', 'npm/mdc/text.mdc'].filter(p => existsSync(p))
|
|
189
|
+
const textRulePaths = ['.cursor/rules/n-text.mdc', 'npm/mdc/text.mdc'].filter(p => existsSync(join(cwd, p)))
|
|
181
190
|
if (textRulePaths.length === 0) {
|
|
182
191
|
pass('n-text.mdc / npm/mdc/text.mdc відсутні — перевірку абзацу про апостроф пропущено')
|
|
183
192
|
} else {
|
|
184
193
|
for (const p of textRulePaths) {
|
|
185
|
-
verifyUkApostropheRuleParagraph(p, await readFile(p, 'utf8'), fail, pass)
|
|
194
|
+
verifyUkApostropheRuleParagraph(p, await readFile(join(cwd, p), 'utf8'), fail, pass)
|
|
186
195
|
}
|
|
187
196
|
}
|
|
188
197
|
|
|
189
|
-
await checkPackageJsonText(pass, fail)
|
|
198
|
+
await checkPackageJsonText(pass, fail, cwd)
|
|
190
199
|
|
|
191
200
|
return reporter.getExitCode()
|
|
192
201
|
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* Окремо в `.vue` SFC заборонено імпорти Node-нативних модулів — `node:*` префікс або bare-ім’я
|
|
21
21
|
* вбудованого модуля Node (`fs`, `path`, `timers/promises` тощо). Vue SFC виконується у браузері,
|
|
22
22
|
* де Node API недоступне; такий код треба тримати у server-side утілітах.
|
|
23
|
+
* @param {string} cwd корінь репозиторію
|
|
23
24
|
*/
|
|
24
25
|
import { existsSync } from 'node:fs'
|
|
25
26
|
import { readFile } from 'node:fs/promises'
|
|
@@ -194,17 +195,18 @@ function ukFilesCountPhrase(n) {
|
|
|
194
195
|
* @param {(msg: string) => void} passFn успіх
|
|
195
196
|
* @param {(msg: string) => void} fail помилка
|
|
196
197
|
* @returns {Promise<void>}
|
|
198
|
+
* @param {string} cwd корінь репозиторію
|
|
197
199
|
*/
|
|
198
|
-
async function checkViteClientEnvAndEditorConfig(rootDir, prefix, passFn, fail) {
|
|
199
|
-
const
|
|
200
|
-
if (!existsSync(
|
|
200
|
+
async function checkViteClientEnvAndEditorConfig(rootDir, prefix, passFn, fail, cwd) {
|
|
201
|
+
const envAbs = join(cwd, rootDir, 'src/vite-env.d.ts')
|
|
202
|
+
if (!existsSync(envAbs)) {
|
|
201
203
|
fail(
|
|
202
204
|
`${prefix}немає src/vite-env.d.ts — додай файл з рядком /// <reference types="vite/client" /> ` +
|
|
203
205
|
`(інакше TS/Volar не бачать типів для імпортів асетів: png, avif, css як URL).`
|
|
204
206
|
)
|
|
205
207
|
return
|
|
206
208
|
}
|
|
207
|
-
const envContent = await readFile(
|
|
209
|
+
const envContent = await readFile(envAbs, 'utf8')
|
|
208
210
|
if (!VITE_CLIENT_REFERENCE_RE.test(envContent)) {
|
|
209
211
|
fail(
|
|
210
212
|
`${prefix}src/vite-env.d.ts має містити /// <reference types="vite/client" /> ` +
|
|
@@ -214,7 +216,7 @@ async function checkViteClientEnvAndEditorConfig(rootDir, prefix, passFn, fail)
|
|
|
214
216
|
}
|
|
215
217
|
passFn(`${prefix}src/vite-env.d.ts посилається на vite/client`)
|
|
216
218
|
|
|
217
|
-
if (!existsSync(join(rootDir, 'jsconfig.json'))) {
|
|
219
|
+
if (!existsSync(join(cwd, rootDir, 'jsconfig.json'))) {
|
|
218
220
|
fail(
|
|
219
221
|
`${prefix}немає jsconfig.json у корені пакета — додай файл з "include": ["src/**/*"] тощо, ` +
|
|
220
222
|
`щоб IDE підхопила vite-env.d.ts і .vue.`
|
|
@@ -268,15 +270,16 @@ function viteConfigHasVueInAutoImports(content) {
|
|
|
268
270
|
* @param {(msg: string) => void} passFn callback при успішній перевірці
|
|
269
271
|
* @param {(msg: string) => void} fail callback при помилці
|
|
270
272
|
* @returns {Promise<{ hasVueAutoImport: boolean }>} ознака успішно сконфігурованого vue-auto-import (для checkVueImportViolations)
|
|
273
|
+
* @param {string} cwd корінь репозиторію
|
|
271
274
|
*/
|
|
272
|
-
async function checkViteConfig(rootDir, prefix, passFn, fail) {
|
|
275
|
+
async function checkViteConfig(rootDir, prefix, passFn, fail, cwd) {
|
|
273
276
|
const configFiles = ['vite.config.js', 'vite.config.ts', 'vite.config.mjs']
|
|
274
|
-
const viteConfig = configFiles.find(f => existsSync(join(rootDir, f)))
|
|
277
|
+
const viteConfig = configFiles.find(f => existsSync(join(cwd, rootDir, f)))
|
|
275
278
|
if (!viteConfig) {
|
|
276
279
|
fail(`${prefix}немає vite.config.js|ts|mjs у каталозі пакета`)
|
|
277
280
|
return { hasVueAutoImport: false }
|
|
278
281
|
}
|
|
279
|
-
const content = await readFile(join(rootDir, viteConfig), 'utf8')
|
|
282
|
+
const content = await readFile(join(cwd, rootDir, viteConfig), 'utf8')
|
|
280
283
|
if (ESBUILD_RE.test(content)) {
|
|
281
284
|
fail(`${prefix}${viteConfig} містить 'esbuild' — заміни на 'rolldown'`)
|
|
282
285
|
}
|
|
@@ -410,37 +413,39 @@ async function checkVueImportViolations(rootDir, absPackageRoot, ignorePaths, ha
|
|
|
410
413
|
* @param {(msg: string) => void} fail функція зворотного виклику для реєстрації помилки перевірки
|
|
411
414
|
* @param {(msg: string) => void} passFn успішне повідомлення (як у check-reporter)
|
|
412
415
|
* @returns {Promise<void>} завершується після перевірок залежностей, `vite.config` і сканування джерел на імпорти з `vue`
|
|
416
|
+
* @param {string} cwd корінь репозиторію
|
|
413
417
|
*/
|
|
414
|
-
async function checkVuePackage(rootDir, ignorePaths, fail, passFn) {
|
|
418
|
+
async function checkVuePackage(rootDir, ignorePaths, fail, passFn, cwd) {
|
|
415
419
|
const prefix = `[${packageLabel(rootDir)}] `
|
|
416
420
|
passFn(`${prefix}package.json залежності перевіряє npx @nitra/cursor fix → vue.package_json`)
|
|
417
421
|
|
|
418
|
-
await checkViteClientEnvAndEditorConfig(rootDir, prefix, passFn, fail)
|
|
422
|
+
await checkViteClientEnvAndEditorConfig(rootDir, prefix, passFn, fail, cwd)
|
|
419
423
|
|
|
420
|
-
const { hasVueAutoImport } = await checkViteConfig(rootDir, prefix, passFn, fail)
|
|
424
|
+
const { hasVueAutoImport } = await checkViteConfig(rootDir, prefix, passFn, fail, cwd)
|
|
421
425
|
await checkVueImportViolations(
|
|
422
426
|
rootDir,
|
|
423
|
-
join(
|
|
427
|
+
join(cwd, rootDir),
|
|
424
428
|
ignorePaths,
|
|
425
429
|
hasVueAutoImport,
|
|
426
430
|
prefix,
|
|
427
431
|
passFn,
|
|
428
432
|
fail
|
|
429
433
|
)
|
|
430
|
-
await checkVueNodeImportViolations(rootDir, join(
|
|
431
|
-
await checkEsbuildMentions(rootDir, join(
|
|
434
|
+
await checkVueNodeImportViolations(rootDir, join(cwd, rootDir), ignorePaths, prefix, passFn, fail)
|
|
435
|
+
await checkEsbuildMentions(rootDir, join(cwd, rootDir), ignorePaths, prefix, passFn, fail)
|
|
432
436
|
}
|
|
433
437
|
|
|
434
438
|
/**
|
|
435
439
|
* Збирає корені пакетів, у яких у `dependencies` є `vue`.
|
|
436
440
|
* @param {string[]} roots усі корені пакетів monorepo
|
|
437
441
|
* @returns {Promise<string[]>} перелік пакетів з vue у dependencies
|
|
442
|
+
* @param {string} cwd корінь репозиторію
|
|
438
443
|
*/
|
|
439
|
-
async function collectVueRoots(roots) {
|
|
444
|
+
async function collectVueRoots(roots, cwd) {
|
|
440
445
|
/** @type {string[]} */
|
|
441
446
|
const vueRoots = []
|
|
442
447
|
for (const r of roots) {
|
|
443
|
-
const p = join(r, 'package.json')
|
|
448
|
+
const p = join(cwd, r, 'package.json')
|
|
444
449
|
if (!existsSync(p)) continue
|
|
445
450
|
const pkg = JSON.parse(await readFile(p, 'utf8'))
|
|
446
451
|
if (pkg.dependencies?.vue) vueRoots.push(r)
|
|
@@ -453,13 +458,15 @@ async function collectVueRoots(roots) {
|
|
|
453
458
|
* @param {(msg: string) => void} pass pass callback
|
|
454
459
|
* @param {(msg: string) => void} fail fail callback
|
|
455
460
|
* @returns {Promise<void>}
|
|
461
|
+
* @param {string} cwd корінь репозиторію
|
|
456
462
|
*/
|
|
457
|
-
async function checkVueVolarRecommendation(pass, fail) {
|
|
458
|
-
|
|
463
|
+
async function checkVueVolarRecommendation(pass, fail, cwd) {
|
|
464
|
+
const extPath = join(cwd, '.vscode/extensions.json')
|
|
465
|
+
if (!existsSync(extPath)) {
|
|
459
466
|
fail('.vscode/extensions.json не існує (для Vue-проєкту потрібна рекомендація Vue.volar)')
|
|
460
467
|
return
|
|
461
468
|
}
|
|
462
|
-
const ext = JSON.parse(await readFile(
|
|
469
|
+
const ext = JSON.parse(await readFile(extPath, 'utf8'))
|
|
463
470
|
if (ext.recommendations?.includes('Vue.volar')) {
|
|
464
471
|
pass('extensions.json містить Vue.volar')
|
|
465
472
|
} else {
|
|
@@ -469,14 +476,15 @@ async function checkVueVolarRecommendation(pass, fail) {
|
|
|
469
476
|
|
|
470
477
|
/**
|
|
471
478
|
* Перевіряє відповідність проєкту правилам vue.mdc (корінь і всі workspace-пакети з `vue` у dependencies).
|
|
479
|
+
* @param {string} [cwd] корінь репозиторію
|
|
472
480
|
* @returns {Promise<number>} 0 — все OK, 1 — є проблеми
|
|
473
481
|
*/
|
|
474
|
-
export async function check() {
|
|
482
|
+
export async function check(cwd = process.cwd()) {
|
|
475
483
|
const reporter = createCheckReporter()
|
|
476
484
|
const { pass, fail } = reporter
|
|
477
485
|
|
|
478
|
-
const roots = await getMonorepoPackageRootDirs()
|
|
479
|
-
const vueRoots = await collectVueRoots(roots)
|
|
486
|
+
const roots = await getMonorepoPackageRootDirs(cwd)
|
|
487
|
+
const vueRoots = await collectVueRoots(roots, cwd)
|
|
480
488
|
|
|
481
489
|
if (vueRoots.length === 0) {
|
|
482
490
|
pass('Vue.volar: пропущено (у repo немає пакетів з vue у dependencies)')
|
|
@@ -484,11 +492,11 @@ export async function check() {
|
|
|
484
492
|
return reporter.getExitCode()
|
|
485
493
|
}
|
|
486
494
|
|
|
487
|
-
await checkVueVolarRecommendation(pass, fail)
|
|
495
|
+
await checkVueVolarRecommendation(pass, fail, cwd)
|
|
488
496
|
|
|
489
|
-
const ignorePaths = await loadCursorIgnorePaths(
|
|
497
|
+
const ignorePaths = await loadCursorIgnorePaths(cwd)
|
|
490
498
|
for (const r of vueRoots) {
|
|
491
|
-
await checkVuePackage(r, ignorePaths, fail, pass)
|
|
499
|
+
await checkVuePackage(r, ignorePaths, fail, pass, cwd)
|
|
492
500
|
}
|
|
493
501
|
|
|
494
502
|
return reporter.getExitCode()
|