pr-checkmate 1.21.15 → 1.21.16
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 +8 -1
- package/cspell.json +149 -1
- package/dist/config/loader.js +4 -0
- package/dist/core/checks/commitlint.js +17 -9
- package/dist/core/checks/diff-security.js +42 -32
- package/dist/core/checks/duplicate-check.js +1 -2
- package/dist/core/checks/license-check.js +7 -1
- package/dist/core/checks/markdown-lint.js +11 -15
- package/dist/core/checks/sensitive-file-guard.js +41 -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/package.json +4 -4
- package/src/config/cspell.json +149 -1
package/README.md
CHANGED
|
@@ -4,6 +4,13 @@
|
|
|
4
4
|

|
|
5
5
|

|
|
6
6
|
|
|
7
|
+
**Bundled tools:**
|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+

|
|
12
|
+

|
|
13
|
+
|
|
7
14
|
> **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
15
|
|
|
9
16
|
---
|
|
@@ -280,7 +287,7 @@ In GitHub Actions PR context, language checks automatically switch to **delta mo
|
|
|
280
287
|
|
|
281
288
|
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
289
|
|
|
283
|
-
```
|
|
290
|
+
```text
|
|
284
291
|
## PR CheckMate Summary
|
|
285
292
|
|
|
286
293
|
11 passed · 3 warnings · 0 failed · 0 skipped
|
package/cspell.json
CHANGED
|
@@ -189,6 +189,16 @@
|
|
|
189
189
|
"sultansadikov",
|
|
190
190
|
"telaviv",
|
|
191
191
|
"travis",
|
|
192
|
+
"kotlin",
|
|
193
|
+
"Werror",
|
|
194
|
+
"positiving",
|
|
195
|
+
"pyflakes",
|
|
196
|
+
"pycodestyle",
|
|
197
|
+
"swiftlint",
|
|
198
|
+
"pathspec",
|
|
199
|
+
"Pipfile",
|
|
200
|
+
"conanfile",
|
|
201
|
+
"ktlint",
|
|
192
202
|
"Términos",
|
|
193
203
|
"Teudat",
|
|
194
204
|
"Tiempo",
|
|
@@ -543,7 +553,145 @@
|
|
|
543
553
|
"Makefiles",
|
|
544
554
|
"dpdm",
|
|
545
555
|
"commitlintrc",
|
|
546
|
-
"shortstat"
|
|
556
|
+
"shortstat",
|
|
557
|
+
"Cooldown",
|
|
558
|
+
"SCRIPTERROR",
|
|
559
|
+
"attendace",
|
|
560
|
+
"attendance",
|
|
561
|
+
"kupat",
|
|
562
|
+
"gemel",
|
|
563
|
+
"hishtalmut",
|
|
564
|
+
"keren",
|
|
565
|
+
"tofes",
|
|
566
|
+
"Bdikat",
|
|
567
|
+
"Hishuv",
|
|
568
|
+
"formcontrolname",
|
|
569
|
+
"zrufot",
|
|
570
|
+
"Klali",
|
|
571
|
+
"contenteditable",
|
|
572
|
+
"האניקומב",
|
|
573
|
+
"ishur",
|
|
574
|
+
"Yeshut",
|
|
575
|
+
"Nihul",
|
|
576
|
+
"pratim",
|
|
577
|
+
"nispahim",
|
|
578
|
+
"networkidle",
|
|
579
|
+
"domcontentloaded",
|
|
580
|
+
"secapp",
|
|
581
|
+
"taxinfo",
|
|
582
|
+
"gmishurim",
|
|
583
|
+
"ezor",
|
|
584
|
+
"ishi",
|
|
585
|
+
"ishurim",
|
|
586
|
+
"nikui",
|
|
587
|
+
"mekabel",
|
|
588
|
+
"shdochshana",
|
|
589
|
+
"gershayim",
|
|
590
|
+
"Rashum",
|
|
591
|
+
"Shem",
|
|
592
|
+
"Mishpaha",
|
|
593
|
+
"Rshum",
|
|
594
|
+
"Prati",
|
|
595
|
+
"Ktovet",
|
|
596
|
+
"Doar",
|
|
597
|
+
"Megurim",
|
|
598
|
+
"ddlkidomet",
|
|
599
|
+
"Aher",
|
|
600
|
+
"Esek",
|
|
601
|
+
"Isuk",
|
|
602
|
+
"Mazav",
|
|
603
|
+
"Mish",
|
|
604
|
+
"Beyachad",
|
|
605
|
+
"Shlichat",
|
|
606
|
+
"Neezarti",
|
|
607
|
+
"Doch",
|
|
608
|
+
"Klalim",
|
|
609
|
+
"Hehzer",
|
|
610
|
+
"Ozer",
|
|
611
|
+
"נייע",
|
|
612
|
+
"recognised",
|
|
613
|
+
"קולומייץ",
|
|
614
|
+
"אירינה",
|
|
615
|
+
"סרגיי",
|
|
616
|
+
"פולישוק",
|
|
617
|
+
"ולדימיר",
|
|
618
|
+
"moov",
|
|
619
|
+
"virtualenv",
|
|
620
|
+
"pytesseract",
|
|
621
|
+
"qrdet",
|
|
622
|
+
"Jsons",
|
|
623
|
+
"easyocr",
|
|
624
|
+
"EXIF",
|
|
625
|
+
"fliplr",
|
|
626
|
+
"noautorotate",
|
|
627
|
+
"timestep",
|
|
628
|
+
"VAQH",
|
|
629
|
+
"DSBL",
|
|
630
|
+
"MAXU",
|
|
631
|
+
"XCUI",
|
|
632
|
+
"Apks",
|
|
633
|
+
"bootstatus",
|
|
634
|
+
"BEGINSWITH",
|
|
635
|
+
"anypassword",
|
|
636
|
+
"wrongpassword",
|
|
637
|
+
"xmark",
|
|
638
|
+
"didnt",
|
|
639
|
+
"Anthropic",
|
|
640
|
+
"bartowski",
|
|
641
|
+
"specialisation",
|
|
642
|
+
"cursorrules",
|
|
643
|
+
"deepseek",
|
|
644
|
+
"GGUF",
|
|
645
|
+
"hf",
|
|
646
|
+
"llm",
|
|
647
|
+
"machdep",
|
|
648
|
+
"migrat",
|
|
649
|
+
"modelcontextprotocol",
|
|
650
|
+
"mxbai",
|
|
651
|
+
"noheader",
|
|
652
|
+
"codedb",
|
|
653
|
+
"nomic",
|
|
654
|
+
"nounits",
|
|
655
|
+
"nvidia",
|
|
656
|
+
"ollama",
|
|
657
|
+
"qwen",
|
|
658
|
+
"shellcheck",
|
|
659
|
+
"standards",
|
|
660
|
+
"vram",
|
|
661
|
+
"ATATT",
|
|
662
|
+
"ATSTT",
|
|
663
|
+
"AUTOINCREMENT",
|
|
664
|
+
"BEAGENT",
|
|
665
|
+
"CEREBRAS",
|
|
666
|
+
"cpucore",
|
|
667
|
+
"Fingerprinter",
|
|
668
|
+
"FREELLM",
|
|
669
|
+
"ircular",
|
|
670
|
+
"integrat",
|
|
671
|
+
"LOOKBACK",
|
|
672
|
+
"markitdown",
|
|
673
|
+
"memswap",
|
|
674
|
+
"nknown",
|
|
675
|
+
"processlist",
|
|
676
|
+
"unixepoch",
|
|
677
|
+
"unreviewed",
|
|
678
|
+
"xapp",
|
|
679
|
+
"bpoa",
|
|
680
|
+
"installroot",
|
|
681
|
+
"dedups",
|
|
682
|
+
"behaviourally",
|
|
683
|
+
"normalised",
|
|
684
|
+
"раннера",
|
|
685
|
+
"раннере",
|
|
686
|
+
"пайплайн",
|
|
687
|
+
"dind",
|
|
688
|
+
"dlib",
|
|
689
|
+
"pugixml",
|
|
690
|
+
"opencvcommit",
|
|
691
|
+
"litert",
|
|
692
|
+
"SHTPB",
|
|
693
|
+
"FUTX",
|
|
694
|
+
"subdirs"
|
|
547
695
|
],
|
|
548
696
|
"ignorePaths": [
|
|
549
697
|
"**/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], {
|
|
@@ -99,6 +99,46 @@ 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
|
+
}
|
|
116
|
+
/** Log up to 3 sample lines per label at the given severity. */
|
|
117
|
+
function logFindings(bucket, isError) {
|
|
118
|
+
const emit = isError ? utils_1.logger.error.bind(utils_1.logger) : utils_1.logger.warn.bind(utils_1.logger);
|
|
119
|
+
const icon = isError ? '❌' : '⚠️';
|
|
120
|
+
for (const [label, occurrences] of Object.entries(bucket)) {
|
|
121
|
+
emit(`[Diff Security]: ${icon} ${label}: ${occurrences.length} occurrence(s)`);
|
|
122
|
+
occurrences.slice(0, 3).forEach(l => emit(` ${l}`));
|
|
123
|
+
if (occurrences.length > 3)
|
|
124
|
+
emit(` ... and ${occurrences.length - 3} more`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/** Report findings and produce the check outcome. */
|
|
128
|
+
function reportFindings(errors, warnings) {
|
|
129
|
+
const totalErrors = Object.values(errors).reduce((s, a) => s + a.length, 0);
|
|
130
|
+
const totalWarnings = Object.values(warnings).reduce((s, a) => s + a.length, 0);
|
|
131
|
+
if (totalErrors === 0 && totalWarnings === 0)
|
|
132
|
+
return (0, types_1.pass)();
|
|
133
|
+
logFindings(errors, true);
|
|
134
|
+
logFindings(warnings, false);
|
|
135
|
+
const parts = [];
|
|
136
|
+
if (totalErrors > 0)
|
|
137
|
+
parts.push(`${totalErrors} issue(s)`);
|
|
138
|
+
if (totalWarnings > 0)
|
|
139
|
+
parts.push(`${totalWarnings} warning(s)`);
|
|
140
|
+
return (0, types_1.warn)(parts.join(', '));
|
|
141
|
+
}
|
|
102
142
|
exports.diffSecurityCheck = {
|
|
103
143
|
name: 'Diff Security',
|
|
104
144
|
phase: 'informational',
|
|
@@ -121,38 +161,8 @@ exports.diffSecurityCheck = {
|
|
|
121
161
|
'*.mjs',
|
|
122
162
|
'*.cjs',
|
|
123
163
|
], { cwd: ctx.cwd });
|
|
124
|
-
const errors =
|
|
125
|
-
|
|
126
|
-
for (const line of stdout.split('\n')) {
|
|
127
|
-
for (const { pattern, label, severity } of SECURITY_PATTERNS) {
|
|
128
|
-
if (pattern.test(line)) {
|
|
129
|
-
const bucket = severity === 'error' ? errors : warnings;
|
|
130
|
-
(bucket[label] ?? (bucket[label] = [])).push(line.slice(1).trim().substring(0, 120));
|
|
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(', '));
|
|
164
|
+
const { errors, warnings } = scanDiff(stdout);
|
|
165
|
+
return reportFindings(errors, warnings);
|
|
156
166
|
}
|
|
157
167
|
catch (err) {
|
|
158
168
|
utils_1.logger.warn(`[Diff Security]: ⚠️ Could not analyze diff\n${err}`);
|
|
@@ -26,7 +26,7 @@ exports.duplicateCheck = {
|
|
|
26
26
|
return (0, types_1.skip)('no relevant files changed');
|
|
27
27
|
const srcPath = node_path_1.default.join(ctx.cwd, 'src');
|
|
28
28
|
const targetPaths = changedFiles ?? (node_fs_1.default.existsSync(srcPath) ? [srcPath] : [ctx.cwd]);
|
|
29
|
-
const jscpdBin = (0, utils_1.resolvePackageBin)('jscpd', '
|
|
29
|
+
const jscpdBin = (0, utils_1.resolvePackageBin)('jscpd', 'run-jscpd.js');
|
|
30
30
|
try {
|
|
31
31
|
const result = await (0, execa_1.execa)(process.execPath, [
|
|
32
32
|
jscpdBin,
|
|
@@ -38,7 +38,6 @@ exports.duplicateCheck = {
|
|
|
38
38
|
'console',
|
|
39
39
|
'--ignore',
|
|
40
40
|
'node_modules/**,dist/**,.git/**',
|
|
41
|
-
'--quiet',
|
|
42
41
|
...targetPaths,
|
|
43
42
|
], { cwd: ctx.cwd, reject: false });
|
|
44
43
|
const output = [result.stdout, result.stderr].filter(Boolean).join('\n');
|
|
@@ -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;
|
|
@@ -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
|
};
|
|
@@ -53,6 +53,45 @@ const SENSITIVE_CATEGORIES = [
|
|
|
53
53
|
severity: 'warn',
|
|
54
54
|
},
|
|
55
55
|
];
|
|
56
|
+
/** Bucket changed files by category severity, keyed by category label. */
|
|
57
|
+
function categorizeFiles(changedFiles) {
|
|
58
|
+
var _a;
|
|
59
|
+
const errors = {};
|
|
60
|
+
const warnings = {};
|
|
61
|
+
for (const filePath of changedFiles) {
|
|
62
|
+
for (const category of SENSITIVE_CATEGORIES) {
|
|
63
|
+
if (category.patterns.some(p => p.test(filePath))) {
|
|
64
|
+
const bucket = category.severity === 'error' ? errors : warnings;
|
|
65
|
+
(bucket[_a = category.label] ?? (bucket[_a] = [])).push(filePath);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return { errors, warnings };
|
|
70
|
+
}
|
|
71
|
+
/** Log every file per label at the given severity. */
|
|
72
|
+
function logFiles(bucket, isError) {
|
|
73
|
+
const emit = isError ? utils_1.logger.error.bind(utils_1.logger) : utils_1.logger.warn.bind(utils_1.logger);
|
|
74
|
+
const icon = isError ? '❌' : '⚠️';
|
|
75
|
+
for (const [label, files] of Object.entries(bucket)) {
|
|
76
|
+
emit(`[Sensitive Files]: ${icon} ${label}:`);
|
|
77
|
+
files.forEach(f => emit(` ${f}`));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Report categorized files and produce the check outcome. */
|
|
81
|
+
function reportFiles(errors, warnings) {
|
|
82
|
+
const totalErrors = Object.values(errors).reduce((s, a) => s + a.length, 0);
|
|
83
|
+
const totalWarnings = Object.values(warnings).reduce((s, a) => s + a.length, 0);
|
|
84
|
+
if (totalErrors === 0 && totalWarnings === 0)
|
|
85
|
+
return (0, types_1.pass)();
|
|
86
|
+
logFiles(errors, true);
|
|
87
|
+
logFiles(warnings, false);
|
|
88
|
+
const parts = [];
|
|
89
|
+
if (totalErrors > 0)
|
|
90
|
+
parts.push(`${totalErrors} sensitive file(s) changed`);
|
|
91
|
+
if (totalWarnings > 0)
|
|
92
|
+
parts.push(`${totalWarnings} notable file(s) changed`);
|
|
93
|
+
return (0, types_1.warn)(parts.join(', '));
|
|
94
|
+
}
|
|
56
95
|
exports.sensitiveFileGuardCheck = {
|
|
57
96
|
name: 'Sensitive Files',
|
|
58
97
|
phase: 'informational',
|
|
@@ -60,40 +99,13 @@ exports.sensitiveFileGuardCheck = {
|
|
|
60
99
|
return ctx.config.sensitiveFileGuard?.enabled !== false && Boolean(ctx.baseSha);
|
|
61
100
|
},
|
|
62
101
|
async run(ctx) {
|
|
63
|
-
var _a;
|
|
64
102
|
try {
|
|
65
103
|
const { stdout } = await (0, execa_1.execa)('git', ['diff', '--name-only', '--diff-filter=ACMRD', ctx.baseSha, 'HEAD'], { cwd: ctx.cwd });
|
|
66
104
|
const changedFiles = stdout.split('\n').filter(Boolean);
|
|
67
105
|
if (changedFiles.length === 0)
|
|
68
106
|
return (0, types_1.pass)();
|
|
69
|
-
const errors =
|
|
70
|
-
|
|
71
|
-
for (const filePath of changedFiles) {
|
|
72
|
-
for (const category of SENSITIVE_CATEGORIES) {
|
|
73
|
-
if (category.patterns.some(p => p.test(filePath))) {
|
|
74
|
-
const bucket = category.severity === 'error' ? errors : warnings;
|
|
75
|
-
(bucket[_a = category.label] ?? (bucket[_a] = [])).push(filePath);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
const totalErrors = Object.values(errors).reduce((s, a) => s + a.length, 0);
|
|
80
|
-
const totalWarnings = Object.values(warnings).reduce((s, a) => s + a.length, 0);
|
|
81
|
-
if (totalErrors === 0 && totalWarnings === 0)
|
|
82
|
-
return (0, types_1.pass)();
|
|
83
|
-
for (const [label, files] of Object.entries(errors)) {
|
|
84
|
-
utils_1.logger.error(`[Sensitive Files]: ❌ ${label}:`);
|
|
85
|
-
files.forEach(f => utils_1.logger.error(` ${f}`));
|
|
86
|
-
}
|
|
87
|
-
for (const [label, files] of Object.entries(warnings)) {
|
|
88
|
-
utils_1.logger.warn(`[Sensitive Files]: ⚠️ ${label}:`);
|
|
89
|
-
files.forEach(f => utils_1.logger.warn(` ${f}`));
|
|
90
|
-
}
|
|
91
|
-
const parts = [];
|
|
92
|
-
if (totalErrors > 0)
|
|
93
|
-
parts.push(`${totalErrors} sensitive file(s) changed`);
|
|
94
|
-
if (totalWarnings > 0)
|
|
95
|
-
parts.push(`${totalWarnings} notable file(s) changed`);
|
|
96
|
-
return (0, types_1.warn)(parts.join(', '));
|
|
107
|
+
const { errors, warnings } = categorizeFiles(changedFiles);
|
|
108
|
+
return reportFiles(errors, warnings);
|
|
97
109
|
}
|
|
98
110
|
catch (err) {
|
|
99
111
|
utils_1.logger.warn(`[Sensitive Files]: ⚠️ Could not check file changes\n${err}`);
|
package/dist/core/registry.js
CHANGED
|
@@ -7,27 +7,27 @@ const commitlint_1 = require("./checks/commitlint");
|
|
|
7
7
|
const cpp_format_1 = require("./checks/cpp-format");
|
|
8
8
|
const dead_code_1 = require("./checks/dead-code");
|
|
9
9
|
const dependency_check_1 = require("./checks/dependency-check");
|
|
10
|
+
const diff_security_1 = require("./checks/diff-security");
|
|
10
11
|
const diff_smell_1 = require("./checks/diff-smell");
|
|
12
|
+
const duplicate_check_1 = require("./checks/duplicate-check");
|
|
13
|
+
const kotlin_lint_1 = require("./checks/kotlin-lint");
|
|
11
14
|
const license_check_1 = require("./checks/license-check");
|
|
12
15
|
const lint_1 = require("./checks/lint");
|
|
16
|
+
const markdown_lint_1 = require("./checks/markdown-lint");
|
|
13
17
|
const npm_audit_1 = require("./checks/npm-audit");
|
|
14
18
|
// informational — warn/skip only, never fail the run
|
|
15
|
-
const diff_security_1 = require("./checks/diff-security");
|
|
16
19
|
const outdated_deps_1 = require("./checks/outdated-deps");
|
|
17
20
|
const pr_body_1 = require("./checks/pr-body");
|
|
18
21
|
const pr_size_check_1 = require("./checks/pr-size-check");
|
|
19
22
|
const pr_title_lint_1 = require("./checks/pr-title-lint");
|
|
23
|
+
const prettier_1 = require("./checks/prettier");
|
|
24
|
+
const python_format_1 = require("./checks/python-format");
|
|
25
|
+
const python_lint_1 = require("./checks/python-lint");
|
|
26
|
+
const security_1 = require("./checks/security");
|
|
20
27
|
const sensitive_file_guard_1 = require("./checks/sensitive-file-guard");
|
|
21
|
-
const duplicate_check_1 = require("./checks/duplicate-check");
|
|
22
|
-
const markdown_lint_1 = require("./checks/markdown-lint");
|
|
23
28
|
const spellcheck_1 = require("./checks/spellcheck");
|
|
24
|
-
const python_lint_1 = require("./checks/python-lint");
|
|
25
29
|
const swift_lint_1 = require("./checks/swift-lint");
|
|
26
|
-
const kotlin_lint_1 = require("./checks/kotlin-lint");
|
|
27
30
|
// format — runs last, may write files
|
|
28
|
-
const prettier_1 = require("./checks/prettier");
|
|
29
|
-
const python_format_1 = require("./checks/python-format");
|
|
30
|
-
const security_1 = require("./checks/security");
|
|
31
31
|
const typecheck_1 = require("./checks/typecheck");
|
|
32
32
|
/**
|
|
33
33
|
* Every check, in declaration order. The orchestrator groups them by `phase`
|
package/dist/core/reporters.js
CHANGED
|
@@ -14,6 +14,8 @@ const STATUS_ICON = {
|
|
|
14
14
|
};
|
|
15
15
|
// Unique marker so we can find and update the comment on re-runs.
|
|
16
16
|
const COMMENT_MARKER = '<!-- pr-checkmate-summary -->';
|
|
17
|
+
const SUMMARY_BORDER = '═══════════════════════════════════════════════════════════════';
|
|
18
|
+
const SUMMARY_RULE = '───────────────────────────────────────────────────────────────';
|
|
17
19
|
function getPrContext() {
|
|
18
20
|
const token = process.env.GITHUB_TOKEN;
|
|
19
21
|
const repository = process.env.GITHUB_REPOSITORY;
|
|
@@ -57,16 +59,16 @@ function buildMarkdownReport(report) {
|
|
|
57
59
|
function printStdout(report) {
|
|
58
60
|
const { passed, warned, failed, skipped } = report.summary;
|
|
59
61
|
utils_1.logger.info('');
|
|
60
|
-
utils_1.logger.info(
|
|
62
|
+
utils_1.logger.info(SUMMARY_BORDER);
|
|
61
63
|
utils_1.logger.info(' PR CheckMate Summary');
|
|
62
|
-
utils_1.logger.info(
|
|
64
|
+
utils_1.logger.info(SUMMARY_BORDER);
|
|
63
65
|
for (const r of report.results) {
|
|
64
66
|
const details = r.details ? `: ${r.details}` : '';
|
|
65
67
|
utils_1.logger.info(` ${STATUS_ICON[r.status]} ${r.name}${details}`);
|
|
66
68
|
}
|
|
67
|
-
utils_1.logger.info(
|
|
69
|
+
utils_1.logger.info(SUMMARY_RULE);
|
|
68
70
|
utils_1.logger.info(` ✅ ${passed} passed ⚠️ ${warned} warnings ❌ ${failed} failed ⏭️ ${skipped} skipped`);
|
|
69
|
-
utils_1.logger.info(
|
|
71
|
+
utils_1.logger.info(SUMMARY_BORDER);
|
|
70
72
|
}
|
|
71
73
|
async function upsertGithubComment(body) {
|
|
72
74
|
const ctx = getPrContext();
|
package/dist/init.js
CHANGED
|
@@ -59,105 +59,124 @@ const DEFAULT_CONFIG = {
|
|
|
59
59
|
},
|
|
60
60
|
documentation: config_1.documentation,
|
|
61
61
|
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
62
|
+
const MANUAL_ENTRY = 'Enter manually';
|
|
63
|
+
/** Prompt the user for the project source path, allowing a manual entry. */
|
|
64
|
+
async function promptSourcePath() {
|
|
65
|
+
const answers = await inquirer_1.default.prompt([
|
|
66
|
+
{
|
|
67
|
+
type: 'list',
|
|
68
|
+
name: 'sourcePath',
|
|
69
|
+
message: 'Where is your project source code?',
|
|
70
|
+
choices: ['src', 'app', MANUAL_ENTRY],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: 'input',
|
|
74
|
+
name: 'manualPath',
|
|
75
|
+
message: 'Enter path:',
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
77
|
+
when: (a) => a.sourcePath === MANUAL_ENTRY,
|
|
78
|
+
validate: (input) => input.trim().length > 0 || 'Cannot be empty',
|
|
79
|
+
},
|
|
80
|
+
]);
|
|
81
|
+
return answers.sourcePath === MANUAL_ENTRY ? answers.manualPath : answers.sourcePath;
|
|
82
|
+
}
|
|
83
|
+
/** Copy the bundled GitHub Actions workflow into the consumer repo. */
|
|
84
|
+
function setupWorkflow(root) {
|
|
85
|
+
const workflowSource = path_1.default.join(__dirname, 'config', 'pr-checkmate-workflow.yml');
|
|
86
|
+
const workflowDir = path_1.default.join(root, '.github', 'workflows');
|
|
87
|
+
const workflowDest = path_1.default.join(workflowDir, 'pr-checkmate.yml');
|
|
88
|
+
if (!fs_1.default.existsSync(workflowSource)) {
|
|
89
|
+
utils_1.logger.warn('[init]: ⚠️ Workflow template not found, skipping workflow setup');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!fs_1.default.existsSync(workflowDir)) {
|
|
93
|
+
fs_1.default.mkdirSync(workflowDir, { recursive: true });
|
|
94
|
+
}
|
|
95
|
+
fs_1.default.copyFileSync(workflowSource, workflowDest);
|
|
96
|
+
utils_1.logger.info('[init]: ✅ Created .github/workflows/pr-checkmate.yml');
|
|
97
|
+
utils_1.logger.info('[init]: GitHub Actions workflow is ready for your PRs!');
|
|
98
|
+
}
|
|
99
|
+
/** Create a tsconfig.json scaffold if the consumer repo lacks one. */
|
|
100
|
+
function setupTsConfig(root, sourcePath) {
|
|
101
|
+
const tsconfigPath = path_1.default.join(root, 'tsconfig.json');
|
|
102
|
+
if (fs_1.default.existsSync(tsconfigPath))
|
|
103
|
+
return;
|
|
104
|
+
const newTsConfig = {
|
|
105
|
+
compilerOptions: {
|
|
106
|
+
target: 'ES2020',
|
|
107
|
+
module: 'NodeNext',
|
|
108
|
+
outDir: 'dist',
|
|
109
|
+
strict: true,
|
|
110
|
+
rootDir: sourcePath,
|
|
111
|
+
moduleResolution: 'NodeNext',
|
|
112
|
+
esModuleInterop: true,
|
|
113
|
+
resolveJsonModule: true,
|
|
114
|
+
forceConsistentCasingInFileNames: true,
|
|
115
|
+
skipLibCheck: true,
|
|
116
|
+
types: ['node'],
|
|
117
|
+
},
|
|
118
|
+
include: [`${sourcePath}/**/*.ts`],
|
|
119
|
+
exclude: ['node_modules', 'dist'],
|
|
120
|
+
};
|
|
121
|
+
fs_1.default.writeFileSync(tsconfigPath, JSON.stringify(newTsConfig, null, 2));
|
|
122
|
+
utils_1.logger.info('[init]: ✅ Created tsconfig.json using your sourcePath');
|
|
123
|
+
}
|
|
124
|
+
/** First-run setup: prompt, write config, and scaffold workflow + tsconfig. */
|
|
125
|
+
async function createConfig(root) {
|
|
126
|
+
const sourcePath = await promptSourcePath();
|
|
127
|
+
const languages = await (0, utils_1.detectLanguages)(root);
|
|
128
|
+
utils_1.logger.info(`[init]: 🔍 Detected languages: ${languages.length ? languages.join(', ') : 'none'}`);
|
|
129
|
+
const config = {
|
|
130
|
+
...DEFAULT_CONFIG,
|
|
131
|
+
sourcePath,
|
|
132
|
+
languages,
|
|
133
|
+
};
|
|
134
|
+
fs_1.default.writeFileSync(exports.CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
135
|
+
utils_1.logger.info(`[init]: ✅ Created pr-checkmate.json with sourcePath: ${sourcePath}`);
|
|
136
|
+
utils_1.logger.info(`[init]: 💡 You can now customize ESLint rules, Prettier settings,
|
|
137
|
+
and cspell words in pr-checkmate.json`);
|
|
138
|
+
setupWorkflow(root);
|
|
139
|
+
setupTsConfig(root, sourcePath);
|
|
140
|
+
}
|
|
141
|
+
/** Re-run setup: merge the existing config with current defaults. */
|
|
142
|
+
async function updateConfig(root) {
|
|
143
|
+
utils_1.logger.info('[init]: pr-checkmate.json already exists — updating it.');
|
|
144
|
+
try {
|
|
145
|
+
const raw = fs_1.default.readFileSync(exports.CONFIG_FILE, 'utf-8');
|
|
146
|
+
const json = JSON.parse(raw);
|
|
85
147
|
const languages = await (0, utils_1.detectLanguages)(root);
|
|
86
148
|
utils_1.logger.info(`[init]: 🔍 Detected languages: ${languages.length ? languages.join(', ') : 'none'}`);
|
|
87
|
-
|
|
149
|
+
// Merge with defaults to add new fields
|
|
150
|
+
const updated = {
|
|
88
151
|
...DEFAULT_CONFIG,
|
|
89
|
-
|
|
152
|
+
...json,
|
|
153
|
+
// Refresh detected languages on every init so new source dirs are picked up
|
|
90
154
|
languages,
|
|
155
|
+
eslint: { ...DEFAULT_CONFIG.eslint, ...(json.eslint || {}) },
|
|
156
|
+
prettier: { ...DEFAULT_CONFIG.prettier, ...(json.prettier || {}) },
|
|
157
|
+
cspell: { ...DEFAULT_CONFIG.cspell, ...(json.cspell || {}) },
|
|
158
|
+
typecheck: { ...DEFAULT_CONFIG.typecheck, ...(json.typecheck || {}) },
|
|
159
|
+
diffSmell: { ...DEFAULT_CONFIG.diffSmell, ...(json.diffSmell || {}) },
|
|
160
|
+
prBody: { ...DEFAULT_CONFIG.prBody, ...(json.prBody || {}) },
|
|
161
|
+
duplicate: { ...DEFAULT_CONFIG.duplicate, ...(json.duplicate || {}) },
|
|
91
162
|
};
|
|
92
|
-
fs_1.default.writeFileSync(exports.CONFIG_FILE, JSON.stringify(
|
|
93
|
-
utils_1.logger.info(
|
|
94
|
-
utils_1.logger.info(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
utils_1.logger.warn('[init]: ⚠️ Workflow template not found, skipping workflow setup');
|
|
110
|
-
}
|
|
111
|
-
const tsconfigPath = path_1.default.join(root, 'tsconfig.json');
|
|
112
|
-
if (!fs_1.default.existsSync(tsconfigPath)) {
|
|
113
|
-
const newTsConfig = {
|
|
114
|
-
compilerOptions: {
|
|
115
|
-
target: 'ES2020',
|
|
116
|
-
module: 'NodeNext',
|
|
117
|
-
outDir: 'dist',
|
|
118
|
-
strict: true,
|
|
119
|
-
rootDir: sourcePath,
|
|
120
|
-
moduleResolution: 'NodeNext',
|
|
121
|
-
esModuleInterop: true,
|
|
122
|
-
resolveJsonModule: true,
|
|
123
|
-
forceConsistentCasingInFileNames: true,
|
|
124
|
-
skipLibCheck: true,
|
|
125
|
-
types: ['node'],
|
|
126
|
-
},
|
|
127
|
-
include: [`${sourcePath}/**/*.ts`],
|
|
128
|
-
exclude: ['node_modules', 'dist'],
|
|
129
|
-
};
|
|
130
|
-
fs_1.default.writeFileSync(tsconfigPath, JSON.stringify(newTsConfig, null, 2));
|
|
131
|
-
utils_1.logger.info('[init]: ✅ Created tsconfig.json using your sourcePath');
|
|
132
|
-
}
|
|
163
|
+
fs_1.default.writeFileSync(exports.CONFIG_FILE, JSON.stringify(updated, null, 2));
|
|
164
|
+
utils_1.logger.info('[init]: 🔧 Updated pr-checkmate.json with customization options.');
|
|
165
|
+
utils_1.logger.info('[init]: 💡 You can now add custom ESLint rules, cspell words, and more!');
|
|
166
|
+
}
|
|
167
|
+
catch (e) {
|
|
168
|
+
utils_1.logger.error(`[init]: ❌ Failed to update pr-checkmate.json: ${e}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Initialize (or refresh) pr-checkmate configuration in the current project.
|
|
173
|
+
*/
|
|
174
|
+
async function init() {
|
|
175
|
+
const root = process.cwd();
|
|
176
|
+
if (fs_1.default.existsSync(exports.CONFIG_FILE)) {
|
|
177
|
+
await updateConfig(root);
|
|
133
178
|
}
|
|
134
179
|
else {
|
|
135
|
-
|
|
136
|
-
try {
|
|
137
|
-
const raw = fs_1.default.readFileSync(exports.CONFIG_FILE, 'utf-8');
|
|
138
|
-
const json = JSON.parse(raw);
|
|
139
|
-
const languages = await (0, utils_1.detectLanguages)(root);
|
|
140
|
-
utils_1.logger.info(`[init]: 🔍 Detected languages: ${languages.length ? languages.join(', ') : 'none'}`);
|
|
141
|
-
// Merge with defaults to add new fields
|
|
142
|
-
const updated = {
|
|
143
|
-
...DEFAULT_CONFIG,
|
|
144
|
-
...json,
|
|
145
|
-
// Refresh detected languages on every init so new source dirs are picked up
|
|
146
|
-
languages,
|
|
147
|
-
eslint: { ...DEFAULT_CONFIG.eslint, ...(json.eslint || {}) },
|
|
148
|
-
prettier: { ...DEFAULT_CONFIG.prettier, ...(json.prettier || {}) },
|
|
149
|
-
cspell: { ...DEFAULT_CONFIG.cspell, ...(json.cspell || {}) },
|
|
150
|
-
typecheck: { ...DEFAULT_CONFIG.typecheck, ...(json.typecheck || {}) },
|
|
151
|
-
diffSmell: { ...DEFAULT_CONFIG.diffSmell, ...(json.diffSmell || {}) },
|
|
152
|
-
prBody: { ...DEFAULT_CONFIG.prBody, ...(json.prBody || {}) },
|
|
153
|
-
duplicate: { ...DEFAULT_CONFIG.duplicate, ...(json.duplicate || {}) },
|
|
154
|
-
};
|
|
155
|
-
fs_1.default.writeFileSync(exports.CONFIG_FILE, JSON.stringify(updated, null, 2));
|
|
156
|
-
utils_1.logger.info('[init]: 🔧 Updated pr-checkmate.json with customization options.');
|
|
157
|
-
utils_1.logger.info('[init]: 💡 You can now add custom ESLint rules, cspell words, and more!');
|
|
158
|
-
}
|
|
159
|
-
catch (e) {
|
|
160
|
-
utils_1.logger.error(`[init]: ❌ Failed to update pr-checkmate.json: ${e}`);
|
|
161
|
-
}
|
|
180
|
+
await createConfig(root);
|
|
162
181
|
}
|
|
163
182
|
}
|
package/dist/utils/index.js
CHANGED
|
@@ -24,6 +24,7 @@ __exportStar(require("./logger"), exports);
|
|
|
24
24
|
__exportStar(require("./delta"), exports);
|
|
25
25
|
__exportStar(require("./result-collector"), exports);
|
|
26
26
|
__exportStar(require("./resolve-bin"), exports);
|
|
27
|
+
__exportStar(require("./own-deps"), exports);
|
|
27
28
|
__exportStar(require("./detect-language"), exports);
|
|
28
29
|
/**
|
|
29
30
|
*
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getOwnDependencyClosure = getOwnDependencyClosure;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const OWN_PACKAGE_NAME = 'pr-checkmate';
|
|
10
|
+
const PACKAGE_JSON = 'package.json';
|
|
11
|
+
let cachedClosure;
|
|
12
|
+
/** Read a package.json's `dependencies` keys, tolerating missing/broken files. */
|
|
13
|
+
function readDependencyNames(pkgDir) {
|
|
14
|
+
try {
|
|
15
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(pkgDir, PACKAGE_JSON), 'utf-8'));
|
|
16
|
+
return Object.keys(pkg.dependencies ?? {});
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Walk up from this module until we find pr-checkmate's own package root. */
|
|
23
|
+
function findOwnPackageRoot() {
|
|
24
|
+
let dir = __dirname;
|
|
25
|
+
while (dir !== node_path_1.default.dirname(dir)) {
|
|
26
|
+
const pkgJsonPath = node_path_1.default.join(dir, PACKAGE_JSON);
|
|
27
|
+
if (node_fs_1.default.existsSync(pkgJsonPath)) {
|
|
28
|
+
try {
|
|
29
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgJsonPath, 'utf-8'));
|
|
30
|
+
if (pkg.name === OWN_PACKAGE_NAME)
|
|
31
|
+
return dir;
|
|
32
|
+
}
|
|
33
|
+
catch { }
|
|
34
|
+
}
|
|
35
|
+
dir = node_path_1.default.dirname(dir);
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve a dependency's install directory by name, starting the search from
|
|
41
|
+
* `fromDir`. Relies on Node's upward node_modules walk so hoisted packages are
|
|
42
|
+
* found regardless of physical layout.
|
|
43
|
+
*/
|
|
44
|
+
function resolvePackageDir(name, fromDir) {
|
|
45
|
+
// Prefer the explicit package.json subpath.
|
|
46
|
+
try {
|
|
47
|
+
return node_path_1.default.dirname(require.resolve(`${name}/package.json`, { paths: [fromDir] }));
|
|
48
|
+
}
|
|
49
|
+
catch { }
|
|
50
|
+
// Fallback for packages whose exports map hides ./package.json: resolve the
|
|
51
|
+
// entry point and walk up to the package's own root.
|
|
52
|
+
try {
|
|
53
|
+
let dir = node_path_1.default.dirname(require.resolve(name, { paths: [fromDir] }));
|
|
54
|
+
while (dir !== node_path_1.default.dirname(dir)) {
|
|
55
|
+
const pkgJsonPath = node_path_1.default.join(dir, PACKAGE_JSON);
|
|
56
|
+
if (node_fs_1.default.existsSync(pkgJsonPath)) {
|
|
57
|
+
try {
|
|
58
|
+
const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgJsonPath, 'utf-8'));
|
|
59
|
+
if (pkg.name === name)
|
|
60
|
+
return dir;
|
|
61
|
+
}
|
|
62
|
+
catch { }
|
|
63
|
+
}
|
|
64
|
+
dir = node_path_1.default.dirname(dir);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch { }
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Compute the full set of package names in pr-checkmate's own dependency
|
|
72
|
+
* closure (direct + transitive runtime dependencies), plus pr-checkmate itself.
|
|
73
|
+
*
|
|
74
|
+
* pr-checkmate installs into the consumer's node_modules, so its own runtime
|
|
75
|
+
* deps (eslint plugins, cspell dictionaries, …) become part of the consumer's
|
|
76
|
+
* production dependency tree. Some carry copyleft licenses that would otherwise
|
|
77
|
+
* trip pr-checkmate's own license check against an innocent client. These are
|
|
78
|
+
* pr-checkmate's dependencies, not the client's, so they must be exempt.
|
|
79
|
+
*
|
|
80
|
+
* Result is cached for the process. Returns a set containing only pr-checkmate
|
|
81
|
+
* itself if the package root cannot be located (defensive; never throws).
|
|
82
|
+
*/
|
|
83
|
+
function getOwnDependencyClosure() {
|
|
84
|
+
if (cachedClosure)
|
|
85
|
+
return cachedClosure;
|
|
86
|
+
const closure = new Set([OWN_PACKAGE_NAME]);
|
|
87
|
+
const root = findOwnPackageRoot();
|
|
88
|
+
if (!root) {
|
|
89
|
+
cachedClosure = closure;
|
|
90
|
+
return closure;
|
|
91
|
+
}
|
|
92
|
+
// BFS over the dependency graph, resolving each package relative to the parent
|
|
93
|
+
// that declared it so nested (non-hoisted) installs resolve correctly.
|
|
94
|
+
const queue = readDependencyNames(root).map(name => ({
|
|
95
|
+
name,
|
|
96
|
+
fromDir: root,
|
|
97
|
+
}));
|
|
98
|
+
while (queue.length > 0) {
|
|
99
|
+
const { name, fromDir } = queue.shift();
|
|
100
|
+
if (closure.has(name))
|
|
101
|
+
continue;
|
|
102
|
+
closure.add(name);
|
|
103
|
+
const depDir = resolvePackageDir(name, fromDir);
|
|
104
|
+
if (!depDir)
|
|
105
|
+
continue;
|
|
106
|
+
for (const child of readDependencyNames(depDir)) {
|
|
107
|
+
if (!closure.has(child))
|
|
108
|
+
queue.push({ name: child, fromDir: depDir });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
cachedClosure = closure;
|
|
112
|
+
return closure;
|
|
113
|
+
}
|
|
@@ -14,6 +14,9 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
14
14
|
* 2. Via package.json — for packages that export ./package.json but not the bin subpath (e.g. eslint)
|
|
15
15
|
* 3. Walk up from main entry — for packages that export neither (e.g. cspell)
|
|
16
16
|
*
|
|
17
|
+
* @param pkgName - The package whose binary to resolve (e.g. `eslint`).
|
|
18
|
+
* @param binRelPath - The bin path relative to the package root (e.g. `bin/eslint.js`).
|
|
19
|
+
* @param options - Resolution options.
|
|
17
20
|
* @param options.paths - Directories to resolve from (passed to `require.resolve`).
|
|
18
21
|
* Use `[process.cwd()]` to prefer the consumer project's copy of the package.
|
|
19
22
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pr-checkmate",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.16",
|
|
4
4
|
"description": "Automated PR quality checks: linting, formatting, dependency analysis, and spellcheck",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"github-actions",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"@typescript-eslint/eslint-plugin": "^8.47.0",
|
|
72
72
|
"@typescript-eslint/parser": "^8.47.0",
|
|
73
73
|
"@wasm-fmt/clang-format": "^22.1.8",
|
|
74
|
-
"cspell": "^
|
|
74
|
+
"cspell": "^10.0.1",
|
|
75
75
|
"depcheck": "^1.4.7",
|
|
76
76
|
"eslint": "^9.26.0",
|
|
77
77
|
"eslint-config-prettier": "^10.1.8",
|
|
@@ -81,8 +81,8 @@
|
|
|
81
81
|
"eslint-plugin-sonarjs": "^4.1.0",
|
|
82
82
|
"execa": "^9.6.0",
|
|
83
83
|
"gitleaks-secret-scanner-fixed": "^2.1.3",
|
|
84
|
-
"inquirer": "^
|
|
85
|
-
"jscpd": "^
|
|
84
|
+
"inquirer": "^14.0.2",
|
|
85
|
+
"jscpd": "^5.0.11",
|
|
86
86
|
"license-checker": "^25.0.1",
|
|
87
87
|
"markdownlint-cli2": "^0.23.0",
|
|
88
88
|
"prettier": ">=3.0.0",
|
package/src/config/cspell.json
CHANGED
|
@@ -189,6 +189,16 @@
|
|
|
189
189
|
"sultansadikov",
|
|
190
190
|
"telaviv",
|
|
191
191
|
"travis",
|
|
192
|
+
"kotlin",
|
|
193
|
+
"Werror",
|
|
194
|
+
"positiving",
|
|
195
|
+
"pyflakes",
|
|
196
|
+
"pycodestyle",
|
|
197
|
+
"swiftlint",
|
|
198
|
+
"pathspec",
|
|
199
|
+
"Pipfile",
|
|
200
|
+
"conanfile",
|
|
201
|
+
"ktlint",
|
|
192
202
|
"Términos",
|
|
193
203
|
"Teudat",
|
|
194
204
|
"Tiempo",
|
|
@@ -543,7 +553,145 @@
|
|
|
543
553
|
"Makefiles",
|
|
544
554
|
"dpdm",
|
|
545
555
|
"commitlintrc",
|
|
546
|
-
"shortstat"
|
|
556
|
+
"shortstat",
|
|
557
|
+
"Cooldown",
|
|
558
|
+
"SCRIPTERROR",
|
|
559
|
+
"attendace",
|
|
560
|
+
"attendance",
|
|
561
|
+
"kupat",
|
|
562
|
+
"gemel",
|
|
563
|
+
"hishtalmut",
|
|
564
|
+
"keren",
|
|
565
|
+
"tofes",
|
|
566
|
+
"Bdikat",
|
|
567
|
+
"Hishuv",
|
|
568
|
+
"formcontrolname",
|
|
569
|
+
"zrufot",
|
|
570
|
+
"Klali",
|
|
571
|
+
"contenteditable",
|
|
572
|
+
"האניקומב",
|
|
573
|
+
"ishur",
|
|
574
|
+
"Yeshut",
|
|
575
|
+
"Nihul",
|
|
576
|
+
"pratim",
|
|
577
|
+
"nispahim",
|
|
578
|
+
"networkidle",
|
|
579
|
+
"domcontentloaded",
|
|
580
|
+
"secapp",
|
|
581
|
+
"taxinfo",
|
|
582
|
+
"gmishurim",
|
|
583
|
+
"ezor",
|
|
584
|
+
"ishi",
|
|
585
|
+
"ishurim",
|
|
586
|
+
"nikui",
|
|
587
|
+
"mekabel",
|
|
588
|
+
"shdochshana",
|
|
589
|
+
"gershayim",
|
|
590
|
+
"Rashum",
|
|
591
|
+
"Shem",
|
|
592
|
+
"Mishpaha",
|
|
593
|
+
"Rshum",
|
|
594
|
+
"Prati",
|
|
595
|
+
"Ktovet",
|
|
596
|
+
"Doar",
|
|
597
|
+
"Megurim",
|
|
598
|
+
"ddlkidomet",
|
|
599
|
+
"Aher",
|
|
600
|
+
"Esek",
|
|
601
|
+
"Isuk",
|
|
602
|
+
"Mazav",
|
|
603
|
+
"Mish",
|
|
604
|
+
"Beyachad",
|
|
605
|
+
"Shlichat",
|
|
606
|
+
"Neezarti",
|
|
607
|
+
"Doch",
|
|
608
|
+
"Klalim",
|
|
609
|
+
"Hehzer",
|
|
610
|
+
"Ozer",
|
|
611
|
+
"נייע",
|
|
612
|
+
"recognised",
|
|
613
|
+
"קולומייץ",
|
|
614
|
+
"אירינה",
|
|
615
|
+
"סרגיי",
|
|
616
|
+
"פולישוק",
|
|
617
|
+
"ולדימיר",
|
|
618
|
+
"moov",
|
|
619
|
+
"virtualenv",
|
|
620
|
+
"pytesseract",
|
|
621
|
+
"qrdet",
|
|
622
|
+
"Jsons",
|
|
623
|
+
"easyocr",
|
|
624
|
+
"EXIF",
|
|
625
|
+
"fliplr",
|
|
626
|
+
"noautorotate",
|
|
627
|
+
"timestep",
|
|
628
|
+
"VAQH",
|
|
629
|
+
"DSBL",
|
|
630
|
+
"MAXU",
|
|
631
|
+
"XCUI",
|
|
632
|
+
"Apks",
|
|
633
|
+
"bootstatus",
|
|
634
|
+
"BEGINSWITH",
|
|
635
|
+
"anypassword",
|
|
636
|
+
"wrongpassword",
|
|
637
|
+
"xmark",
|
|
638
|
+
"didnt",
|
|
639
|
+
"Anthropic",
|
|
640
|
+
"bartowski",
|
|
641
|
+
"specialisation",
|
|
642
|
+
"cursorrules",
|
|
643
|
+
"deepseek",
|
|
644
|
+
"GGUF",
|
|
645
|
+
"hf",
|
|
646
|
+
"llm",
|
|
647
|
+
"machdep",
|
|
648
|
+
"migrat",
|
|
649
|
+
"modelcontextprotocol",
|
|
650
|
+
"mxbai",
|
|
651
|
+
"noheader",
|
|
652
|
+
"codedb",
|
|
653
|
+
"nomic",
|
|
654
|
+
"nounits",
|
|
655
|
+
"nvidia",
|
|
656
|
+
"ollama",
|
|
657
|
+
"qwen",
|
|
658
|
+
"shellcheck",
|
|
659
|
+
"standards",
|
|
660
|
+
"vram",
|
|
661
|
+
"ATATT",
|
|
662
|
+
"ATSTT",
|
|
663
|
+
"AUTOINCREMENT",
|
|
664
|
+
"BEAGENT",
|
|
665
|
+
"CEREBRAS",
|
|
666
|
+
"cpucore",
|
|
667
|
+
"Fingerprinter",
|
|
668
|
+
"FREELLM",
|
|
669
|
+
"ircular",
|
|
670
|
+
"integrat",
|
|
671
|
+
"LOOKBACK",
|
|
672
|
+
"markitdown",
|
|
673
|
+
"memswap",
|
|
674
|
+
"nknown",
|
|
675
|
+
"processlist",
|
|
676
|
+
"unixepoch",
|
|
677
|
+
"unreviewed",
|
|
678
|
+
"xapp",
|
|
679
|
+
"bpoa",
|
|
680
|
+
"installroot",
|
|
681
|
+
"dedups",
|
|
682
|
+
"behaviourally",
|
|
683
|
+
"normalised",
|
|
684
|
+
"раннера",
|
|
685
|
+
"раннере",
|
|
686
|
+
"пайплайн",
|
|
687
|
+
"dind",
|
|
688
|
+
"dlib",
|
|
689
|
+
"pugixml",
|
|
690
|
+
"opencvcommit",
|
|
691
|
+
"litert",
|
|
692
|
+
"SHTPB",
|
|
693
|
+
"FUTX",
|
|
694
|
+
"subdirs"
|
|
547
695
|
],
|
|
548
696
|
"ignorePaths": [
|
|
549
697
|
"**/node_modules/**",
|