pr-checkmate 1.21.15 → 1.22.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/.eslintrc.js +3 -3
- package/README.md +7 -1
- package/cspell.json +151 -1
- package/dist/config/loader.js +4 -0
- package/dist/core/checks/commitlint.js +17 -9
- package/dist/core/checks/diff-security.js +24 -41
- package/dist/core/checks/diff-smell.js +4 -21
- package/dist/core/checks/duplicate-check.js +19 -3
- package/dist/core/checks/findings.js +55 -0
- package/dist/core/checks/license-check.js +7 -1
- package/dist/core/checks/lint.js +8 -2
- package/dist/core/checks/markdown-lint.js +11 -15
- package/dist/core/checks/sensitive-file-guard.js +23 -29
- package/dist/core/registry.js +8 -8
- package/dist/core/reporters.js +6 -4
- package/dist/init.js +112 -93
- package/dist/utils/index.js +1 -0
- package/dist/utils/own-deps.js +113 -0
- package/dist/utils/resolve-bin.js +3 -0
- package/eslint.config.mjs +1 -1
- package/package.json +9 -9
- package/src/config/.eslintrc.js +3 -3
- package/src/config/cspell.json +151 -1
- package/src/config/eslint.config.mjs +1 -1
package/.eslintrc.js
CHANGED
|
@@ -41,7 +41,7 @@ module.exports = {
|
|
|
41
41
|
sourceType: 'module',
|
|
42
42
|
project: './tsconfig.json',
|
|
43
43
|
},
|
|
44
|
-
plugins: ['@typescript-eslint', 'prettier', 'import', 'jsdoc'],
|
|
44
|
+
plugins: ['@typescript-eslint', 'prettier', 'import-x', 'jsdoc'],
|
|
45
45
|
extends: [
|
|
46
46
|
'eslint:recommended',
|
|
47
47
|
'plugin:@typescript-eslint/recommended',
|
|
@@ -83,14 +83,14 @@ module.exports = {
|
|
|
83
83
|
// Complexity analysis (#61)
|
|
84
84
|
complexity: ['warn', 15],
|
|
85
85
|
// Import ordering (#65)
|
|
86
|
-
'import/order': [
|
|
86
|
+
'import-x/order': [
|
|
87
87
|
'warn',
|
|
88
88
|
{
|
|
89
89
|
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
|
|
90
90
|
alphabetize: { order: 'asc', caseInsensitive: true },
|
|
91
91
|
},
|
|
92
92
|
],
|
|
93
|
-
'import/no-duplicates': 'error',
|
|
93
|
+
'import-x/no-duplicates': 'error',
|
|
94
94
|
// Documentation (#62)
|
|
95
95
|
'jsdoc/require-jsdoc': [
|
|
96
96
|
'warn',
|
package/README.md
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|

|
|
5
5
|

|
|
6
6
|
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+

|
|
12
|
+
|
|
7
13
|
> **PR CheckMate** is a universal PR quality checker that runs as a single `npm install` and works across **TypeScript, JavaScript, Python, C++, Swift, and Android/Kotlin** repositories — no per-language toolchain setup required.
|
|
8
14
|
|
|
9
15
|
---
|
|
@@ -280,7 +286,7 @@ In GitHub Actions PR context, language checks automatically switch to **delta mo
|
|
|
280
286
|
|
|
281
287
|
After `npx pr-checkmate all`, a summary table is posted to the PR. On repeated pushes the comment is updated in place — no duplicate comments.
|
|
282
288
|
|
|
283
|
-
```
|
|
289
|
+
```text
|
|
284
290
|
## PR CheckMate Summary
|
|
285
291
|
|
|
286
292
|
11 passed · 3 warnings · 0 failed · 0 skipped
|
package/cspell.json
CHANGED
|
@@ -189,6 +189,18 @@
|
|
|
189
189
|
"sultansadikov",
|
|
190
190
|
"telaviv",
|
|
191
191
|
"travis",
|
|
192
|
+
"kotlin",
|
|
193
|
+
"kucherenko",
|
|
194
|
+
"opencollective",
|
|
195
|
+
"Werror",
|
|
196
|
+
"positiving",
|
|
197
|
+
"pyflakes",
|
|
198
|
+
"pycodestyle",
|
|
199
|
+
"swiftlint",
|
|
200
|
+
"pathspec",
|
|
201
|
+
"Pipfile",
|
|
202
|
+
"conanfile",
|
|
203
|
+
"ktlint",
|
|
192
204
|
"Términos",
|
|
193
205
|
"Teudat",
|
|
194
206
|
"Tiempo",
|
|
@@ -543,7 +555,145 @@
|
|
|
543
555
|
"Makefiles",
|
|
544
556
|
"dpdm",
|
|
545
557
|
"commitlintrc",
|
|
546
|
-
"shortstat"
|
|
558
|
+
"shortstat",
|
|
559
|
+
"Cooldown",
|
|
560
|
+
"SCRIPTERROR",
|
|
561
|
+
"attendace",
|
|
562
|
+
"attendance",
|
|
563
|
+
"kupat",
|
|
564
|
+
"gemel",
|
|
565
|
+
"hishtalmut",
|
|
566
|
+
"keren",
|
|
567
|
+
"tofes",
|
|
568
|
+
"Bdikat",
|
|
569
|
+
"Hishuv",
|
|
570
|
+
"formcontrolname",
|
|
571
|
+
"zrufot",
|
|
572
|
+
"Klali",
|
|
573
|
+
"contenteditable",
|
|
574
|
+
"האניקומב",
|
|
575
|
+
"ishur",
|
|
576
|
+
"Yeshut",
|
|
577
|
+
"Nihul",
|
|
578
|
+
"pratim",
|
|
579
|
+
"nispahim",
|
|
580
|
+
"networkidle",
|
|
581
|
+
"domcontentloaded",
|
|
582
|
+
"secapp",
|
|
583
|
+
"taxinfo",
|
|
584
|
+
"gmishurim",
|
|
585
|
+
"ezor",
|
|
586
|
+
"ishi",
|
|
587
|
+
"ishurim",
|
|
588
|
+
"nikui",
|
|
589
|
+
"mekabel",
|
|
590
|
+
"shdochshana",
|
|
591
|
+
"gershayim",
|
|
592
|
+
"Rashum",
|
|
593
|
+
"Shem",
|
|
594
|
+
"Mishpaha",
|
|
595
|
+
"Rshum",
|
|
596
|
+
"Prati",
|
|
597
|
+
"Ktovet",
|
|
598
|
+
"Doar",
|
|
599
|
+
"Megurim",
|
|
600
|
+
"ddlkidomet",
|
|
601
|
+
"Aher",
|
|
602
|
+
"Esek",
|
|
603
|
+
"Isuk",
|
|
604
|
+
"Mazav",
|
|
605
|
+
"Mish",
|
|
606
|
+
"Beyachad",
|
|
607
|
+
"Shlichat",
|
|
608
|
+
"Neezarti",
|
|
609
|
+
"Doch",
|
|
610
|
+
"Klalim",
|
|
611
|
+
"Hehzer",
|
|
612
|
+
"Ozer",
|
|
613
|
+
"נייע",
|
|
614
|
+
"recognised",
|
|
615
|
+
"קולומייץ",
|
|
616
|
+
"אירינה",
|
|
617
|
+
"סרגיי",
|
|
618
|
+
"פולישוק",
|
|
619
|
+
"ולדימיר",
|
|
620
|
+
"moov",
|
|
621
|
+
"virtualenv",
|
|
622
|
+
"pytesseract",
|
|
623
|
+
"qrdet",
|
|
624
|
+
"Jsons",
|
|
625
|
+
"easyocr",
|
|
626
|
+
"EXIF",
|
|
627
|
+
"fliplr",
|
|
628
|
+
"noautorotate",
|
|
629
|
+
"timestep",
|
|
630
|
+
"VAQH",
|
|
631
|
+
"DSBL",
|
|
632
|
+
"MAXU",
|
|
633
|
+
"XCUI",
|
|
634
|
+
"Apks",
|
|
635
|
+
"bootstatus",
|
|
636
|
+
"BEGINSWITH",
|
|
637
|
+
"anypassword",
|
|
638
|
+
"wrongpassword",
|
|
639
|
+
"xmark",
|
|
640
|
+
"didnt",
|
|
641
|
+
"Anthropic",
|
|
642
|
+
"bartowski",
|
|
643
|
+
"specialisation",
|
|
644
|
+
"cursorrules",
|
|
645
|
+
"deepseek",
|
|
646
|
+
"GGUF",
|
|
647
|
+
"hf",
|
|
648
|
+
"llm",
|
|
649
|
+
"machdep",
|
|
650
|
+
"migrat",
|
|
651
|
+
"modelcontextprotocol",
|
|
652
|
+
"mxbai",
|
|
653
|
+
"noheader",
|
|
654
|
+
"codedb",
|
|
655
|
+
"nomic",
|
|
656
|
+
"nounits",
|
|
657
|
+
"nvidia",
|
|
658
|
+
"ollama",
|
|
659
|
+
"qwen",
|
|
660
|
+
"shellcheck",
|
|
661
|
+
"standards",
|
|
662
|
+
"vram",
|
|
663
|
+
"ATATT",
|
|
664
|
+
"ATSTT",
|
|
665
|
+
"AUTOINCREMENT",
|
|
666
|
+
"BEAGENT",
|
|
667
|
+
"CEREBRAS",
|
|
668
|
+
"cpucore",
|
|
669
|
+
"Fingerprinter",
|
|
670
|
+
"FREELLM",
|
|
671
|
+
"ircular",
|
|
672
|
+
"integrat",
|
|
673
|
+
"LOOKBACK",
|
|
674
|
+
"markitdown",
|
|
675
|
+
"memswap",
|
|
676
|
+
"nknown",
|
|
677
|
+
"processlist",
|
|
678
|
+
"unixepoch",
|
|
679
|
+
"unreviewed",
|
|
680
|
+
"xapp",
|
|
681
|
+
"bpoa",
|
|
682
|
+
"installroot",
|
|
683
|
+
"dedups",
|
|
684
|
+
"behaviourally",
|
|
685
|
+
"normalised",
|
|
686
|
+
"раннера",
|
|
687
|
+
"раннере",
|
|
688
|
+
"пайплайн",
|
|
689
|
+
"dind",
|
|
690
|
+
"dlib",
|
|
691
|
+
"pugixml",
|
|
692
|
+
"opencvcommit",
|
|
693
|
+
"litert",
|
|
694
|
+
"SHTPB",
|
|
695
|
+
"FUTX",
|
|
696
|
+
"subdirs"
|
|
547
697
|
],
|
|
548
698
|
"ignorePaths": [
|
|
549
699
|
"**/node_modules/**",
|
package/dist/config/loader.js
CHANGED
|
@@ -163,6 +163,10 @@ function getDefaultConfig() {
|
|
|
163
163
|
'test',
|
|
164
164
|
'build',
|
|
165
165
|
],
|
|
166
|
+
// Skip release PRs and squash-merge titles that don't follow conventional
|
|
167
|
+
// commits (e.g. "Release 15 (#114)"). commitlint already ignores literal
|
|
168
|
+
// "Merge"/"Revert" messages, but not GitHub squash-merge PR titles.
|
|
169
|
+
ignores: ['^Release\\b', '^chore\\(release\\):'],
|
|
166
170
|
},
|
|
167
171
|
licenseCheck: {
|
|
168
172
|
allowPackages: [...constants_1.DEFAULT_ALLOW_PACKAGES],
|
|
@@ -36,14 +36,21 @@ const DEFAULT_TYPES = [
|
|
|
36
36
|
'test',
|
|
37
37
|
'build',
|
|
38
38
|
];
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
39
|
+
const DEFAULT_IGNORES = ['^Release\\b', '^chore\\(release\\):'];
|
|
40
|
+
function buildTempConfig(types, ignores) {
|
|
41
|
+
// Emit a CJS module so `ignores` can hold functions — commitlint requires
|
|
42
|
+
// predicate functions there, which a JSON config cannot express.
|
|
43
|
+
// Resolve config-conventional via Node module resolution — robust to hoisting.
|
|
44
|
+
const configConventional = require.resolve('@commitlint/config-conventional');
|
|
45
|
+
const content = `const patterns = ${JSON.stringify(ignores)}.map((p) => new RegExp(p, 'i'));
|
|
46
|
+
module.exports = {
|
|
47
|
+
extends: [${JSON.stringify(configConventional)}],
|
|
48
|
+
rules: { 'type-enum': [2, 'always', ${JSON.stringify(types)}] },
|
|
49
|
+
ignores: [(commit) => patterns.some((re) => re.test(commit))],
|
|
50
|
+
};
|
|
51
|
+
`;
|
|
52
|
+
const tmpFile = node_path_1.default.join(node_os_1.default.tmpdir(), `.commitlint-${process.pid}.cjs`);
|
|
53
|
+
node_fs_1.default.writeFileSync(tmpFile, content);
|
|
47
54
|
return tmpFile;
|
|
48
55
|
}
|
|
49
56
|
exports.commitlintCheck = {
|
|
@@ -65,7 +72,8 @@ exports.commitlintCheck = {
|
|
|
65
72
|
}
|
|
66
73
|
else {
|
|
67
74
|
const types = ctx.config.commitlint?.types ?? DEFAULT_TYPES;
|
|
68
|
-
|
|
75
|
+
const ignores = ctx.config.commitlint?.ignores ?? DEFAULT_IGNORES;
|
|
76
|
+
tempConfigFile = buildTempConfig(types, ignores);
|
|
69
77
|
args.push('--config', tempConfigFile);
|
|
70
78
|
}
|
|
71
79
|
const result = await (0, execa_1.execa)(process.execPath, [commitlintBin, ...args], {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.diffSecurityCheck = void 0;
|
|
4
|
-
const execa_1 = require("execa");
|
|
5
4
|
const utils_1 = require("../../utils");
|
|
6
5
|
const types_1 = require("../types");
|
|
6
|
+
const findings_1 = require("./findings");
|
|
7
7
|
const SECURITY_PATTERNS = [
|
|
8
8
|
{
|
|
9
9
|
pattern: /^\+(?!\+).*dangerouslySetInnerHTML\s*=/,
|
|
@@ -99,6 +99,20 @@ const SECURITY_PATTERNS = [
|
|
|
99
99
|
severity: 'error',
|
|
100
100
|
},
|
|
101
101
|
];
|
|
102
|
+
/** Bucket added-line matches by severity, keyed by pattern label. */
|
|
103
|
+
function scanDiff(stdout) {
|
|
104
|
+
const errors = {};
|
|
105
|
+
const warnings = {};
|
|
106
|
+
for (const line of stdout.split('\n')) {
|
|
107
|
+
for (const { pattern, label, severity } of SECURITY_PATTERNS) {
|
|
108
|
+
if (pattern.test(line)) {
|
|
109
|
+
const bucket = severity === 'error' ? errors : warnings;
|
|
110
|
+
(bucket[label] ?? (bucket[label] = [])).push(line.slice(1).trim().substring(0, 120));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return { errors, warnings };
|
|
115
|
+
}
|
|
102
116
|
exports.diffSecurityCheck = {
|
|
103
117
|
name: 'Diff Security',
|
|
104
118
|
phase: 'informational',
|
|
@@ -107,52 +121,21 @@ exports.diffSecurityCheck = {
|
|
|
107
121
|
},
|
|
108
122
|
async run(ctx) {
|
|
109
123
|
try {
|
|
110
|
-
const
|
|
111
|
-
'diff',
|
|
112
|
-
'--unified=0',
|
|
113
|
-
'--diff-filter=ACMR',
|
|
114
|
-
ctx.baseSha,
|
|
115
|
-
'HEAD',
|
|
116
|
-
'--',
|
|
124
|
+
const stdout = await (0, findings_1.diffAddedLines)(ctx, [
|
|
117
125
|
'*.ts',
|
|
118
126
|
'*.tsx',
|
|
119
127
|
'*.js',
|
|
120
128
|
'*.jsx',
|
|
121
129
|
'*.mjs',
|
|
122
130
|
'*.cjs',
|
|
123
|
-
]
|
|
124
|
-
const errors =
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
const totalErrors = Object.values(errors).reduce((s, a) => s + a.length, 0);
|
|
135
|
-
const totalWarnings = Object.values(warnings).reduce((s, a) => s + a.length, 0);
|
|
136
|
-
if (totalErrors === 0 && totalWarnings === 0)
|
|
137
|
-
return (0, types_1.pass)();
|
|
138
|
-
for (const [label, occurrences] of Object.entries(errors)) {
|
|
139
|
-
utils_1.logger.error(`[Diff Security]: ❌ ${label}: ${occurrences.length} occurrence(s)`);
|
|
140
|
-
occurrences.slice(0, 3).forEach(l => utils_1.logger.error(` ${l}`));
|
|
141
|
-
if (occurrences.length > 3)
|
|
142
|
-
utils_1.logger.error(` ... and ${occurrences.length - 3} more`);
|
|
143
|
-
}
|
|
144
|
-
for (const [label, occurrences] of Object.entries(warnings)) {
|
|
145
|
-
utils_1.logger.warn(`[Diff Security]: ⚠️ ${label}: ${occurrences.length} occurrence(s)`);
|
|
146
|
-
occurrences.slice(0, 3).forEach(l => utils_1.logger.warn(` ${l}`));
|
|
147
|
-
if (occurrences.length > 3)
|
|
148
|
-
utils_1.logger.warn(` ... and ${occurrences.length - 3} more`);
|
|
149
|
-
}
|
|
150
|
-
const parts = [];
|
|
151
|
-
if (totalErrors > 0)
|
|
152
|
-
parts.push(`${totalErrors} issue(s)`);
|
|
153
|
-
if (totalWarnings > 0)
|
|
154
|
-
parts.push(`${totalWarnings} warning(s)`);
|
|
155
|
-
return (0, types_1.warn)(parts.join(', '));
|
|
131
|
+
]);
|
|
132
|
+
const { errors, warnings } = scanDiff(stdout);
|
|
133
|
+
return (0, findings_1.reportSeverityBuckets)(errors, warnings, {
|
|
134
|
+
prefix: 'Diff Security',
|
|
135
|
+
errorSummary: n => `${n} issue(s)`,
|
|
136
|
+
warningSummary: n => `${n} warning(s)`,
|
|
137
|
+
sampleLimit: 3,
|
|
138
|
+
});
|
|
156
139
|
}
|
|
157
140
|
catch (err) {
|
|
158
141
|
utils_1.logger.warn(`[Diff Security]: ⚠️ Could not analyze diff\n${err}`);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.diffSmellCheck = void 0;
|
|
4
|
-
const execa_1 = require("execa");
|
|
5
4
|
const utils_1 = require("../../utils");
|
|
6
5
|
const types_1 = require("../types");
|
|
6
|
+
const findings_1 = require("./findings");
|
|
7
7
|
const SMELL_PATTERNS = [
|
|
8
8
|
{
|
|
9
9
|
pattern: /^\+(?!\+)(?:[^/]|\/[^/]).*\bconsole\.(log|error|warn|debug|info)\s*\(/,
|
|
@@ -23,18 +23,7 @@ exports.diffSmellCheck = {
|
|
|
23
23
|
},
|
|
24
24
|
async run(ctx) {
|
|
25
25
|
try {
|
|
26
|
-
const
|
|
27
|
-
'diff',
|
|
28
|
-
'--unified=0',
|
|
29
|
-
'--diff-filter=ACMR',
|
|
30
|
-
ctx.baseSha,
|
|
31
|
-
'HEAD',
|
|
32
|
-
'--',
|
|
33
|
-
'*.ts',
|
|
34
|
-
'*.tsx',
|
|
35
|
-
'*.js',
|
|
36
|
-
'*.jsx',
|
|
37
|
-
], { cwd: ctx.cwd });
|
|
26
|
+
const stdout = await (0, findings_1.diffAddedLines)(ctx, ['*.ts', '*.tsx', '*.js', '*.jsx']);
|
|
38
27
|
const findings = {};
|
|
39
28
|
for (const line of stdout.split('\n')) {
|
|
40
29
|
for (const { pattern, label } of SMELL_PATTERNS) {
|
|
@@ -43,15 +32,9 @@ exports.diffSmellCheck = {
|
|
|
43
32
|
}
|
|
44
33
|
}
|
|
45
34
|
}
|
|
46
|
-
|
|
47
|
-
if (total === 0)
|
|
35
|
+
if ((0, findings_1.bucketTotal)(findings) === 0)
|
|
48
36
|
return (0, types_1.pass)();
|
|
49
|
-
|
|
50
|
-
utils_1.logger.warn(`[Diff Smells]: ⚠️ ${label}: ${occurrences.length} occurrence(s)`);
|
|
51
|
-
occurrences.slice(0, 3).forEach(l => utils_1.logger.warn(` ${l}`));
|
|
52
|
-
if (occurrences.length > 3)
|
|
53
|
-
utils_1.logger.warn(` ... and ${occurrences.length - 3} more`);
|
|
54
|
-
}
|
|
37
|
+
(0, findings_1.logFindings)(findings, { prefix: 'Diff Smells', sampleLimit: 3 });
|
|
55
38
|
const summary = Object.entries(findings)
|
|
56
39
|
.map(([label, arr]) => `${arr.length}× ${label}`)
|
|
57
40
|
.join(', ');
|
|
@@ -10,6 +10,23 @@ const execa_1 = require("execa");
|
|
|
10
10
|
const utils_1 = require("../../utils");
|
|
11
11
|
const delta_1 = require("../../utils/delta");
|
|
12
12
|
const types_1 = require("../types");
|
|
13
|
+
// jscpd v5 appends promotional/funding lines after its report. Strip them so
|
|
14
|
+
// they never reach the client's terminal when running our package.
|
|
15
|
+
const JSCPD_PROMO_MARKERS = [
|
|
16
|
+
'Auto-refactor with AI',
|
|
17
|
+
'github.com/kucherenko/jscpd',
|
|
18
|
+
'Gangsta Agents',
|
|
19
|
+
'gangsta.page',
|
|
20
|
+
'Support jscpd project',
|
|
21
|
+
'opencollective.com/jscpd',
|
|
22
|
+
];
|
|
23
|
+
function stripJscpdPromo(text) {
|
|
24
|
+
return text
|
|
25
|
+
.split('\n')
|
|
26
|
+
.filter(line => !JSCPD_PROMO_MARKERS.some(marker => line.includes(marker)))
|
|
27
|
+
.join('\n')
|
|
28
|
+
.trimEnd();
|
|
29
|
+
}
|
|
13
30
|
exports.duplicateCheck = {
|
|
14
31
|
name: 'Duplicate Code',
|
|
15
32
|
phase: 'informational',
|
|
@@ -26,7 +43,7 @@ exports.duplicateCheck = {
|
|
|
26
43
|
return (0, types_1.skip)('no relevant files changed');
|
|
27
44
|
const srcPath = node_path_1.default.join(ctx.cwd, 'src');
|
|
28
45
|
const targetPaths = changedFiles ?? (node_fs_1.default.existsSync(srcPath) ? [srcPath] : [ctx.cwd]);
|
|
29
|
-
const jscpdBin = (0, utils_1.resolvePackageBin)('jscpd', '
|
|
46
|
+
const jscpdBin = (0, utils_1.resolvePackageBin)('jscpd', 'run-jscpd.js');
|
|
30
47
|
try {
|
|
31
48
|
const result = await (0, execa_1.execa)(process.execPath, [
|
|
32
49
|
jscpdBin,
|
|
@@ -38,10 +55,9 @@ exports.duplicateCheck = {
|
|
|
38
55
|
'console',
|
|
39
56
|
'--ignore',
|
|
40
57
|
'node_modules/**,dist/**,.git/**',
|
|
41
|
-
'--quiet',
|
|
42
58
|
...targetPaths,
|
|
43
59
|
], { cwd: ctx.cwd, reject: false });
|
|
44
|
-
const output = [result.stdout, result.stderr].filter(Boolean).join('\n');
|
|
60
|
+
const output = stripJscpdPromo([result.stdout, result.stderr].filter(Boolean).join('\n'));
|
|
45
61
|
const clonesMatch = output.match(/(\d+)\s+clone/i);
|
|
46
62
|
const cloneCount = clonesMatch ? parseInt(clonesMatch[1], 10) : 0;
|
|
47
63
|
if (cloneCount > 0) {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.diffAddedLines = diffAddedLines;
|
|
4
|
+
exports.bucketTotal = bucketTotal;
|
|
5
|
+
exports.logFindings = logFindings;
|
|
6
|
+
exports.reportSeverityBuckets = reportSeverityBuckets;
|
|
7
|
+
const execa_1 = require("execa");
|
|
8
|
+
const utils_1 = require("../../utils");
|
|
9
|
+
const types_1 = require("../types");
|
|
10
|
+
/**
|
|
11
|
+
* Run `git diff` over the given file globs, returning the raw unified diff
|
|
12
|
+
* (zero context, added/copied/modified/renamed files) for added-line scanning.
|
|
13
|
+
*/
|
|
14
|
+
async function diffAddedLines(ctx, globs) {
|
|
15
|
+
const { stdout } = await (0, execa_1.execa)('git', ['diff', '--unified=0', '--diff-filter=ACMR', ctx.baseSha, 'HEAD', '--', ...globs], { cwd: ctx.cwd });
|
|
16
|
+
return stdout;
|
|
17
|
+
}
|
|
18
|
+
/** Total number of findings across all labels in a bucket. */
|
|
19
|
+
function bucketTotal(bucket) {
|
|
20
|
+
return Object.values(bucket).reduce((sum, arr) => sum + arr.length, 0);
|
|
21
|
+
}
|
|
22
|
+
/** Log one bucket of findings using the shared check format. */
|
|
23
|
+
function logFindings(bucket, opts) {
|
|
24
|
+
const emit = opts.isError ? utils_1.logger.error.bind(utils_1.logger) : utils_1.logger.warn.bind(utils_1.logger);
|
|
25
|
+
const icon = opts.isError ? '❌' : '⚠️';
|
|
26
|
+
const showCount = opts.showCount ?? true;
|
|
27
|
+
const { sampleLimit } = opts;
|
|
28
|
+
for (const [label, items] of Object.entries(bucket)) {
|
|
29
|
+
emit(`[${opts.prefix}]: ${icon} ${label}${showCount ? `: ${items.length} occurrence(s)` : ':'}`);
|
|
30
|
+
const shown = sampleLimit === undefined ? items : items.slice(0, sampleLimit);
|
|
31
|
+
shown.forEach(item => emit(` ${item}`));
|
|
32
|
+
if (sampleLimit !== undefined && items.length > sampleLimit) {
|
|
33
|
+
emit(` ... and ${items.length - sampleLimit} more`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Report error/warning finding buckets: log both, then produce the outcome.
|
|
39
|
+
* Returns pass() when both buckets are empty, otherwise warn() with a summary.
|
|
40
|
+
*/
|
|
41
|
+
function reportSeverityBuckets(errors, warnings, opts) {
|
|
42
|
+
const totalErrors = bucketTotal(errors);
|
|
43
|
+
const totalWarnings = bucketTotal(warnings);
|
|
44
|
+
if (totalErrors === 0 && totalWarnings === 0)
|
|
45
|
+
return (0, types_1.pass)();
|
|
46
|
+
const logOpts = { prefix: opts.prefix, showCount: opts.showCount, sampleLimit: opts.sampleLimit };
|
|
47
|
+
logFindings(errors, { ...logOpts, isError: true });
|
|
48
|
+
logFindings(warnings, { ...logOpts, isError: false });
|
|
49
|
+
const parts = [];
|
|
50
|
+
if (totalErrors > 0)
|
|
51
|
+
parts.push(opts.errorSummary(totalErrors));
|
|
52
|
+
if (totalWarnings > 0)
|
|
53
|
+
parts.push(opts.warningSummary(totalWarnings));
|
|
54
|
+
return (0, types_1.warn)(parts.join(', '));
|
|
55
|
+
}
|
|
@@ -24,6 +24,11 @@ exports.licenseCheck = {
|
|
|
24
24
|
async run(ctx) {
|
|
25
25
|
const allowPackages = new Set(ctx.config.licenseCheck?.allowPackages ?? []);
|
|
26
26
|
const allowLicenses = ctx.config.licenseCheck?.allowLicenses ?? [];
|
|
27
|
+
// pr-checkmate installs into the consumer's node_modules, so its own runtime
|
|
28
|
+
// deps (eslint plugins, cspell dictionaries, …) leak into the client's
|
|
29
|
+
// production tree. Those are our dependencies, not the client's — never fail
|
|
30
|
+
// a client's PR because of a license we ourselves pulled in.
|
|
31
|
+
const ownDeps = (0, utils_1.getOwnDependencyClosure)();
|
|
27
32
|
// Resolve the bundled license-checker binary and run it via the current node — never
|
|
28
33
|
// shell out to `npx`, which fails (exit 127) when license-checker isn't on the path.
|
|
29
34
|
let licenseCheckerBin;
|
|
@@ -44,7 +49,8 @@ exports.licenseCheck = {
|
|
|
44
49
|
}
|
|
45
50
|
const violations = [];
|
|
46
51
|
for (const [pkg, info] of Object.entries(licenses)) {
|
|
47
|
-
|
|
52
|
+
const name = bareName(pkg);
|
|
53
|
+
if (allowPackages.has(name) || ownDeps.has(name))
|
|
48
54
|
continue;
|
|
49
55
|
const raw = info.licenses ?? '';
|
|
50
56
|
const license = Array.isArray(raw) ? raw.join(' OR ') : raw;
|
package/dist/core/checks/lint.js
CHANGED
|
@@ -38,7 +38,14 @@ exports.lintCheck = {
|
|
|
38
38
|
'.eslintrc.json',
|
|
39
39
|
'.eslintrc',
|
|
40
40
|
]);
|
|
41
|
-
const
|
|
41
|
+
const version = await getESLintMajorVersion(eslintBin);
|
|
42
|
+
const args = [...targets];
|
|
43
|
+
// `--ext` is an eslintrc-only flag. Under flat config (ESLint 9+) it is
|
|
44
|
+
// obsolete — extensions come from the config's `files` glob — and in
|
|
45
|
+
// ESLint 10 passing it makes explicit files fall back to the default JS
|
|
46
|
+
// parser, breaking TypeScript linting. Only pass it on the legacy path.
|
|
47
|
+
if (version < 9)
|
|
48
|
+
args.push('--ext', '.ts,.tsx,.js,.jsx');
|
|
42
49
|
// Only auto-fix when the caller allows file mutations.
|
|
43
50
|
if (ctx.write)
|
|
44
51
|
args.push('--fix');
|
|
@@ -46,7 +53,6 @@ exports.lintCheck = {
|
|
|
46
53
|
utils_1.logger.info(`[ESLint]: Using local config (checking: ${sourcePath})`);
|
|
47
54
|
}
|
|
48
55
|
else {
|
|
49
|
-
const version = await getESLintMajorVersion(eslintBin);
|
|
50
56
|
const configFile = version >= 9 ? 'eslint.config.mjs' : '.eslintrc.js';
|
|
51
57
|
args.push('--config', (0, config_1.getPackageConfigPath)(configFile));
|
|
52
58
|
utils_1.logger.info(`[ESLint]: Using pr-checkmate config (ESLint v${version}, ${configFile})`);
|
|
@@ -87,19 +87,17 @@ exports.markdownLintCheck = {
|
|
|
87
87
|
'.claude/**',
|
|
88
88
|
...(userConfig.ignores ?? []),
|
|
89
89
|
];
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
tmpIgnoreFile,
|
|
102
|
-
];
|
|
90
|
+
// markdownlint-cli2 has no --ignore-path flag; exclusions are expressed as
|
|
91
|
+
// negated globs (`#pattern`). A bare directory name also needs `#name/**`
|
|
92
|
+
// to exclude its contents.
|
|
93
|
+
const ignoreGlobs = defaultIgnorePatterns.flatMap(pattern => {
|
|
94
|
+
const clean = pattern.replace(/\/+$/, '');
|
|
95
|
+
const globs = [`#${clean}`];
|
|
96
|
+
if (!clean.includes('*'))
|
|
97
|
+
globs.push(`#${clean}/**`);
|
|
98
|
+
return globs;
|
|
99
|
+
});
|
|
100
|
+
const args = [...(ctx.write ? ['--fix'] : []), ...targets, ...ignoreGlobs, ...configArg];
|
|
103
101
|
try {
|
|
104
102
|
const result = await (0, execa_1.execa)(process.execPath, [markdownlintBin, ...args], {
|
|
105
103
|
cwd: ctx.cwd,
|
|
@@ -111,8 +109,6 @@ exports.markdownLintCheck = {
|
|
|
111
109
|
finally {
|
|
112
110
|
if (tempConfigFile && node_fs_1.default.existsSync(tempConfigFile))
|
|
113
111
|
node_fs_1.default.unlinkSync(tempConfigFile);
|
|
114
|
-
if (node_fs_1.default.existsSync(tmpIgnoreFile))
|
|
115
|
-
node_fs_1.default.unlinkSync(tmpIgnoreFile);
|
|
116
112
|
}
|
|
117
113
|
},
|
|
118
114
|
};
|