pr-checkmate 1.22.7 → 1.23.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/README.md +101 -333
- package/cspell.json +9 -1
- package/dist/cli.js +32 -2
- package/dist/config/constants.d.ts +25 -1
- package/dist/config/constants.js +157 -38
- package/dist/config/index.d.ts +15 -2
- package/dist/config/index.js +28 -7
- package/dist/config/loader.js +5 -1
- package/dist/core/checks/dependencies/banned-imports.d.ts +2 -0
- package/dist/core/checks/dependencies/banned-imports.js +71 -0
- package/dist/core/checks/{circular-deps.d.ts → dependencies/circular-deps.d.ts} +1 -1
- package/dist/core/checks/{circular-deps.js → dependencies/circular-deps.js} +9 -10
- package/dist/core/checks/{dependency-check.d.ts → dependencies/dependency-check.d.ts} +1 -1
- package/dist/core/checks/{dependency-check.js → dependencies/dependency-check.js} +3 -3
- package/dist/core/checks/{license-check.d.ts → dependencies/license-check.d.ts} +1 -1
- package/dist/core/checks/{license-check.js → dependencies/license-check.js} +3 -3
- package/dist/core/checks/{npm-audit.d.ts → dependencies/npm-audit.d.ts} +1 -1
- package/dist/core/checks/{npm-audit.js → dependencies/npm-audit.js} +3 -3
- package/dist/core/checks/{outdated-deps.d.ts → dependencies/outdated-deps.d.ts} +1 -1
- package/dist/core/checks/{outdated-deps.js → dependencies/outdated-deps.js} +3 -3
- package/dist/core/checks/dependencies/vuln-scan.d.ts +9 -0
- package/dist/core/checks/dependencies/vuln-scan.js +41 -0
- package/dist/core/checks/findings.d.ts +37 -1
- package/dist/core/checks/findings.js +80 -3
- package/dist/core/checks/git/custom-rules.d.ts +2 -0
- package/dist/core/checks/git/custom-rules.js +102 -0
- package/dist/core/checks/{diff-smell.d.ts → git/diff-smell.d.ts} +1 -1
- package/dist/core/checks/{diff-smell.js → git/diff-smell.js} +3 -3
- package/dist/core/checks/git/large-files.d.ts +2 -0
- package/dist/core/checks/git/large-files.js +83 -0
- package/dist/core/checks/git/leftover-debug.d.ts +2 -0
- package/dist/core/checks/git/leftover-debug.js +77 -0
- package/dist/core/checks/git/lockfile-drift.d.ts +2 -0
- package/dist/core/checks/git/lockfile-drift.js +114 -0
- package/dist/core/checks/git/merge-conflict.d.ts +2 -0
- package/dist/core/checks/git/merge-conflict.js +109 -0
- package/dist/core/checks/git/missing-tests.d.ts +2 -0
- package/dist/core/checks/git/missing-tests.js +92 -0
- package/dist/core/checks/git/todo-fixme.d.ts +7 -0
- package/dist/core/checks/git/todo-fixme.js +74 -0
- package/dist/core/checks/{cpp-format.d.ts → languages/cpp-format.d.ts} +1 -1
- package/dist/core/checks/{cpp-format.js → languages/cpp-format.js} +3 -3
- package/dist/core/checks/languages/csharp-format.d.ts +13 -0
- package/dist/core/checks/languages/csharp-format.js +52 -0
- package/dist/core/checks/languages/go-format.d.ts +8 -0
- package/dist/core/checks/languages/go-format.js +48 -0
- package/dist/core/checks/languages/go-lint.d.ts +8 -0
- package/dist/core/checks/languages/go-lint.js +48 -0
- package/dist/core/checks/{kotlin-lint.d.ts → languages/kotlin-lint.d.ts} +1 -1
- package/dist/core/checks/{kotlin-lint.js → languages/kotlin-lint.js} +13 -17
- package/dist/core/checks/{lint.d.ts → languages/lint.d.ts} +1 -1
- package/dist/core/checks/{lint.js → languages/lint.js} +25 -14
- package/dist/core/checks/languages/php-format.d.ts +9 -0
- package/dist/core/checks/languages/php-format.js +49 -0
- package/dist/core/checks/{prettier.d.ts → languages/prettier.d.ts} +1 -1
- package/dist/core/checks/{prettier.js → languages/prettier.js} +10 -10
- package/dist/core/checks/{python-format.d.ts → languages/python-format.d.ts} +1 -1
- package/dist/core/checks/{python-format.js → languages/python-format.js} +3 -3
- package/dist/core/checks/{python-lint.d.ts → languages/python-lint.d.ts} +1 -1
- package/dist/core/checks/{python-lint.js → languages/python-lint.js} +3 -3
- package/dist/core/checks/languages/python-typecheck.d.ts +9 -0
- package/dist/core/checks/languages/python-typecheck.js +66 -0
- package/dist/core/checks/languages/ruby-lint.d.ts +8 -0
- package/dist/core/checks/languages/ruby-lint.js +50 -0
- package/dist/core/checks/languages/rust-format.d.ts +9 -0
- package/dist/core/checks/languages/rust-format.js +45 -0
- package/dist/core/checks/languages/rust-lint.d.ts +8 -0
- package/dist/core/checks/languages/rust-lint.js +50 -0
- package/dist/core/checks/{swift-lint.d.ts → languages/swift-lint.d.ts} +1 -1
- package/dist/core/checks/{swift-lint.js → languages/swift-lint.js} +13 -17
- package/dist/core/checks/{typecheck.d.ts → languages/typecheck.d.ts} +1 -1
- package/dist/core/checks/{typecheck.js → languages/typecheck.js} +3 -3
- package/dist/core/checks/{commitlint.d.ts → pr/commitlint.d.ts} +1 -1
- package/dist/core/checks/{commitlint.js → pr/commitlint.js} +4 -4
- package/dist/core/checks/{pr-body.d.ts → pr/pr-body.d.ts} +1 -1
- package/dist/core/checks/{pr-body.js → pr/pr-body.js} +2 -2
- package/dist/core/checks/{pr-size-check.d.ts → pr/pr-size-check.d.ts} +1 -1
- package/dist/core/checks/{pr-size-check.js → pr/pr-size-check.js} +13 -11
- package/dist/core/checks/{pr-title-lint.d.ts → pr/pr-title-lint.d.ts} +1 -1
- package/dist/core/checks/{pr-title-lint.js → pr/pr-title-lint.js} +2 -2
- package/dist/core/checks/quality/case-collision.d.ts +8 -0
- package/dist/core/checks/quality/case-collision.js +40 -0
- package/dist/core/checks/quality/config-validation.d.ts +8 -0
- package/dist/core/checks/quality/config-validation.js +63 -0
- package/dist/core/checks/quality/coverage.d.ts +2 -0
- package/dist/core/checks/quality/coverage.js +63 -0
- package/dist/core/checks/{dead-code.d.ts → quality/dead-code.d.ts} +1 -1
- package/dist/core/checks/{dead-code.js → quality/dead-code.js} +4 -4
- package/dist/core/checks/{duplicate-check.d.ts → quality/duplicate-check.d.ts} +1 -1
- package/dist/core/checks/{duplicate-check.js → quality/duplicate-check.js} +11 -15
- package/dist/core/checks/quality/license-header.d.ts +7 -0
- package/dist/core/checks/quality/license-header.js +74 -0
- package/dist/core/checks/{markdown-lint.d.ts → quality/markdown-lint.d.ts} +1 -1
- package/dist/core/checks/{markdown-lint.js → quality/markdown-lint.js} +8 -21
- package/dist/core/checks/{spellcheck.d.ts → quality/spellcheck.d.ts} +1 -1
- package/dist/core/checks/{spellcheck.js → quality/spellcheck.js} +11 -19
- package/dist/core/checks/{diff-security.d.ts → security/diff-security.d.ts} +1 -1
- package/dist/core/checks/security/diff-security.js +262 -0
- package/dist/core/checks/security/dockerfile-security.d.ts +2 -0
- package/dist/core/checks/security/dockerfile-security.js +92 -0
- package/dist/core/checks/security/migration-safety.d.ts +2 -0
- package/dist/core/checks/security/migration-safety.js +110 -0
- package/dist/core/checks/{security.d.ts → security/security.d.ts} +1 -1
- package/dist/core/checks/{security.js → security/security.js} +4 -4
- package/dist/core/checks/{sensitive-file-guard.d.ts → security/sensitive-file-guard.d.ts} +1 -1
- package/dist/core/checks/{sensitive-file-guard.js → security/sensitive-file-guard.js} +4 -4
- package/dist/core/checks/security/workflow-security.d.ts +2 -0
- package/dist/core/checks/security/workflow-security.js +75 -0
- package/dist/core/context.js +54 -10
- package/dist/core/registry.js +73 -28
- package/dist/core/reporters.d.ts +4 -3
- package/dist/core/reporters.js +104 -4
- package/dist/core/run-checks.js +21 -3
- package/dist/core/targets.d.ts +17 -1
- package/dist/core/targets.js +74 -4
- package/dist/core/types.d.ts +13 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -1
- package/dist/init.js +86 -8
- package/dist/interfaces/index.d.ts +101 -1
- package/dist/scripts/copy-configs.js +6 -1
- package/dist/utils/detect-language.d.ts +1 -1
- package/dist/utils/detect-language.js +32 -3
- package/dist/utils/glob.d.ts +10 -0
- package/dist/utils/glob.js +25 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/eslint-default-rules.json +62 -0
- package/eslint.config.mjs +6 -66
- package/package.json +3 -1
- package/pr-checkmate.schema.json +166 -0
- package/src/config/cspell.json +9 -1
- package/src/config/eslint-default-rules.json +62 -0
- package/src/config/pr-checkmate.schema.json +166 -0
- package/dist/core/checks/diff-security.js +0 -145
- package/dist/utils/delta.d.ts +0 -5
- package/dist/utils/delta.js +0 -53
- package/src/config/eslint.config.mjs +0 -180
- /package/dist/core/checks/{ruff-shared.d.ts → languages/ruff-shared.d.ts} +0 -0
- /package/dist/core/checks/{ruff-shared.js → languages/ruff-shared.js} +0 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.migrationSafetyCheck = void 0;
|
|
4
|
+
const utils_1 = require("../../../utils");
|
|
5
|
+
const execa_1 = require("../../../utils/execa");
|
|
6
|
+
const targets_1 = require("../../targets");
|
|
7
|
+
const types_1 = require("../../types");
|
|
8
|
+
const findings_1 = require("../findings");
|
|
9
|
+
/**
|
|
10
|
+
* Paths treated as database migrations. A client overrides via
|
|
11
|
+
* `migrationSafety.paths` (REPLACES this list).
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_MIGRATION_GLOBS = [
|
|
14
|
+
'**/migrations/**',
|
|
15
|
+
'**/migrate/**',
|
|
16
|
+
'**/db/migrate/**', // Rails
|
|
17
|
+
'**/versions/**', // Alembic
|
|
18
|
+
'**/*.sql',
|
|
19
|
+
];
|
|
20
|
+
/** Destructive / locking operations, matched on added diff lines. */
|
|
21
|
+
const DDL_PATTERNS = [
|
|
22
|
+
{ pattern: /^\+(?!\+).*\bDROP\s+TABLE\b/i, label: 'DROP TABLE — irreversible data loss' },
|
|
23
|
+
{ pattern: /^\+(?!\+).*\bDROP\s+COLUMN\b/i, label: 'DROP COLUMN — irreversible data loss' },
|
|
24
|
+
{ pattern: /^\+(?!\+).*\bTRUNCATE\b/i, label: 'TRUNCATE — irreversible data loss' },
|
|
25
|
+
{
|
|
26
|
+
pattern: /^\+(?!\+).*\bRENAME\s+(?:COLUMN|TABLE)\b/i,
|
|
27
|
+
label: 'RENAME — breaks code still reading the old name',
|
|
28
|
+
},
|
|
29
|
+
{ pattern: /^\+(?!\+).*\bDROP\s+(?:CONSTRAINT|INDEX)\b/i, label: 'DROP CONSTRAINT/INDEX' },
|
|
30
|
+
{
|
|
31
|
+
pattern: /^\+(?!\+).*\bALTER\s+COLUMN\b.*\bSET\s+NOT\s+NULL\b/i,
|
|
32
|
+
label: 'SET NOT NULL — locks the table, rejects existing NULL rows',
|
|
33
|
+
},
|
|
34
|
+
// Alembic (Python) operations.
|
|
35
|
+
{ pattern: /^\+(?!\+).*\bop\.drop_column\b/, label: 'op.drop_column — irreversible data loss' },
|
|
36
|
+
{ pattern: /^\+(?!\+).*\bop\.drop_table\b/, label: 'op.drop_table — irreversible data loss' },
|
|
37
|
+
{ pattern: /^\+(?!\+).*\bop\.drop_constraint\b/, label: 'op.drop_constraint' },
|
|
38
|
+
];
|
|
39
|
+
const NOT_NULL_NO_DEFAULT = 'ADD COLUMN … NOT NULL without DEFAULT — fails/locks on a non-empty table';
|
|
40
|
+
/**
|
|
41
|
+
* A new NOT NULL column with no DEFAULT: the classic migration that breaks on a
|
|
42
|
+
* populated table. Split out because it's a conjunction (ADD COLUMN + NOT NULL,
|
|
43
|
+
* and NO DEFAULT on the line), not a single keyword.
|
|
44
|
+
*/
|
|
45
|
+
function isAddNotNullNoDefault(line) {
|
|
46
|
+
return (/^\+(?!\+)/.test(line) &&
|
|
47
|
+
/\bADD\s+COLUMN\b/i.test(line) &&
|
|
48
|
+
/\bNOT\s+NULL\b/i.test(line) &&
|
|
49
|
+
!/\bDEFAULT\b/i.test(line));
|
|
50
|
+
}
|
|
51
|
+
/** Bucket dangerous added lines by risk label, honouring the opt-out directive. */
|
|
52
|
+
function scan(stdout, findings) {
|
|
53
|
+
for (const line of stdout.split('\n')) {
|
|
54
|
+
if (line.includes('pr-checkmate-ignore'))
|
|
55
|
+
continue;
|
|
56
|
+
for (const { pattern, label } of DDL_PATTERNS) {
|
|
57
|
+
if (pattern.test(line))
|
|
58
|
+
(findings[label] ?? (findings[label] = [])).push(line.slice(1).trim().substring(0, 120));
|
|
59
|
+
}
|
|
60
|
+
if (isAddNotNullNoDefault(line)) {
|
|
61
|
+
(findings[NOT_NULL_NO_DEFAULT] ?? (findings[NOT_NULL_NO_DEFAULT] = [])).push(line.slice(1).trim().substring(0, 120));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.migrationSafetyCheck = {
|
|
66
|
+
name: 'Migration Safety',
|
|
67
|
+
phase: 'informational',
|
|
68
|
+
applies(ctx) {
|
|
69
|
+
return ctx.config.migrationSafety?.enabled !== false && Boolean(ctx.baseSha);
|
|
70
|
+
},
|
|
71
|
+
async run(ctx) {
|
|
72
|
+
// Not sourcePath-scoped: migrations commonly live outside `src` (db/, alembic/).
|
|
73
|
+
// ignoreDirs still applies via resolveTargetFiles.
|
|
74
|
+
const files = await (0, targets_1.resolveTargetFiles)(ctx, ['*']);
|
|
75
|
+
if (files === null)
|
|
76
|
+
return (0, types_1.skip)('git unavailable');
|
|
77
|
+
const globs = ctx.config.migrationSafety?.paths ?? DEFAULT_MIGRATION_GLOBS;
|
|
78
|
+
const migrationFiles = files.filter(f => (0, utils_1.matchesAnyGlob)(f, globs));
|
|
79
|
+
if (migrationFiles.length === 0)
|
|
80
|
+
return (0, types_1.pass)();
|
|
81
|
+
try {
|
|
82
|
+
const { stdout } = await (0, execa_1.execa)('git', [
|
|
83
|
+
'diff',
|
|
84
|
+
'--unified=0',
|
|
85
|
+
ctx.baseSha,
|
|
86
|
+
ctx.headSha ?? 'HEAD',
|
|
87
|
+
'--',
|
|
88
|
+
...migrationFiles,
|
|
89
|
+
], { cwd: ctx.cwd });
|
|
90
|
+
const findings = {};
|
|
91
|
+
scan(stdout, findings);
|
|
92
|
+
if ((0, findings_1.bucketTotal)(findings) === 0)
|
|
93
|
+
return (0, types_1.pass)();
|
|
94
|
+
const sev = (0, findings_1.severityEmitter)(ctx.config.migrationSafety?.severity, 'warn');
|
|
95
|
+
(0, findings_1.logFindings)(findings, {
|
|
96
|
+
prefix: 'Migration Safety',
|
|
97
|
+
sampleLimit: 2,
|
|
98
|
+
isError: sev.icon === '❌',
|
|
99
|
+
});
|
|
100
|
+
const summary = Object.entries(findings)
|
|
101
|
+
.map(([label, arr]) => `${arr.length}× ${label.split(' — ')[0]}`)
|
|
102
|
+
.join(', ');
|
|
103
|
+
return sev.emit(summary);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
utils_1.logger.warn(`[Migration Safety]: ⚠️ Could not analyze diff\n${err}`);
|
|
107
|
+
return (0, types_1.skip)('diff unavailable');
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.securityCheck = void 0;
|
|
4
|
-
const config_1 = require("
|
|
5
|
-
const utils_1 = require("
|
|
6
|
-
const execa_1 = require("
|
|
7
|
-
const types_1 = require("
|
|
4
|
+
const config_1 = require("../../../config");
|
|
5
|
+
const utils_1 = require("../../../utils");
|
|
6
|
+
const execa_1 = require("../../../utils/execa");
|
|
7
|
+
const types_1 = require("../../types");
|
|
8
8
|
/**
|
|
9
9
|
* Secret scanning via the bundled gitleaks package. Language-agnostic — runs on
|
|
10
10
|
* every repo (Node, Python, C++, mobile). Secrets found → fail (blocking).
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { Check } from '
|
|
1
|
+
import { Check } from '../../types';
|
|
2
2
|
export declare const sensitiveFileGuardCheck: Check;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.sensitiveFileGuardCheck = void 0;
|
|
4
|
-
const utils_1 = require("
|
|
5
|
-
const execa_1 = require("
|
|
6
|
-
const types_1 = require("
|
|
7
|
-
const findings_1 = require("
|
|
4
|
+
const utils_1 = require("../../../utils");
|
|
5
|
+
const execa_1 = require("../../../utils/execa");
|
|
6
|
+
const types_1 = require("../../types");
|
|
7
|
+
const findings_1 = require("../findings");
|
|
8
8
|
const SENSITIVE_CATEGORIES = [
|
|
9
9
|
{
|
|
10
10
|
label: 'Environment files (.env)',
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.workflowSecurityCheck = void 0;
|
|
4
|
+
const utils_1 = require("../../../utils");
|
|
5
|
+
const types_1 = require("../../types");
|
|
6
|
+
const findings_1 = require("../findings");
|
|
7
|
+
/**
|
|
8
|
+
* GitHub Actions workflow risks, matched on added diff lines of workflow files.
|
|
9
|
+
* These are the high-frequency supply-chain / injection footguns.
|
|
10
|
+
*/
|
|
11
|
+
const WORKFLOW_PATTERNS = [
|
|
12
|
+
{
|
|
13
|
+
// Untrusted PR-controlled input interpolated into an expression — the classic
|
|
14
|
+
// `run:` script-injection vector when it reaches a shell.
|
|
15
|
+
pattern: /^\+(?!\+).*\$\{\{\s*github\.event\.(?:issue|pull_request|comment|review|discussion|head_commit|commits)[^}]*\.(?:title|body|head_ref|ref|label|name|email|message|url)/,
|
|
16
|
+
label: 'untrusted github.event input in expression (script injection risk)',
|
|
17
|
+
severity: 'error',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
pattern: /^\+(?!\+).*\$\{\{\s*github\.head_ref\s*\}\}/,
|
|
21
|
+
label: 'github.head_ref interpolation (injection risk — use an env var)',
|
|
22
|
+
severity: 'warn',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
// Third-party action not pinned to a full 40-char commit SHA.
|
|
26
|
+
pattern: /^\+(?!\+).*\buses:\s*['"]?[\w.-]+\/[\w.-]+@(?![0-9a-fA-F]{40}\b)[\w./-]+/,
|
|
27
|
+
label: 'action not pinned to a full commit SHA (supply-chain risk)',
|
|
28
|
+
severity: 'warn',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
pattern: /^\+(?!\+).*\bpull_request_target\b/,
|
|
32
|
+
label: 'pull_request_target trigger (runs with repo secrets on untrusted PRs)',
|
|
33
|
+
severity: 'warn',
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
function scanDiff(stdout) {
|
|
37
|
+
const errors = {};
|
|
38
|
+
const warnings = {};
|
|
39
|
+
for (const line of stdout.split('\n')) {
|
|
40
|
+
for (const { pattern, label, severity } of WORKFLOW_PATTERNS) {
|
|
41
|
+
if (pattern.test(line)) {
|
|
42
|
+
const bucket = severity === 'error' ? errors : warnings;
|
|
43
|
+
(bucket[label] ?? (bucket[label] = [])).push(line.slice(1).trim().substring(0, 120));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { errors, warnings };
|
|
48
|
+
}
|
|
49
|
+
exports.workflowSecurityCheck = {
|
|
50
|
+
name: 'Workflow Security',
|
|
51
|
+
phase: 'informational',
|
|
52
|
+
applies(ctx) {
|
|
53
|
+
return ctx.config.workflowSecurity?.enabled !== false && Boolean(ctx.baseSha);
|
|
54
|
+
},
|
|
55
|
+
async run(ctx) {
|
|
56
|
+
try {
|
|
57
|
+
const stdout = await (0, findings_1.diffAddedLines)(ctx, [
|
|
58
|
+
'.github/workflows/*.yml',
|
|
59
|
+
'.github/workflows/*.yaml',
|
|
60
|
+
]);
|
|
61
|
+
const { errors, warnings } = scanDiff(stdout);
|
|
62
|
+
return (0, findings_1.reportSeverityBuckets)(errors, warnings, {
|
|
63
|
+
prefix: 'Workflow Security',
|
|
64
|
+
errorSummary: n => `${n} issue(s)`,
|
|
65
|
+
warningSummary: n => `${n} warning(s)`,
|
|
66
|
+
sampleLimit: 3,
|
|
67
|
+
ignore: ctx.config.workflowSecurity?.ignore,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
utils_1.logger.warn(`[Workflow Security]: ⚠️ Could not analyze diff\n${err}`);
|
|
72
|
+
return (0, types_1.skip)('diff unavailable');
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
};
|
package/dist/core/context.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.buildContext = buildContext;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
4
8
|
const loader_1 = require("../config/loader");
|
|
5
9
|
const utils_1 = require("../utils");
|
|
6
10
|
const detect_language_1 = require("../utils/detect-language");
|
|
@@ -10,7 +14,35 @@ const execa_1 = require("../utils/execa");
|
|
|
10
14
|
* Prefers explicit opts, then GitHub PR event, then Bitbucket PR env, then git HEAD^.
|
|
11
15
|
* Returns undefined SHAs when nothing usable is found → checks fall back to full scan.
|
|
12
16
|
*/
|
|
17
|
+
/**
|
|
18
|
+
* Resolve a git ref to a concrete commit SHA, or undefined when it can't be
|
|
19
|
+
* resolved in this clone. `--verify --quiet` emits nothing (instead of echoing
|
|
20
|
+
* the literal ref) and exits non-zero for a missing ref — e.g. `HEAD^` on a
|
|
21
|
+
* single-commit repo, or a base branch not fetched in a shallow CI clone. That
|
|
22
|
+
* way an unresolvable base yields `undefined` and callers fall back to a full
|
|
23
|
+
* scan, rather than diffing against a garbage ref and silently checking nothing.
|
|
24
|
+
*/
|
|
25
|
+
async function resolveRef(cwd, ref) {
|
|
26
|
+
const { stdout } = await (0, execa_1.execa)('git', ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], {
|
|
27
|
+
cwd,
|
|
28
|
+
reject: false,
|
|
29
|
+
});
|
|
30
|
+
return stdout.trim() || undefined;
|
|
31
|
+
}
|
|
13
32
|
async function resolveDiffRange(cwd, opts) {
|
|
33
|
+
// Pre-commit mode: diff HEAD against the current index. `git write-tree`
|
|
34
|
+
// materialises the staged index as a real tree object, so every check that
|
|
35
|
+
// diffs baseSha..headSha sees exactly the staged changes — no per-check code.
|
|
36
|
+
if (opts.staged) {
|
|
37
|
+
try {
|
|
38
|
+
const head = (await (0, execa_1.execa)('git', ['rev-parse', 'HEAD'], { cwd })).stdout.trim();
|
|
39
|
+
const tree = (await (0, execa_1.execa)('git', ['write-tree'], { cwd })).stdout.trim();
|
|
40
|
+
return { baseSha: head, headSha: tree };
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
14
46
|
if (opts.baseSha || opts.headSha) {
|
|
15
47
|
return { baseSha: opts.baseSha, headSha: opts.headSha };
|
|
16
48
|
}
|
|
@@ -22,17 +54,28 @@ async function resolveDiffRange(cwd, opts) {
|
|
|
22
54
|
catch {
|
|
23
55
|
return {};
|
|
24
56
|
}
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
57
|
+
// GitHub Actions: the PR event payload carries the base branch SHA. Read it
|
|
58
|
+
// directly so delta works without relying on any client-set variable
|
|
59
|
+
// (GITHUB_EVENT_* are injected by the runner).
|
|
60
|
+
if (process.env.GITHUB_EVENT_NAME === 'pull_request' && process.env.GITHUB_EVENT_PATH) {
|
|
61
|
+
try {
|
|
62
|
+
const event = JSON.parse(node_fs_1.default.readFileSync(process.env.GITHUB_EVENT_PATH, 'utf-8'));
|
|
63
|
+
const eventBase = event?.pull_request?.base?.sha;
|
|
64
|
+
// Only use it if the commit is actually present in this clone; otherwise
|
|
65
|
+
// fall through so a shallow clone degrades to a full scan, not 0 files.
|
|
66
|
+
const baseSha = eventBase ? await resolveRef(cwd, eventBase) : undefined;
|
|
67
|
+
if (baseSha)
|
|
68
|
+
return { baseSha, headSha };
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Malformed/unreadable event — fall through to the git-based resolution.
|
|
72
|
+
}
|
|
35
73
|
}
|
|
74
|
+
// Bitbucket exposes the PR destination branch as an env var (also runner-set).
|
|
75
|
+
const bitbucketTarget = process.env.BITBUCKET_PR_DESTINATION_BRANCH;
|
|
76
|
+
const ref = bitbucketTarget ? `origin/${bitbucketTarget}` : 'HEAD^';
|
|
77
|
+
const baseSha = await resolveRef(cwd, ref);
|
|
78
|
+
return { baseSha, headSha };
|
|
36
79
|
}
|
|
37
80
|
/**
|
|
38
81
|
* Assemble the shared CheckContext once per run. Language detection is skipped
|
|
@@ -51,5 +94,6 @@ async function buildContext(opts = {}) {
|
|
|
51
94
|
write: opts.write ?? false,
|
|
52
95
|
baseSha,
|
|
53
96
|
headSha,
|
|
97
|
+
cache: new Map(),
|
|
54
98
|
};
|
|
55
99
|
}
|
package/dist/core/registry.js
CHANGED
|
@@ -1,40 +1,62 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.REGISTRY = void 0;
|
|
4
|
-
|
|
5
|
-
const circular_deps_1 = require("./checks/circular-deps");
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
const
|
|
4
|
+
const banned_imports_1 = require("./checks/dependencies/banned-imports");
|
|
5
|
+
const circular_deps_1 = require("./checks/dependencies/circular-deps");
|
|
6
|
+
const dependency_check_1 = require("./checks/dependencies/dependency-check");
|
|
7
|
+
const license_check_1 = require("./checks/dependencies/license-check");
|
|
8
|
+
const npm_audit_1 = require("./checks/dependencies/npm-audit");
|
|
9
|
+
const outdated_deps_1 = require("./checks/dependencies/outdated-deps");
|
|
10
|
+
const vuln_scan_1 = require("./checks/dependencies/vuln-scan");
|
|
11
|
+
const custom_rules_1 = require("./checks/git/custom-rules");
|
|
12
|
+
const diff_smell_1 = require("./checks/git/diff-smell");
|
|
13
|
+
const large_files_1 = require("./checks/git/large-files");
|
|
14
|
+
const leftover_debug_1 = require("./checks/git/leftover-debug");
|
|
15
|
+
const lockfile_drift_1 = require("./checks/git/lockfile-drift");
|
|
16
|
+
const merge_conflict_1 = require("./checks/git/merge-conflict");
|
|
17
|
+
const missing_tests_1 = require("./checks/git/missing-tests");
|
|
18
|
+
const todo_fixme_1 = require("./checks/git/todo-fixme");
|
|
19
|
+
const cpp_format_1 = require("./checks/languages/cpp-format");
|
|
20
|
+
const csharp_format_1 = require("./checks/languages/csharp-format");
|
|
21
|
+
const go_format_1 = require("./checks/languages/go-format");
|
|
22
|
+
const go_lint_1 = require("./checks/languages/go-lint");
|
|
23
|
+
const kotlin_lint_1 = require("./checks/languages/kotlin-lint");
|
|
24
|
+
const lint_1 = require("./checks/languages/lint");
|
|
25
|
+
const php_format_1 = require("./checks/languages/php-format");
|
|
26
|
+
const prettier_1 = require("./checks/languages/prettier");
|
|
27
|
+
const python_format_1 = require("./checks/languages/python-format");
|
|
28
|
+
const python_lint_1 = require("./checks/languages/python-lint");
|
|
29
|
+
const python_typecheck_1 = require("./checks/languages/python-typecheck");
|
|
30
|
+
const ruby_lint_1 = require("./checks/languages/ruby-lint");
|
|
31
|
+
const rust_format_1 = require("./checks/languages/rust-format");
|
|
32
|
+
const rust_lint_1 = require("./checks/languages/rust-lint");
|
|
33
|
+
const swift_lint_1 = require("./checks/languages/swift-lint");
|
|
34
|
+
const typecheck_1 = require("./checks/languages/typecheck");
|
|
35
|
+
const commitlint_1 = require("./checks/pr/commitlint");
|
|
36
|
+
const pr_body_1 = require("./checks/pr/pr-body");
|
|
37
|
+
const pr_size_check_1 = require("./checks/pr/pr-size-check");
|
|
38
|
+
const pr_title_lint_1 = require("./checks/pr/pr-title-lint");
|
|
39
|
+
const case_collision_1 = require("./checks/quality/case-collision");
|
|
40
|
+
const config_validation_1 = require("./checks/quality/config-validation");
|
|
41
|
+
const coverage_1 = require("./checks/quality/coverage");
|
|
42
|
+
const dead_code_1 = require("./checks/quality/dead-code");
|
|
43
|
+
const duplicate_check_1 = require("./checks/quality/duplicate-check");
|
|
44
|
+
const license_header_1 = require("./checks/quality/license-header");
|
|
45
|
+
const markdown_lint_1 = require("./checks/quality/markdown-lint");
|
|
46
|
+
const spellcheck_1 = require("./checks/quality/spellcheck");
|
|
47
|
+
const diff_security_1 = require("./checks/security/diff-security");
|
|
48
|
+
const dockerfile_security_1 = require("./checks/security/dockerfile-security");
|
|
49
|
+
const migration_safety_1 = require("./checks/security/migration-safety");
|
|
50
|
+
const security_1 = require("./checks/security/security");
|
|
51
|
+
const sensitive_file_guard_1 = require("./checks/security/sensitive-file-guard");
|
|
52
|
+
const workflow_security_1 = require("./checks/security/workflow-security");
|
|
32
53
|
/**
|
|
33
54
|
* Every check, in declaration order. The orchestrator groups them by `phase`
|
|
34
55
|
* (blocking → informational → format) and runs each phase's checks in parallel.
|
|
35
56
|
*/
|
|
36
57
|
exports.REGISTRY = [
|
|
37
58
|
// blocking
|
|
59
|
+
merge_conflict_1.mergeConflictCheck,
|
|
38
60
|
security_1.securityCheck,
|
|
39
61
|
typecheck_1.typecheckCheck,
|
|
40
62
|
lint_1.lintCheck,
|
|
@@ -45,6 +67,21 @@ exports.REGISTRY = [
|
|
|
45
67
|
// informational
|
|
46
68
|
diff_smell_1.diffSmellCheck,
|
|
47
69
|
diff_security_1.diffSecurityCheck,
|
|
70
|
+
workflow_security_1.workflowSecurityCheck,
|
|
71
|
+
dockerfile_security_1.dockerfileSecurityCheck,
|
|
72
|
+
case_collision_1.caseCollisionCheck,
|
|
73
|
+
vuln_scan_1.vulnScanCheck,
|
|
74
|
+
custom_rules_1.customRulesCheck,
|
|
75
|
+
banned_imports_1.bannedImportsCheck,
|
|
76
|
+
license_header_1.licenseHeaderCheck,
|
|
77
|
+
config_validation_1.configValidationCheck,
|
|
78
|
+
leftover_debug_1.leftoverDebugCheck,
|
|
79
|
+
lockfile_drift_1.lockfileDriftCheck,
|
|
80
|
+
large_files_1.largeFilesCheck,
|
|
81
|
+
todo_fixme_1.todoFixmeCheck,
|
|
82
|
+
missing_tests_1.missingTestsCheck,
|
|
83
|
+
coverage_1.coverageCheck,
|
|
84
|
+
migration_safety_1.migrationSafetyCheck,
|
|
48
85
|
sensitive_file_guard_1.sensitiveFileGuardCheck,
|
|
49
86
|
pr_body_1.prBodyCheck,
|
|
50
87
|
duplicate_check_1.duplicateCheck,
|
|
@@ -55,11 +92,19 @@ exports.REGISTRY = [
|
|
|
55
92
|
markdown_lint_1.markdownLintCheck,
|
|
56
93
|
spellcheck_1.spellcheckCheck,
|
|
57
94
|
python_lint_1.pythonLintCheck,
|
|
95
|
+
python_typecheck_1.pythonTypecheckCheck,
|
|
58
96
|
dead_code_1.deadCodeCheck,
|
|
59
97
|
swift_lint_1.swiftLintCheck,
|
|
60
98
|
kotlin_lint_1.kotlinLintCheck,
|
|
99
|
+
go_lint_1.goLintCheck,
|
|
100
|
+
rust_lint_1.rustLintCheck,
|
|
101
|
+
ruby_lint_1.rubyLintCheck,
|
|
61
102
|
// format
|
|
62
103
|
prettier_1.prettierCheck,
|
|
63
104
|
cpp_format_1.cppFormatCheck,
|
|
64
105
|
python_format_1.pythonFormatCheck,
|
|
106
|
+
go_format_1.goFormatCheck,
|
|
107
|
+
rust_format_1.rustFormatCheck,
|
|
108
|
+
csharp_format_1.csharpFormatCheck,
|
|
109
|
+
php_format_1.phpFormatCheck,
|
|
65
110
|
];
|
package/dist/core/reporters.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { CheckReport } from './types';
|
|
1
|
+
import type { CheckReport, ReporterKind } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Emit the report through the selected channel.
|
|
4
|
-
* none: nothing · stdout: console table · github: table +
|
|
4
|
+
* none: nothing · stdout: console table · github/gitlab/bitbucket: table +
|
|
5
|
+
* upsert the PR/MR comment · auto: detect the platform from the environment.
|
|
5
6
|
*/
|
|
6
|
-
export declare function report(kind:
|
|
7
|
+
export declare function report(kind: ReporterKind, result: CheckReport): Promise<void>;
|
package/dist/core/reporters.js
CHANGED
|
@@ -110,15 +110,115 @@ async function upsertGithubComment(body) {
|
|
|
110
110
|
utils_1.logger.warn(`[reporter]: ⚠️ Could not post GitHub comment: ${err}`);
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Upsert a GitLab MR note (find our marker → update, else create). Uses the
|
|
115
|
+
* standard CI variables plus a `GITLAB_TOKEN` with `api` scope. Skips gracefully
|
|
116
|
+
* when not in an MR pipeline or the token is missing.
|
|
117
|
+
*/
|
|
118
|
+
async function upsertGitlabNote(body) {
|
|
119
|
+
const base = process.env.CI_API_V4_URL;
|
|
120
|
+
const project = process.env.CI_PROJECT_ID;
|
|
121
|
+
const iid = process.env.CI_MERGE_REQUEST_IID;
|
|
122
|
+
const token = process.env.GITLAB_TOKEN;
|
|
123
|
+
if (!base || !project || !iid || !token) {
|
|
124
|
+
utils_1.logger.info('[reporter]: Skipping GitLab note (not an MR pipeline or GITLAB_TOKEN missing)');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const notes = `${base}/projects/${project}/merge_requests/${iid}/notes`;
|
|
128
|
+
const headers = { 'PRIVATE-TOKEN': token, 'Content-Type': 'application/json' };
|
|
129
|
+
try {
|
|
130
|
+
const listRes = await fetch(notes, { headers });
|
|
131
|
+
if (listRes.ok) {
|
|
132
|
+
const existing = (await listRes.json()).find(n => typeof n.body === 'string' && n.body.includes(COMMENT_MARKER));
|
|
133
|
+
if (existing) {
|
|
134
|
+
await fetch(`${notes}/${existing.id}`, {
|
|
135
|
+
method: 'PUT',
|
|
136
|
+
headers,
|
|
137
|
+
body: JSON.stringify({ body }),
|
|
138
|
+
});
|
|
139
|
+
utils_1.logger.info('[reporter]: ✅ Updated PR CheckMate note on GitLab MR');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
await fetch(notes, { method: 'POST', headers, body: JSON.stringify({ body }) });
|
|
144
|
+
utils_1.logger.info('[reporter]: ✅ Posted PR CheckMate summary to GitLab MR');
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
utils_1.logger.warn(`[reporter]: ⚠️ Could not post GitLab note: ${err}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Upsert a Bitbucket PR comment (find our marker → update, else create). Uses
|
|
152
|
+
* Bitbucket Pipelines variables plus a `BITBUCKET_TOKEN` (repo/PR write). Skips
|
|
153
|
+
* gracefully when not in a PR pipeline or the token is missing.
|
|
154
|
+
*/
|
|
155
|
+
async function upsertBitbucketComment(body) {
|
|
156
|
+
const fullName = process.env.BITBUCKET_REPO_FULL_NAME;
|
|
157
|
+
const prId = process.env.BITBUCKET_PR_ID;
|
|
158
|
+
const token = process.env.BITBUCKET_TOKEN;
|
|
159
|
+
if (!fullName || !prId || !token) {
|
|
160
|
+
utils_1.logger.info('[reporter]: Skipping Bitbucket comment (not a PR pipeline or BITBUCKET_TOKEN missing)');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const commentsUrl = `https://api.bitbucket.org/2.0/repositories/${fullName}/pullrequests/${prId}/comments`;
|
|
164
|
+
const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };
|
|
165
|
+
try {
|
|
166
|
+
const listRes = await fetch(`${commentsUrl}?pagelen=100`, { headers });
|
|
167
|
+
if (listRes.ok) {
|
|
168
|
+
const values = (await listRes.json()).values;
|
|
169
|
+
const existing = values?.find(c => c.content?.raw?.includes(COMMENT_MARKER));
|
|
170
|
+
if (existing) {
|
|
171
|
+
await fetch(`${commentsUrl}/${existing.id}`, {
|
|
172
|
+
method: 'PUT',
|
|
173
|
+
headers,
|
|
174
|
+
body: JSON.stringify({ content: { raw: body } }),
|
|
175
|
+
});
|
|
176
|
+
utils_1.logger.info('[reporter]: ✅ Updated PR CheckMate comment on Bitbucket PR');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
await fetch(commentsUrl, {
|
|
181
|
+
method: 'POST',
|
|
182
|
+
headers,
|
|
183
|
+
body: JSON.stringify({ content: { raw: body } }),
|
|
184
|
+
});
|
|
185
|
+
utils_1.logger.info('[reporter]: ✅ Posted PR CheckMate summary to Bitbucket PR');
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
utils_1.logger.warn(`[reporter]: ⚠️ Could not post Bitbucket comment: ${err}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/** Detect the CI platform from the environment, or null when unknown/local. */
|
|
192
|
+
function detectPlatform() {
|
|
193
|
+
if (process.env.GITHUB_ACTIONS || process.env.GITHUB_EVENT_NAME)
|
|
194
|
+
return 'github';
|
|
195
|
+
if (process.env.GITLAB_CI)
|
|
196
|
+
return 'gitlab';
|
|
197
|
+
if (process.env.BITBUCKET_PIPELINE_UUID || process.env.BITBUCKET_PR_ID)
|
|
198
|
+
return 'bitbucket';
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
/** Post the markdown report to a specific platform's PR/MR. */
|
|
202
|
+
async function postToPlatform(platform, result) {
|
|
203
|
+
const body = buildMarkdownReport(result);
|
|
204
|
+
if (platform === 'github')
|
|
205
|
+
return upsertGithubComment(body);
|
|
206
|
+
if (platform === 'gitlab')
|
|
207
|
+
return upsertGitlabNote(body);
|
|
208
|
+
return upsertBitbucketComment(body);
|
|
209
|
+
}
|
|
113
210
|
/**
|
|
114
211
|
* Emit the report through the selected channel.
|
|
115
|
-
* none: nothing · stdout: console table · github: table +
|
|
212
|
+
* none: nothing · stdout: console table · github/gitlab/bitbucket: table +
|
|
213
|
+
* upsert the PR/MR comment · auto: detect the platform from the environment.
|
|
116
214
|
*/
|
|
117
215
|
async function report(kind, result) {
|
|
118
216
|
if (kind === 'none')
|
|
119
217
|
return;
|
|
120
218
|
printStdout(result);
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
|
|
219
|
+
if (result.results.length === 0)
|
|
220
|
+
return;
|
|
221
|
+
const platform = kind === 'auto' ? detectPlatform() : kind === 'stdout' ? null : kind;
|
|
222
|
+
if (platform)
|
|
223
|
+
await postToPlatform(platform, result);
|
|
124
224
|
}
|
package/dist/core/run-checks.js
CHANGED
|
@@ -50,6 +50,20 @@ function selectChecks(checks, opts) {
|
|
|
50
50
|
function errMessage(err) {
|
|
51
51
|
return err instanceof Error ? err.message.split('\n')[0].slice(0, 160) : String(err);
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Apply the universal `config.severity` override for a check. Only remaps
|
|
55
|
+
* between warn↔fail (pass/skip are never changed); `off` is handled earlier by
|
|
56
|
+
* excluding the check from the run entirely. This is what lets a repo turn any
|
|
57
|
+
* advisory check into a gate, or downgrade a blocking one, by name.
|
|
58
|
+
*/
|
|
59
|
+
function applySeverity(name, status, ctx) {
|
|
60
|
+
const sev = ctx.config.severity?.[name];
|
|
61
|
+
if (sev === 'error' && status === 'warn')
|
|
62
|
+
return 'fail';
|
|
63
|
+
if (sev === 'warn' && status === 'fail')
|
|
64
|
+
return 'warn';
|
|
65
|
+
return status;
|
|
66
|
+
}
|
|
53
67
|
/** Run one check: gate on `applies`, execute, translate throws into `fail`. */
|
|
54
68
|
async function runOne(check, ctx, collector) {
|
|
55
69
|
try {
|
|
@@ -63,12 +77,13 @@ async function runOne(check, ctx, collector) {
|
|
|
63
77
|
return;
|
|
64
78
|
}
|
|
65
79
|
const outcome = await check.run(ctx);
|
|
66
|
-
collector.register(check.name, outcome.status, outcome.details);
|
|
80
|
+
collector.register(check.name, applySeverity(check.name, outcome.status, ctx), outcome.details);
|
|
67
81
|
}
|
|
68
82
|
catch (err) {
|
|
69
83
|
// An unexpected throw in a blocking check fails the run; in informational/format
|
|
70
84
|
// phases it only warns — those phases never block, matching the legacy pipeline.
|
|
71
|
-
const
|
|
85
|
+
const base = check.phase === 'blocking' ? 'fail' : 'warn';
|
|
86
|
+
const status = applySeverity(check.name, base, ctx);
|
|
72
87
|
utils_1.logger.error(`[runChecks]: ${status === 'fail' ? '❌' : '⚠️'} ${check.name} threw: ${err}`);
|
|
73
88
|
collector.register(check.name, status, errMessage(err));
|
|
74
89
|
}
|
|
@@ -82,7 +97,10 @@ async function runOne(check, ctx, collector) {
|
|
|
82
97
|
*/
|
|
83
98
|
async function runChecks(opts = {}) {
|
|
84
99
|
const ctx = await (0, context_1.buildContext)(opts);
|
|
85
|
-
|
|
100
|
+
// `severity: { "<name>": "off" }` disables any check by name, universally —
|
|
101
|
+
// a superset of the per-check `enabled: false` flags.
|
|
102
|
+
const off = ctx.config.severity ?? {};
|
|
103
|
+
const selected = selectChecks(resolveChecks(opts), opts).filter(c => off[c.name] !== 'off');
|
|
86
104
|
const collector = new result_collector_1.ResultCollector();
|
|
87
105
|
for (const phase of types_1.PHASES) {
|
|
88
106
|
const inPhase = selected.filter(c => c.phase === phase);
|