@7n/rules 1.19.1 → 1.20.1

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 (77) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/package.json +1 -1
  3. package/rules/bun/licensee/docs/fix-licensee.md +1 -2
  4. package/rules/bun/licensee/docs/main.md +1 -1
  5. package/rules/bun/licensee/fix-licensee.mjs +3 -4
  6. package/rules/bun/licensee/main.mjs +7 -7
  7. package/rules/docker/lib/docker-hadolint.mjs +6 -8
  8. package/rules/docker/lib/docs/docker-hadolint.md +1 -1
  9. package/rules/docker/lint/docs/main.md +1 -1
  10. package/rules/docker/lint/main.mjs +1 -1
  11. package/rules/image-compress/check/docs/fix-check.md +1 -2
  12. package/rules/image-compress/check/docs/main.md +1 -1
  13. package/rules/image-compress/check/fix-check.mjs +27 -22
  14. package/rules/image-compress/check/main.mjs +14 -14
  15. package/rules/js/jscpd_duplicates/docs/main.md +1 -1
  16. package/rules/js/jscpd_duplicates/main.mjs +7 -8
  17. package/rules/k8s/kubeconform/docs/main.md +1 -1
  18. package/rules/k8s/kubeconform/main.mjs +1 -1
  19. package/rules/k8s/manifests/main.mjs +52 -39
  20. package/rules/php/cs_fixer/docs/main.md +1 -2
  21. package/rules/php/cs_fixer/main.mjs +8 -7
  22. package/rules/php/phpcs/docs/main.md +1 -2
  23. package/rules/php/phpcs/main.mjs +12 -11
  24. package/rules/php/project/docs/main.md +1 -2
  25. package/rules/php/project/main.mjs +19 -16
  26. package/rules/python/lib/docs/uv-run.md +1 -2
  27. package/rules/python/lib/uv-run.mjs +12 -8
  28. package/rules/python/mypy/docs/main.md +1 -1
  29. package/rules/python/mypy/main.mjs +9 -8
  30. package/rules/python/project/docs/main.md +1 -2
  31. package/rules/python/project/main.mjs +33 -23
  32. package/rules/python/ruff/docs/fix-ruff.md +1 -2
  33. package/rules/python/ruff/docs/main.md +1 -1
  34. package/rules/python/ruff/fix-ruff.mjs +12 -10
  35. package/rules/python/ruff/main.mjs +13 -12
  36. package/rules/rego/conftest_verify/docs/main.md +1 -2
  37. package/rules/rego/conftest_verify/main.mjs +12 -10
  38. package/rules/rego/lib/docs/run-external-tool.md +1 -1
  39. package/rules/rego/lib/run-external-tool.mjs +10 -7
  40. package/rules/rego/opa_check/docs/main.md +1 -1
  41. package/rules/rego/opa_check/main.mjs +3 -3
  42. package/rules/rego/regal/docs/main.md +1 -1
  43. package/rules/rego/regal/main.mjs +3 -3
  44. package/rules/rust/check/docs/fix-check.md +1 -1
  45. package/rules/rust/check/docs/main.md +1 -1
  46. package/rules/rust/check/fix-check.mjs +13 -12
  47. package/rules/rust/check/main.mjs +19 -13
  48. package/rules/security/scan/docs/main.md +1 -1
  49. package/rules/security/scan/main.mjs +9 -9
  50. package/rules/style/lint/docs/fix-lint.md +1 -1
  51. package/rules/style/lint/docs/main.md +1 -1
  52. package/rules/style/lint/fix-lint.mjs +8 -8
  53. package/rules/style/lint/main.mjs +6 -6
  54. package/rules/text/cspell-fix/docs/fix-worker.md +1 -1
  55. package/rules/text/cspell-fix/docs/main.md +1 -1
  56. package/rules/text/cspell-fix/fix-worker.mjs +1 -1
  57. package/rules/text/cspell-fix/main.mjs +14 -15
  58. package/rules/text/oxfmt/docs/fix-oxfmt.md +1 -1
  59. package/rules/text/oxfmt/docs/main.md +1 -1
  60. package/rules/text/oxfmt/fix-oxfmt.mjs +3 -3
  61. package/rules/text/oxfmt/main.mjs +6 -5
  62. package/rules/text/run-dotenv-linter/docs/fix-run-dotenv-linter.md +1 -2
  63. package/rules/text/run-dotenv-linter/docs/main.md +1 -1
  64. package/rules/text/run-dotenv-linter/fix-run-dotenv-linter.mjs +2 -2
  65. package/rules/text/run-dotenv-linter/main.mjs +19 -24
  66. package/rules/text/run-shellcheck/docs/fix-run-shellcheck.md +1 -2
  67. package/rules/text/run-shellcheck/docs/main.md +1 -1
  68. package/rules/text/run-shellcheck/fix-run-shellcheck.mjs +3 -3
  69. package/rules/text/run-shellcheck/main.mjs +46 -57
  70. package/rules/text/run-v8r/docs/main.md +1 -1
  71. package/rules/text/run-v8r/main.mjs +31 -26
  72. package/scripts/lib/docs/resolve-plugins.md +1 -1
  73. package/scripts/lib/lint-surface/blocking-inventory.mjs +12 -40
  74. package/scripts/lib/lint-surface/docs/blocking-inventory.md +1 -2
  75. package/scripts/lib/resolve-plugins.mjs +49 -6
  76. package/scripts/utils/docs/spawn-async.md +1 -2
  77. package/scripts/utils/spawn-async.mjs +4 -1
