pr-checkmate 1.20.5 → 1.21.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/dist/config/pr-checkmate-workflow.yml +97 -0
- package/dist/index.js +55 -11
- package/dist/init.js +2 -2
- package/dist/scripts/circular-deps.js +37 -0
- package/dist/scripts/copy-configs.js +1 -1
- package/dist/scripts/dependency-check.js +29 -20
- package/dist/scripts/index.js +44 -11
- package/dist/scripts/license-check.js +44 -0
- package/dist/scripts/lint.js +8 -13
- package/dist/scripts/npm-audit.js +6 -9
- package/dist/scripts/outdated-deps.js +52 -0
- package/dist/scripts/pr-size-check.js +56 -0
- package/dist/scripts/pr-title-lint.js +40 -0
- package/dist/scripts/prettier-autoformat.js +11 -22
- package/dist/scripts/security.js +17 -25
- package/dist/scripts/spellcheck.js +15 -18
- package/dist/utils/delta.js +53 -0
- package/dist/utils/index.js +17 -0
- package/dist/utils/logger.js +3 -0
- package/package.json +1 -1
- package/src/config/pr-checkmate-workflow.yml +97 -0
|
@@ -145,3 +145,100 @@ jobs:
|
|
|
145
145
|
- name: Run Dependency Check
|
|
146
146
|
run: npx pr-checkmate dependencies
|
|
147
147
|
continue-on-error: true
|
|
148
|
+
|
|
149
|
+
circular-deps:
|
|
150
|
+
name: Circular Dependencies
|
|
151
|
+
runs-on: ubuntu-latest
|
|
152
|
+
steps:
|
|
153
|
+
- name: Checkout code
|
|
154
|
+
uses: actions/checkout@v4
|
|
155
|
+
|
|
156
|
+
- name: Setup Node.js
|
|
157
|
+
uses: actions/setup-node@v4
|
|
158
|
+
with:
|
|
159
|
+
node-version: 20
|
|
160
|
+
|
|
161
|
+
- name: Install dependencies
|
|
162
|
+
run: npm ci
|
|
163
|
+
|
|
164
|
+
- name: Run Circular Dependency Check
|
|
165
|
+
run: npx pr-checkmate circular
|
|
166
|
+
continue-on-error: true
|
|
167
|
+
|
|
168
|
+
license-check:
|
|
169
|
+
name: License Check
|
|
170
|
+
runs-on: ubuntu-latest
|
|
171
|
+
steps:
|
|
172
|
+
- name: Checkout code
|
|
173
|
+
uses: actions/checkout@v4
|
|
174
|
+
|
|
175
|
+
- name: Setup Node.js
|
|
176
|
+
uses: actions/setup-node@v4
|
|
177
|
+
with:
|
|
178
|
+
node-version: 20
|
|
179
|
+
|
|
180
|
+
- name: Install dependencies
|
|
181
|
+
run: npm ci
|
|
182
|
+
|
|
183
|
+
- name: Run License Check
|
|
184
|
+
run: npx pr-checkmate license
|
|
185
|
+
continue-on-error: true
|
|
186
|
+
|
|
187
|
+
pr-size:
|
|
188
|
+
name: PR Size
|
|
189
|
+
runs-on: ubuntu-latest
|
|
190
|
+
steps:
|
|
191
|
+
- name: Checkout code
|
|
192
|
+
uses: actions/checkout@v4
|
|
193
|
+
with:
|
|
194
|
+
fetch-depth: 0
|
|
195
|
+
|
|
196
|
+
- name: Setup Node.js
|
|
197
|
+
uses: actions/setup-node@v4
|
|
198
|
+
with:
|
|
199
|
+
node-version: 20
|
|
200
|
+
|
|
201
|
+
- name: Install dependencies
|
|
202
|
+
run: npm ci
|
|
203
|
+
|
|
204
|
+
- name: Check PR Size
|
|
205
|
+
run: npx pr-checkmate pr-size
|
|
206
|
+
continue-on-error: true
|
|
207
|
+
|
|
208
|
+
pr-title:
|
|
209
|
+
name: PR Title
|
|
210
|
+
runs-on: ubuntu-latest
|
|
211
|
+
steps:
|
|
212
|
+
- name: Checkout code
|
|
213
|
+
uses: actions/checkout@v4
|
|
214
|
+
|
|
215
|
+
- name: Setup Node.js
|
|
216
|
+
uses: actions/setup-node@v4
|
|
217
|
+
with:
|
|
218
|
+
node-version: 20
|
|
219
|
+
|
|
220
|
+
- name: Install dependencies
|
|
221
|
+
run: npm ci
|
|
222
|
+
|
|
223
|
+
- name: Check PR Title
|
|
224
|
+
run: npx pr-checkmate pr-title
|
|
225
|
+
continue-on-error: true
|
|
226
|
+
|
|
227
|
+
outdated-deps:
|
|
228
|
+
name: Outdated Dependencies
|
|
229
|
+
runs-on: ubuntu-latest
|
|
230
|
+
steps:
|
|
231
|
+
- name: Checkout code
|
|
232
|
+
uses: actions/checkout@v4
|
|
233
|
+
|
|
234
|
+
- name: Setup Node.js
|
|
235
|
+
uses: actions/setup-node@v4
|
|
236
|
+
with:
|
|
237
|
+
node-version: 20
|
|
238
|
+
|
|
239
|
+
- name: Install dependencies
|
|
240
|
+
run: npm ci
|
|
241
|
+
|
|
242
|
+
- name: Check Outdated Dependencies
|
|
243
|
+
run: npx pr-checkmate outdated
|
|
244
|
+
continue-on-error: true
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.runNpmAudit = exports.runSecurityScan = exports.runSpellcheck = exports.runPrettier = exports.runDependencyCheck = exports.runLint = exports.hasLocalConfig = exports.getPackageConfigPath = exports.getSourcePath = void 0;
|
|
3
|
+
exports.runOutdatedDeps = exports.runPrTitleLint = exports.runPrSizeCheck = exports.runLicenseCheck = exports.runCircularDeps = exports.runNpmAudit = exports.runSecurityScan = exports.runSpellcheck = exports.runPrettier = exports.runDependencyCheck = exports.runLint = exports.hasLocalConfig = exports.getPackageConfigPath = exports.getSourcePath = void 0;
|
|
4
4
|
exports.runAllChecks = runAllChecks;
|
|
5
5
|
exports.runChecks = runChecks;
|
|
6
6
|
/**
|
|
@@ -14,6 +14,12 @@ const prettier_autoformat_1 = require("./scripts/prettier-autoformat");
|
|
|
14
14
|
const spellcheck_1 = require("./scripts/spellcheck");
|
|
15
15
|
const security_1 = require("./scripts/security");
|
|
16
16
|
const npm_audit_1 = require("./scripts/npm-audit");
|
|
17
|
+
const circular_deps_1 = require("./scripts/circular-deps");
|
|
18
|
+
const license_check_1 = require("./scripts/license-check");
|
|
19
|
+
const pr_size_check_1 = require("./scripts/pr-size-check");
|
|
20
|
+
const pr_title_lint_1 = require("./scripts/pr-title-lint");
|
|
21
|
+
const outdated_deps_1 = require("./scripts/outdated-deps");
|
|
22
|
+
const utils_1 = require("./utils");
|
|
17
23
|
var config_1 = require("./config");
|
|
18
24
|
Object.defineProperty(exports, "getSourcePath", { enumerable: true, get: function () { return config_1.getSourcePath; } });
|
|
19
25
|
Object.defineProperty(exports, "getPackageConfigPath", { enumerable: true, get: function () { return config_1.getPackageConfigPath; } });
|
|
@@ -30,28 +36,66 @@ var security_2 = require("./scripts/security");
|
|
|
30
36
|
Object.defineProperty(exports, "runSecurityScan", { enumerable: true, get: function () { return security_2.runSecurityScan; } });
|
|
31
37
|
var npm_audit_2 = require("./scripts/npm-audit");
|
|
32
38
|
Object.defineProperty(exports, "runNpmAudit", { enumerable: true, get: function () { return npm_audit_2.runNpmAudit; } });
|
|
39
|
+
var circular_deps_2 = require("./scripts/circular-deps");
|
|
40
|
+
Object.defineProperty(exports, "runCircularDeps", { enumerable: true, get: function () { return circular_deps_2.runCircularDeps; } });
|
|
41
|
+
var license_check_2 = require("./scripts/license-check");
|
|
42
|
+
Object.defineProperty(exports, "runLicenseCheck", { enumerable: true, get: function () { return license_check_2.runLicenseCheck; } });
|
|
43
|
+
var pr_size_check_2 = require("./scripts/pr-size-check");
|
|
44
|
+
Object.defineProperty(exports, "runPrSizeCheck", { enumerable: true, get: function () { return pr_size_check_2.runPrSizeCheck; } });
|
|
45
|
+
var pr_title_lint_2 = require("./scripts/pr-title-lint");
|
|
46
|
+
Object.defineProperty(exports, "runPrTitleLint", { enumerable: true, get: function () { return pr_title_lint_2.runPrTitleLint; } });
|
|
47
|
+
var outdated_deps_2 = require("./scripts/outdated-deps");
|
|
48
|
+
Object.defineProperty(exports, "runOutdatedDeps", { enumerable: true, get: function () { return outdated_deps_2.runOutdatedDeps; } });
|
|
33
49
|
/**
|
|
34
|
-
* Run all checks
|
|
35
|
-
*
|
|
50
|
+
* Run all checks with parallel execution where possible.
|
|
51
|
+
* Blocking: lint, deps, npm-audit, security, circular-deps, license.
|
|
52
|
+
* Informational: pr-size, pr-title, outdated, spellcheck.
|
|
53
|
+
* Last: prettier (writes files).
|
|
36
54
|
*/
|
|
37
55
|
async function runAllChecks() {
|
|
38
|
-
|
|
39
|
-
await
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
// Phase 1: blocking checks in parallel
|
|
57
|
+
await Promise.all([
|
|
58
|
+
(0, lint_1.runLint)(),
|
|
59
|
+
(0, dependency_check_1.runDependencyCheck)(),
|
|
60
|
+
(0, npm_audit_1.runNpmAudit)(),
|
|
61
|
+
(0, security_1.runSecurityScan)(),
|
|
62
|
+
(0, circular_deps_1.runCircularDeps)(),
|
|
63
|
+
(0, license_check_1.runLicenseCheck)(),
|
|
64
|
+
]);
|
|
65
|
+
// Phase 2: informational checks in parallel
|
|
66
|
+
await Promise.all([
|
|
67
|
+
(0, pr_size_check_1.runPrSizeCheck)(),
|
|
68
|
+
(0, pr_title_lint_1.runPrTitleLint)(),
|
|
69
|
+
(0, outdated_deps_1.runOutdatedDeps)(),
|
|
70
|
+
(0, spellcheck_1.runSpellcheck)().catch(() => utils_1.logger.warn('⚠️ Spellcheck found issues, continuing...')),
|
|
71
|
+
]);
|
|
72
|
+
// Phase 3: prettier last — writes files
|
|
73
|
+
await (0, prettier_autoformat_1.runPrettier)();
|
|
44
74
|
}
|
|
45
75
|
/**
|
|
46
|
-
* Run checks based on configuration
|
|
76
|
+
* Run checks based on configuration (programmatic API).
|
|
47
77
|
*/
|
|
48
78
|
async function runChecks(config = {}) {
|
|
49
79
|
if (!config.skipLint)
|
|
50
80
|
await (0, lint_1.runLint)();
|
|
51
81
|
if (!config.skipDeps)
|
|
52
82
|
await (0, dependency_check_1.runDependencyCheck)();
|
|
83
|
+
if (!config.skipSecurity)
|
|
84
|
+
await (0, security_1.runSecurityScan)();
|
|
85
|
+
if (!config.skipNpmAudit)
|
|
86
|
+
await (0, npm_audit_1.runNpmAudit)();
|
|
87
|
+
if (!config.skipCircularDeps)
|
|
88
|
+
await (0, circular_deps_1.runCircularDeps)();
|
|
89
|
+
if (!config.skipLicense)
|
|
90
|
+
await (0, license_check_1.runLicenseCheck)();
|
|
91
|
+
if (!config.skipPrSize)
|
|
92
|
+
await (0, pr_size_check_1.runPrSizeCheck)();
|
|
93
|
+
if (!config.skipPrTitle)
|
|
94
|
+
await (0, pr_title_lint_1.runPrTitleLint)();
|
|
95
|
+
if (!config.skipOutdated)
|
|
96
|
+
await (0, outdated_deps_1.runOutdatedDeps)();
|
|
53
97
|
if (!config.skipSpell)
|
|
54
98
|
await (0, spellcheck_1.runSpellcheck)();
|
|
55
|
-
if (!config.skipFormat
|
|
99
|
+
if (!config.skipFormat)
|
|
56
100
|
await (0, prettier_autoformat_1.runPrettier)();
|
|
57
101
|
}
|
package/dist/init.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
4
|
};
|
|
@@ -49,6 +48,7 @@ async function init() {
|
|
|
49
48
|
type: 'input',
|
|
50
49
|
name: 'manualPath',
|
|
51
50
|
message: 'Enter path:',
|
|
51
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
52
52
|
when: (a) => a.sourcePath === 'Enter manually',
|
|
53
53
|
validate: (input) => input.trim().length > 0 || 'Cannot be empty',
|
|
54
54
|
},
|
|
@@ -72,7 +72,7 @@ async function init() {
|
|
|
72
72
|
}
|
|
73
73
|
fs_1.default.copyFileSync(workflowSource, workflowDest);
|
|
74
74
|
utils_1.logger.info('[init]: ✅ Created .github/workflows/pr-checkmate.yml');
|
|
75
|
-
utils_1.logger.info('[init]:
|
|
75
|
+
utils_1.logger.info('[init]: GitHub Actions workflow is ready for your PRs!');
|
|
76
76
|
}
|
|
77
77
|
else {
|
|
78
78
|
utils_1.logger.warn('[init]: ⚠️ Workflow template not found, skipping workflow setup');
|
|
@@ -0,0 +1,37 @@
|
|
|
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.runCircularDeps = runCircularDeps;
|
|
7
|
+
const execa_1 = require("execa");
|
|
8
|
+
const utils_1 = require("../utils");
|
|
9
|
+
const config_1 = require("../config");
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
async function runCircularDeps() {
|
|
12
|
+
utils_1.logger.info('[runCircularDeps]: Checking for circular dependencies...');
|
|
13
|
+
const sourcePath = (0, config_1.getSourcePath)();
|
|
14
|
+
const pattern = node_path_1.default.join(process.cwd(), sourcePath, '**/*.{ts,tsx,js,jsx}');
|
|
15
|
+
try {
|
|
16
|
+
const result = await (0, execa_1.execa)('npx', ['dpdm', '--no-warning', '--no-tree', '--circular', pattern], {
|
|
17
|
+
reject: false,
|
|
18
|
+
});
|
|
19
|
+
const output = (result.stdout + result.stderr).trim();
|
|
20
|
+
// dpdm success message also contains "circular" — match only its error patterns:
|
|
21
|
+
// "Circular Dependencies:" header or numbered entry "[1] src/a.ts -> src/b.ts"
|
|
22
|
+
const hasCircular = /circular dependencies:/i.test(output) || /^\s*\[\d+\]/m.test(output);
|
|
23
|
+
if (hasCircular) {
|
|
24
|
+
utils_1.logger.error('[runCircularDeps]: ❌ Circular dependencies detected:');
|
|
25
|
+
utils_1.logger.error(output);
|
|
26
|
+
throw new Error('Circular dependencies found');
|
|
27
|
+
}
|
|
28
|
+
utils_1.logger.info('[runCircularDeps]: ✅ No circular dependencies found');
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
const error = err;
|
|
32
|
+
if (error.message === 'Circular dependencies found')
|
|
33
|
+
throw err;
|
|
34
|
+
utils_1.logger.error(`[runCircularDeps]: ❌ Circular dep check failed\n${err}`);
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -18,7 +18,7 @@ const configFiles = [
|
|
|
18
18
|
];
|
|
19
19
|
function copyConfigs() {
|
|
20
20
|
const root = node_path_1.default.resolve(__dirname, '../..');
|
|
21
|
-
utils_1.logger.log('[copyConfigs]:
|
|
21
|
+
utils_1.logger.log('[copyConfigs]: Copying config files to package root...\n');
|
|
22
22
|
configFiles.forEach(({ source, dest }) => {
|
|
23
23
|
const srcPath = node_path_1.default.join(root, source);
|
|
24
24
|
const destPath = node_path_1.default.join(root, dest);
|
|
@@ -4,33 +4,42 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.runDependencyCheck = runDependencyCheck;
|
|
7
|
-
const
|
|
7
|
+
const execa_1 = require("execa");
|
|
8
8
|
const utils_1 = require("../utils");
|
|
9
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
10
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
-
/**
|
|
12
|
-
* Basic dependency checks with sourcePath support
|
|
13
|
-
*/
|
|
14
11
|
async function runDependencyCheck() {
|
|
15
|
-
utils_1.logger.info('
|
|
12
|
+
utils_1.logger.info('[runDependencyCheck]: Checking dependencies...');
|
|
16
13
|
const projectRoot = process.cwd();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
utils_1.logger.warn('[runDependencyCheck]: ⚠️ No package.json — skipping dependency check');
|
|
14
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(projectRoot, 'package.json'))) {
|
|
15
|
+
utils_1.logger.warn('[runDependencyCheck]: ⚠️ No package.json found, skipping');
|
|
20
16
|
return;
|
|
21
17
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
try {
|
|
19
|
+
const { stdout } = await (0, execa_1.execa)('npx', ['depcheck', '--json'], { cwd: projectRoot });
|
|
20
|
+
const result = JSON.parse(stdout);
|
|
21
|
+
const unused = result.dependencies ?? [];
|
|
22
|
+
const missing = Object.keys(result.missing ?? {});
|
|
23
|
+
if (unused.length === 0 && missing.length === 0) {
|
|
24
|
+
utils_1.logger.info('[runDependencyCheck]: ✅ Dependencies are clean');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (unused.length > 0) {
|
|
28
|
+
utils_1.logger.warn(`[runDependencyCheck]: ⚠️ ${unused.length} unused dependency(ies):`);
|
|
29
|
+
unused.forEach(dep => utils_1.logger.warn(` ${dep}`));
|
|
30
|
+
}
|
|
31
|
+
if (missing.length > 0) {
|
|
32
|
+
utils_1.logger.error(`[runDependencyCheck]: ❌ ${missing.length} missing dependency(ies):`);
|
|
33
|
+
missing.forEach(dep => utils_1.logger.error(` ${dep} (used in: ${result.missing[dep]?.join(', ')})`));
|
|
34
|
+
throw new Error(`Missing dependencies: ${missing.join(', ')}`);
|
|
35
|
+
}
|
|
36
|
+
// Unused deps are warnings — don't block the build
|
|
27
37
|
}
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
catch (err) {
|
|
39
|
+
const error = err;
|
|
40
|
+
if (error.message?.startsWith('Missing dependencies'))
|
|
41
|
+
throw err;
|
|
42
|
+
utils_1.logger.error(`[runDependencyCheck]: ❌ Dependency check failed\n${err}`);
|
|
43
|
+
throw err;
|
|
34
44
|
}
|
|
35
|
-
utils_1.logger.info('[runDependencyCheck]: ✅ Dependencies OK');
|
|
36
45
|
}
|
package/dist/scripts/index.js
CHANGED
|
@@ -20,6 +20,11 @@ const prettier_autoformat_1 = require("./prettier-autoformat");
|
|
|
20
20
|
const spellcheck_1 = require("./spellcheck");
|
|
21
21
|
const security_1 = require("./security");
|
|
22
22
|
const npm_audit_1 = require("./npm-audit");
|
|
23
|
+
const circular_deps_1 = require("./circular-deps");
|
|
24
|
+
const license_check_1 = require("./license-check");
|
|
25
|
+
const pr_size_check_1 = require("./pr-size-check");
|
|
26
|
+
const pr_title_lint_1 = require("./pr-title-lint");
|
|
27
|
+
const outdated_deps_1 = require("./outdated-deps");
|
|
23
28
|
const utils_1 = require("../utils");
|
|
24
29
|
const lint_1 = require("./lint");
|
|
25
30
|
__exportStar(require("./dependency-check"), exports);
|
|
@@ -27,32 +32,45 @@ __exportStar(require("./lint"), exports);
|
|
|
27
32
|
__exportStar(require("./prettier-autoformat"), exports);
|
|
28
33
|
__exportStar(require("./spellcheck"), exports);
|
|
29
34
|
__exportStar(require("./security"), exports);
|
|
35
|
+
__exportStar(require("./circular-deps"), exports);
|
|
36
|
+
__exportStar(require("./license-check"), exports);
|
|
37
|
+
__exportStar(require("./pr-size-check"), exports);
|
|
38
|
+
__exportStar(require("./pr-title-lint"), exports);
|
|
39
|
+
__exportStar(require("./outdated-deps"), exports);
|
|
30
40
|
async function runJob(jobName) {
|
|
31
41
|
utils_1.logger.info(`[runJob]: 🚀 Running job: ${jobName}`);
|
|
32
42
|
try {
|
|
33
43
|
switch (jobName) {
|
|
34
44
|
case 'all':
|
|
35
|
-
|
|
36
|
-
await
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
// Phase 1: blocking checks in parallel
|
|
46
|
+
await Promise.all([
|
|
47
|
+
(0, lint_1.runLint)(),
|
|
48
|
+
(0, dependency_check_1.runDependencyCheck)(),
|
|
49
|
+
(0, npm_audit_1.runNpmAudit)(),
|
|
50
|
+
(0, security_1.runSecurityScan)(),
|
|
51
|
+
(0, circular_deps_1.runCircularDeps)(),
|
|
52
|
+
(0, license_check_1.runLicenseCheck)(),
|
|
53
|
+
]);
|
|
54
|
+
// Phase 2: informational checks in parallel (never block)
|
|
55
|
+
await Promise.all([
|
|
56
|
+
(0, pr_size_check_1.runPrSizeCheck)(),
|
|
57
|
+
(0, pr_title_lint_1.runPrTitleLint)(),
|
|
58
|
+
(0, outdated_deps_1.runOutdatedDeps)(),
|
|
59
|
+
(0, spellcheck_1.runSpellcheck)().catch(() => utils_1.logger.warn('⚠️ Spellcheck found issues, continuing...')),
|
|
60
|
+
]);
|
|
61
|
+
// Phase 3: prettier last — writes files
|
|
39
62
|
try {
|
|
40
63
|
await (0, prettier_autoformat_1.runPrettier)();
|
|
41
64
|
}
|
|
42
65
|
catch {
|
|
43
66
|
utils_1.logger.warn('⚠️ Prettier failed, continuing...');
|
|
44
67
|
}
|
|
45
|
-
try {
|
|
46
|
-
await (0, spellcheck_1.runSpellcheck)();
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
utils_1.logger.warn('⚠️ Spellcheck failed, continuing...');
|
|
50
|
-
}
|
|
51
68
|
break;
|
|
52
69
|
case 'lint':
|
|
53
70
|
await (0, lint_1.runLint)();
|
|
54
71
|
break;
|
|
55
72
|
case 'deps':
|
|
73
|
+
case 'dependencies':
|
|
56
74
|
await (0, dependency_check_1.runDependencyCheck)();
|
|
57
75
|
break;
|
|
58
76
|
case 'npm-audit':
|
|
@@ -67,8 +85,23 @@ async function runJob(jobName) {
|
|
|
67
85
|
case 'security':
|
|
68
86
|
await (0, security_1.runSecurityScan)();
|
|
69
87
|
break;
|
|
88
|
+
case 'circular':
|
|
89
|
+
await (0, circular_deps_1.runCircularDeps)();
|
|
90
|
+
break;
|
|
91
|
+
case 'license':
|
|
92
|
+
await (0, license_check_1.runLicenseCheck)();
|
|
93
|
+
break;
|
|
94
|
+
case 'pr-size':
|
|
95
|
+
await (0, pr_size_check_1.runPrSizeCheck)();
|
|
96
|
+
break;
|
|
97
|
+
case 'pr-title':
|
|
98
|
+
await (0, pr_title_lint_1.runPrTitleLint)();
|
|
99
|
+
break;
|
|
100
|
+
case 'outdated':
|
|
101
|
+
await (0, outdated_deps_1.runOutdatedDeps)();
|
|
102
|
+
break;
|
|
70
103
|
default:
|
|
71
|
-
utils_1.logger.warn(`[runJob]: Unknown job "${jobName}". Available: all, lint, deps, prettier, spellcheck`);
|
|
104
|
+
utils_1.logger.warn(`[runJob]: Unknown job "${jobName}". Available: all, lint, deps, npm-audit, prettier, spellcheck, security, circular, license, pr-size, pr-title, outdated`);
|
|
72
105
|
}
|
|
73
106
|
}
|
|
74
107
|
catch (err) {
|
|
@@ -0,0 +1,44 @@
|
|
|
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.runLicenseCheck = runLicenseCheck;
|
|
7
|
+
const execa_1 = require("execa");
|
|
8
|
+
const utils_1 = require("../utils");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const FORBIDDEN_LICENSES = ['GPL-2.0', 'GPL-3.0', 'AGPL-3.0', 'LGPL-2.0', 'LGPL-2.1', 'LGPL-3.0'];
|
|
12
|
+
async function runLicenseCheck() {
|
|
13
|
+
utils_1.logger.info('[runLicenseCheck]: Checking dependency licenses...');
|
|
14
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(process.cwd(), 'package.json'))) {
|
|
15
|
+
utils_1.logger.warn('[runLicenseCheck]: ⚠️ No package.json found, skipping');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const { stdout } = await (0, execa_1.execa)('npx', ['license-checker', '--json', '--production'], {
|
|
20
|
+
cwd: process.cwd(),
|
|
21
|
+
});
|
|
22
|
+
const licenses = JSON.parse(stdout);
|
|
23
|
+
const violations = [];
|
|
24
|
+
for (const [pkg, info] of Object.entries(licenses)) {
|
|
25
|
+
const license = info.licenses ?? '';
|
|
26
|
+
if (FORBIDDEN_LICENSES.some(f => license.includes(f))) {
|
|
27
|
+
violations.push(` ${pkg}: ${license}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (violations.length > 0) {
|
|
31
|
+
utils_1.logger.error(`[runLicenseCheck]: ❌ ${violations.length} forbidden license(s) found:`);
|
|
32
|
+
violations.forEach(v => utils_1.logger.error(v));
|
|
33
|
+
throw new Error(`License violations: ${violations.length}`);
|
|
34
|
+
}
|
|
35
|
+
utils_1.logger.info('[runLicenseCheck]: ✅ All licenses are compliant');
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
const error = err;
|
|
39
|
+
if (error.message?.startsWith('License violations'))
|
|
40
|
+
throw err;
|
|
41
|
+
utils_1.logger.error(`[runLicenseCheck]: ❌ License check failed\n${err}`);
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
}
|
package/dist/scripts/lint.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.runLint = runLint;
|
|
7
7
|
const config_1 = require("../config");
|
|
8
8
|
const utils_1 = require("../utils");
|
|
9
|
+
const delta_1 = require("../utils/delta");
|
|
9
10
|
const execa_1 = require("execa");
|
|
10
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
12
|
/**
|
|
@@ -26,6 +27,8 @@ async function runLint() {
|
|
|
26
27
|
const projectRoot = process.cwd();
|
|
27
28
|
const sourcePath = (0, config_1.getSourcePath)();
|
|
28
29
|
const sourceFullPath = node_path_1.default.join(projectRoot, sourcePath);
|
|
30
|
+
const changedFiles = await (0, delta_1.getChangedFiles)(['.ts', '.tsx', '.js', '.jsx']);
|
|
31
|
+
const targets = changedFiles ?? [sourceFullPath];
|
|
29
32
|
const hasLocal = (0, config_1.hasLocalConfig)([
|
|
30
33
|
'eslint.config.mjs',
|
|
31
34
|
'eslint.config.js',
|
|
@@ -35,25 +38,17 @@ async function runLint() {
|
|
|
35
38
|
]);
|
|
36
39
|
let args;
|
|
37
40
|
if (hasLocal) {
|
|
38
|
-
utils_1.logger.info(`[runLint]:
|
|
39
|
-
args = ['eslint',
|
|
41
|
+
utils_1.logger.info(`[runLint]: Using local ESLint config (checking: ${sourcePath})`);
|
|
42
|
+
args = ['eslint', ...targets, '--ext', '.ts,.tsx,.js,.jsx', '--fix'];
|
|
40
43
|
}
|
|
41
44
|
else {
|
|
42
|
-
utils_1.logger.info(
|
|
45
|
+
utils_1.logger.info(`[runLint]: Using pr-checkmate ESLint config (checking: ${sourcePath})`);
|
|
43
46
|
// Detect ESLint version and choose appropriate config
|
|
44
47
|
const eslintVersion = await getESLintMajorVersion();
|
|
45
48
|
const configFile = eslintVersion >= 9 ? 'eslint.config.mjs' : '.eslintrc.js';
|
|
46
49
|
const configPath = (0, config_1.getPackageConfigPath)(configFile);
|
|
47
|
-
utils_1.logger.info(`[runLint]:
|
|
48
|
-
args = [
|
|
49
|
-
'eslint',
|
|
50
|
-
sourceFullPath,
|
|
51
|
-
'--ext',
|
|
52
|
-
'.ts,.tsx,.js,.jsx',
|
|
53
|
-
'--fix',
|
|
54
|
-
'--config',
|
|
55
|
-
configPath,
|
|
56
|
-
];
|
|
50
|
+
utils_1.logger.info(`[runLint]: Detected ESLint v${eslintVersion}, using ${configFile}`);
|
|
51
|
+
args = ['eslint', ...targets, '--ext', '.ts,.tsx,.js,.jsx', '--fix', '--config', configPath];
|
|
57
52
|
}
|
|
58
53
|
config_1.IGNORE_PATTERNS.forEach(p => args.push('--ignore-pattern', p));
|
|
59
54
|
try {
|
|
@@ -7,14 +7,12 @@ exports.runNpmAudit = runNpmAudit;
|
|
|
7
7
|
const utils_1 = require("../utils");
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
-
const
|
|
11
|
-
const node_util_1 = require("node:util");
|
|
12
|
-
const execAsync = (0, node_util_1.promisify)(node_child_process_1.exec);
|
|
10
|
+
const execa_1 = require("execa");
|
|
13
11
|
/**
|
|
14
12
|
* Run npm audit on the current project
|
|
15
13
|
*/
|
|
16
14
|
async function runNpmAudit() {
|
|
17
|
-
utils_1.logger.info('
|
|
15
|
+
utils_1.logger.info('Running npm audit...');
|
|
18
16
|
const projectRoot = process.cwd();
|
|
19
17
|
const packageJsonPath = node_path_1.default.join(projectRoot, 'package.json');
|
|
20
18
|
if (!node_fs_1.default.existsSync(packageJsonPath)) {
|
|
@@ -22,15 +20,13 @@ async function runNpmAudit() {
|
|
|
22
20
|
return;
|
|
23
21
|
}
|
|
24
22
|
try {
|
|
25
|
-
const { stdout, stderr } = await
|
|
23
|
+
const { stdout, stderr } = await (0, execa_1.execa)('npm', ['audit', '--audit-level=moderate'], {
|
|
26
24
|
cwd: projectRoot,
|
|
27
25
|
});
|
|
28
|
-
if (stdout)
|
|
26
|
+
if (stdout)
|
|
29
27
|
utils_1.logger.info(stdout);
|
|
30
|
-
|
|
31
|
-
if (stderr) {
|
|
28
|
+
if (stderr)
|
|
32
29
|
utils_1.logger.warn(stderr);
|
|
33
|
-
}
|
|
34
30
|
utils_1.logger.info('[runNpmAudit]: ✅ NPM audit finished successfully');
|
|
35
31
|
}
|
|
36
32
|
catch (error) {
|
|
@@ -42,5 +38,6 @@ async function runNpmAudit() {
|
|
|
42
38
|
utils_1.logger.warn(err.stderr);
|
|
43
39
|
if (!err.stdout && !err.stderr)
|
|
44
40
|
utils_1.logger.error(err.message);
|
|
41
|
+
throw err;
|
|
45
42
|
}
|
|
46
43
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
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.runOutdatedDeps = runOutdatedDeps;
|
|
7
|
+
const execa_1 = require("execa");
|
|
8
|
+
const utils_1 = require("../utils");
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
async function runOutdatedDeps() {
|
|
12
|
+
utils_1.logger.info('[runOutdatedDeps]: Checking for outdated dependencies...');
|
|
13
|
+
if (!node_fs_1.default.existsSync(node_path_1.default.join(process.cwd(), 'package.json'))) {
|
|
14
|
+
utils_1.logger.warn('[runOutdatedDeps]: ⚠️ No package.json found, skipping');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
// npm outdated exits with code 1 when outdated packages exist — use reject:false
|
|
18
|
+
const result = await (0, execa_1.execa)('npm', ['outdated', '--json'], { reject: false });
|
|
19
|
+
if (!result.stdout?.trim()) {
|
|
20
|
+
utils_1.logger.info('[runOutdatedDeps]: ✅ All dependencies are up to date');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
let outdated;
|
|
24
|
+
try {
|
|
25
|
+
outdated = JSON.parse(result.stdout);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
utils_1.logger.info('[runOutdatedDeps]: ✅ All dependencies are up to date');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const entries = Object.entries(outdated);
|
|
32
|
+
if (entries.length === 0) {
|
|
33
|
+
utils_1.logger.info('[runOutdatedDeps]: ✅ All dependencies are up to date');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const majorBehind = entries.filter(([, info]) => {
|
|
37
|
+
const current = parseInt(info.current?.split('.')[0] ?? '0');
|
|
38
|
+
const latest = parseInt(info.latest?.split('.')[0] ?? '0');
|
|
39
|
+
return latest > current;
|
|
40
|
+
});
|
|
41
|
+
const minorBehind = entries.length - majorBehind.length;
|
|
42
|
+
if (majorBehind.length > 0) {
|
|
43
|
+
utils_1.logger.warn(`[runOutdatedDeps]: ⚠️ ${majorBehind.length} package(s) are major versions behind:`);
|
|
44
|
+
majorBehind.forEach(([pkg, info]) => {
|
|
45
|
+
utils_1.logger.warn(` ${pkg}: ${info.current} → ${info.latest}`);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (minorBehind > 0) {
|
|
49
|
+
utils_1.logger.info(`[runOutdatedDeps]: ${minorBehind} package(s) have minor/patch updates available`);
|
|
50
|
+
}
|
|
51
|
+
// informational only — never throws
|
|
52
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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.runPrSizeCheck = runPrSizeCheck;
|
|
7
|
+
const execa_1 = require("execa");
|
|
8
|
+
const utils_1 = require("../utils");
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const WARN_FILES = 50;
|
|
11
|
+
const WARN_LINES = 1000;
|
|
12
|
+
function readBaseSha() {
|
|
13
|
+
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
14
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
15
|
+
if (eventName !== 'pull_request' || !eventPath || !node_fs_1.default.existsSync(eventPath)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const event = JSON.parse(node_fs_1.default.readFileSync(eventPath, 'utf-8'));
|
|
20
|
+
return event?.pull_request?.base?.sha ?? null;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async function runPrSizeCheck() {
|
|
27
|
+
utils_1.logger.info('[runPrSizeCheck]: Checking PR size...');
|
|
28
|
+
const baseSha = readBaseSha();
|
|
29
|
+
if (!baseSha) {
|
|
30
|
+
utils_1.logger.info('[runPrSizeCheck]: Not in GitHub PR context, skipping');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const { stdout } = await (0, execa_1.execa)('git', ['diff', '--shortstat', baseSha, 'HEAD']);
|
|
35
|
+
// "5 files changed, 123 insertions(+), 45 deletions(-)"
|
|
36
|
+
const filesMatch = stdout.match(/(\d+) files? changed/);
|
|
37
|
+
const insertions = stdout.match(/(\d+) insertion/);
|
|
38
|
+
const deletions = stdout.match(/(\d+) deletion/);
|
|
39
|
+
const fileCount = filesMatch ? parseInt(filesMatch[1]) : 0;
|
|
40
|
+
const lines = (insertions ? parseInt(insertions[1]) : 0) + (deletions ? parseInt(deletions[1]) : 0);
|
|
41
|
+
utils_1.logger.info(`[runPrSizeCheck]: ${fileCount} files changed, ${lines} lines`);
|
|
42
|
+
if (fileCount > WARN_FILES) {
|
|
43
|
+
utils_1.logger.warn(`[runPrSizeCheck]: ⚠️ Large PR: ${fileCount} files changed (recommended < ${WARN_FILES})`);
|
|
44
|
+
}
|
|
45
|
+
if (lines > WARN_LINES) {
|
|
46
|
+
utils_1.logger.warn(`[runPrSizeCheck]: ⚠️ Large PR: ${lines} lines changed (recommended < ${WARN_LINES})`);
|
|
47
|
+
}
|
|
48
|
+
if (fileCount <= WARN_FILES && lines <= WARN_LINES) {
|
|
49
|
+
utils_1.logger.info('[runPrSizeCheck]: ✅ PR size is within limits');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
utils_1.logger.warn(`[runPrSizeCheck]: ⚠️ Could not measure PR size\n${err}`);
|
|
54
|
+
// informational — never block
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
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.runPrTitleLint = runPrTitleLint;
|
|
7
|
+
const utils_1 = require("../utils");
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const CONVENTIONAL_PREFIX = /^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?!?: .+/;
|
|
10
|
+
async function runPrTitleLint() {
|
|
11
|
+
utils_1.logger.info('[runPrTitleLint]: Checking PR title...');
|
|
12
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
13
|
+
if (!eventPath || !node_fs_1.default.existsSync(eventPath)) {
|
|
14
|
+
utils_1.logger.info('[runPrTitleLint]: Not in GitHub CI, skipping');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
let event;
|
|
18
|
+
try {
|
|
19
|
+
event = JSON.parse(node_fs_1.default.readFileSync(eventPath, 'utf-8'));
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
utils_1.logger.warn(`[runPrTitleLint]: ⚠️ Could not read GitHub event\n${err}`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const pr = event.pull_request;
|
|
26
|
+
if (!pr?.title) {
|
|
27
|
+
utils_1.logger.info('[runPrTitleLint]: No PR title found, skipping');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const title = pr.title;
|
|
31
|
+
if (!CONVENTIONAL_PREFIX.test(title)) {
|
|
32
|
+
utils_1.logger.warn(`[runPrTitleLint]: ⚠️ PR title doesn't follow Conventional Commits`);
|
|
33
|
+
utils_1.logger.warn(` Got: "${title}"`);
|
|
34
|
+
utils_1.logger.warn(` Expected: type(scope): description`);
|
|
35
|
+
utils_1.logger.warn(` Types: feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert`);
|
|
36
|
+
// warning only — teams may not use Conventional Commits
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
utils_1.logger.info(`[runPrTitleLint]: ✅ PR title is valid: "${title}"`);
|
|
40
|
+
}
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.runPrettier = runPrettier;
|
|
7
7
|
const config_1 = require("../config");
|
|
8
8
|
const utils_1 = require("../utils");
|
|
9
|
+
const delta_1 = require("../utils/delta");
|
|
9
10
|
const execa_1 = require("execa");
|
|
10
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
@@ -41,7 +42,7 @@ function createTempPrettierConfig() {
|
|
|
41
42
|
// Log customizations
|
|
42
43
|
const customRulesCount = Object.keys(userConfig).length;
|
|
43
44
|
if (customRulesCount > 0) {
|
|
44
|
-
utils_1.logger.info(`[createTempPrettierConfig]:
|
|
45
|
+
utils_1.logger.info(`[createTempPrettierConfig]: Applied ${customRulesCount}
|
|
45
46
|
custom Prettier rules from pr-checkmate.json`);
|
|
46
47
|
utils_1.logger.debug(`[createTempPrettierConfig]: Custom rules: ${JSON.stringify(userConfig)}`);
|
|
47
48
|
}
|
|
@@ -58,24 +59,12 @@ function createTempIgnoreFile() {
|
|
|
58
59
|
node_fs_1.default.writeFileSync(file, config_1.IGNORE_PATTERNS.join('\n'), 'utf-8');
|
|
59
60
|
return file;
|
|
60
61
|
}
|
|
61
|
-
/**
|
|
62
|
-
* Remove temporary file
|
|
63
|
-
*/
|
|
64
|
-
function cleanupTempFile(filePath) {
|
|
65
|
-
try {
|
|
66
|
-
if (node_fs_1.default.existsSync(filePath)) {
|
|
67
|
-
node_fs_1.default.unlinkSync(filePath);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
catch (err) {
|
|
71
|
-
utils_1.logger.warn(`[cleanupTempFile]: ⚠️ Failed cleanup: ${filePath}\n${err}`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
62
|
async function runPrettier() {
|
|
75
|
-
utils_1.logger.info('[runPrettier]:
|
|
63
|
+
utils_1.logger.info('[runPrettier]: Running Prettier...');
|
|
76
64
|
const root = process.cwd();
|
|
77
65
|
const sourcePath = (0, config_1.getSourcePath)();
|
|
78
|
-
const
|
|
66
|
+
const changedFiles = await (0, delta_1.getChangedFiles)(['.ts', '.tsx', '.js', '.jsx', '.json', '.md']);
|
|
67
|
+
const targets = changedFiles ?? [`${node_path_1.default.join(root, sourcePath)}/**/*.{ts,tsx,js,jsx,json,md}`];
|
|
79
68
|
const projectIgnore = node_path_1.default.join(root, '.prettierignore');
|
|
80
69
|
const gitignore = node_path_1.default.join(root, '.gitignore');
|
|
81
70
|
let ignorePath;
|
|
@@ -99,14 +88,14 @@ async function runPrettier() {
|
|
|
99
88
|
let args;
|
|
100
89
|
let tempConfig = null;
|
|
101
90
|
if (hasLocal) {
|
|
102
|
-
utils_1.logger.info(`[runPrettier]:
|
|
103
|
-
args = ['prettier', '--write',
|
|
91
|
+
utils_1.logger.info(`[runPrettier]: Using local Prettier config (checking: ${sourcePath})`);
|
|
92
|
+
args = ['prettier', '--write', ...targets, '--ignore-path', ignorePath];
|
|
104
93
|
}
|
|
105
94
|
else {
|
|
106
|
-
utils_1.logger.info(`[runPrettier]:
|
|
95
|
+
utils_1.logger.info(`[runPrettier]: Using pr-checkmate Prettier config (checking: ${sourcePath})`);
|
|
107
96
|
// Create temporary config with merged settings
|
|
108
97
|
tempConfig = createTempPrettierConfig();
|
|
109
|
-
args = ['prettier', '--write',
|
|
98
|
+
args = ['prettier', '--write', ...targets, '--config', tempConfig, '--ignore-path', ignorePath];
|
|
110
99
|
}
|
|
111
100
|
try {
|
|
112
101
|
await (0, execa_1.execa)('npx', args, { stdio: 'inherit' });
|
|
@@ -119,8 +108,8 @@ async function runPrettier() {
|
|
|
119
108
|
finally {
|
|
120
109
|
// Cleanup temporary files
|
|
121
110
|
if (tempIgnore)
|
|
122
|
-
cleanupTempFile(tempIgnore);
|
|
111
|
+
(0, utils_1.cleanupTempFile)(tempIgnore);
|
|
123
112
|
if (tempConfig)
|
|
124
|
-
cleanupTempFile(tempConfig);
|
|
113
|
+
(0, utils_1.cleanupTempFile)(tempConfig);
|
|
125
114
|
}
|
|
126
115
|
}
|
package/dist/scripts/security.js
CHANGED
|
@@ -6,15 +6,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.runSecurityScan = runSecurityScan;
|
|
7
7
|
const execa_1 = require("execa");
|
|
8
8
|
const utils_1 = require("../utils");
|
|
9
|
-
const node_fs_1 = require("node:fs");
|
|
10
|
-
const node_child_process_1 = require("node:child_process");
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
12
11
|
/**
|
|
13
12
|
* Get HEAD SHA from git
|
|
14
13
|
*/
|
|
15
|
-
function getHeadSha() {
|
|
14
|
+
async function getHeadSha() {
|
|
16
15
|
try {
|
|
17
|
-
|
|
16
|
+
const { stdout } = await (0, execa_1.execa)('git', ['rev-parse', 'HEAD']);
|
|
17
|
+
return stdout.trim();
|
|
18
18
|
}
|
|
19
19
|
catch (err) {
|
|
20
20
|
utils_1.logger.warn(`⚠️ Could not determine HEAD SHA from git\n${err}`);
|
|
@@ -24,28 +24,25 @@ function getHeadSha() {
|
|
|
24
24
|
/**
|
|
25
25
|
* Get BASE SHA (for PR) or parent commit (for push)
|
|
26
26
|
*/
|
|
27
|
-
function getBaseSha() {
|
|
27
|
+
async function getBaseSha() {
|
|
28
28
|
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
29
29
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
30
30
|
try {
|
|
31
|
-
if (eventName === 'pull_request' && eventPath &&
|
|
32
|
-
|
|
33
|
-
const fs = require('node:fs');
|
|
34
|
-
const event = JSON.parse(fs.readFileSync(eventPath, 'utf-8'));
|
|
31
|
+
if (eventName === 'pull_request' && eventPath && node_fs_1.default.existsSync(eventPath)) {
|
|
32
|
+
const event = JSON.parse(node_fs_1.default.readFileSync(eventPath, 'utf-8'));
|
|
35
33
|
const baseSha = event?.pull_request?.base?.sha;
|
|
36
34
|
if (baseSha) {
|
|
37
|
-
utils_1.logger.info(`[getBaseSha]:
|
|
35
|
+
utils_1.logger.info(`[getBaseSha]: Using PR base SHA: ${baseSha}`);
|
|
38
36
|
return baseSha;
|
|
39
37
|
}
|
|
40
38
|
}
|
|
41
39
|
}
|
|
42
40
|
catch (err) {
|
|
43
|
-
utils_1.logger.warn(`[getBaseSha]: ⚠️ Could not read GitHub event, trying git parent commit
|
|
44
|
-
${err}`);
|
|
41
|
+
utils_1.logger.warn(`[getBaseSha]: ⚠️ Could not read GitHub event, trying git parent commit\n${err}`);
|
|
45
42
|
}
|
|
46
|
-
// For push: use parent commit
|
|
47
43
|
try {
|
|
48
|
-
|
|
44
|
+
const { stdout } = await (0, execa_1.execa)('git', ['rev-parse', 'HEAD^']);
|
|
45
|
+
return stdout.trim();
|
|
49
46
|
}
|
|
50
47
|
catch (err) {
|
|
51
48
|
utils_1.logger.warn(`⚠️ Could not determine BASE SHA (might be first commit)\n${err}`);
|
|
@@ -57,11 +54,11 @@ function getBaseSha() {
|
|
|
57
54
|
*/
|
|
58
55
|
function getGitleaksCommand() {
|
|
59
56
|
const localPath = node_path_1.default.join(process.cwd(), 'node_modules', '.bin', 'gitleaks-secret-scanner');
|
|
60
|
-
if (
|
|
61
|
-
utils_1.logger.info('[getGitleaksCommand]:
|
|
57
|
+
if (node_fs_1.default.existsSync(localPath)) {
|
|
58
|
+
utils_1.logger.info('[getGitleaksCommand]: Using local gitleaks-secret-scanner');
|
|
62
59
|
return localPath;
|
|
63
60
|
}
|
|
64
|
-
utils_1.logger.info('[getGitleaksCommand]:
|
|
61
|
+
utils_1.logger.info('[getGitleaksCommand]: Using npx gitleaks-secret-scanner');
|
|
65
62
|
return 'npx';
|
|
66
63
|
}
|
|
67
64
|
/**
|
|
@@ -70,22 +67,19 @@ function getGitleaksCommand() {
|
|
|
70
67
|
async function runSecurityScan() {
|
|
71
68
|
utils_1.logger.info('🔐 Running security scan with gitleaks-secret-scanner...');
|
|
72
69
|
try {
|
|
73
|
-
|
|
74
|
-
const headSha = getHeadSha();
|
|
70
|
+
const headSha = await getHeadSha();
|
|
75
71
|
if (!headSha) {
|
|
76
72
|
utils_1.logger.warn('⚠️ Could not determine HEAD SHA, security scan may not work properly');
|
|
77
73
|
}
|
|
78
|
-
const baseSha = getBaseSha();
|
|
74
|
+
const baseSha = await getBaseSha();
|
|
79
75
|
if (!baseSha) {
|
|
80
76
|
utils_1.logger.warn('⚠️ Could not determine BASE SHA, will scan all commits');
|
|
81
77
|
}
|
|
82
|
-
// Prepare environment
|
|
83
78
|
const env = {
|
|
84
79
|
...process.env,
|
|
85
80
|
...(headSha && { HEAD_SHA: headSha }),
|
|
86
81
|
...(baseSha && { BASE_SHA: baseSha }),
|
|
87
82
|
};
|
|
88
|
-
// Get gitleaks command
|
|
89
83
|
const cmd = getGitleaksCommand();
|
|
90
84
|
const args = cmd === 'npx' ? ['gitleaks-secret-scanner', '--diff-mode', 'ci'] : ['--diff-mode', 'ci'];
|
|
91
85
|
utils_1.logger.info(`Running: ${cmd} ${args.join(' ')}`);
|
|
@@ -97,12 +91,10 @@ async function runSecurityScan() {
|
|
|
97
91
|
utils_1.logger.info('✅ Security scan completed successfully');
|
|
98
92
|
}
|
|
99
93
|
catch (err) {
|
|
100
|
-
// gitleaks returns exit code 1 if secrets are found
|
|
101
|
-
// This is expected behavior, we should inform but not necessarily crash
|
|
102
94
|
if (err instanceof Error && 'exitCode' in err && err.exitCode === 1) {
|
|
103
95
|
utils_1.logger.warn('⚠️ gitleaks found potential secrets - please review gitleaks-report.html');
|
|
104
96
|
utils_1.logger.warn('💡 If this is a false positive, add it to .gitleaksignore');
|
|
105
|
-
throw err;
|
|
97
|
+
throw err;
|
|
106
98
|
}
|
|
107
99
|
utils_1.logger.error('[runSecurityScan]: ❌ Security scan failed');
|
|
108
100
|
throw err;
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.runSpellcheck = runSpellcheck;
|
|
7
7
|
const config_1 = require("../config");
|
|
8
8
|
const utils_1 = require("../utils");
|
|
9
|
+
const delta_1 = require("../utils/delta");
|
|
9
10
|
const execa_1 = require("execa");
|
|
10
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
@@ -44,7 +45,7 @@ function createTempCspellConfig() {
|
|
|
44
45
|
const customWordsCount = (userConfig.words || []).length;
|
|
45
46
|
const customIgnoresCount = (userConfig.ignorePaths || []).length;
|
|
46
47
|
if (customWordsCount > 0) {
|
|
47
|
-
utils_1.logger.info(`[createTempCspellConfig]:
|
|
48
|
+
utils_1.logger.info(`[createTempCspellConfig]: Added ${customWordsCount} custom words from pr-checkmate.json`);
|
|
48
49
|
}
|
|
49
50
|
if (customIgnoresCount > 0) {
|
|
50
51
|
utils_1.logger.info(`[createTempCspellConfig]: 🚫 Added ${customIgnoresCount}
|
|
@@ -55,19 +56,6 @@ function createTempCspellConfig() {
|
|
|
55
56
|
node_fs_1.default.writeFileSync(tempConfigPath, JSON.stringify(mergedConfig, null, 2));
|
|
56
57
|
return tempConfigPath;
|
|
57
58
|
}
|
|
58
|
-
/**
|
|
59
|
-
* Clean up temporary config file
|
|
60
|
-
*/
|
|
61
|
-
function cleanupTempConfig(configPath) {
|
|
62
|
-
try {
|
|
63
|
-
if (node_fs_1.default.existsSync(configPath)) {
|
|
64
|
-
node_fs_1.default.unlinkSync(configPath);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
utils_1.logger.warn(`⚠️ Failed to cleanup temp config: ${err}`);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
59
|
/**
|
|
72
60
|
* Build cspell patterns + ignore patterns
|
|
73
61
|
*/
|
|
@@ -77,10 +65,19 @@ function buildSpellcheckPatterns(projectRoot, sourcePath) {
|
|
|
77
65
|
return [base, ...excludes];
|
|
78
66
|
}
|
|
79
67
|
async function runSpellcheck() {
|
|
80
|
-
utils_1.logger.info('[runSpellcheck]:
|
|
68
|
+
utils_1.logger.info('[runSpellcheck]: Running Spellcheck...');
|
|
81
69
|
const projectRoot = process.cwd();
|
|
82
70
|
const sourcePath = (0, config_1.getSourcePath)();
|
|
83
|
-
const
|
|
71
|
+
const changedFiles = await (0, delta_1.getChangedFiles)([
|
|
72
|
+
'.ts',
|
|
73
|
+
'.tsx',
|
|
74
|
+
'.js',
|
|
75
|
+
'.jsx',
|
|
76
|
+
'.md',
|
|
77
|
+
'.mdx',
|
|
78
|
+
'.json',
|
|
79
|
+
]);
|
|
80
|
+
const patterns = changedFiles ?? buildSpellcheckPatterns(projectRoot, sourcePath);
|
|
84
81
|
const hasLocal = (0, config_1.hasLocalConfig)(['cspell.json', '.cspell.json', 'cspell.config.json']);
|
|
85
82
|
let args;
|
|
86
83
|
let tempConfigPath = null;
|
|
@@ -89,7 +86,7 @@ async function runSpellcheck() {
|
|
|
89
86
|
args = ['cspell', ...patterns, '--no-progress'];
|
|
90
87
|
}
|
|
91
88
|
else {
|
|
92
|
-
utils_1.logger.info(`[runSpellcheck]:
|
|
89
|
+
utils_1.logger.info(`[runSpellcheck]: Using pr-checkmate cspell config (checking: ${sourcePath})`);
|
|
93
90
|
// Create temporary config with merged settings
|
|
94
91
|
tempConfigPath = createTempCspellConfig();
|
|
95
92
|
args = ['cspell', ...patterns, '--config', tempConfigPath, '--no-progress'];
|
|
@@ -105,7 +102,7 @@ async function runSpellcheck() {
|
|
|
105
102
|
finally {
|
|
106
103
|
// Cleanup temporary config
|
|
107
104
|
if (tempConfigPath) {
|
|
108
|
-
|
|
105
|
+
(0, utils_1.cleanupTempFile)(tempConfigPath);
|
|
109
106
|
}
|
|
110
107
|
}
|
|
111
108
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
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.getChangedFiles = getChangedFiles;
|
|
7
|
+
const execa_1 = require("execa");
|
|
8
|
+
const logger_1 = require("./logger");
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
/**
|
|
12
|
+
* Returns list of changed files in the current PR filtered by extension.
|
|
13
|
+
* Returns null when not in a GitHub PR context (falls back to full-project scan).
|
|
14
|
+
*/
|
|
15
|
+
async function getChangedFiles(extensions) {
|
|
16
|
+
const eventName = process.env.GITHUB_EVENT_NAME;
|
|
17
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
18
|
+
if (eventName !== 'pull_request' || !eventPath || !node_fs_1.default.existsSync(eventPath)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
let baseSha = null;
|
|
22
|
+
try {
|
|
23
|
+
const event = JSON.parse(node_fs_1.default.readFileSync(eventPath, 'utf-8'));
|
|
24
|
+
baseSha = event?.pull_request?.base?.sha ?? null;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
if (!baseSha)
|
|
30
|
+
return null;
|
|
31
|
+
try {
|
|
32
|
+
const { stdout } = await (0, execa_1.execa)('git', [
|
|
33
|
+
'diff',
|
|
34
|
+
'--name-only',
|
|
35
|
+
'--diff-filter=ACMR',
|
|
36
|
+
baseSha,
|
|
37
|
+
'HEAD',
|
|
38
|
+
]);
|
|
39
|
+
const files = stdout
|
|
40
|
+
.split('\n')
|
|
41
|
+
.map(f => f.trim())
|
|
42
|
+
.filter(f => f && extensions.some(ext => f.endsWith(ext)))
|
|
43
|
+
.map(f => node_path_1.default.join(process.cwd(), f));
|
|
44
|
+
if (files.length === 0)
|
|
45
|
+
return null;
|
|
46
|
+
logger_1.logger.info(`[delta]: 📂 Delta mode: checking ${files.length} changed file(s)`);
|
|
47
|
+
return files;
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
logger_1.logger.warn(`[delta]: ⚠️ Could not get changed files, falling back to full scan\n${err}`);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
package/dist/utils/index.js
CHANGED
|
@@ -13,5 +13,22 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
|
|
|
13
13
|
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
16
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.cleanupTempFile = cleanupTempFile;
|
|
21
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
22
|
+
const logger_1 = require("./logger");
|
|
17
23
|
__exportStar(require("./logger"), exports);
|
|
24
|
+
__exportStar(require("./delta"), exports);
|
|
25
|
+
function cleanupTempFile(filePath) {
|
|
26
|
+
try {
|
|
27
|
+
if (node_fs_1.default.existsSync(filePath)) {
|
|
28
|
+
node_fs_1.default.unlinkSync(filePath);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
logger_1.logger.warn(`[cleanupTempFile]: ⚠️ Failed cleanup: ${filePath}\n${err}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
package/dist/utils/logger.js
CHANGED
package/package.json
CHANGED
|
@@ -145,3 +145,100 @@ jobs:
|
|
|
145
145
|
- name: Run Dependency Check
|
|
146
146
|
run: npx pr-checkmate dependencies
|
|
147
147
|
continue-on-error: true
|
|
148
|
+
|
|
149
|
+
circular-deps:
|
|
150
|
+
name: Circular Dependencies
|
|
151
|
+
runs-on: ubuntu-latest
|
|
152
|
+
steps:
|
|
153
|
+
- name: Checkout code
|
|
154
|
+
uses: actions/checkout@v4
|
|
155
|
+
|
|
156
|
+
- name: Setup Node.js
|
|
157
|
+
uses: actions/setup-node@v4
|
|
158
|
+
with:
|
|
159
|
+
node-version: 20
|
|
160
|
+
|
|
161
|
+
- name: Install dependencies
|
|
162
|
+
run: npm ci
|
|
163
|
+
|
|
164
|
+
- name: Run Circular Dependency Check
|
|
165
|
+
run: npx pr-checkmate circular
|
|
166
|
+
continue-on-error: true
|
|
167
|
+
|
|
168
|
+
license-check:
|
|
169
|
+
name: License Check
|
|
170
|
+
runs-on: ubuntu-latest
|
|
171
|
+
steps:
|
|
172
|
+
- name: Checkout code
|
|
173
|
+
uses: actions/checkout@v4
|
|
174
|
+
|
|
175
|
+
- name: Setup Node.js
|
|
176
|
+
uses: actions/setup-node@v4
|
|
177
|
+
with:
|
|
178
|
+
node-version: 20
|
|
179
|
+
|
|
180
|
+
- name: Install dependencies
|
|
181
|
+
run: npm ci
|
|
182
|
+
|
|
183
|
+
- name: Run License Check
|
|
184
|
+
run: npx pr-checkmate license
|
|
185
|
+
continue-on-error: true
|
|
186
|
+
|
|
187
|
+
pr-size:
|
|
188
|
+
name: PR Size
|
|
189
|
+
runs-on: ubuntu-latest
|
|
190
|
+
steps:
|
|
191
|
+
- name: Checkout code
|
|
192
|
+
uses: actions/checkout@v4
|
|
193
|
+
with:
|
|
194
|
+
fetch-depth: 0
|
|
195
|
+
|
|
196
|
+
- name: Setup Node.js
|
|
197
|
+
uses: actions/setup-node@v4
|
|
198
|
+
with:
|
|
199
|
+
node-version: 20
|
|
200
|
+
|
|
201
|
+
- name: Install dependencies
|
|
202
|
+
run: npm ci
|
|
203
|
+
|
|
204
|
+
- name: Check PR Size
|
|
205
|
+
run: npx pr-checkmate pr-size
|
|
206
|
+
continue-on-error: true
|
|
207
|
+
|
|
208
|
+
pr-title:
|
|
209
|
+
name: PR Title
|
|
210
|
+
runs-on: ubuntu-latest
|
|
211
|
+
steps:
|
|
212
|
+
- name: Checkout code
|
|
213
|
+
uses: actions/checkout@v4
|
|
214
|
+
|
|
215
|
+
- name: Setup Node.js
|
|
216
|
+
uses: actions/setup-node@v4
|
|
217
|
+
with:
|
|
218
|
+
node-version: 20
|
|
219
|
+
|
|
220
|
+
- name: Install dependencies
|
|
221
|
+
run: npm ci
|
|
222
|
+
|
|
223
|
+
- name: Check PR Title
|
|
224
|
+
run: npx pr-checkmate pr-title
|
|
225
|
+
continue-on-error: true
|
|
226
|
+
|
|
227
|
+
outdated-deps:
|
|
228
|
+
name: Outdated Dependencies
|
|
229
|
+
runs-on: ubuntu-latest
|
|
230
|
+
steps:
|
|
231
|
+
- name: Checkout code
|
|
232
|
+
uses: actions/checkout@v4
|
|
233
|
+
|
|
234
|
+
- name: Setup Node.js
|
|
235
|
+
uses: actions/setup-node@v4
|
|
236
|
+
with:
|
|
237
|
+
node-version: 20
|
|
238
|
+
|
|
239
|
+
- name: Install dependencies
|
|
240
|
+
run: npm ci
|
|
241
|
+
|
|
242
|
+
- name: Check Outdated Dependencies
|
|
243
|
+
run: npx pr-checkmate outdated
|
|
244
|
+
continue-on-error: true
|