@7n/rules 1.14.0 → 1.14.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 (31) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +1 -1
  3. package/rules/graphql/tooling/docs/main.md +1 -1
  4. package/rules/graphql/tooling/main.mjs +4 -4
  5. package/rules/js/eslint/docs/main.md +1 -1
  6. package/rules/js/eslint/main.mjs +8 -7
  7. package/rules/js-run/runtime/docs/main.md +1 -1
  8. package/rules/js-run/runtime/main.mjs +3 -3
  9. package/rules/k8s/manifests/main.mjs +6 -6
  10. package/rules/nginx-default-tpl/template/docs/main.md +1 -1
  11. package/rules/nginx-default-tpl/template/main.mjs +5 -5
  12. package/rules/tauri/tooling/docs/main.md +1 -1
  13. package/rules/tauri/tooling/main.mjs +1 -1
  14. package/scripts/lib/docs/ensure-tool.md +24 -22
  15. package/scripts/lib/docs/run-conftest-batch.md +5 -3
  16. package/scripts/lib/ensure-tool.mjs +125 -31
  17. package/scripts/lib/lint-surface/blocking-inventory.mjs +59 -0
  18. package/scripts/lib/lint-surface/docs/blocking-inventory.md +30 -0
  19. package/scripts/lib/lint-surface/docs/index.md +2 -0
  20. package/scripts/lib/lint-surface/docs/policy-lint-adapter.md +2 -3
  21. package/scripts/lib/lint-surface/docs/run-detectors.md +3 -1
  22. package/scripts/lib/lint-surface/docs/scheduler.md +39 -0
  23. package/scripts/lib/lint-surface/docs/types.md +2 -2
  24. package/scripts/lib/lint-surface/policy-lint-adapter.mjs +4 -3
  25. package/scripts/lib/lint-surface/run-detectors.mjs +142 -31
  26. package/scripts/lib/lint-surface/scheduler.mjs +96 -0
  27. package/scripts/lib/lint-surface/types.mjs +3 -0
  28. package/scripts/lib/run-conftest-batch.mjs +16 -11
  29. package/scripts/utils/docs/index.md +1 -0
  30. package/scripts/utils/docs/spawn-async.md +33 -0
  31. package/scripts/utils/spawn-async.mjs +112 -0