@@ -6,47 +6,18 @@
6
6
  * `execSync` (прямо або через спільний helper), бо це блокує event loop цілком і
7
7
  * зробило б паралельний пул ілюзорним.
8
8
  *
9
- * Нова міграція (наступний shared helper на `spawnAsync`, за протоколом
10
- * `runConftestBatch`/`runOxlintJson`): переведи helper → онови caller-и (`await`) →
11
- * прибери відповідний запис звідси розшир `docs/blocking-inventory-guard.test.mjs`
12
- * (guard-тест сам перевірить, що жоден із них більше не викликає `spawnSync`/`execSync`).
13
- */
14
-
15
- /**
16
- * Concern-и з прямим `spawnSync`/`execSync` у власному `main.mjs` кожен спавнить свій
17
- * зовнішній тул напряму, ще не через `spawnAsync`.
18
- */
19
- const DIRECT_SPAWN_CONCERNS = [
20
- 'text/run-dotenv-linter',
21
- 'text/oxfmt',
22
- 'text/cspell-fix',
23
- 'text/run-v8r',
24
- 'text/run-shellcheck',
25
- 'image-compress/check',
26
- 'php/phpcs',
27
- 'php/project',
28
- 'php/cs_fixer',
29
- 'js/jscpd_duplicates',
30
- 'k8s/manifests',
31
- 'style/lint',
32
- 'bun/licensee',
33
- 'python/ruff',
34
- 'python/project',
35
- 'python/mypy',
36
- 'security/scan',
37
- 'rust/check',
38
- 'rego/conftest_verify'
39
- ]
40
-
41
- /**
42
- * Concern-и, чий detector сам не викликає `spawnSync`/`execSync`, але делегує спільному
43
- * helper-у, який його викликає: `docker/lib/docker-hadolint.mjs` (hadolint) і
44
- * `rego/lib/run-external-tool.mjs` (regal/opa).
9
+ * Список зараз порожній: усі 22 concern-и, що на момент ADR мали прямий чи
10
+ * shared-helper `spawnSync`/`execSync`, мігровано на `spawnAsync`
11
+ * (`npm/scripts/utils/spawn-async.mjs`). Інвентар лишається як живий guard
12
+ * `tests/blocking-inventory.test.mjs` сканує активні concern-и репо і провалиться,
13
+ * щойно новий (чи regressed) concern звернеться до `spawnSync`/`execSync` без
14
+ * відповідного запису тут.
15
+ *
16
+ * Протокол для нового blocking concern-а: перевести на `spawnAsync` оновити
17
+ * caller-и (`await`) якщо міграція ще не завершена, додати запис сюди на час
18
+ * переходу; guard-тест сам підтвердить, коли можна прибрати.
45
19
  */
