pr-checkmate 1.21.0 → 1.21.2
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/.markdownlint.json +10 -0
- package/README.md +251 -71
- package/dist/config/constants.js +4 -0
- package/dist/config/loader.js +8 -0
- package/dist/config/pr-checkmate-workflow.yml +109 -0
- package/dist/index.js +7 -1
- package/dist/init.js +29 -0
- package/dist/scripts/dependency-check.js +34 -22
- package/dist/scripts/diff-smell.js +97 -0
- package/dist/scripts/duplicate-check.js +67 -0
- package/dist/scripts/index.js +78 -20
- package/dist/scripts/license-check.js +2 -1
- package/dist/scripts/markdown-lint.js +62 -0
- package/dist/scripts/npm-audit.js +26 -16
- package/dist/scripts/pr-body.js +62 -0
- package/dist/scripts/pr-summary.js +119 -0
- package/dist/scripts/typecheck.js +34 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/result-collector.js +33 -0
- package/package.json +4 -1
- package/src/config/pr-checkmate-workflow.yml +109 -0
|
@@ -0,0 +1,119 @@
|
|
|
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.runPrSummary = runPrSummary;
|
|
7
|
+
const utils_1 = require("../utils");
|
|
8
|
+
const result_collector_1 = require("../utils/result-collector");
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const STATUS_ICON = {
|
|
11
|
+
pass: '✅',
|
|
12
|
+
warn: '⚠️',
|
|
13
|
+
fail: '❌',
|
|
14
|
+
skip: '⏭️',
|
|
15
|
+
};
|
|
16
|
+
// Unique marker so we can find and update the comment on re-runs
|
|
17
|
+
const COMMENT_MARKER = '<!-- pr-checkmate-summary -->';
|
|
18
|
+
function getPrContext() {
|
|
19
|
+
const token = process.env.GITHUB_TOKEN;
|
|
20
|
+
const repository = process.env.GITHUB_REPOSITORY;
|
|
21
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
22
|
+
if (!token || !repository || !eventPath || !node_fs_1.default.existsSync(eventPath)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
const event = JSON.parse(node_fs_1.default.readFileSync(eventPath, 'utf-8'));
|
|
27
|
+
const prNumber = event?.pull_request?.number;
|
|
28
|
+
if (!prNumber)
|
|
29
|
+
return null;
|
|
30
|
+
const [owner, repo] = repository.split('/');
|
|
31
|
+
return { token, owner, repo, prNumber };
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function buildMarkdownReport(results) {
|
|
38
|
+
const { passed, warned, failed, skipped } = result_collector_1.resultCollector.getSummary();
|
|
39
|
+
const rows = results
|
|
40
|
+
.map(r => {
|
|
41
|
+
const icon = STATUS_ICON[r.status];
|
|
42
|
+
const details = r.details ? ` — ${r.details}` : '';
|
|
43
|
+
return `| ${r.name} | ${icon} ${r.status}${details} |`;
|
|
44
|
+
})
|
|
45
|
+
.join('\n');
|
|
46
|
+
return [
|
|
47
|
+
COMMENT_MARKER,
|
|
48
|
+
'## 🔍 PR CheckMate Summary',
|
|
49
|
+
'',
|
|
50
|
+
`**${passed} passed** · **${warned} warnings** · **${failed} failed** · **${skipped} skipped**`,
|
|
51
|
+
'',
|
|
52
|
+
'| Check | Result |',
|
|
53
|
+
'|---|---|',
|
|
54
|
+
rows,
|
|
55
|
+
'',
|
|
56
|
+
'_Generated by [PR CheckMate](https://www.npmjs.com/package/pr-checkmate)_',
|
|
57
|
+
].join('\n');
|
|
58
|
+
}
|
|
59
|
+
async function upsertGithubComment(body) {
|
|
60
|
+
const ctx = getPrContext();
|
|
61
|
+
if (!ctx) {
|
|
62
|
+
utils_1.logger.info('[runPrSummary]: Skipping GitHub comment (not in PR context or GITHUB_TOKEN missing)');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const { token, owner, repo, prNumber } = ctx;
|
|
66
|
+
const apiBase = `https://api.github.com/repos/${owner}/${repo}`;
|
|
67
|
+
const headers = {
|
|
68
|
+
Authorization: `Bearer ${token}`,
|
|
69
|
+
'User-Agent': 'pr-checkmate',
|
|
70
|
+
Accept: 'application/vnd.github.v3+json',
|
|
71
|
+
'Content-Type': 'application/json',
|
|
72
|
+
};
|
|
73
|
+
try {
|
|
74
|
+
// Look for an existing pr-checkmate comment to update (avoids spam on repeated pushes)
|
|
75
|
+
const listRes = await fetch(`${apiBase}/issues/${prNumber}/comments`, { headers });
|
|
76
|
+
if (listRes.ok) {
|
|
77
|
+
const comments = (await listRes.json());
|
|
78
|
+
const existing = comments.find(c => typeof c.body === 'string' && c.body.includes(COMMENT_MARKER));
|
|
79
|
+
if (existing) {
|
|
80
|
+
await fetch(`${apiBase}/issues/comments/${existing.id}`, {
|
|
81
|
+
method: 'PATCH',
|
|
82
|
+
headers,
|
|
83
|
+
body: JSON.stringify({ body }),
|
|
84
|
+
});
|
|
85
|
+
utils_1.logger.info('[runPrSummary]: ✅ Updated PR CheckMate comment on PR');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// No existing comment — post a new one
|
|
90
|
+
await fetch(`${apiBase}/issues/${prNumber}/comments`, {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
headers,
|
|
93
|
+
body: JSON.stringify({ body }),
|
|
94
|
+
});
|
|
95
|
+
utils_1.logger.info('[runPrSummary]: ✅ Posted PR CheckMate summary comment to PR');
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
utils_1.logger.warn(`[runPrSummary]: ⚠️ Could not post GitHub comment: ${err}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function runPrSummary() {
|
|
102
|
+
const results = result_collector_1.resultCollector.getResults();
|
|
103
|
+
if (results.length === 0)
|
|
104
|
+
return;
|
|
105
|
+
const { passed, warned, failed, skipped } = result_collector_1.resultCollector.getSummary();
|
|
106
|
+
utils_1.logger.info('');
|
|
107
|
+
utils_1.logger.info('═══════════════════════════════════════════════════════════════');
|
|
108
|
+
utils_1.logger.info(' PR CheckMate Summary');
|
|
109
|
+
utils_1.logger.info('═══════════════════════════════════════════════════════════════');
|
|
110
|
+
for (const r of results) {
|
|
111
|
+
const icon = STATUS_ICON[r.status];
|
|
112
|
+
const details = r.details ? `: ${r.details}` : '';
|
|
113
|
+
utils_1.logger.info(` ${icon} ${r.name}${details}`);
|
|
114
|
+
}
|
|
115
|
+
utils_1.logger.info('───────────────────────────────────────────────────────────────');
|
|
116
|
+
utils_1.logger.info(` ✅ ${passed} passed ⚠️ ${warned} warnings ❌ ${failed} failed ⏭️ ${skipped} skipped`);
|
|
117
|
+
utils_1.logger.info('═══════════════════════════════════════════════════════════════');
|
|
118
|
+
await upsertGithubComment(buildMarkdownReport(results));
|
|
119
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
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.runTypecheck = runTypecheck;
|
|
7
|
+
const execa_1 = require("execa");
|
|
8
|
+
const utils_1 = require("../utils");
|
|
9
|
+
const loader_1 = require("../config/loader");
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
async function runTypecheck() {
|
|
13
|
+
const cfg = (0, loader_1.loadProjectConfig)();
|
|
14
|
+
if (cfg.typecheck?.enabled === false) {
|
|
15
|
+
utils_1.logger.info('[runTypecheck]: Disabled in pr-checkmate.json, skipping');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
utils_1.logger.info('[runTypecheck]: Running TypeScript type check...');
|
|
19
|
+
const tsConfigPath = node_path_1.default.join(process.cwd(), 'tsconfig.json');
|
|
20
|
+
if (!node_fs_1.default.existsSync(tsConfigPath)) {
|
|
21
|
+
utils_1.logger.info('[runTypecheck]: No tsconfig.json found, skipping');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const result = await (0, execa_1.execa)('npx', ['tsc', '--noEmit'], { reject: false });
|
|
25
|
+
if (result.exitCode !== 0) {
|
|
26
|
+
const output = [result.stdout, result.stderr].filter(Boolean).join('\n');
|
|
27
|
+
const errorCount = (output.match(/error TS\d+/g) ?? []).length;
|
|
28
|
+
utils_1.logger.warn(`[runTypecheck]: ⚠️ TypeScript found ${errorCount} type error(s):`);
|
|
29
|
+
if (output)
|
|
30
|
+
utils_1.logger.warn(output);
|
|
31
|
+
throw new Error(`TypeScript: ${errorCount} type error(s)`);
|
|
32
|
+
}
|
|
33
|
+
utils_1.logger.info('[runTypecheck]: ✅ TypeScript type check passed');
|
|
34
|
+
}
|
package/dist/utils/index.js
CHANGED
|
@@ -22,6 +22,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
22
22
|
const logger_1 = require("./logger");
|
|
23
23
|
__exportStar(require("./logger"), exports);
|
|
24
24
|
__exportStar(require("./delta"), exports);
|
|
25
|
+
__exportStar(require("./result-collector"), exports);
|
|
25
26
|
function cleanupTempFile(filePath) {
|
|
26
27
|
try {
|
|
27
28
|
if (node_fs_1.default.existsSync(filePath)) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resultCollector = void 0;
|
|
4
|
+
class ResultCollector {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.results = [];
|
|
7
|
+
}
|
|
8
|
+
register(name, status, details) {
|
|
9
|
+
const idx = this.results.findIndex(r => r.name === name);
|
|
10
|
+
const entry = { name, status, ...(details && { details }) };
|
|
11
|
+
if (idx >= 0) {
|
|
12
|
+
this.results[idx] = entry;
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
this.results.push(entry);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
getResults() {
|
|
19
|
+
return [...this.results];
|
|
20
|
+
}
|
|
21
|
+
clear() {
|
|
22
|
+
this.results = [];
|
|
23
|
+
}
|
|
24
|
+
getSummary() {
|
|
25
|
+
return {
|
|
26
|
+
passed: this.results.filter(r => r.status === 'pass').length,
|
|
27
|
+
warned: this.results.filter(r => r.status === 'warn').length,
|
|
28
|
+
failed: this.results.filter(r => r.status === 'fail').length,
|
|
29
|
+
skipped: this.results.filter(r => r.status === 'skip').length,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.resultCollector = new ResultCollector();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pr-checkmate",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.2",
|
|
4
4
|
"description": "Automated PR quality checks: linting, formatting, dependency analysis, and spellcheck",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"github-actions",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
".eslintignore",
|
|
27
27
|
"cspell.json",
|
|
28
28
|
"src/config/pr-checkmate-workflow.yml",
|
|
29
|
+
".markdownlint.json",
|
|
29
30
|
"LICENSE"
|
|
30
31
|
],
|
|
31
32
|
"scripts": {
|
|
@@ -73,6 +74,8 @@
|
|
|
73
74
|
"eslint-plugin-prettier": "^5.5.4",
|
|
74
75
|
"execa": "^9.6.0",
|
|
75
76
|
"gitleaks-secret-scanner-fixed": "^2.1.3",
|
|
77
|
+
"jscpd": "^3.5.10",
|
|
78
|
+
"markdownlint-cli": "^0.45.0",
|
|
76
79
|
"inquirer": "^13.0.1",
|
|
77
80
|
"prettier": ">=3.0.0",
|
|
78
81
|
"ts-node": "^10.9.1",
|
|
@@ -242,3 +242,112 @@ jobs:
|
|
|
242
242
|
- name: Check Outdated Dependencies
|
|
243
243
|
run: npx pr-checkmate outdated
|
|
244
244
|
continue-on-error: true
|
|
245
|
+
|
|
246
|
+
markdown-lint:
|
|
247
|
+
name: Markdown Lint
|
|
248
|
+
runs-on: ubuntu-latest
|
|
249
|
+
steps:
|
|
250
|
+
- name: Checkout code
|
|
251
|
+
uses: actions/checkout@v4
|
|
252
|
+
with:
|
|
253
|
+
fetch-depth: 0
|
|
254
|
+
|
|
255
|
+
- name: Setup Node.js
|
|
256
|
+
uses: actions/setup-node@v4
|
|
257
|
+
with:
|
|
258
|
+
node-version: 20
|
|
259
|
+
|
|
260
|
+
- name: Install dependencies
|
|
261
|
+
run: npm ci
|
|
262
|
+
|
|
263
|
+
- name: Run Markdown Lint
|
|
264
|
+
run: npx pr-checkmate markdown
|
|
265
|
+
continue-on-error: true
|
|
266
|
+
|
|
267
|
+
typecheck:
|
|
268
|
+
name: TypeScript Type Check
|
|
269
|
+
runs-on: ubuntu-latest
|
|
270
|
+
steps:
|
|
271
|
+
- name: Checkout code
|
|
272
|
+
uses: actions/checkout@v4
|
|
273
|
+
with:
|
|
274
|
+
ref: ${{ github.head_ref }}
|
|
275
|
+
fetch-depth: 0
|
|
276
|
+
|
|
277
|
+
- name: Setup Node.js
|
|
278
|
+
uses: actions/setup-node@v4
|
|
279
|
+
with:
|
|
280
|
+
node-version: 20
|
|
281
|
+
|
|
282
|
+
- name: Install dependencies
|
|
283
|
+
run: npm ci
|
|
284
|
+
|
|
285
|
+
- name: Run TypeScript Type Check
|
|
286
|
+
run: npx pr-checkmate typecheck
|
|
287
|
+
continue-on-error: true
|
|
288
|
+
|
|
289
|
+
diff-smell:
|
|
290
|
+
name: Diff Code Smell
|
|
291
|
+
runs-on: ubuntu-latest
|
|
292
|
+
steps:
|
|
293
|
+
- name: Checkout code
|
|
294
|
+
uses: actions/checkout@v4
|
|
295
|
+
with:
|
|
296
|
+
ref: ${{ github.head_ref }}
|
|
297
|
+
fetch-depth: 0
|
|
298
|
+
|
|
299
|
+
- name: Setup Node.js
|
|
300
|
+
uses: actions/setup-node@v4
|
|
301
|
+
with:
|
|
302
|
+
node-version: 20
|
|
303
|
+
|
|
304
|
+
- name: Install dependencies
|
|
305
|
+
run: npm ci
|
|
306
|
+
|
|
307
|
+
- name: Scan Diff for Code Smells
|
|
308
|
+
run: npx pr-checkmate diff-smell
|
|
309
|
+
continue-on-error: true
|
|
310
|
+
|
|
311
|
+
pr-body:
|
|
312
|
+
name: PR Description
|
|
313
|
+
runs-on: ubuntu-latest
|
|
314
|
+
steps:
|
|
315
|
+
- name: Checkout code
|
|
316
|
+
uses: actions/checkout@v4
|
|
317
|
+
with:
|
|
318
|
+
ref: ${{ github.head_ref }}
|
|
319
|
+
fetch-depth: 0
|
|
320
|
+
|
|
321
|
+
- name: Setup Node.js
|
|
322
|
+
uses: actions/setup-node@v4
|
|
323
|
+
with:
|
|
324
|
+
node-version: 20
|
|
325
|
+
|
|
326
|
+
- name: Install dependencies
|
|
327
|
+
run: npm ci
|
|
328
|
+
|
|
329
|
+
- name: Check PR Description
|
|
330
|
+
run: npx pr-checkmate pr-body
|
|
331
|
+
continue-on-error: true
|
|
332
|
+
|
|
333
|
+
duplicate-check:
|
|
334
|
+
name: Duplicate Code
|
|
335
|
+
runs-on: ubuntu-latest
|
|
336
|
+
steps:
|
|
337
|
+
- name: Checkout code
|
|
338
|
+
uses: actions/checkout@v4
|
|
339
|
+
with:
|
|
340
|
+
ref: ${{ github.head_ref }}
|
|
341
|
+
fetch-depth: 0
|
|
342
|
+
|
|
343
|
+
- name: Setup Node.js
|
|
344
|
+
uses: actions/setup-node@v4
|
|
345
|
+
with:
|
|
346
|
+
node-version: 20
|
|
347
|
+
|
|
348
|
+
- name: Install dependencies
|
|
349
|
+
run: npm ci
|
|
350
|
+
|
|
351
|
+
- name: Check for Duplicate Code
|
|
352
|
+
run: npx pr-checkmate duplicate
|
|
353
|
+
continue-on-error: true
|