@7n/rules 1.20.0 → 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.
- package/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/rules/bun/licensee/docs/fix-licensee.md +1 -2
- package/rules/bun/licensee/docs/main.md +1 -1
- package/rules/bun/licensee/fix-licensee.mjs +3 -4
- package/rules/bun/licensee/main.mjs +7 -7
- package/rules/docker/lib/docker-hadolint.mjs +6 -8
- package/rules/docker/lib/docs/docker-hadolint.md +1 -1
- package/rules/docker/lint/docs/main.md +1 -1
- package/rules/docker/lint/main.mjs +1 -1
- package/rules/image-compress/check/docs/fix-check.md +1 -2
- package/rules/image-compress/check/docs/main.md +1 -1
- package/rules/image-compress/check/fix-check.mjs +27 -22
- package/rules/image-compress/check/main.mjs +14 -14
- package/rules/js/jscpd_duplicates/docs/main.md +1 -1
- package/rules/js/jscpd_duplicates/main.mjs +7 -8
- package/rules/k8s/kubeconform/docs/main.md +1 -1
- package/rules/k8s/kubeconform/main.mjs +1 -1
- package/rules/k8s/manifests/main.mjs +52 -39
- package/rules/php/cs_fixer/docs/main.md +1 -2
- package/rules/php/cs_fixer/main.mjs +8 -7
- package/rules/php/phpcs/docs/main.md +1 -2
- package/rules/php/phpcs/main.mjs +12 -11
- package/rules/php/project/docs/main.md +1 -2
- package/rules/php/project/main.mjs +19 -16
- package/rules/python/lib/docs/uv-run.md +1 -2
- package/rules/python/lib/uv-run.mjs +12 -8
- package/rules/python/mypy/docs/main.md +1 -1
- package/rules/python/mypy/main.mjs +9 -8
- package/rules/python/project/docs/main.md +1 -2
- package/rules/python/project/main.mjs +33 -23
- package/rules/python/ruff/docs/fix-ruff.md +1 -2
- package/rules/python/ruff/docs/main.md +1 -1
- package/rules/python/ruff/fix-ruff.mjs +12 -10
- package/rules/python/ruff/main.mjs +13 -12
- package/rules/rego/conftest_verify/docs/main.md +1 -2
- package/rules/rego/conftest_verify/main.mjs +12 -10
- package/rules/rego/lib/docs/run-external-tool.md +1 -1
- package/rules/rego/lib/run-external-tool.mjs +10 -7
- package/rules/rego/opa_check/docs/main.md +1 -1
- package/rules/rego/opa_check/main.mjs +3 -3
- package/rules/rego/regal/docs/main.md +1 -1
- package/rules/rego/regal/main.mjs +3 -3
- package/rules/rust/check/docs/fix-check.md +1 -1
- package/rules/rust/check/docs/main.md +1 -1
- package/rules/rust/check/fix-check.mjs +13 -12
- package/rules/rust/check/main.mjs +19 -13
- package/rules/security/scan/docs/main.md +1 -1
- package/rules/security/scan/main.mjs +9 -9
- package/rules/style/lint/docs/fix-lint.md +1 -1
- package/rules/style/lint/docs/main.md +1 -1
- package/rules/style/lint/fix-lint.mjs +8 -8
- package/rules/style/lint/main.mjs +6 -6
- package/rules/text/cspell-fix/docs/fix-worker.md +1 -1
- package/rules/text/cspell-fix/docs/main.md +1 -1
- package/rules/text/cspell-fix/fix-worker.mjs +1 -1
- package/rules/text/cspell-fix/main.mjs +14 -15
- package/rules/text/oxfmt/docs/fix-oxfmt.md +1 -1
- package/rules/text/oxfmt/docs/main.md +1 -1
- package/rules/text/oxfmt/fix-oxfmt.mjs +3 -3
- package/rules/text/oxfmt/main.mjs +6 -5
- package/rules/text/run-dotenv-linter/docs/fix-run-dotenv-linter.md +1 -2
- package/rules/text/run-dotenv-linter/docs/main.md +1 -1
- package/rules/text/run-dotenv-linter/fix-run-dotenv-linter.mjs +2 -2
- package/rules/text/run-dotenv-linter/main.mjs +19 -24
- package/rules/text/run-shellcheck/docs/fix-run-shellcheck.md +1 -2
- package/rules/text/run-shellcheck/docs/main.md +1 -1
- package/rules/text/run-shellcheck/fix-run-shellcheck.mjs +3 -3
- package/rules/text/run-shellcheck/main.mjs +46 -57
- package/rules/text/run-v8r/docs/main.md +1 -1
- package/rules/text/run-v8r/main.mjs +31 -26
- package/scripts/lib/lint-surface/blocking-inventory.mjs +12 -40
- package/scripts/lib/lint-surface/docs/blocking-inventory.md +1 -2
- package/scripts/utils/docs/spawn-async.md +1 -2
- package/scripts/utils/spawn-async.mjs +4 -1
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { resolve } from 'node:path'
|
|
9
9
|
import { existsSync } from 'node:fs'
|
|
10
|
-
import { spawnSync } from 'node:child_process'
|
|
11
10
|
|
|
12
11
|
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
12
|
+
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
13
13
|
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
14
14
|
|
|
15
15
|
const LINT_TARGETS = ['npm/rules']
|
|
@@ -19,23 +19,25 @@ const LINT_TARGETS = ['npm/rules']
|
|
|
19
19
|
* @param {string} bin абсолютний шлях до бінарника
|
|
20
20
|
* @param {string[]} args аргументи
|
|
21
21
|
* @param {string} cwd робоча директорія
|
|
22
|
-
* @returns {{ status: number, output: string }} код завершення й обрізаний stdout+stderr
|
|
22
|
+
* @returns {Promise<{ status: number, output: string }>} код завершення й обрізаний stdout+stderr
|
|
23
23
|
*/
|
|
24
|
-
function runStep(bin, args, cwd) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
async function runStep(bin, args, cwd) {
|
|
25
|
+
let result
|
|
26
|
+
try {
|
|
27
|
+
result = await spawnAsync(bin, args, { cwd, env: process.env })
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return { status: 1, output: `Не вдалося запустити ${bin}: ${error.message}` }
|
|
28
30
|
}
|
|
29
31
|
const output = `${result.stdout ?? ''}${result.stderr ?? ''}`.trim().slice(0, 2000)
|
|
30
|
-
return { status: result.
|
|
32
|
+
return { status: result.exitCode ?? 1, output }
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* Detector rego/conftest_verify (read-only).
|
|
35
37
|
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону.
|
|
36
|
-
* @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат зі зібраними violations
|
|
38
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
|
|
37
39
|
*/
|
|
38
|
-
export function lint(ctx) {
|
|
40
|
+
export async function lint(ctx) {
|
|
39
41
|
const reporter = createViolationReporter(ctx)
|
|
40
42
|
const { fail } = reporter
|
|
41
43
|
const root = resolve(ctx.cwd)
|
|
@@ -46,7 +48,7 @@ export function lint(ctx) {
|
|
|
46
48
|
const conftest = resolveCmd('conftest')
|
|
47
49
|
if (!conftest) return reporter.result() // conftest відсутній → пропускаємо verify (старий код повертав 0)
|
|
48
50
|
|
|
49
|
-
const verifyRes = runStep(conftest, ['verify', ...targets.flatMap(t => ['-p', t])], root)
|
|
51
|
+
const verifyRes = await runStep(conftest, ['verify', ...targets.flatMap(t => ['-p', t])], root)
|
|
50
52
|
if (verifyRes.status !== 0) {
|
|
51
53
|
const verifySuffix = verifyRes.output ? `\n${verifyRes.output}` : ''
|
|
52
54
|
fail(`lint-rego: conftest verify — помилка (код ${verifyRes.status})${verifySuffix}`, 'conftest-verify-violation')
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync } from 'node:fs'
|
|
7
7
|
import { resolve } from 'node:path'
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
9
10
|
|
|
10
11
|
/** Full-режим (ctx.files undefined): корінь policy-дерева, якщо існує. */
|
|
11
12
|
export const FULL_TARGET = 'npm/rules'
|
|
@@ -44,13 +45,15 @@ export function resolveTargets(files, root) {
|
|
|
44
45
|
* @param {string} bin абсолютний шлях до бінарника
|
|
45
46
|
* @param {string[]} args аргументи
|
|
46
47
|
* @param {string} cwd робоча директорія
|
|
47
|
-
* @returns {{ status: number, output: string }} код завершення й обрізаний stdout+stderr
|
|
48
|
+
* @returns {Promise<{ status: number, output: string }>} код завершення й обрізаний stdout+stderr
|
|
48
49
|
*/
|
|
49
|
-
export function runStep(bin, args, cwd) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
export async function runStep(bin, args, cwd) {
|
|
51
|
+
let result
|
|
52
|
+
try {
|
|
53
|
+
result = await spawnAsync(bin, args, { cwd, env: process.env })
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return { status: 1, output: `Не вдалося запустити ${bin}: ${error.message}` }
|
|
53
56
|
}
|
|
54
57
|
const output = `${result.stdout ?? ''}${result.stderr ?? ''}`.trim().slice(0, 2000)
|
|
55
|
-
return { status: result.
|
|
58
|
+
return { status: result.exitCode ?? 1, output }
|
|
56
59
|
}
|
|
@@ -14,9 +14,9 @@ import { resolveTargets, runStep } from '../lib/run-external-tool.mjs'
|
|
|
14
14
|
/**
|
|
15
15
|
* Detector rego/opa_check (read-only).
|
|
16
16
|
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону.
|
|
17
|
-
* @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат зі зібраними violations
|
|
17
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
|
|
18
18
|
*/
|
|
19
|
-
export function lint(ctx) {
|
|
19
|
+
export async function lint(ctx) {
|
|
20
20
|
const reporter = createViolationReporter(ctx)
|
|
21
21
|
const { fail } = reporter
|
|
22
22
|
const root = resolve(ctx.cwd)
|
|
@@ -25,7 +25,7 @@ export function lint(ctx) {
|
|
|
25
25
|
if (targets.length === 0) return reporter.result()
|
|
26
26
|
|
|
27
27
|
const opa = ensureTool('opa')
|
|
28
|
-
const opaRes = runStep(opa, ['check', '--strict', ...targets], root)
|
|
28
|
+
const opaRes = await runStep(opa, ['check', '--strict', ...targets], root)
|
|
29
29
|
if (opaRes.status !== 0) {
|
|
30
30
|
const opaSuffix = opaRes.output ? `\n${opaRes.output}` : ''
|
|
31
31
|
fail(`lint-rego: opa check --strict — помилка (код ${opaRes.status})${opaSuffix}`, 'opa-check-violation')
|
|
@@ -14,9 +14,9 @@ import { resolveTargets, runStep } from '../lib/run-external-tool.mjs'
|
|
|
14
14
|
/**
|
|
15
15
|
* Detector rego/regal (read-only).
|
|
16
16
|
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону.
|
|
17
|
-
* @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат зі зібраними violations
|
|
17
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
|
|
18
18
|
*/
|
|
19
|
-
export function lint(ctx) {
|
|
19
|
+
export async function lint(ctx) {
|
|
20
20
|
const reporter = createViolationReporter(ctx)
|
|
21
21
|
const { fail } = reporter
|
|
22
22
|
const root = resolve(ctx.cwd)
|
|
@@ -25,7 +25,7 @@ export function lint(ctx) {
|
|
|
25
25
|
if (targets.length === 0) return reporter.result()
|
|
26
26
|
|
|
27
27
|
const regal = ensureTool('regal')
|
|
28
|
-
const regalRes = runStep(regal, ['lint', ...targets], root)
|
|
28
|
+
const regalRes = await runStep(regal, ['lint', ...targets], root)
|
|
29
29
|
if (regalRes.status !== 0) {
|
|
30
30
|
const regalSuffix = regalRes.output ? `\n${regalRes.output}` : ''
|
|
31
31
|
fail(`lint-rego: regal lint — помилка (код ${regalRes.status})${regalSuffix}`, 'regal-lint-violation')
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* (`deny-config-missing`). clippy не автофіксимо (його `--fix` потенційно небезпечний) — ці
|
|
7
7
|
* порушення й далі йдуть у LLM-ladder. Запис permanent. Відсутній `cargo`/`cargo-deny` → no-op.
|
|
8
8
|
*/
|
|
9
|
-
import { spawnSync } from 'node:child_process'
|
|
10
9
|
import { existsSync, readFileSync } from 'node:fs'
|
|
11
10
|
import { join, resolve } from 'node:path'
|
|
12
11
|
|
|
13
12
|
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
13
|
+
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Вміст файлу або null, якщо не читається.
|
|
@@ -26,13 +26,13 @@ function readOrNull(abs) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Tracked *.rs файли проєкту (через git).
|
|
29
|
+
* Tracked *.rs файли проєкту (через git). Async (не блокує event loop, ADR 260716-1354).
|
|
30
30
|
* @param {string} cwd корінь
|
|
31
|
-
* @returns {string[]} posix-relative шляхи
|
|
31
|
+
* @returns {Promise<string[]>} posix-relative шляхи
|
|
32
32
|
*/
|
|
33
|
-
function listRsFiles(cwd) {
|
|
34
|
-
const r =
|
|
35
|
-
if (r.
|
|
33
|
+
async function listRsFiles(cwd) {
|
|
34
|
+
const r = await spawnAsync('git', ['ls-files', '-z', '--', '*.rs'], { cwd })
|
|
35
|
+
if (r.exitCode !== 0) return []
|
|
36
36
|
return (r.stdout ?? '').split('\0').filter(Boolean)
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -42,15 +42,15 @@ export const patterns = [
|
|
|
42
42
|
id: 'rust-cargo-fmt',
|
|
43
43
|
standalone: true, // §8 Phase 2: apply самостійно перелічує *.rs (git ls-files), cargo fmt сам ре-аналізує
|
|
44
44
|
test: violations => violations.some(v => v.reason === 'cargo-fmt-violation'),
|
|
45
|
-
apply: (violations, ctx) => {
|
|
45
|
+
apply: async (violations, ctx) => {
|
|
46
46
|
const cargo = resolveCmd('cargo')
|
|
47
47
|
if (!cargo) return { touchedFiles: [] }
|
|
48
|
-
const files = listRsFiles(ctx.cwd)
|
|
48
|
+
const files = await listRsFiles(ctx.cwd)
|
|
49
49
|
if (files.length === 0) return { touchedFiles: [] }
|
|
50
50
|
|
|
51
51
|
const abs = files.map(f => resolve(ctx.cwd, f))
|
|
52
52
|
const before = new Map(abs.map(a => [a, readOrNull(a)]))
|
|
53
|
-
|
|
53
|
+
await spawnAsync(cargo, ['fmt', '--all'], { cwd: ctx.cwd })
|
|
54
54
|
|
|
55
55
|
const touchedFiles = abs.filter(a => readOrNull(a) !== before.get(a))
|
|
56
56
|
for (const a of touchedFiles) ctx.recordWrite?.(a)
|
|
@@ -62,14 +62,15 @@ export const patterns = [
|
|
|
62
62
|
{
|
|
63
63
|
id: 'rust-cargo-deny-init',
|
|
64
64
|
test: violations => violations.some(v => v.reason === 'deny-config-missing'),
|
|
65
|
-
apply: (violations, ctx) => {
|
|
65
|
+
apply: async (violations, ctx) => {
|
|
66
66
|
const cargo = resolveCmd('cargo')
|
|
67
67
|
if (!cargo) return { touchedFiles: [] }
|
|
68
|
-
const
|
|
68
|
+
const denyVersionResult = await spawnAsync(cargo, ['deny', '--version'])
|
|
69
|
+
const hasDeny = denyVersionResult.exitCode === 0
|
|
69
70
|
if (!hasDeny) return { touchedFiles: [] }
|
|
70
71
|
|
|
71
72
|
const denyConfigPath = join(ctx.cwd, 'deny.toml')
|
|
72
|
-
|
|
73
|
+
await spawnAsync(cargo, ['deny', 'init'], { cwd: ctx.cwd })
|
|
73
74
|
if (!existsSync(denyConfigPath)) return { touchedFiles: [] }
|
|
74
75
|
|
|
75
76
|
ctx.recordWrite?.(denyConfigPath)
|
|
@@ -2,26 +2,28 @@
|
|
|
2
2
|
* lint-поверхня rust: read-only detector (cargo fmt --check + clippy -D warnings +
|
|
3
3
|
* cargo deny check licenses). Генерація deny.toml — окремий T0-fix, не в detector-і.
|
|
4
4
|
*/
|
|
5
|
-
import { spawnSync } from 'node:child_process'
|
|
6
5
|
import { existsSync } from 'node:fs'
|
|
7
6
|
import { join } from 'node:path'
|
|
8
7
|
|
|
9
8
|
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
10
9
|
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
10
|
+
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
+
* Async (не блокує event loop) — детектор може виконуватись у parallel lane `detectAll()`
|
|
14
|
+
* (ADR 260716-1354).
|
|
13
15
|
* @param {string} label назва кроку.
|
|
14
16
|
* @param {string} cargo шлях до бінарника cargo.
|
|
15
17
|
* @param {string[]} args аргументи cargo.
|
|
16
18
|
* @param {string} cwd робочий каталог.
|
|
17
19
|
* @param {(msg: string, reason: string) => void} fail колбек реєстрації порушення.
|
|
18
20
|
* @param {string} reason машиночитна причина порушення.
|
|
19
|
-
* @returns {boolean} true якщо OK
|
|
21
|
+
* @returns {Promise<boolean>} true якщо OK
|
|
20
22
|
*/
|
|
21
|
-
function runCargo(label, cargo, args, cwd, fail, reason) {
|
|
22
|
-
const r =
|
|
23
|
-
if (r.
|
|
24
|
-
const code = typeof r.
|
|
23
|
+
async function runCargo(label, cargo, args, cwd, fail, reason) {
|
|
24
|
+
const r = await spawnAsync(cargo, args, { cwd })
|
|
25
|
+
if (r.exitCode === 0) return true
|
|
26
|
+
const code = typeof r.exitCode === 'number' ? r.exitCode : 1
|
|
25
27
|
const out = `${r.stdout ?? ''}${r.stderr ?? ''}`.trim().slice(0, 2000)
|
|
26
28
|
const outTail = out ? `\n${out}` : ''
|
|
27
29
|
fail(`lint-rust: ${label} — помилка (код ${code}, rust.mdc)${outTail}`, reason)
|
|
@@ -29,11 +31,12 @@ function runCargo(label, cargo, args, cwd, fail, reason) {
|
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
|
-
* Detector rust/check (read-only).
|
|
34
|
+
* Detector rust/check (read-only). Async (не блокує event loop) — детектор може виконуватись
|
|
35
|
+
* у parallel lane `detectAll()` (ADR 260716-1354).
|
|
33
36
|
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст лінту.
|
|
34
|
-
* @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат із порушеннями
|
|
37
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат із порушеннями
|
|
35
38
|
*/
|
|
36
|
-
export function lint(ctx) {
|
|
39
|
+
export async function lint(ctx) {
|
|
37
40
|
const reporter = createViolationReporter(ctx)
|
|
38
41
|
const { fail } = reporter
|
|
39
42
|
const cwd = ctx.cwd
|
|
@@ -49,11 +52,13 @@ export function lint(ctx) {
|
|
|
49
52
|
return reporter.result()
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
if (
|
|
55
|
+
if (
|
|
56
|
+
!(await runCargo('cargo fmt --check', cargo, ['fmt', '--all', '--', '--check'], cwd, fail, 'cargo-fmt-violation'))
|
|
57
|
+
) {
|
|
53
58
|
return reporter.result()
|
|
54
59
|
}
|
|
55
60
|
|
|
56
|
-
runCargo(
|
|
61
|
+
await runCargo(
|
|
57
62
|
'cargo clippy -D warnings',
|
|
58
63
|
cargo,
|
|
59
64
|
['clippy', '--all-targets', '--all-features', '--', '-D', 'warnings'],
|
|
@@ -71,9 +76,10 @@ export function lint(ctx) {
|
|
|
71
76
|
return reporter.result()
|
|
72
77
|
}
|
|
73
78
|
|
|
74
|
-
const
|
|
79
|
+
const denyVersionResult = await spawnAsync(cargo, ['deny', '--version'])
|
|
80
|
+
const hasDeny = denyVersionResult.exitCode === 0
|
|
75
81
|
if (hasDeny) {
|
|
76
|
-
runCargo('cargo deny check licenses', cargo, ['deny', 'check', 'licenses'], cwd, fail, 'cargo-deny-violation')
|
|
82
|
+
await runCargo('cargo deny check licenses', cargo, ['deny', 'check', 'licenses'], cwd, fail, 'cargo-deny-violation')
|
|
77
83
|
}
|
|
78
84
|
// cargo-deny не встановлено → перевірку ліцензій пропущено (старий код — pass)
|
|
79
85
|
|
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
* lint-поверхня security/scan: read-only detector секретів (`trufflehog` filesystem скан
|
|
3
3
|
* усього репо). Фіксу немає — детектор лише сигналить про знайдені секрети.
|
|
4
4
|
*/
|
|
5
|
-
import { spawnSync } from 'node:child_process'
|
|
6
|
-
|
|
7
5
|
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
8
6
|
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
7
|
+
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Detector security/scan: trufflehog filesystem скан (read-only). Якщо бінарника немає —
|
|
12
|
-
* скан пропускається (як і раніше при відсутності інструмента).
|
|
11
|
+
* скан пропускається (як і раніше при відсутності інструмента). Async (не блокує event loop) —
|
|
12
|
+
* детектор може виконуватись у parallel lane `detectAll()` (ADR 260716-1354).
|
|
13
13
|
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону
|
|
14
|
-
* @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат detector-а
|
|
14
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат detector-а
|
|
15
15
|
*/
|
|
16
|
-
export function lint(ctx) {
|
|
16
|
+
export async function lint(ctx) {
|
|
17
17
|
const reporter = createViolationReporter(ctx)
|
|
18
18
|
const { fail } = reporter
|
|
19
19
|
const cwd = ctx.cwd
|
|
@@ -26,7 +26,7 @@ export function lint(ctx) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
const r =
|
|
29
|
+
const r = await spawnAsync(
|
|
30
30
|
trufflehog,
|
|
31
31
|
[
|
|
32
32
|
'filesystem',
|
|
@@ -37,12 +37,12 @@ export function lint(ctx) {
|
|
|
37
37
|
'--results=verified,unknown',
|
|
38
38
|
'--fail'
|
|
39
39
|
],
|
|
40
|
-
{ cwd
|
|
40
|
+
{ cwd }
|
|
41
41
|
)
|
|
42
|
-
if (r.
|
|
42
|
+
if (r.exitCode !== 0) {
|
|
43
43
|
const out = `${r.stdout ?? ''}${r.stderr ?? ''}`.trim().slice(0, 4000)
|
|
44
44
|
const outSuffix = out ? `\n${out}` : ''
|
|
45
|
-
fail(`security/scan: trufflehog знайшов секрети (код ${r.
|
|
45
|
+
fail(`security/scan: trufflehog знайшов секрети (код ${r.exitCode})${outSuffix}`, 'secret-found')
|
|
46
46
|
}
|
|
47
47
|
return reporter.result()
|
|
48
48
|
}
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
* режимі), форматує їх і повертає лише фактично змінені. Запис permanent. Відсутній
|
|
8
8
|
* stylelint → no-op.
|
|
9
9
|
*/
|
|
10
|
-
import { spawnSync } from 'node:child_process'
|
|
11
10
|
import { readFileSync } from 'node:fs'
|
|
12
11
|
import { resolve } from 'node:path'
|
|
13
12
|
|
|
14
13
|
import { filterStyleFiles, resolveStylelint } from './main.mjs'
|
|
14
|
+
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Вміст файлу або null, якщо не читається.
|
|
@@ -31,12 +31,12 @@ function readOrNull(abs) {
|
|
|
31
31
|
* css/scss/vue через `git ls-files`.
|
|
32
32
|
* @param {string} cwd корінь
|
|
33
33
|
* @param {string[]|undefined} ctxFiles файли з контексту (дельта) або undefined (повний режим)
|
|
34
|
-
* @returns {string[]} posix-relative шляхи
|
|
34
|
+
* @returns {Promise<string[]>} posix-relative шляхи
|
|
35
35
|
*/
|
|
36
|
-
function listStyleFiles(cwd, ctxFiles) {
|
|
36
|
+
async function listStyleFiles(cwd, ctxFiles) {
|
|
37
37
|
if (ctxFiles !== undefined) return filterStyleFiles(ctxFiles)
|
|
38
|
-
const r =
|
|
39
|
-
if (r.
|
|
38
|
+
const r = await spawnAsync('git', ['ls-files', '-z', '--', '*.css', '*.scss', '*.vue'], { cwd })
|
|
39
|
+
if (r.exitCode !== 0) return []
|
|
40
40
|
return (r.stdout ?? '').split('\0').filter(Boolean)
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -46,15 +46,15 @@ export const patterns = [
|
|
|
46
46
|
id: 'style-stylelint-fix',
|
|
47
47
|
standalone: true, // §8 Phase 2: apply бере ctx.files (не violations), stylelint --fix сам ре-аналізує
|
|
48
48
|
test: violations => violations.some(v => v.reason === 'stylelint-violation'),
|
|
49
|
-
apply: (violations, ctx) => {
|
|
49
|
+
apply: async (violations, ctx) => {
|
|
50
50
|
const stylelint = resolveStylelint(ctx.cwd)
|
|
51
51
|
if (!stylelint) return { touchedFiles: [] }
|
|
52
|
-
const files = listStyleFiles(ctx.cwd, ctx.files)
|
|
52
|
+
const files = await listStyleFiles(ctx.cwd, ctx.files)
|
|
53
53
|
if (files.length === 0) return { touchedFiles: [] }
|
|
54
54
|
|
|
55
55
|
const abs = files.map(f => resolve(ctx.cwd, f))
|
|
56
56
|
const before = new Map(abs.map(a => [a, readOrNull(a)]))
|
|
57
|
-
|
|
57
|
+
await spawnAsync(stylelint, ['--fix', ...files], { cwd: ctx.cwd })
|
|
58
58
|
|
|
59
59
|
const touchedFiles = abs.filter(a => readOrNull(a) !== before.get(a))
|
|
60
60
|
for (const a of touchedFiles) ctx.recordWrite?.(a)
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
* Per-file (ctx.files) або весь проєкт (ctx.files === undefined). Якщо stylelint
|
|
4
4
|
* недоступний — крок пропускається (без npx-автовстановлення).
|
|
5
5
|
*/
|
|
6
|
-
import { spawnSync } from 'node:child_process'
|
|
7
6
|
import { existsSync } from 'node:fs'
|
|
8
7
|
import { join } from 'node:path'
|
|
9
8
|
|
|
10
9
|
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
11
10
|
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
11
|
+
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
12
12
|
|
|
13
13
|
const STYLE_EXT_RE = /\.(?:css|scss|vue)$/u
|
|
14
14
|
|
|
@@ -34,9 +34,9 @@ export function resolveStylelint(cwd) {
|
|
|
34
34
|
/**
|
|
35
35
|
* Detector style/lint (read-only).
|
|
36
36
|
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону (cwd, files)
|
|
37
|
-
* @returns {import('../../../scripts/lib/lint-surface/types.mjs').LintResult} результат зі зібраними violations
|
|
37
|
+
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат зі зібраними violations
|
|
38
38
|
*/
|
|
39
|
-
export function lint(ctx) {
|
|
39
|
+
export async function lint(ctx) {
|
|
40
40
|
const reporter = createViolationReporter(ctx)
|
|
41
41
|
const { fail } = reporter
|
|
42
42
|
const cwd = ctx.cwd
|
|
@@ -58,11 +58,11 @@ export function lint(ctx) {
|
|
|
58
58
|
return reporter.result()
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
const r =
|
|
62
|
-
if (r.
|
|
61
|
+
const r = await spawnAsync(stylelint, targets, { cwd, shell: false })
|
|
62
|
+
if (r.exitCode !== 0) {
|
|
63
63
|
const out = `${r.stdout ?? ''}${r.stderr ?? ''}`.trim().slice(0, 2000)
|
|
64
64
|
const outSuffix = out ? `\n${out}` : ''
|
|
65
|
-
fail(`lint-style: stylelint — порушення (код ${r.
|
|
65
|
+
fail(`lint-style: stylelint — порушення (код ${r.exitCode ?? 1}, style.mdc)${outSuffix}`, 'stylelint-violation')
|
|
66
66
|
}
|
|
67
67
|
return reporter.result()
|
|
68
68
|
}
|
|
@@ -40,7 +40,7 @@ export async function fixWorker(violations, ctx) {
|
|
|
40
40
|
const bin = resolveCmd('npx')
|
|
41
41
|
if (!bin) return { touchedFiles: [] }
|
|
42
42
|
|
|
43
|
-
const first = detectCspell(ctx.cwd, bin, ctx.files)
|
|
43
|
+
const first = await detectCspell(ctx.cwd, bin, ctx.files)
|
|
44
44
|
if (first.code === 0) return { touchedFiles: [] }
|
|
45
45
|
|
|
46
46
|
const words = unknownWords(first.out)
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
* typo лишаються → cspell повертає !=0 → exit 1 (людина доправляє одруки вручну).
|
|
17
17
|
*/
|
|
18
18
|
import { env } from 'node:process'
|
|
19
|
-
import { spawnSync } from 'node:child_process'
|
|
20
19
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
21
20
|
import { join } from 'node:path'
|
|
22
21
|
|
|
23
22
|
import { resolveCmd } from '../../../scripts/utils/resolve-cmd.mjs'
|
|
23
|
+
import { spawnAsync } from '../../../scripts/utils/spawn-async.mjs'
|
|
24
24
|
import { createViolationReporter } from '../../../scripts/lib/lint-surface/violation-reporter.mjs'
|
|
25
25
|
|
|
26
26
|
/** Слово у рядку cspell: `<file>:<line>:<col> - Unknown word (xxx)`. */
|
|
@@ -44,21 +44,18 @@ export const fixModel = () => env.N_LOCAL_MIN_MODEL || ''
|
|
|
44
44
|
* рядок (`--no-summary` НЕ передається) лишається — з нього парситься `FILES_CHECKED_RE`.
|
|
45
45
|
* @param {string} cwd корінь
|
|
46
46
|
* @param {string} bin шлях до cspell (npx/локальний)
|
|
47
|
+
* Async (не блокує event loop) — детектор може виконуватись у parallel lane `detectAll()`
|
|
48
|
+
* (ADR 260716-1354).
|
|
47
49
|
* @param {string[]} [files] явний перелік файлів (delta); без нього — `cspell .`
|
|
48
50
|
* @param {boolean} [verbose] показати повний нативний вивід cspell (включно з прогресом)
|
|
49
|
-
* @returns {{ code:number, out:string }} код + обʼєднаний stdout/stderr
|
|
51
|
+
* @returns {Promise<{ code:number, out:string }>} код + обʼєднаний stdout/stderr
|
|
50
52
|
*/
|
|
51
|
-
export function detectCspell(cwd, bin, files, verbose = false) {
|
|
53
|
+
export async function detectCspell(cwd, bin, files, verbose = false) {
|
|
52
54
|
const targets = files === undefined ? ['.'] : files
|
|
53
55
|
const quietArgs = verbose ? [] : ['--no-progress']
|
|
54
|
-
const r =
|
|
55
|
-
cwd,
|
|
56
|
-
encoding: 'utf8',
|
|
57
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
58
|
-
env: process.env
|
|
59
|
-
})
|
|
56
|
+
const r = await spawnAsync(bin, ['cspell', ...quietArgs, ...targets], { cwd, env: process.env })
|
|
60
57
|
const out = `${r.stdout ?? ''}${r.stderr ?? ''}`
|
|
61
|
-
const code = typeof r.
|
|
58
|
+
const code = typeof r.exitCode === 'number' ? r.exitCode : 1
|
|
62
59
|
// cspell повертає !=0 і коли жоден переданий файл не пройшов ignorePaths
|
|
63
60
|
// (`.cspell.json`) — «Files checked: 0» означає «нічого перевіряти», не порушення.
|
|
64
61
|
const checked = FILES_CHECKED_RE.exec(out)
|
|
@@ -138,19 +135,21 @@ export function appendWordsToDict(cwd, words) {
|
|
|
138
135
|
/**
|
|
139
136
|
* cspell-крок lint-text: read-only детект (нуль мутацій). LLM-класифікація знахідок
|
|
140
137
|
* і поповнення `.cspell.json#words` живуть у `fix-worker.mjs` (Central Runner Pipeline).
|
|
138
|
+
* Async (не блокує event loop) — детектор може виконуватись у parallel lane `detectAll()`
|
|
139
|
+
* (ADR 260716-1354).
|
|
141
140
|
* @param {string[]} [files] явний перелік файлів (delta); без нього — `cspell .`
|
|
142
141
|
* @param {string} [cwd] корінь
|
|
143
142
|
* @param {boolean} [verbose] показати повний нативний вивід cspell (включно з прогресом)
|
|
144
|
-
* @returns {number} 0 — чисто; 1 — лишились знахідки / помилка середовища
|
|
143
|
+
* @returns {Promise<number>} 0 — чисто; 1 — лишились знахідки / помилка середовища
|
|
145
144
|
*/
|
|
146
|
-
export function runCspellText(files, cwd = process.cwd(), verbose = false) {
|
|
145
|
+
export async function runCspellText(files, cwd = process.cwd(), verbose = false) {
|
|
147
146
|
const bin = resolveCmd('npx')
|
|
148
147
|
if (!bin) {
|
|
149
148
|
process.stderr.write('❌ npx не знайдено в PATH (cspell).\n')
|
|
150
149
|
return 1
|
|
151
150
|
}
|
|
152
151
|
|
|
153
|
-
const first = detectCspell(cwd, bin, files, verbose)
|
|
152
|
+
const first = await detectCspell(cwd, bin, files, verbose)
|
|
154
153
|
if (first.code === 0) {
|
|
155
154
|
// «Files checked: 0» при явно переданих файлах — легітимно (усі під ignorePaths),
|
|
156
155
|
// але так само виглядає зламане середовище (спостережено 2026-07-12: cspell-gitignore
|
|
@@ -174,12 +173,12 @@ export function runCspellText(files, cwd = process.cwd(), verbose = false) {
|
|
|
174
173
|
* @param {import('../../../scripts/lib/lint-surface/types.mjs').LintContext} ctx контекст lint-прогону
|
|
175
174
|
* @returns {Promise<import('../../../scripts/lib/lint-surface/types.mjs').LintResult>} результат детектора
|
|
176
175
|
*/
|
|
177
|
-
export function lint(ctx) {
|
|
176
|
+
export async function lint(ctx) {
|
|
178
177
|
const reporter = createViolationReporter(ctx)
|
|
179
178
|
const { fail } = reporter
|
|
180
179
|
if (ctx.files !== undefined && ctx.files.length === 0) return reporter.result()
|
|
181
180
|
|
|
182
|
-
const code = runCspellText(ctx.files, ctx.cwd, ctx.verbose === true)
|
|
181
|
+
const code = await runCspellText(ctx.files, ctx.cwd, ctx.verbose === true)
|
|
183
182
|
if (code !== 0) fail('cspell знайшов порушення правопису (text.mdc)', 'cspell')
|
|
184
183
|
return reporter.result()
|
|
185
184
|
}
|