46
- const SHARED_HELPER_CONCERNS = ['docker/lint', 'rego/regal', 'rego/opa_check']
47
-
48
- /** Повний serial-lane список: `${ruleId}/${concernId}` — 19 прямих + 3 через shared helper = 22. */
49
- export const SERIAL_LANE_CONCERNS = new Set([...DIRECT_SPAWN_CONCERNS, ...SHARED_HELPER_CONCERNS])
20
+ export const SERIAL_LANE_CONCERNS = new Set()
50
21
 
51
22
  /**
52
23
  * Чи concern лишається у serial lane `detectAll()` (недоведений non-blocking).
@@ -55,5 +26,6 @@ export const SERIAL_LANE_CONCERNS = new Set([...DIRECT_SPAWN_CONCERNS, ...SHARED
55
26
  * @returns {boolean} true — serial lane; false — parallel-safe
56
27
  */
57
28
  export function isSerialLane(ruleId, concernId) {
29
+ // eslint-disable-next-line sonarjs/no-empty-collection -- навмисно порожній зараз (протокол вище); живий registry, не dead code
58
30
  return SERIAL_LANE_CONCERNS.has(`${ruleId}/${concernId}`)
59
31
  }
@@ -3,9 +3,8 @@ type: JS Module
3
3
  title: blocking-inventory.mjs
4
4
  resource: npm/scripts/lib/lint-surface/blocking-inventory.mjs
5
5
  docgen:
6
- crc: 2e0e8624
6
+ crc: dce1fafd
7
7
  model: openai-codex/gpt-5.4-mini
8
- tier: cloud-min
9
8
  score: 100
10
9
  issues: judge:inaccurate:0.98
11
10
  judgeModel: openai-codex/gpt-5.4-mini
@@ -28,12 +28,55 @@ export const KNOWN_CI_PLUGINS = Object.freeze({
28
28
  azure: '@7n/rules-ci-azure'
29
29
  })
30
30
 
31
- /** Відомі мовні плагіни: файловий сигнал екосистеми в корені репо → npm-пакет. */
31
+ /**
32
+ * Відомі мовні плагіни: файловий сигнал екосистеми → npm-пакет. `maxDepth` —
33
+ * до якої глибини шукати сигнал: python — лише корінь (uv-провайдер v1
34
+ * обробляє тільки кореневий pyproject.toml); rust — до 3 рівнів, бо в
35
+ * монорепо Cargo.toml часто вкладений (Tauri `app/src-tauri/Cargo.toml`),
36
+ * а провайдер обробляє всі знайдені маніфести.
37
+ */
32
38
  export const KNOWN_LANG_PLUGINS = Object.freeze({
33
- python: { signal: 'pyproject.toml', pkg: '@7n/rules-lang-python' },
34
- rust: { signal: 'Cargo.toml', pkg: '@7n/rules-lang-rust' }
39
+ python: { signal: 'pyproject.toml', pkg: '@7n/rules-lang-python', maxDepth: 0 },
40
+ rust: { signal: 'Cargo.toml', pkg: '@7n/rules-lang-rust', maxDepth: 3 }
35
41
  })
36
42
 
43
+ /** Теки, у які неглибокий скан мовних сигналів не заходить (плюс усі приховані). */
44
+ const LANG_SCAN_SKIP_DIRS = new Set(['node_modules', 'target', 'dist', 'coverage', 'build', 'vendor'])
45
+
46
+ /**
47
+ * Чи є файл-сигнал у корені або підтеках до `maxDepth` рівнів — обмежений
48
+ * BFS-обхід (пропускає приховані теки, node_modules, target тощо), щоб
49
+ * лишатись дешевим на hot-path (hook викликається на кожен файл).
50
+ * @param {string} projectRoot корінь репозиторію
51
+ * @param {string} signal ім'я файлу-сигналу (напр. `Cargo.toml`)
52
+ * @param {number} maxDepth 0 — лише корінь; N — до N рівнів підтек
53
+ * @returns {boolean} true — сигнал знайдено
54
+ */
55
+ function hasLangSignal(projectRoot, signal, maxDepth) {
56
+ let level = [projectRoot]
57
+ for (let depth = 0; depth <= maxDepth && level.length > 0; depth++) {
58
+ if (level.some(dir => existsSync(join(dir, signal)))) return true
59
+ if (depth < maxDepth) level = level.flatMap(listScannableSubdirs)
60
+ }
61
+ return false
62
+ }
63
+
64
+ /**
65
+ * Видимі підтеки для неглибокого скану (без прихованих і службових).
66
+ * @param {string} dir тека
67
+ * @returns {string[]} абсолютні шляхи підтек (порожньо, якщо нечитабельна)
68
+ */
69
+ function listScannableSubdirs(dir) {
70
+ try {
71
+ return readdirSync(dir, { withFileTypes: true })
72
+ .filter(entry => entry.isDirectory() && !entry.name.startsWith('.') && !LANG_SCAN_SKIP_DIRS.has(entry.name))
73
+ .map(entry => join(dir, entry.name))
74
+ } catch {
75
+ /* нечитабельна тека — пропускаємо гілку */
76
+ return []
77
+ }
78
+ }
79
+
37
80
  const WORKFLOW_YML_RE = /\.ya?ml$/u
