pr-checkmate 1.21.16 → 1.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.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
@@ -1,15 +1,12 @@
1
1
  # ♟️ PR CheckMate
2
2
 
3
3
  ![Build](https://img.shields.io/github/actions/workflow/status/Mybono/sdk_automation/publish.yml?branch=main&label=CI&color=blue)
4
- ![npm](https://img.shields.io/npm/v/pr-checkmate?color=blue&label=npm)
5
4
  ![License](https://img.shields.io/badge/license-Proprietary-lightgrey)
6
-
7
- **Bundled tools:**
8
- ![ESLint](https://img.shields.io/badge/ESLint-9-4B32C3?logo=eslint&logoColor=white)
5
+ ![ESLint](https://img.shields.io/badge/ESLint-10-4B32C3?logo=eslint&logoColor=white)
9
6
  ![Prettier](https://img.shields.io/badge/Prettier-3-F7B93E?logo=prettier&logoColor=white)
10
- ![TypeScript](https://img.shields.io/badge/TypeScript-5.9-3178C6?logo=typescript&logoColor=white)
7
+ ![TypeScript](https://img.shields.io/badge/TypeScript-6-3178C6?logo=typescript&logoColor=white)
11
8
  ![cspell](https://img.shields.io/badge/cspell-10-4285F4)
12
- ![jscpd](https://img.shields.io/badge/jscpd-3.5-blue)
9
+ ![jscpd](https://img.shields.io/badge/jscpd-5-blue)
13
10
 
14
11
  > **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.
15
12
 
package/cspell.json CHANGED
@@ -190,6 +190,8 @@
190
190
  "telaviv",
191
191
  "travis",
192
192
  "kotlin",
193
+ "kucherenko",
194
+ "opencollective",
193
195
  "Werror",
194
196
  "positiving",
195
197
  "pyflakes",
@@ -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*=/,
@@ -113,32 +113,6 @@ function scanDiff(stdout) {
113
113
  }
114
114
  return { errors, warnings };
115
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
- }
142
116
  exports.diffSecurityCheck = {
143
117
  name: 'Diff Security',
144
118
  phase: 'informational',
@@ -147,22 +121,21 @@ exports.diffSecurityCheck = {
147
121
  },
148
122
  async run(ctx) {
149
123
  try {
150
- const { stdout } = await (0, execa_1.execa)('git', [
151
- 'diff',
152
- '--unified=0',
153
- '--diff-filter=ACMR',
154
- ctx.baseSha,
155
- 'HEAD',
156
- '--',
124
+ const stdout = await (0, findings_1.diffAddedLines)(ctx, [
157
125
  '*.ts',
158
126
  '*.tsx',
159
127
  '*.js',
160
128
  '*.jsx',
161
129
  '*.mjs',
162
130
  '*.cjs',
163
- ], { cwd: ctx.cwd });
131
+ ]);
164
132
  const { errors, warnings } = scanDiff(stdout);
165
- return reportFindings(errors, warnings);
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
+ });
166
139
  }
167
140
  catch (err) {
168
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 { stdout } = await (0, execa_1.execa)('git', [
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
- const total = Object.values(findings).reduce((sum, arr) => sum + arr.length, 0);
47
- if (total === 0)
35
+ if ((0, findings_1.bucketTotal)(findings) === 0)
48
36
  return (0, types_1.pass)();
49
- for (const [label, occurrences] of Object.entries(findings)) {
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',
@@ -40,7 +57,7 @@ exports.duplicateCheck = {
40
57
  'node_modules/**,dist/**,.git/**',
41
58
  ...targetPaths,
42
59
  ], { cwd: ctx.cwd, reject: false });
43
- const output = [result.stdout, result.stderr].filter(Boolean).join('\n');
60
+ const output = stripJscpdPromo([result.stdout, result.stderr].filter(Boolean).join('\n'));
44
61
  const clonesMatch = output.match(/(\d+)\s+clone/i);
45
62
  const cloneCount = clonesMatch ? parseInt(clonesMatch[1], 10) : 0;
46
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
+ }
@@ -38,7 +38,14 @@ exports.lintCheck = {
38
38
  '.eslintrc.json',
39
39
  '.eslintrc',
40
40
  ]);
41
- const args = [...targets, '--ext', '.ts,.tsx,.js,.jsx'];
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})`);
@@ -4,6 +4,7 @@ exports.sensitiveFileGuardCheck = void 0;
4
4
  const execa_1 = require("execa");
5
5
  const utils_1 = require("../../utils");
6
6
  const types_1 = require("../types");
7
+ const findings_1 = require("./findings");
7
8
  const SENSITIVE_CATEGORIES = [
8
9
  {
9
10
  label: 'Environment files (.env)',
@@ -68,30 +69,6 @@ function categorizeFiles(changedFiles) {
68
69
  }
69
70
  return { errors, warnings };
70
71
  }
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
- }
95
72
  exports.sensitiveFileGuardCheck = {
96
73
  name: 'Sensitive Files',
97
74
  phase: 'informational',
@@ -105,7 +82,12 @@ exports.sensitiveFileGuardCheck = {
105
82
  if (changedFiles.length === 0)
106
83
  return (0, types_1.pass)();
107
84
  const { errors, warnings } = categorizeFiles(changedFiles);
108
- return reportFiles(errors, warnings);
85
+ return (0, findings_1.reportSeverityBuckets)(errors, warnings, {
86
+ prefix: 'Sensitive Files',
87
+ errorSummary: n => `${n} sensitive file(s) changed`,
88
+ warningSummary: n => `${n} notable file(s) changed`,
89
+ showCount: false,
90
+ });
109
91
  }
110
92
  catch (err) {
111
93
  utils_1.logger.warn(`[Sensitive Files]: ⚠️ Could not check file changes\n${err}`);
@@ -32,6 +32,15 @@ exports.typecheckCheck = {
32
32
  return node_fs_1.default.existsSync(node_path_1.default.join(ctx.cwd, 'tsconfig.json'));
33
33
  },
34
34
  async run(ctx) {
35
+ // tsc needs the project's installed type declarations — everything listed in
36
+ // tsconfig's `types`, plus every imported @types package — to resolve. When the
37
+ // project's dependencies aren't installed (e.g. a CI step that runs pr-checkmate
38
+ // via npx without a prior `npm ci`), tsc emits a wall of cryptic TS2688
39
+ // "cannot find type definition file" errors that look like code problems but are
40
+ // really a setup gap. Skip with an actionable message instead of failing the PR.
41
+ if (!node_fs_1.default.existsSync(node_path_1.default.join(ctx.cwd, 'node_modules'))) {
42
+ return (0, types_1.skip)('project dependencies not installed (no node_modules) — run `npm ci` before typecheck');
43
+ }
35
44
  const tscBin = resolveTsc(ctx.cwd);
36
45
  const result = await (0, execa_1.execa)(process.execPath, [tscBin, '--noEmit'], {
37
46
  cwd: ctx.cwd,
package/eslint.config.mjs CHANGED
@@ -4,7 +4,7 @@ import tsPlugin from '@typescript-eslint/eslint-plugin';
4
4
  import tsParser from '@typescript-eslint/parser';
5
5
  import prettierPlugin from 'eslint-plugin-prettier';
6
6
  import sonarjsPlugin from 'eslint-plugin-sonarjs';
7
- import importPlugin from 'eslint-plugin-import';
7
+ import importPlugin from 'eslint-plugin-import-x';
8
8
  import jsdocPlugin from 'eslint-plugin-jsdoc';
9
9
 
10
10
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-checkmate",
3
- "version": "1.21.16",
3
+ "version": "1.22.1",
4
4
  "description": "Automated PR quality checks: linting, formatting, dependency analysis, and spellcheck",
5
5
  "keywords": [
6
6
  "github-actions",
@@ -68,14 +68,14 @@
68
68
  "@cspell/dict-ru_ru": "^2.2.1",
69
69
  "@cspell/dict-uk-ua": "^4.0.0",
70
70
  "@types/node": "^26.1.0",
71
- "@typescript-eslint/eslint-plugin": "^8.47.0",
72
- "@typescript-eslint/parser": "^8.47.0",
71
+ "@typescript-eslint/eslint-plugin": "^8.62.1",
72
+ "@typescript-eslint/parser": "^8.62.1",
73
73
  "@wasm-fmt/clang-format": "^22.1.8",
74
74
  "cspell": "^10.0.1",
75
75
  "depcheck": "^1.4.7",
76
- "eslint": "^9.26.0",
76
+ "eslint": "^10.6.0",
77
77
  "eslint-config-prettier": "^10.1.8",
78
- "eslint-plugin-import": "^2.29.0",
78
+ "eslint-plugin-import-x": "^4.17.1",
79
79
  "eslint-plugin-jsdoc": "^63.0.12",
80
80
  "eslint-plugin-prettier": "^5.5.4",
81
81
  "eslint-plugin-sonarjs": "^4.1.0",
@@ -88,7 +88,7 @@
88
88
  "prettier": ">=3.0.0",
89
89
  "ts-node": "^10.9.1",
90
90
  "ts-unused-exports": "^11.0.1",
91
- "typescript": "^5.9.3"
91
+ "typescript": "^6.0.3"
92
92
  },
93
93
  "devDependencies": {
94
94
  "standard-version": "^9.5.0"
@@ -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',
@@ -190,6 +190,8 @@
190
190
  "telaviv",
191
191
  "travis",
192
192
  "kotlin",
193
+ "kucherenko",
194
+ "opencollective",
193
195
  "Werror",
194
196
  "positiving",
195
197
  "pyflakes",
@@ -4,7 +4,7 @@ import tsPlugin from '@typescript-eslint/eslint-plugin';
4
4
  import tsParser from '@typescript-eslint/parser';
5
5
  import prettierPlugin from 'eslint-plugin-prettier';
6
6
  import sonarjsPlugin from 'eslint-plugin-sonarjs';
7
- import importPlugin from 'eslint-plugin-import';
7
+ import importPlugin from 'eslint-plugin-import-x';
8
8
  import jsdocPlugin from 'eslint-plugin-jsdoc';
9
9
 
10
10
  /**