@@ -0,0 +1,33 @@
1
+ ---
2
+ type: JS Module
3
+ title: spawn-async.mjs
4
+ resource: npm/scripts/utils/spawn-async.mjs
5
+ docgen:
6
+ crc: cc5d92e0
7
+ model: openai-codex/gpt-5.4-mini
8
+ tier: cloud-min
9
+ score: 100
10
+ issues: judge:inaccurate:0.98
11
+ judgeModel: openai-codex/gpt-5.4-mini
12
+ ---
13
+
14
+ ## Огляд
15
+
16
+ Забезпечує асинхронну заміну `spawnSync` для важких зовнішніх CLI на кшталт `conftest` і `oxlint`, щоб не блокувати Node event loop і не створювати ілюзію паралельності під час запуску кількох detector’ів. Обгортає `child_process.spawn` через `events.once`, підтримує зовнішній `AbortSignal` і `timeoutMs` з ескалацією `SIGTERM` → `SIGKILL`, а результат повертає нормалізовано без винятку на non-zero exit — рішення про це лишається за caller.
17
+
18
+ ## Поведінка
19
+
20
+ 1. `spawnAsync` запускає зовнішній CLI асинхронно, щоб не блокувати event loop під час важких системних перевірок.
21
+ 2. Якщо запуск уже скасовано до старту, одразу повертає помилку скасування.
22
+ 3. Збирає `stdout` і `stderr` у нормалізований текстовий результат для подальшого аналізу викликачем.
23
+ 4. Підтримує зовнішнє скасування та часовий ліміт: у цих випадках спершу намагається завершити процес м’яко, а потім примусово, якщо він не зупинився в межах grace-періоду.
24
+ 5. Повертає код завершення, сигнал завершення та ознаки скасування або timeout як звичайний результат, а не як виняток.
25
+ 6. Кидає помилку лише тоді, коли сам запуск процесу не відбувся або середовище не змогло стартувати команду; non-zero exit лишається відповідальністю викликачa.
26
+
27
+ ## Публічний API
28
+
29
+ - spawnAsync — асинхронно запускає зовнішню команду, дочікується завершення, збирає її вихід і повертає результат; не падає через code ≠ 0, а кидає лише коли процес не вдалося створити або якщо сигнал уже був aborted до старту
30
+
31
+ ## Гарантії поведінки
32
+
33
+ - Read-only: не виконує операцій запису (ФС/БД).
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Async (non-blocking) заміна `spawnSync` для важких зовнішніх CLI-викликів (conftest, oxlint тощо).
3
+ *
4
+ * `spawnSync` блокує весь Node event loop цілком — паралельний виклик кількох
5
+ * concern-детекторів навколо `spawnSync` не дає реальної паралельності, лише ілюзію.
6
+ * `spawnAsync` обгортає `child_process.spawn` через `events.once` (без `new Promise`,
7
+ * `promise/avoid-new` заборонений у цьому пакеті), підтримує зовнішній `AbortSignal`
8
+ * і `timeoutMs` (обидва ведуть до `SIGTERM` → ескалація `SIGKILL`, якщо процес не
9
+ * завершився за grace-період), і повертає нормалізований результат без винятку на
10
+ * non-zero exit — це, як і раніше, вирішує caller.
11
+ */
12
+ import { spawn } from 'node:child_process'
13
+ import { once } from 'node:events'
14
+
15
+ /**
16
+ * @typedef {object} SpawnAsyncResult
17
+ * @property {string} stdout зібраний stdout (utf8)
18
+ * @property {string} stderr зібраний stderr (utf8)
19
+ * @property {number|null} exitCode код завершення (`null` — процес вбито сигналом)
20
+ * @property {string|null} signal сигнал, яким вбито процес (`null` — завершився сам)
21
+ * @property {boolean} timedOut true, якщо процес вбито через `timeoutMs`
22
+ * @property {boolean} aborted true, якщо процес вбито через зовнішній `AbortSignal`
23
+ */
24
+
25
+ /** `AbortError` (DOM `AbortController` семантика) для вже-скасованого `signal` до старту спавна. */
26
+ class AbortError extends Error {
27
+ /** @param {string} [message] текст помилки */
28
+ constructor(message = 'The operation was aborted') {
29
+ super(message)
30
+ this.name = 'AbortError'
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Запускає зовнішній процес асинхронно (не блокує event loop) і збирає його результат.
36
+ * Ніколи не кидає на non-zero exit — кидає лише на `spawn`-помилку (ENOENT тощо) або
37
+ * якщо `opts.signal` вже `aborted` до виклику.
38
+ * @param {string} cmd бінарник (шлях або ім'я в PATH)
39
+ * @param {string[]} args аргументи запуску
40
+ * @param {object} [opts] опції
41
+ * @param {AbortSignal} [opts.signal] зовнішній сигнал скасування
42
+ * @param {number} [opts.timeoutMs] ліміт у мілісекундах (без ліміту — не задано / ≤0)
43
+ * @param {number} [opts.killGraceMs] пауза між `SIGTERM` і ескалацією до `SIGKILL` (дефолт 5000)
44
+ * @param {string} [opts.cwd] робочий каталог дочірнього процесу
45
+ * @param {Record<string, string>} [opts.env] оточення дочірнього процесу
46
+ * @returns {Promise<SpawnAsyncResult>} нормалізований результат виконання
47
+ */
48
+ export async function spawnAsync(cmd, args, opts = {}) {
49
+ const { signal, timeoutMs, killGraceMs = 5000, ...spawnOpts } = opts
50
+ if (signal?.aborted) throw new AbortError()
51
+
52
+ const child = spawn(cmd, args, spawnOpts)
53
+ child.stdout?.setEncoding('utf8')
54
+ child.stderr?.setEncoding('utf8')
55
+
56
+ let stdout = ''
57
+ let stderr = ''
58
+ child.stdout?.on('data', chunk => {
59
+ stdout += chunk
60
+ })
61
+ child.stderr?.on('data', chunk => {
62
+ stderr += chunk
63
+ })
64
+
65
+ let timedOut = false
66
+ let aborted = false
67
+ let settled = false
68
+ let killTimer = null
69
+ let timeoutTimer = null
70
+
71
+ /** SIGTERM негайно, ескалація до SIGKILL якщо процес не завершився за killGraceMs. */
72
+ const killWithEscalation = () => {
73
+ child.kill('SIGTERM')
74
+ killTimer = setTimeout(() => {
75
+ if (!settled) child.kill('SIGKILL')
76
+ }, killGraceMs)
77
+ killTimer.unref?.()
78
+ }
79
+ const onAbort = () => {
80
+ aborted = true
81
+ killWithEscalation()
82
+ }
83
+ if (signal) signal.addEventListener('abort', onAbort)
84
+ if (timeoutMs && timeoutMs > 0) {
85
+ timeoutTimer = setTimeout(() => {
86
+ timedOut = true
87
+ killWithEscalation()
88
+ }, timeoutMs)
89
+ timeoutTimer.unref?.()
90
+ }
91
+
92
+ /** @returns {Promise<{code: number|null, killSignal: string|null}>} результат події `close` */
93
+ const waitForClose = async () => {
94
+ const [code, killSignal] = await once(child, 'close')
95
+ return { code, killSignal }
96
+ }
97
+ /** @returns {Promise<never>} ніколи не резолвиться — кидає подію `error` */
98
+ const waitForError = async () => {
99
+ const [error] = await once(child, 'error')
100
+ throw error
101
+ }
102
+
103
+ try {
104
+ const { code, killSignal } = await Promise.race([waitForClose(), waitForError()])
105
+ return { stdout, stderr, exitCode: code, signal: killSignal, timedOut, aborted }
106
+ } finally {
107
+ settled = true
108
+ if (killTimer) clearTimeout(killTimer)
109
+ if (timeoutTimer) clearTimeout(timeoutTimer)
110
+ if (signal) signal.removeEventListener('abort', onAbort)
111
+ }
112
+ }