38
81
  const GITHUB_URL_RE = /github\.com/iu
39
82
  const AZURE_URL_RE = /dev\.azure\.com|visualstudio\.com/iu
@@ -97,14 +140,14 @@ function detectCiPlugins(projectRoot) {
97
140
  /**
98
141
  * Автодетект плагінів за станом репозиторію: CI-плагіни (файлові сигнали з
99
142
  * fallback на `repository.url`) + мовні плагіни (лише файлові сигнали —
100
- * маніфест екосистеми в корені pyproject.toml, кореневий Cargo.toml; URL-fallback для мов безглуздий).
143
+ * маніфест екосистеми в корені або, для rust, у підтеках до 3 рівнів; URL-fallback для мов безглуздий).
101
144
  * @param {string} projectRoot корінь репозиторію
102
145
  * @returns {string[]} npm-імена плагінів
103
146
  */
104
147
  export function detectPluginsFromRepo(projectRoot) {
105
148
  const out = detectCiPlugins(projectRoot)
106
- for (const { signal, pkg } of Object.values(KNOWN_LANG_PLUGINS)) {
107
- if (existsSync(join(projectRoot, signal))) out.push(pkg)
149
+ for (const { signal, pkg, maxDepth } of Object.values(KNOWN_LANG_PLUGINS)) {
150
+ if (hasLangSignal(projectRoot, signal, maxDepth)) out.push(pkg)
108
151
  }
109
152
  return out
110
153
  }
@@ -3,9 +3,8 @@ type: JS Module
3
3
  title: spawn-async.mjs
4
4
  resource: npm/scripts/utils/spawn-async.mjs
5
5
  docgen:
6
- crc: cc5d92e0
6
+ crc: 0e8ce2ef
7
7
  model: openai-codex/gpt-5.4-mini
8
- tier: cloud-min
9
8
  score: 100
10
9
  issues: judge:inaccurate:0.98
11
10
  judgeModel: openai-codex/gpt-5.4-mini
@@ -43,15 +43,18 @@ class AbortError extends Error {
43
43
  * @param {number} [opts.killGraceMs] пауза між `SIGTERM` і ескалацією до `SIGKILL` (дефолт 5000)
44
44
  * @param {string} [opts.cwd] робочий каталог дочірнього процесу
45
45
  * @param {Record<string, string>} [opts.env] оточення дочірнього процесу
46
+ * @param {string} [opts.input] дані для запису в stdin дочірнього процесу (`utf8`, аналог
47
+ * `spawnSync`-опції `input`); stdin закривається (`end`) одразу після запису
46
48
  * @returns {Promise<SpawnAsyncResult>} нормалізований результат виконання
47
49
  */
48
50
  export async function spawnAsync(cmd, args, opts = {}) {
49
- const { signal, timeoutMs, killGraceMs = 5000, ...spawnOpts } = opts
51
+ const { signal, timeoutMs, killGraceMs = 5000, input, ...spawnOpts } = opts
50
52
  if (signal?.aborted) throw new AbortError()
51
53
 
52
54
  const child = spawn(cmd, args, spawnOpts)
53
55
  child.stdout?.setEncoding('utf8')
54
56
  child.stderr?.setEncoding('utf8')
57
+ if (input !== undefined) child.stdin?.end(input, 'utf8')
55
58
 
56
59
  let stdout = ''
57
60
  let stderr = ''