@nitra/cursor 1.27.7 → 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.
Files changed (39) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/package.json +1 -1
  3. package/rules/abie/js/applies.mjs +3 -2
  4. package/rules/abie/js/env_dns.mjs +4 -2
  5. package/rules/abie/js/firebase_hosting.mjs +3 -2
  6. package/rules/abie/js/hc_pairing.mjs +3 -2
  7. package/rules/abie/js/ua_http_route.mjs +4 -2
  8. package/rules/abie/js/ua_node_selector.mjs +4 -2
  9. package/rules/adr/js/hooks.mjs +36 -28
  10. package/rules/bun/js/layout.mjs +16 -11
  11. package/rules/capacitor/js/platforms.mjs +3 -2
  12. package/rules/changelog/js/consistency.mjs +85 -63
  13. package/rules/changelog/lib/package-manifest.mjs +5 -4
  14. package/rules/docker/js/lint.mjs +3 -2
  15. package/rules/ga/js/workflows.mjs +41 -32
  16. package/rules/graphql/js/tooling.mjs +15 -11
  17. package/rules/hasura/js/internal_urls.mjs +14 -10
  18. package/rules/image-avif/js/avif_generation.mjs +36 -23
  19. package/rules/image-compress/js/package_setup.mjs +18 -12
  20. package/rules/js-bun-db/js/safety.mjs +3 -2
  21. package/rules/js-lint/js/tooling.mjs +45 -32
  22. package/rules/js-run/js/runtime.mjs +21 -15
  23. package/rules/k8s/js/manifests.mjs +3 -2
  24. package/rules/nginx-default-tpl/js/template.mjs +3 -2
  25. package/rules/npm-module/js/package_structure.mjs +82 -57
  26. package/rules/rego/js/applies.mjs +4 -4
  27. package/rules/rust/js/applies.mjs +5 -3
  28. package/rules/security/js/sample_secret.mjs +2 -2
  29. package/rules/security/js/trufflehog.mjs +6 -4
  30. package/rules/style-lint/js/tooling.mjs +15 -8
  31. package/rules/test/coverage/coverage.mjs +2 -1
  32. package/rules/test/js/data/vitest_config/vitest.config.baseline.js +7 -0
  33. package/rules/test/js/location.mjs +3 -2
  34. package/rules/test/js/no-process-chdir.mjs +89 -0
  35. package/rules/test/js/vitest-config-pool-forks.mjs +52 -0
  36. package/rules/test/test.mdc +17 -0
  37. package/rules/text/js/forbidden-prettier.mjs +4 -2
  38. package/rules/text/js/formatting.mjs +25 -16
  39. package/rules/vue/js/packages.mjs +33 -25
@@ -327,13 +327,14 @@ function messageForBunSqlInListGuard(rel, v) {
327
327
 
328
328
  /**
329
329
  * Перевіряє відповідність проєкту правилу js-bun-db.mdc
330
+ * @param {string} [cwd] корінь репозиторію
330
331
  * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
331
332
  */
332
- export async function check() {
333
+ export async function check(cwd = process.cwd()) {
333
334
  const reporter = createCheckReporter()
334
335
  const { pass } = reporter
335
336
 
336
- const repoRoot = process.cwd()
337
+ const repoRoot = cwd
337
338
  const rootPkg = join(repoRoot, 'package.json')
338
339
  if (!existsSync(rootPkg)) {
339
340
  pass('js-bun-db: package.json у корені відсутній — перевірку пропущено')
@@ -9,6 +9,7 @@
9
9
  *
10
10
  * Per-document вимоги (`lint-js`, `@nitra/eslint-config`, root `engines`, `.jscpd.json`,
11
11
  * `.vscode/extensions.json`, `lint-js.yml`) — у policy-пакетах `js_lint.*`.
12
+ * @param {string} cwd корінь репозиторію
12
13
  */
13
14
  import { existsSync } from 'node:fs'
14
15
  import { copyFile, readFile } from 'node:fs/promises'
@@ -170,20 +171,21 @@ export function verifyOxlintRcAgainstCanonical(cfg, canonical) {
170
171
  * Перевіряє ESLint flat config файл.
171
172
  * @param {(msg: string) => void} passFn callback при успішній перевірці
172
173
  * @param {(msg: string) => void} failFn callback при помилці
174
+ * @param {string} cwd корінь репозиторію
173
175
  */
174
- async function checkEslintConfig(passFn, failFn) {
176
+ async function checkEslintConfig(passFn, failFn, cwd) {
175
177
  let eslintPath
176
- if (existsSync('eslint.config.js')) {
178
+ if (existsSync(join(cwd, 'eslint.config.js'))) {
177
179
  eslintPath = 'eslint.config.js'
178
180
  passFn('eslint.config.js існує')
179
- } else if (existsSync('eslint.config.mjs')) {
181
+ } else if (existsSync(join(cwd, 'eslint.config.mjs'))) {
180
182
  eslintPath = 'eslint.config.mjs'
181
183
  passFn('eslint.config.mjs існує')
182
184
  } else {
183
185
  failFn('Відсутній eslint.config.js або eslint.config.mjs — flat config з getConfig (js-lint.mdc)')
184
186
  return
185
187
  }
186
- const eslintRaw = await readFile(eslintPath, 'utf8')
188
+ const eslintRaw = await readFile(join(cwd, eslintPath), 'utf8')
187
189
  const checks = [
188
190
  {
189
191
  needle: 'getConfig',
@@ -236,15 +238,17 @@ function checkPackageJsonTypeModule(label, pkg, passFn, failFn) {
236
238
  * @param {unknown[]} workspaces поле workspaces з package.json
237
239
  * @param {(msg: string) => void} passFn callback при успішній перевірці
238
240
  * @param {(msg: string) => void} failFn callback при помилці
241
+ * @param {string} cwd корінь репозиторію
239
242
  */
240
- async function checkWorkspacePackages(workspaces, passFn, failFn) {
243
+ async function checkWorkspacePackages(workspaces, passFn, failFn, cwd) {
241
244
  for (const ws of workspaces) {
242
- const wsPkgPath = `${ws}/package.json`
243
- if (existsSync(wsPkgPath)) {
244
- const wsPkg = JSON.parse(await readFile(wsPkgPath, 'utf8'))
245
- checkPackageJsonTypeModule(wsPkgPath, wsPkg, passFn, failFn)
246
- checkEnginesNode(wsPkgPath, wsPkg, passFn, failFn)
247
- checkEnginesBun(wsPkgPath, wsPkg, passFn, failFn)
245
+ const wsPkgRel = `${ws}/package.json`
246
+ const wsPkgAbs = join(cwd, wsPkgRel)
247
+ if (existsSync(wsPkgAbs)) {
248
+ const wsPkg = JSON.parse(await readFile(wsPkgAbs, 'utf8'))
249
+ checkPackageJsonTypeModule(wsPkgRel, wsPkg, passFn, failFn)
250
+ checkEnginesNode(wsPkgRel, wsPkg, passFn, failFn)
251
+ checkEnginesBun(wsPkgRel, wsPkg, passFn, failFn)
248
252
  }
249
253
  }
250
254
  }
@@ -298,27 +302,31 @@ function checkEnginesBun(label, pkg, passFn, failFn) {
298
302
  * — теж у Rego.
299
303
  * @param {(msg: string) => void} passFn callback при успішній перевірці
300
304
  * @param {(msg: string) => void} failFn callback при помилці
305
+ * @param {string} cwd корінь репозиторію
301
306
  */
302
- async function checkPackageJsonJsLint(passFn, failFn) {
303
- if (!existsSync('package.json')) return
304
- const pkg = JSON.parse(await readFile('package.json', 'utf8'))
307
+ async function checkPackageJsonJsLint(passFn, failFn, cwd) {
308
+ const pkgPath = join(cwd, 'package.json')
309
+ if (!existsSync(pkgPath)) return
310
+ const pkg = JSON.parse(await readFile(pkgPath, 'utf8'))
305
311
  const workspaces = Array.isArray(pkg.workspaces) ? pkg.workspaces : []
306
- await checkWorkspacePackages(workspaces, passFn, failFn)
312
+ await checkWorkspacePackages(workspaces, passFn, failFn, cwd)
307
313
  }
308
314
 
309
315
  /**
310
316
  * Перевіряє .oxlintrc.json.
311
317
  * @param {(msg: string) => void} passFn callback при успішній перевірці
312
318
  * @param {(msg: string) => void} failFn callback при помилці
319
+ * @param {string} cwd корінь репозиторію
313
320
  */
314
- async function checkOxlintRc(passFn, failFn) {
315
- if (!existsSync('.oxlintrc.json')) {
321
+ async function checkOxlintRc(passFn, failFn, cwd) {
322
+ const oxPath = join(cwd, '.oxlintrc.json')
323
+ if (!existsSync(oxPath)) {
316
324
  failFn('.oxlintrc.json не існує — додай конфіг oxlint (js-lint.mdc)')
317
325
  return
318
326
  }
319
327
  let oxCfg
320
328
  try {
321
- oxCfg = JSON.parse(await readFile('.oxlintrc.json', 'utf8'))
329
+ oxCfg = JSON.parse(await readFile(oxPath, 'utf8'))
322
330
  } catch {
323
331
  failFn('.oxlintrc.json не є валідним JSON')
324
332
  return
@@ -348,16 +356,18 @@ async function checkOxlintRc(passFn, failFn) {
348
356
  * заборона `--fix` у CI) валідує `npm/policy/js_lint/lint_js_yml/`.
349
357
  * @param {(msg: string) => void} passFn callback при успішній перевірці
350
358
  * @param {(msg: string) => void} failFn callback при помилці
359
+ * @param {string} cwd корінь репозиторію
351
360
  */
352
- async function checkLintJsWorkflows(passFn, failFn) {
353
- if (existsSync('.github/workflows/lint-js.yml')) {
361
+ async function checkLintJsWorkflows(passFn, failFn, cwd) {
362
+ if (existsSync(join(cwd, '.github/workflows/lint-js.yml'))) {
354
363
  passFn('.github/workflows/lint-js.yml є (структуру перевіряє npx @nitra/cursor fix → js_lint.lint_js_yml)')
355
364
  } else {
356
365
  failFn('.github/workflows/lint-js.yml не існує — створи його (див. rules/js-lint/fix.mjs / js-lint.mdc)')
357
366
  }
358
367
 
359
- if (existsSync('.github/workflows/lint.yml')) {
360
- const lintYml = await readFile('.github/workflows/lint.yml', 'utf8')
368
+ const lintYmlPath = join(cwd, '.github/workflows/lint.yml')
369
+ if (existsSync(lintYmlPath)) {
370
+ const lintYml = await readFile(lintYmlPath, 'utf8')
361
371
  if (lintYml.includes('bunx oxlint') && lintYml.includes('bunx eslint') && lintYml.includes('jscpd')) {
362
372
  failFn('.github/workflows/lint.yml дублює кроки lint-js.yml — залиш один workflow на лінт JS (js-lint.mdc)')
363
373
  } else {
@@ -374,9 +384,11 @@ async function checkLintJsWorkflows(passFn, failFn) {
374
384
  * будь-які; це side effect — описано у js-lint.mdc).
375
385
  * @param {(msg: string) => void} passFn callback при успішній перевірці
376
386
  * @param {(msg: string) => void} failFn callback при помилці
387
+ * @param {string} cwd корінь репозиторію
377
388
  */
378
- async function checkKnipConfig(passFn, failFn) {
379
- if (existsSync('knip.json')) {
389
+ async function checkKnipConfig(passFn, failFn, cwd) {
390
+ const knipPath = join(cwd, 'knip.json')
391
+ if (existsSync(knipPath)) {
380
392
  passFn('knip.json існує')
381
393
  return
382
394
  }
@@ -387,26 +399,27 @@ async function checkKnipConfig(passFn, failFn) {
387
399
  )
388
400
  return
389
401
  }
390
- await copyFile(KNIP_CANONICAL_JSON_PATH, 'knip.json')
402
+ await copyFile(KNIP_CANONICAL_JSON_PATH, knipPath)
391
403
  passFn('knip.json створено з канонічного npm/rules/js-lint/js/data/tooling/knip-canonical.json (js-lint.mdc)')
392
404
  }
393
405
 
394
406
  /**
395
407
  * Перевіряє відповідність проєкту правилам js-lint.mdc
408
+ * @param {string} [cwd] корінь репозиторію
396
409
  * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
397
410
  */
398
- export async function check() {
411
+ export async function check(cwd = process.cwd()) {
399
412
  const reporter = createCheckReporter()
400
413
  const { pass, fail } = reporter
401
414
 
402
- await checkEslintConfig(pass, fail)
403
- await checkPackageJsonJsLint(pass, fail)
404
- await checkOxlintRc(pass, fail)
405
- await checkLintJsWorkflows(pass, fail)
406
- await checkKnipConfig(pass, fail)
415
+ await checkEslintConfig(pass, fail, cwd)
416
+ await checkPackageJsonJsLint(pass, fail, cwd)
417
+ await checkOxlintRc(pass, fail, cwd)
418
+ await checkLintJsWorkflows(pass, fail, cwd)
419
+ await checkKnipConfig(pass, fail, cwd)
407
420
 
408
421
  for (const dup of ['.eslintrc', '.eslintrc.js', '.eslintrc.json', '.eslintrc.yml']) {
409
- if (existsSync(dup)) fail(`Знайдено застарілий конфіг ESLint: ${dup} — видали, використовуй flat config`)
422
+ if (existsSync(join(cwd, dup))) fail(`Знайдено застарілий конфіг ESLint: ${dup} — видали, використовуй flat config`)
410
423
  }
411
424
 
412
425
  return reporter.getExitCode()
@@ -31,6 +31,7 @@
31
31
  *
32
32
  * Per-document валідація `package.json` (bunyan, `node` у `scripts`) делегована rego-пакету
33
33
  * `js_run.package_json` у `npm/rules/js-run/policy/package_json/`; JS — cross-file (AST, FS).
34
+ * @param {string} cwd корінь репозиторію
34
35
  */
35
36
  import { existsSync, statSync } from 'node:fs'
36
37
  import { readFile } from 'node:fs/promises'
@@ -78,11 +79,12 @@ function backendPackageHasSrcDir(absPackageRoot) {
78
79
  * @param {(msg: string) => void} fail callback для повідомлень про порушення
79
80
  * @param {(msg: string) => void} passFn callback для повідомлень про успішну перевірку
80
81
  * @returns {void}
82
+ * @param {string} cwd корінь репозиторію
81
83
  */
82
- function checkBackendJsconfigWhenSrcPresent(rootDir, absPackageRoot, label, fail, passFn) {
84
+ function checkBackendJsconfigWhenSrcPresent(rootDir, absPackageRoot, label, fail, passFn, cwd) {
83
85
  if (!backendPackageHasSrcDir(absPackageRoot)) return
84
86
 
85
- const jcPath = join(rootDir, 'jsconfig.json')
87
+ const jcPath = join(cwd, rootDir, 'jsconfig.json')
86
88
  if (!existsSync(jcPath)) {
87
89
  fail(
88
90
  `${label}є каталог src/, але немає jsconfig.json — додай канонічний файл з js-run.mdc ` +
@@ -318,11 +320,12 @@ async function checkPromiseSetTimeoutPause(absPackageRoot, sourcePaths, label, f
318
320
  * @param {(msg: string) => void} fail функція зворотного виклику для реєстрації помилки перевірки
319
321
  * @param {(msg: string) => void} passFn успішне повідомлення (як у check-reporter)
320
322
  * @returns {Promise<void>} завершується після перевірок цього пакета
323
+ * @param {string} cwd корінь репозиторію
321
324
  */
322
- async function checkWorkspacePackage(rootDir, ignorePaths, fail, passFn) {
325
+ async function checkWorkspacePackage(rootDir, ignorePaths, fail, passFn, cwd) {
323
326
  const label = `[${rootDir}] `
324
- const absPackageRoot = join(process.cwd(), rootDir)
325
- const pkgJson = await loadPackageJson(rootDir)
327
+ const absPackageRoot = join(cwd, rootDir)
328
+ const pkgJson = await loadPackageJson(rootDir, cwd)
326
329
 
327
330
  // Frontend-пакети (vite у devDependencies) виходять за межі js-run:
328
331
  // браузерний бандл не має `node:process`, а `process.env.*` бандлер
@@ -333,7 +336,7 @@ async function checkWorkspacePackage(rootDir, ignorePaths, fail, passFn) {
333
336
  return
334
337
  }
335
338
 
336
- await checkBackendJsconfigWhenSrcPresent(rootDir, absPackageRoot, label, fail, passFn)
339
+ await checkBackendJsconfigWhenSrcPresent(rootDir, absPackageRoot, label, fail, passFn, cwd)
337
340
 
338
341
  const importViolations = await checkBunyanImports(absPackageRoot, ignorePaths, label, fail)
339
342
  if (importViolations === 0) {
@@ -368,7 +371,7 @@ async function checkWorkspacePackage(rootDir, ignorePaths, fail, passFn) {
368
371
  passFn(`${label}немає 'new Promise(r => setTimeout(r, ms))' — паузи через 'node:timers/promises'`)
369
372
  }
370
373
 
371
- checkOtelConfigmap(rootDir, passFn)
374
+ checkOtelConfigmap(rootDir, passFn, cwd)
372
375
  }
373
376
 
374
377
  /**
@@ -392,9 +395,10 @@ function packageJsonHasViteDevDependency(pkgJson) {
392
395
  * лише AST-перевірка імпортів.
393
396
  * @param {string} rootDir відносний шлях workspace
394
397
  * @returns {Promise<unknown>} розпарсений package.json або null
398
+ * @param {string} cwd корінь репозиторію
395
399
  */
396
- async function loadPackageJson(rootDir) {
397
- const pkgPath = join(rootDir, 'package.json')
400
+ async function loadPackageJson(rootDir, cwd) {
401
+ const pkgPath = join(cwd, rootDir, 'package.json')
398
402
  if (!existsSync(pkgPath)) return null
399
403
  return JSON.parse(await readFile(pkgPath, 'utf8'))
400
404
  }
@@ -407,9 +411,10 @@ async function loadPackageJson(rootDir) {
407
411
  * @param {string} rootDir відносний шлях workspace
408
412
  * @param {(msg: string) => void} passFn успішне повідомлення
409
413
  * @returns {void}
414
+ * @param {string} cwd корінь репозиторію
410
415
  */
411
- function checkOtelConfigmap(rootDir, passFn) {
412
- const configmapPath = join(rootDir, 'k8s', 'base', 'configmap.yaml')
416
+ function checkOtelConfigmap(rootDir, passFn, cwd) {
417
+ const configmapPath = join(cwd, rootDir, 'k8s', 'base', 'configmap.yaml')
413
418
  if (!existsSync(configmapPath)) return
414
419
  passFn(`${rootDir}/k8s/base/configmap.yaml є (OTEL — npx @nitra/cursor fix → js_run.configmap)`)
415
420
  }
@@ -417,12 +422,13 @@ function checkOtelConfigmap(rootDir, passFn) {
417
422
  /**
418
423
  * Перевіряє відповідність проєкту правилам js-run.mdc лише для workspace-пакетів (не корінь репо).
419
424
  * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
425
+ * @param {string} [cwd] корінь репозиторію
420
426
  */
421
- export async function check() {
427
+ export async function check(cwd = process.cwd()) {
422
428
  const reporter = createCheckReporter()
423
429
  const { pass, fail } = reporter
424
430
 
425
- const roots = await getMonorepoPackageRootDirs()
431
+ const roots = await getMonorepoPackageRootDirs(cwd)
426
432
  const workspaceRoots = roots.filter(r => r !== '.')
427
433
 
428
434
  if (workspaceRoots.length === 0) {
@@ -430,9 +436,9 @@ export async function check() {
430
436
  return reporter.getExitCode()
431
437
  }
432
438
 
433
- const ignorePaths = await loadCursorIgnorePaths(process.cwd())
439
+ const ignorePaths = await loadCursorIgnorePaths(cwd)
434
440
  for (const r of workspaceRoots) {
435
- await checkWorkspacePackage(r, ignorePaths, fail, pass)
441
+ await checkWorkspacePackage(r, ignorePaths, fail, pass, cwd)
436
442
  }
437
443
 
438
444
  return reporter.getExitCode()
@@ -6773,13 +6773,14 @@ function runAllK8sRego(root, yamlFiles, fail) {
6773
6773
 
6774
6774
  /**
6775
6775
  * Точка входу `check k8s`: повний набір перевірок маніфестів і структури `…/k8s` (див. JSDoc на початку файлу).
6776
+ * @param {string} [cwd] корінь репозиторію
6776
6777
  * @returns {Promise<number>} `process.exitCode`: 0 при успіху, 1 при будь-якому `fail(...)`
6777
6778
  */
6778
- export async function check() {
6779
+ export async function check(cwd = process.cwd()) {
6779
6780
  const reporter = createCheckReporter()
6780
6781
  const { pass, fail } = reporter
6781
6782
 
6782
- const root = process.cwd()
6783
+ const root = cwd
6783
6784
  const ignorePaths = await loadCursorIgnorePaths(root)
6784
6785
 
6785
6786
  await rewriteBatchV1beta1ApiVersionInK8sYamlFiles(root, ignorePaths, fail, pass)
@@ -397,13 +397,14 @@ function checkVscodeNginx(passFn, failFn) {
397
397
 
398
398
  /**
399
399
  * Перевіряє відповідність проєкту правилам nginx-default-tpl.mdc
400
+ * @param {string} [cwd] корінь репозиторію
400
401
  * @returns {Promise<number>} 0 — все OK, 1 — є проблеми
401
402
  */
402
- export async function check() {
403
+ export async function check(cwd = process.cwd()) {
403
404
  const reporter = createCheckReporter()
404
405
  const { pass, fail } = reporter
405
406
 
406
- const root = process.cwd()
407
+ const root = cwd
407
408
  const ignorePaths = await loadCursorIgnorePaths(root)
408
409
 
409
410
  const { renamed: tplRenamed, overwritten: tplOverwritten } = await migrateDefaultTplConfFiles(root, ignorePaths)