pr-checkmate 1.22.7 → 1.23.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/README.md +101 -333
- package/cspell.json +9 -1
- package/dist/cli.js +32 -2
- package/dist/config/constants.d.ts +25 -1
- package/dist/config/constants.js +157 -38
- package/dist/config/index.d.ts +15 -2
- package/dist/config/index.js +28 -7
- package/dist/config/loader.js +5 -1
- package/dist/core/checks/dependencies/banned-imports.d.ts +2 -0
- package/dist/core/checks/dependencies/banned-imports.js +71 -0
- package/dist/core/checks/{circular-deps.d.ts → dependencies/circular-deps.d.ts} +1 -1
- package/dist/core/checks/{circular-deps.js → dependencies/circular-deps.js} +9 -10
- package/dist/core/checks/{dependency-check.d.ts → dependencies/dependency-check.d.ts} +1 -1
- package/dist/core/checks/{dependency-check.js → dependencies/dependency-check.js} +3 -3
- package/dist/core/checks/{license-check.d.ts → dependencies/license-check.d.ts} +1 -1
- package/dist/core/checks/{license-check.js → dependencies/license-check.js} +3 -3
- package/dist/core/checks/{npm-audit.d.ts → dependencies/npm-audit.d.ts} +1 -1
- package/dist/core/checks/{npm-audit.js → dependencies/npm-audit.js} +3 -3
- package/dist/core/checks/{outdated-deps.d.ts → dependencies/outdated-deps.d.ts} +1 -1
- package/dist/core/checks/{outdated-deps.js → dependencies/outdated-deps.js} +3 -3
- package/dist/core/checks/dependencies/vuln-scan.d.ts +9 -0
- package/dist/core/checks/dependencies/vuln-scan.js +41 -0
- package/dist/core/checks/findings.d.ts +37 -1
- package/dist/core/checks/findings.js +80 -3
- package/dist/core/checks/git/custom-rules.d.ts +2 -0
- package/dist/core/checks/git/custom-rules.js +102 -0
- package/dist/core/checks/{diff-smell.d.ts → git/diff-smell.d.ts} +1 -1
- package/dist/core/checks/{diff-smell.js → git/diff-smell.js} +3 -3
- package/dist/core/checks/git/large-files.d.ts +2 -0
- package/dist/core/checks/git/large-files.js +83 -0
- package/dist/core/checks/git/leftover-debug.d.ts +2 -0
- package/dist/core/checks/git/leftover-debug.js +77 -0
- package/dist/core/checks/git/lockfile-drift.d.ts +2 -0
- package/dist/core/checks/git/lockfile-drift.js +114 -0
- package/dist/core/checks/git/merge-conflict.d.ts +2 -0
- package/dist/core/checks/git/merge-conflict.js +109 -0
- package/dist/core/checks/git/missing-tests.d.ts +2 -0
- package/dist/core/checks/git/missing-tests.js +92 -0
- package/dist/core/checks/git/todo-fixme.d.ts +7 -0
- package/dist/core/checks/git/todo-fixme.js +74 -0
- package/dist/core/checks/{cpp-format.d.ts → languages/cpp-format.d.ts} +1 -1
- package/dist/core/checks/{cpp-format.js → languages/cpp-format.js} +3 -3
- package/dist/core/checks/languages/csharp-format.d.ts +13 -0
- package/dist/core/checks/languages/csharp-format.js +52 -0
- package/dist/core/checks/languages/go-format.d.ts +8 -0
- package/dist/core/checks/languages/go-format.js +48 -0
- package/dist/core/checks/languages/go-lint.d.ts +8 -0
- package/dist/core/checks/languages/go-lint.js +48 -0
- package/dist/core/checks/{kotlin-lint.d.ts → languages/kotlin-lint.d.ts} +1 -1
- package/dist/core/checks/{kotlin-lint.js → languages/kotlin-lint.js} +13 -17
- package/dist/core/checks/{lint.d.ts → languages/lint.d.ts} +1 -1
- package/dist/core/checks/{lint.js → languages/lint.js} +25 -14
- package/dist/core/checks/languages/php-format.d.ts +9 -0
- package/dist/core/checks/languages/php-format.js +49 -0
- package/dist/core/checks/{prettier.d.ts → languages/prettier.d.ts} +1 -1
- package/dist/core/checks/{prettier.js → languages/prettier.js} +10 -10
- package/dist/core/checks/{python-format.d.ts → languages/python-format.d.ts} +1 -1
- package/dist/core/checks/{python-format.js → languages/python-format.js} +3 -3
- package/dist/core/checks/{python-lint.d.ts → languages/python-lint.d.ts} +1 -1
- package/dist/core/checks/{python-lint.js → languages/python-lint.js} +3 -3
- package/dist/core/checks/languages/python-typecheck.d.ts +9 -0
- package/dist/core/checks/languages/python-typecheck.js +66 -0
- package/dist/core/checks/languages/ruby-lint.d.ts +8 -0
- package/dist/core/checks/languages/ruby-lint.js +50 -0
- package/dist/core/checks/languages/rust-format.d.ts +9 -0
- package/dist/core/checks/languages/rust-format.js +45 -0
- package/dist/core/checks/languages/rust-lint.d.ts +8 -0
- package/dist/core/checks/languages/rust-lint.js +50 -0
- package/dist/core/checks/{swift-lint.d.ts → languages/swift-lint.d.ts} +1 -1
- package/dist/core/checks/{swift-lint.js → languages/swift-lint.js} +13 -17
- package/dist/core/checks/{typecheck.d.ts → languages/typecheck.d.ts} +1 -1
- package/dist/core/checks/{typecheck.js → languages/typecheck.js} +3 -3
- package/dist/core/checks/{commitlint.d.ts → pr/commitlint.d.ts} +1 -1
- package/dist/core/checks/{commitlint.js → pr/commitlint.js} +4 -4
- package/dist/core/checks/{pr-body.d.ts → pr/pr-body.d.ts} +1 -1
- package/dist/core/checks/{pr-body.js → pr/pr-body.js} +2 -2
- package/dist/core/checks/{pr-size-check.d.ts → pr/pr-size-check.d.ts} +1 -1
- package/dist/core/checks/{pr-size-check.js → pr/pr-size-check.js} +13 -11
- package/dist/core/checks/{pr-title-lint.d.ts → pr/pr-title-lint.d.ts} +1 -1
- package/dist/core/checks/{pr-title-lint.js → pr/pr-title-lint.js} +2 -2
- package/dist/core/checks/quality/case-collision.d.ts +8 -0
- package/dist/core/checks/quality/case-collision.js +40 -0
- package/dist/core/checks/quality/config-validation.d.ts +8 -0
- package/dist/core/checks/quality/config-validation.js +63 -0
- package/dist/core/checks/quality/coverage.d.ts +2 -0
- package/dist/core/checks/quality/coverage.js +63 -0
- package/dist/core/checks/{dead-code.d.ts → quality/dead-code.d.ts} +1 -1
- package/dist/core/checks/{dead-code.js → quality/dead-code.js} +4 -4
- package/dist/core/checks/{duplicate-check.d.ts → quality/duplicate-check.d.ts} +1 -1
- package/dist/core/checks/{duplicate-check.js → quality/duplicate-check.js} +11 -15
- package/dist/core/checks/quality/license-header.d.ts +7 -0
- package/dist/core/checks/quality/license-header.js +74 -0
- package/dist/core/checks/{markdown-lint.d.ts → quality/markdown-lint.d.ts} +1 -1
- package/dist/core/checks/{markdown-lint.js → quality/markdown-lint.js} +8 -21
- package/dist/core/checks/{spellcheck.d.ts → quality/spellcheck.d.ts} +1 -1
- package/dist/core/checks/{spellcheck.js → quality/spellcheck.js} +11 -19
- package/dist/core/checks/{diff-security.d.ts → security/diff-security.d.ts} +1 -1
- package/dist/core/checks/security/diff-security.js +262 -0
- package/dist/core/checks/security/dockerfile-security.d.ts +2 -0
- package/dist/core/checks/security/dockerfile-security.js +92 -0
- package/dist/core/checks/security/migration-safety.d.ts +2 -0
- package/dist/core/checks/security/migration-safety.js +110 -0
- package/dist/core/checks/{security.d.ts → security/security.d.ts} +1 -1
- package/dist/core/checks/{security.js → security/security.js} +4 -4
- package/dist/core/checks/{sensitive-file-guard.d.ts → security/sensitive-file-guard.d.ts} +1 -1
- package/dist/core/checks/{sensitive-file-guard.js → security/sensitive-file-guard.js} +4 -4
- package/dist/core/checks/security/workflow-security.d.ts +2 -0
- package/dist/core/checks/security/workflow-security.js +75 -0
- package/dist/core/context.js +54 -10
- package/dist/core/registry.js +73 -28
- package/dist/core/reporters.d.ts +4 -3
- package/dist/core/reporters.js +104 -4
- package/dist/core/run-checks.js +21 -3
- package/dist/core/targets.d.ts +17 -1
- package/dist/core/targets.js +74 -4
- package/dist/core/types.d.ts +13 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -1
- package/dist/init.js +86 -8
- package/dist/interfaces/index.d.ts +101 -1
- package/dist/scripts/copy-configs.js +6 -1
- package/dist/utils/detect-language.d.ts +1 -1
- package/dist/utils/detect-language.js +32 -3
- package/dist/utils/glob.d.ts +10 -0
- package/dist/utils/glob.js +25 -0
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/eslint-default-rules.json +62 -0
- package/eslint.config.mjs +6 -66
- package/package.json +3 -1
- package/pr-checkmate.schema.json +166 -0
- package/src/config/cspell.json +9 -1
- package/src/config/eslint-default-rules.json +62 -0
- package/src/config/pr-checkmate.schema.json +166 -0
- package/dist/core/checks/diff-security.js +0 -145
- package/dist/utils/delta.d.ts +0 -5
- package/dist/utils/delta.js +0 -53
- package/src/config/eslint.config.mjs +0 -180
- /package/dist/core/checks/{ruff-shared.d.ts → languages/ruff-shared.d.ts} +0 -0
- /package/dist/core/checks/{ruff-shared.js → languages/ruff-shared.js} +0 -0
package/dist/core/targets.d.ts
CHANGED
|
@@ -6,6 +6,22 @@ import type { CheckContext } from './types';
|
|
|
6
6
|
* `null` when git itself is unusable (caller should `skip`).
|
|
7
7
|
*
|
|
8
8
|
* Only ACMR files are returned (Added/Copied/Modified/Renamed) — deletions are
|
|
9
|
-
* excluded
|
|
9
|
+
* excluded. Results are additionally filtered to paths that still exist on disk,
|
|
10
|
+
* so a rename/move within the diff range (where the old path shows up but no
|
|
11
|
+
* longer exists) can never hand a missing path to a downstream tool like ESLint
|
|
12
|
+
* or Prettier, which crash hard on a non-existent pattern.
|
|
10
13
|
*/
|
|
11
14
|
export declare function resolveTargetFiles(ctx: CheckContext, globs: string[]): Promise<string[] | null>;
|
|
15
|
+
/**
|
|
16
|
+
* Like {@link resolveTargetFiles} but restricts results to the configured
|
|
17
|
+
* `sourcePath`(s). Preserves the semantics of the old `sourcePath` full-scan
|
|
18
|
+
* fallback while gaining delta support on every platform via `ctx.baseSha`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveScopedTargetFiles(ctx: CheckContext, globs: string[], sourcePaths: string[]): Promise<string[] | null>;
|
|
21
|
+
/**
|
|
22
|
+
* List ALL tracked files matching `globs` (never delta), scoped to
|
|
23
|
+
* `sourcePath`(s). For whole-repo analyses like circular-dependency detection
|
|
24
|
+
* that need the full graph, not just the PR diff. Using `git ls-files` means
|
|
25
|
+
* untracked `node_modules` is excluded by construction.
|
|
26
|
+
*/
|
|
27
|
+
export declare function listTrackedFiles(ctx: CheckContext, globs: string[], sourcePaths: string[]): Promise<string[]>;
|
package/dist/core/targets.js
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.resolveTargetFiles = resolveTargetFiles;
|
|
7
|
+
exports.resolveScopedTargetFiles = resolveScopedTargetFiles;
|
|
8
|
+
exports.listTrackedFiles = listTrackedFiles;
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const config_1 = require("../config");
|
|
4
12
|
const utils_1 = require("../utils");
|
|
5
13
|
const execa_1 = require("../utils/execa");
|
|
14
|
+
/**
|
|
15
|
+
* Drop files that live under any excluded directory (client `ignoreDirs` or the
|
|
16
|
+
* curated default), matched on any path segment so nesting depth doesn't matter.
|
|
17
|
+
* Applied to every check's file list so build/tooling/report dirs are never
|
|
18
|
+
* scanned, even when committed.
|
|
19
|
+
*/
|
|
20
|
+
function excludeIgnoredDirs(files) {
|
|
21
|
+
const ignored = new Set((0, config_1.getIgnoredDirs)());
|
|
22
|
+
if (ignored.size === 0)
|
|
23
|
+
return files;
|
|
24
|
+
return files.filter(f => !f.split('/').some(segment => ignored.has(segment)));
|
|
25
|
+
}
|
|
6
26
|
/**
|
|
7
27
|
* Lists the files a language check should operate on, matched by pathspec `globs`
|
|
8
28
|
* (e.g. `['*.py']`). Uses the PR diff range when available (delta mode), else
|
|
@@ -10,7 +30,10 @@ const execa_1 = require("../utils/execa");
|
|
|
10
30
|
* `null` when git itself is unusable (caller should `skip`).
|
|
11
31
|
*
|
|
12
32
|
* Only ACMR files are returned (Added/Copied/Modified/Renamed) — deletions are
|
|
13
|
-
* excluded
|
|
33
|
+
* excluded. Results are additionally filtered to paths that still exist on disk,
|
|
34
|
+
* so a rename/move within the diff range (where the old path shows up but no
|
|
35
|
+
* longer exists) can never hand a missing path to a downstream tool like ESLint
|
|
36
|
+
* or Prettier, which crash hard on a non-existent pattern.
|
|
14
37
|
*/
|
|
15
38
|
async function resolveTargetFiles(ctx, globs) {
|
|
16
39
|
try {
|
|
@@ -25,11 +48,16 @@ async function resolveTargetFiles(ctx, globs) {
|
|
|
25
48
|
...globs,
|
|
26
49
|
]
|
|
27
50
|
: ['ls-files', ...globs];
|
|
28
|
-
const
|
|
29
|
-
|
|
51
|
+
const cacheKey = `t:${args.join('')}`;
|
|
52
|
+
let stdout = ctx.cache?.get(cacheKey);
|
|
53
|
+
if (stdout === undefined) {
|
|
54
|
+
stdout = (await (0, execa_1.execa)('git', args, { cwd: ctx.cwd, reject: false })).stdout;
|
|
55
|
+
ctx.cache?.set(cacheKey, stdout);
|
|
56
|
+
}
|
|
57
|
+
const files = excludeIgnoredDirs(stdout
|
|
30
58
|
.split('\n')
|
|
31
59
|
.map(f => f.trim())
|
|
32
|
-
.filter(Boolean);
|
|
60
|
+
.filter(Boolean)).filter(f => node_fs_1.default.existsSync(node_path_1.default.join(ctx.cwd, f)));
|
|
33
61
|
utils_1.logger.info(`[targets]: ${ctx.baseSha ? '📂 Delta' : 'Full scan'}: ${files.length} file(s) for ${globs.join(', ')}`);
|
|
34
62
|
return files;
|
|
35
63
|
}
|
|
@@ -38,3 +66,45 @@ async function resolveTargetFiles(ctx, globs) {
|
|
|
38
66
|
return null;
|
|
39
67
|
}
|
|
40
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Restrict a list of repo-relative paths to the configured `sourcePath`(s).
|
|
71
|
+
* A source path of `.` (or one that normalizes to empty) imposes no
|
|
72
|
+
* restriction, so the whole repo is considered.
|
|
73
|
+
*/
|
|
74
|
+
function scopeToSourcePaths(files, sourcePaths) {
|
|
75
|
+
const scopes = sourcePaths.map(p => p.replace(/^\.\/?/, '').replace(/\/+$/, '')).filter(Boolean);
|
|
76
|
+
if (scopes.length === 0)
|
|
77
|
+
return files;
|
|
78
|
+
return files.filter(f => scopes.some(s => f === s || f.startsWith(`${s}/`)));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Like {@link resolveTargetFiles} but restricts results to the configured
|
|
82
|
+
* `sourcePath`(s). Preserves the semantics of the old `sourcePath` full-scan
|
|
83
|
+
* fallback while gaining delta support on every platform via `ctx.baseSha`.
|
|
84
|
+
*/
|
|
85
|
+
async function resolveScopedTargetFiles(ctx, globs, sourcePaths) {
|
|
86
|
+
const files = await resolveTargetFiles(ctx, globs);
|
|
87
|
+
if (files === null)
|
|
88
|
+
return null;
|
|
89
|
+
return scopeToSourcePaths(files, sourcePaths);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* List ALL tracked files matching `globs` (never delta), scoped to
|
|
93
|
+
* `sourcePath`(s). For whole-repo analyses like circular-dependency detection
|
|
94
|
+
* that need the full graph, not just the PR diff. Using `git ls-files` means
|
|
95
|
+
* untracked `node_modules` is excluded by construction.
|
|
96
|
+
*/
|
|
97
|
+
async function listTrackedFiles(ctx, globs, sourcePaths) {
|
|
98
|
+
try {
|
|
99
|
+
const { stdout } = await (0, execa_1.execa)('git', ['ls-files', ...globs], { cwd: ctx.cwd, reject: false });
|
|
100
|
+
const files = excludeIgnoredDirs(stdout
|
|
101
|
+
.split('\n')
|
|
102
|
+
.map(f => f.trim())
|
|
103
|
+
.filter(Boolean));
|
|
104
|
+
return scopeToSourcePaths(files, sourcePaths);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
utils_1.logger.warn(`[targets]: ⚠️ Could not list tracked files\n${err}`);
|
|
108
|
+
return [];
|
|
109
|
+
}
|
|
110
|
+
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -24,6 +24,12 @@ export interface CheckContext {
|
|
|
24
24
|
/** Diff range for delta-aware checks; undefined → full-repo scan. */
|
|
25
25
|
baseSha?: string;
|
|
26
26
|
headSha?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Per-run memoization cache for identical git invocations (many checks diff
|
|
29
|
+
* the same range). Created once per `runChecks`; safe because git state is
|
|
30
|
+
* stable within a run. Keyed by the git args.
|
|
31
|
+
*/
|
|
32
|
+
cache?: Map<string, string>;
|
|
27
33
|
}
|
|
28
34
|
/** Outcome of a single check — the name is attached by the orchestrator. */
|
|
29
35
|
export interface CheckOutcome {
|
|
@@ -42,7 +48,7 @@ export interface Check {
|
|
|
42
48
|
applies(ctx: CheckContext): boolean | Promise<boolean>;
|
|
43
49
|
run(ctx: CheckContext): Promise<CheckOutcome>;
|
|
44
50
|
}
|
|
45
|
-
export type ReporterKind = 'none' | 'stdout' | 'github';
|
|
51
|
+
export type ReporterKind = 'none' | 'stdout' | 'github' | 'gitlab' | 'bitbucket' | 'auto';
|
|
46
52
|
/** Custom reporter: receives the final report to emit however the consumer wants. */
|
|
47
53
|
export type ReporterFn = (report: CheckReport) => void | Promise<void>;
|
|
48
54
|
export interface RunOptions {
|
|
@@ -78,6 +84,12 @@ export interface RunOptions {
|
|
|
78
84
|
config?: PRCheckMateConfig;
|
|
79
85
|
baseSha?: string;
|
|
80
86
|
headSha?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Pre-commit / local mode: check the staged changes (index vs HEAD) instead of
|
|
89
|
+
* a PR range. Implemented by diffing HEAD against the index tree
|
|
90
|
+
* (`git write-tree`), so every delta-aware check works unchanged.
|
|
91
|
+
*/
|
|
92
|
+
staged?: boolean;
|
|
81
93
|
}
|
|
82
94
|
export interface CheckReport {
|
|
83
95
|
results: CheckResult[];
|
package/dist/index.d.ts
CHANGED
|
@@ -6,5 +6,5 @@
|
|
|
6
6
|
export { runChecks, buildContext, REGISTRY } from './core';
|
|
7
7
|
export { pass, warn, fail, skip, defineCheck } from './core';
|
|
8
8
|
export type { Check, CheckContext, CheckOutcome, CheckReport, CheckResult, CheckStatus, Language, Phase, ReporterFn, ReporterKind, RunOptions, } from './core';
|
|
9
|
-
export { getSourcePath, getPackageConfigPath, hasLocalConfig } from './config';
|
|
9
|
+
export { getSourcePath, getSourcePaths, getIgnoredDirs, getPackageConfigPath, hasLocalConfig, } from './config';
|
|
10
10
|
export type { PRCheckMateConfig } from './interfaces';
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @module pr-checkmate
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.hasLocalConfig = exports.getPackageConfigPath = exports.getSourcePath = exports.defineCheck = exports.skip = exports.fail = exports.warn = exports.pass = exports.REGISTRY = exports.buildContext = exports.runChecks = void 0;
|
|
8
|
+
exports.hasLocalConfig = exports.getPackageConfigPath = exports.getIgnoredDirs = exports.getSourcePaths = exports.getSourcePath = exports.defineCheck = exports.skip = exports.fail = exports.warn = exports.pass = exports.REGISTRY = exports.buildContext = exports.runChecks = void 0;
|
|
9
9
|
// Primary programmatic API: run checks and receive a structured report.
|
|
10
10
|
// Never throws for violations, never calls process.exit — the caller decides.
|
|
11
11
|
var core_1 = require("./core");
|
|
@@ -22,5 +22,7 @@ Object.defineProperty(exports, "defineCheck", { enumerable: true, get: function
|
|
|
22
22
|
// Config helpers for consumers embedding pr-checkmate.
|
|
23
23
|
var config_1 = require("./config");
|
|
24
24
|
Object.defineProperty(exports, "getSourcePath", { enumerable: true, get: function () { return config_1.getSourcePath; } });
|
|
25
|
+
Object.defineProperty(exports, "getSourcePaths", { enumerable: true, get: function () { return config_1.getSourcePaths; } });
|
|
26
|
+
Object.defineProperty(exports, "getIgnoredDirs", { enumerable: true, get: function () { return config_1.getIgnoredDirs; } });
|
|
25
27
|
Object.defineProperty(exports, "getPackageConfigPath", { enumerable: true, get: function () { return config_1.getPackageConfigPath; } });
|
|
26
28
|
Object.defineProperty(exports, "hasLocalConfig", { enumerable: true, get: function () { return config_1.hasLocalConfig; } });
|
package/dist/init.js
CHANGED
|
@@ -10,19 +10,45 @@ const inquirer_1 = __importDefault(require("inquirer"));
|
|
|
10
10
|
const config_1 = require("./config");
|
|
11
11
|
const utils_1 = require("./utils");
|
|
12
12
|
const CONFIG_FILE = path_1.default.resolve(process.cwd(), 'pr-checkmate.json');
|
|
13
|
+
/**
|
|
14
|
+
* Load the canonical default ESLint rules (the same set eslint.config.mjs
|
|
15
|
+
* applies) so the generated pr-checkmate.json lists every rule for the user to
|
|
16
|
+
* change or disable. Falls back to an empty object if the file is unavailable.
|
|
17
|
+
*/
|
|
18
|
+
function loadDefaultEslintRules() {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(fs_1.default.readFileSync((0, config_1.getPackageConfigPath)('eslint-default-rules.json'), 'utf-8'));
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
utils_1.logger.warn(`[init]: ⚠️ Could not load default ESLint rules: ${err}`);
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
13
27
|
/**
|
|
14
28
|
* Default configuration structure with customization options
|
|
15
29
|
*/
|
|
16
30
|
const DEFAULT_CONFIG = {
|
|
17
|
-
|
|
31
|
+
// Points editors (VS Code, etc.) at the bundled JSON schema for autocomplete
|
|
32
|
+
// and inline validation of this file.
|
|
33
|
+
$schema: './node_modules/pr-checkmate/pr-checkmate.schema.json',
|
|
34
|
+
// Human-readable hints. JSON has no comments, so these `//`-prefixed keys are
|
|
35
|
+
// just informational strings — PR CheckMate ignores them at runtime.
|
|
36
|
+
'// reporting': [
|
|
37
|
+
'Results post to the PR/MR automatically.',
|
|
38
|
+
'GitHub: no setup — the runner injects GITHUB_TOKEN; just keep `permissions: pull-requests: write` in the workflow.',
|
|
39
|
+
'GitLab: create a token (Project → Settings → Access Tokens, scope `api`) and add it as CI/CD variable GITLAB_TOKEN.',
|
|
40
|
+
'Bitbucket: create a token (Repo → Settings → Access tokens, scope pull-requests:write) and add it as repo variable BITBUCKET_TOKEN.',
|
|
41
|
+
'The token lives in YOUR CI variables, never in this file. Without it, checks still run and print to the log — only the comment is skipped.',
|
|
42
|
+
].join(' '),
|
|
43
|
+
sourcePath: '.',
|
|
44
|
+
// Directories excluded from every check (matched on any path segment). Edit
|
|
45
|
+
// freely — add your own generated/vendored dirs or remove ones you want scanned.
|
|
46
|
+
ignoreDirs: config_1.IGNORED_DIRS,
|
|
18
47
|
commands: config_1.DEFAULT_COMMANDS,
|
|
19
|
-
// ESLint
|
|
48
|
+
// ESLint — the full default rule set is written out so every rule is visible
|
|
49
|
+
// and editable. Change any value, or set a rule to "off" to disable it.
|
|
20
50
|
eslint: {
|
|
21
|
-
rules:
|
|
22
|
-
// Off by default: its `--fix` injects empty `/** */` JSDoc stubs into
|
|
23
|
-
// client source files. Set to 'warn'/'error' to opt into doc enforcement.
|
|
24
|
-
'jsdoc/require-jsdoc': 'off',
|
|
25
|
-
},
|
|
51
|
+
rules: loadDefaultEslintRules(),
|
|
26
52
|
ignorePatterns: [],
|
|
27
53
|
},
|
|
28
54
|
// Prettier customization
|
|
@@ -80,6 +106,57 @@ const DEFAULT_CONFIG = {
|
|
|
80
106
|
// test runner. Add barrels/entry points here to exempt intentional exports.
|
|
81
107
|
ignoreFiles: config_1.DEAD_CODE_IGNORE_DEFAULTS,
|
|
82
108
|
},
|
|
109
|
+
// User-defined rules: regex patterns run against added diff lines. The escape
|
|
110
|
+
// hatch to enforce any project rule without code. Example entry:
|
|
111
|
+
// { "name": "no-focused-tests", "pattern": "\\b(describe|it)\\.only\\(",
|
|
112
|
+
// "message": "focused test left in", "severity": "error",
|
|
113
|
+
// "include": ["src/**"], "exclude": ["**/*.test.*"] }
|
|
114
|
+
customRules: [],
|
|
115
|
+
// Universal per-check severity override, keyed by the check's display name (as
|
|
116
|
+
// shown in the report). "error" fails the run, "warn" is advisory, "off"
|
|
117
|
+
// disables the check. Empty by default — every check uses its built-in
|
|
118
|
+
// severity. Examples: { "Merge Conflict": "warn", "ESLint": "error" }.
|
|
119
|
+
severity: {},
|
|
120
|
+
// Git / PR-diff heuristics (delta-aware). Merge Conflict is blocking by
|
|
121
|
+
// default; the rest are advisory — promote any of them with `severity`.
|
|
122
|
+
mergeConflict: { enabled: true },
|
|
123
|
+
leftoverDebug: { enabled: true },
|
|
124
|
+
lockfileDrift: { enabled: true },
|
|
125
|
+
largeFiles: { enabled: true, maxSizeKb: 500, allow: [] },
|
|
126
|
+
missingTests: { enabled: true },
|
|
127
|
+
todoFixme: { enabled: true, requireTicket: false },
|
|
128
|
+
migrationSafety: { enabled: true },
|
|
129
|
+
// Security (regex diff scans + sensitive-file guard). Secret scan / npm audit
|
|
130
|
+
// live under `security`.
|
|
131
|
+
diffSecurity: { enabled: true },
|
|
132
|
+
sensitiveFileGuard: { enabled: true },
|
|
133
|
+
workflowSecurity: { enabled: true }, // GitHub Actions: injection, unpinned actions
|
|
134
|
+
dockerfileSecurity: { enabled: true }, // Dockerfile: :latest, USER root, curl|bash, …
|
|
135
|
+
caseCollision: { enabled: true }, // files differing only by case
|
|
136
|
+
vulnScan: { enabled: false }, // cross-language dep vulns (osv-scanner, runner-dependency)
|
|
137
|
+
// Test-coverage presence. Opt-in: enable once your CI produces a coverage
|
|
138
|
+
// report (lcov/cobertura/jacoco/…); warns if none is found.
|
|
139
|
+
coverage: { enabled: false },
|
|
140
|
+
// PR metadata / dependency hygiene.
|
|
141
|
+
prSize: { enabled: true, maxFiles: 50, maxLines: 1000 },
|
|
142
|
+
commitlint: { enabled: true },
|
|
143
|
+
// Dependencies you want to forbid. Example: { "name": "moment", "message": "use date-fns" }.
|
|
144
|
+
bannedImports: [],
|
|
145
|
+
// License/copyright header on new source files. Opt-in — set enabled + pattern.
|
|
146
|
+
licenseHeader: { enabled: false, pattern: '', lines: 10 },
|
|
147
|
+
outdatedDeps: { enabled: true, ignore: [] },
|
|
148
|
+
// Per-language checks. Bundled (no client install): TypeScript, Python (Ruff),
|
|
149
|
+
// C++ (clang-format). Runner-dependency (need the tool on the runner, skip
|
|
150
|
+
// gracefully if absent): Swift, Kotlin, Go, Rust, C#, Ruby, PHP.
|
|
151
|
+
python: { enabled: true, typecheck: true },
|
|
152
|
+
cpp: { enabled: true },
|
|
153
|
+
swift: { enabled: true },
|
|
154
|
+
kotlin: { enabled: true },
|
|
155
|
+
go: { enabled: true },
|
|
156
|
+
rust: { enabled: true },
|
|
157
|
+
csharp: { enabled: true },
|
|
158
|
+
ruby: { enabled: true },
|
|
159
|
+
php: { enabled: true },
|
|
83
160
|
documentation: config_1.documentation,
|
|
84
161
|
};
|
|
85
162
|
const MANUAL_ENTRY = 'Enter manually';
|
|
@@ -90,7 +167,8 @@ async function promptSourcePath() {
|
|
|
90
167
|
type: 'list',
|
|
91
168
|
name: 'sourcePath',
|
|
92
169
|
message: 'Where is your project source code?',
|
|
93
|
-
|
|
170
|
+
default: '.',
|
|
171
|
+
choices: [{ name: '. (entire repo — recommended)', value: '.' }, 'src', 'app', MANUAL_ENTRY],
|
|
94
172
|
},
|
|
95
173
|
{
|
|
96
174
|
type: 'input',
|
|
@@ -21,8 +21,19 @@ export interface CspellConfig {
|
|
|
21
21
|
ignoreRegExpList: string[];
|
|
22
22
|
}
|
|
23
23
|
export interface PRCheckMateConfig {
|
|
24
|
-
sourcePath: string;
|
|
24
|
+
sourcePath: string | string[];
|
|
25
|
+
ignoreDirs?: string[];
|
|
25
26
|
languages?: Language[];
|
|
27
|
+
customRules?: Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
pattern: string;
|
|
30
|
+
message?: string;
|
|
31
|
+
severity?: 'error' | 'warn';
|
|
32
|
+
include?: string[];
|
|
33
|
+
exclude?: string[];
|
|
34
|
+
flags?: string;
|
|
35
|
+
}>;
|
|
36
|
+
severity?: Record<string, 'error' | 'warn' | 'off'>;
|
|
26
37
|
lint?: {
|
|
27
38
|
rules?: Record<string, any>;
|
|
28
39
|
ignorePatterns?: string[];
|
|
@@ -59,10 +70,52 @@ export interface PRCheckMateConfig {
|
|
|
59
70
|
lint?: boolean;
|
|
60
71
|
format?: boolean;
|
|
61
72
|
};
|
|
73
|
+
typecheck?: boolean;
|
|
74
|
+
typeChecker?: 'mypy' | 'pyright';
|
|
62
75
|
};
|
|
63
76
|
diffSmell?: {
|
|
64
77
|
enabled?: boolean;
|
|
65
78
|
};
|
|
79
|
+
prSize?: {
|
|
80
|
+
enabled?: boolean;
|
|
81
|
+
maxFiles?: number;
|
|
82
|
+
maxLines?: number;
|
|
83
|
+
};
|
|
84
|
+
leftoverDebug?: {
|
|
85
|
+
enabled?: boolean;
|
|
86
|
+
severity?: 'error' | 'warn';
|
|
87
|
+
};
|
|
88
|
+
todoFixme?: {
|
|
89
|
+
enabled?: boolean;
|
|
90
|
+
severity?: 'error' | 'warn';
|
|
91
|
+
requireTicket?: boolean;
|
|
92
|
+
keywords?: string[];
|
|
93
|
+
};
|
|
94
|
+
lockfileDrift?: {
|
|
95
|
+
enabled?: boolean;
|
|
96
|
+
severity?: 'error' | 'warn';
|
|
97
|
+
};
|
|
98
|
+
missingTests?: {
|
|
99
|
+
enabled?: boolean;
|
|
100
|
+
severity?: 'error' | 'warn';
|
|
101
|
+
ignore?: string[];
|
|
102
|
+
};
|
|
103
|
+
migrationSafety?: {
|
|
104
|
+
enabled?: boolean;
|
|
105
|
+
severity?: 'error' | 'warn';
|
|
106
|
+
paths?: string[];
|
|
107
|
+
};
|
|
108
|
+
coverage?: {
|
|
109
|
+
enabled?: boolean;
|
|
110
|
+
severity?: 'error' | 'warn';
|
|
111
|
+
reportPath?: string | string[];
|
|
112
|
+
};
|
|
113
|
+
largeFiles?: {
|
|
114
|
+
enabled?: boolean;
|
|
115
|
+
severity?: 'error' | 'warn';
|
|
116
|
+
maxSizeKb?: number;
|
|
117
|
+
allow?: string[];
|
|
118
|
+
};
|
|
66
119
|
prBody?: {
|
|
67
120
|
enabled?: boolean;
|
|
68
121
|
minLength?: number;
|
|
@@ -121,6 +174,38 @@ export interface PRCheckMateConfig {
|
|
|
121
174
|
};
|
|
122
175
|
diffSecurity?: {
|
|
123
176
|
enabled?: boolean;
|
|
177
|
+
ignore?: string[];
|
|
178
|
+
};
|
|
179
|
+
mergeConflict?: {
|
|
180
|
+
enabled?: boolean;
|
|
181
|
+
severity?: 'error' | 'warn';
|
|
182
|
+
};
|
|
183
|
+
workflowSecurity?: {
|
|
184
|
+
enabled?: boolean;
|
|
185
|
+
ignore?: string[];
|
|
186
|
+
};
|
|
187
|
+
dockerfileSecurity?: {
|
|
188
|
+
enabled?: boolean;
|
|
189
|
+
ignore?: string[];
|
|
190
|
+
};
|
|
191
|
+
caseCollision?: {
|
|
192
|
+
enabled?: boolean;
|
|
193
|
+
};
|
|
194
|
+
configValidation?: {
|
|
195
|
+
enabled?: boolean;
|
|
196
|
+
};
|
|
197
|
+
vulnScan?: {
|
|
198
|
+
enabled?: boolean;
|
|
199
|
+
};
|
|
200
|
+
bannedImports?: Array<{
|
|
201
|
+
name: string;
|
|
202
|
+
message?: string;
|
|
203
|
+
severity?: 'error' | 'warn';
|
|
204
|
+
}>;
|
|
205
|
+
licenseHeader?: {
|
|
206
|
+
enabled?: boolean;
|
|
207
|
+
pattern?: string;
|
|
208
|
+
lines?: number;
|
|
124
209
|
};
|
|
125
210
|
sensitiveFileGuard?: {
|
|
126
211
|
enabled?: boolean;
|
|
@@ -131,6 +216,21 @@ export interface PRCheckMateConfig {
|
|
|
131
216
|
kotlin?: {
|
|
132
217
|
enabled?: boolean;
|
|
133
218
|
};
|
|
219
|
+
go?: {
|
|
220
|
+
enabled?: boolean;
|
|
221
|
+
};
|
|
222
|
+
rust?: {
|
|
223
|
+
enabled?: boolean;
|
|
224
|
+
};
|
|
225
|
+
csharp?: {
|
|
226
|
+
enabled?: boolean;
|
|
227
|
+
};
|
|
228
|
+
ruby?: {
|
|
229
|
+
enabled?: boolean;
|
|
230
|
+
};
|
|
231
|
+
php?: {
|
|
232
|
+
enabled?: boolean;
|
|
233
|
+
};
|
|
134
234
|
deadCode?: {
|
|
135
235
|
enabled?: boolean;
|
|
136
236
|
/**
|
|
@@ -7,12 +7,17 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
7
7
|
const node_path_1 = __importDefault(require("node:path"));
|
|
8
8
|
const utils_1 = require("../utils");
|
|
9
9
|
const configFiles = [
|
|
10
|
-
|
|
10
|
+
// Source is named so ESLint's auto-discovery does NOT treat it as a nested
|
|
11
|
+
// config for files under src/config/ — it ships to the package root as the
|
|
12
|
+
// canonical `eslint.config.mjs`.
|
|
13
|
+
{ source: 'src/config/eslint-flat-config.mjs', dest: 'eslint.config.mjs' },
|
|
14
|
+
{ source: 'src/config/eslint-default-rules.json', dest: 'eslint-default-rules.json' },
|
|
11
15
|
{ source: 'src/config/.eslintrc.js', dest: '.eslintrc.js' },
|
|
12
16
|
{ source: 'src/config/.prettierrc', dest: '.prettierrc' },
|
|
13
17
|
{ source: 'src/config/cspell.json', dest: 'cspell.json' },
|
|
14
18
|
{ source: 'tsconfig.json', dest: 'tsconfig.json' },
|
|
15
19
|
{ source: 'src/config/pr-checkmate-workflow.yml', dest: 'dist/config/pr-checkmate-workflow.yml' },
|
|
20
|
+
{ source: 'src/config/pr-checkmate.schema.json', dest: 'pr-checkmate.schema.json' },
|
|
16
21
|
];
|
|
17
22
|
/**
|
|
18
23
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type Language = 'typescript' | 'javascript' | 'python' | 'cpp' | 'swift' | 'kotlin';
|
|
1
|
+
export type Language = 'typescript' | 'javascript' | 'python' | 'cpp' | 'swift' | 'kotlin' | 'go' | 'rust' | 'csharp' | 'ruby' | 'php';
|
|
2
2
|
/**
|
|
3
3
|
* Detects which languages a repository uses, combining marker files (definitive)
|
|
4
4
|
* with tracked source-file counts (breadth). A language is reported when it has a
|
|
@@ -7,6 +7,7 @@ exports.detectLanguages = detectLanguages;
|
|
|
7
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const execa_1 = require("./execa");
|
|
10
|
+
const glob_1 = require("./glob");
|
|
10
11
|
const logger_1 = require("./logger");
|
|
11
12
|
/**
|
|
12
13
|
* Marker files that unambiguously indicate a language's presence, independent of
|
|
@@ -26,6 +27,13 @@ const MARKER_FILES = {
|
|
|
26
27
|
'settings.gradle.kts',
|
|
27
28
|
'AndroidManifest.xml',
|
|
28
29
|
],
|
|
30
|
+
go: ['go.mod'],
|
|
31
|
+
rust: ['Cargo.toml'],
|
|
32
|
+
// Glob markers (`*.csproj`/`*.sln`) — matched against root dir entries, since
|
|
33
|
+
// C# projects have no single fixed marker filename.
|
|
34
|
+
csharp: ['*.csproj', '*.sln'],
|
|
35
|
+
ruby: ['Gemfile', '.rubocop.yml'],
|
|
36
|
+
php: ['composer.json'],
|
|
29
37
|
};
|
|
30
38
|
/**
|
|
31
39
|
* Source extensions per language, used to count tracked files via `git ls-files`.
|
|
@@ -37,9 +45,25 @@ const EXTENSIONS = {
|
|
|
37
45
|
cpp: ['.cpp', '.cc', '.cxx', '.c++', '.hpp', '.hh', '.hxx'],
|
|
38
46
|
swift: ['.swift'],
|
|
39
47
|
kotlin: ['.kt', '.kts', '.java'],
|
|
48
|
+
go: ['.go'],
|
|
49
|
+
rust: ['.rs'],
|
|
50
|
+
csharp: ['.cs'],
|
|
51
|
+
ruby: ['.rb', '.rake'],
|
|
52
|
+
php: ['.php'],
|
|
40
53
|
};
|
|
41
54
|
function hasMarker(root, lang) {
|
|
42
|
-
return MARKER_FILES[lang].some(name =>
|
|
55
|
+
return MARKER_FILES[lang].some(name => {
|
|
56
|
+
if (!name.includes('*'))
|
|
57
|
+
return node_fs_1.default.existsSync(node_path_1.default.join(root, name));
|
|
58
|
+
// Glob marker (e.g. `*.csproj`): match against root-directory entries.
|
|
59
|
+
try {
|
|
60
|
+
const re = (0, glob_1.globToRegExp)(name);
|
|
61
|
+
return node_fs_1.default.readdirSync(root).some(entry => re.test(entry));
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
43
67
|
}
|
|
44
68
|
/**
|
|
45
69
|
* Counts tracked files per language using `git ls-files`. Returns an empty map
|
|
@@ -54,6 +78,11 @@ async function countTrackedFiles(root) {
|
|
|
54
78
|
cpp: 0,
|
|
55
79
|
swift: 0,
|
|
56
80
|
kotlin: 0,
|
|
81
|
+
go: 0,
|
|
82
|
+
rust: 0,
|
|
83
|
+
csharp: 0,
|
|
84
|
+
ruby: 0,
|
|
85
|
+
php: 0,
|
|
57
86
|
};
|
|
58
87
|
try {
|
|
59
88
|
const { stdout } = await (0, execa_1.execa)('git', ['ls-files'], { cwd: root });
|
|
@@ -87,7 +116,7 @@ async function detectLanguages(root = process.cwd()) {
|
|
|
87
116
|
const counts = await countTrackedFiles(root);
|
|
88
117
|
const detected = Object.keys(EXTENSIONS).filter(lang => hasMarker(root, lang) || counts[lang] > 0);
|
|
89
118
|
detected.sort((a, b) => counts[b] - counts[a]);
|
|
90
|
-
|
|
91
|
-
|
|
119
|
+
const countStr = Object.keys(counts).map(l => `${l}=${counts[l]}`).join(', ');
|
|
120
|
+
logger_1.logger.info(`[detectLanguages]: Detected ${detected.length ? detected.join(', ') : 'none'} (counts: ${countStr})`);
|
|
92
121
|
return detected;
|
|
93
122
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal glob → RegExp for path matching against config patterns. Supports `*`
|
|
3
|
+
* (matches within a path segment), `**` (across segments), and a leading `** /`
|
|
4
|
+
* that also matches the repo root (zero segments). Anchored to the whole path.
|
|
5
|
+
* Not a full glob implementation — enough for the allow/ignore lists in
|
|
6
|
+
* pr-checkmate.json (e.g. "assets/**", "*.png", "** / *.d.ts").
|
|
7
|
+
*/
|
|
8
|
+
export declare function globToRegExp(glob: string): RegExp;
|
|
9
|
+
/** True when `file` matches any of the glob `patterns`. */
|
|
10
|
+
export declare function matchesAnyGlob(file: string, patterns: string[]): boolean;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.globToRegExp = globToRegExp;
|
|
4
|
+
exports.matchesAnyGlob = matchesAnyGlob;
|
|
5
|
+
/**
|
|
6
|
+
* Minimal glob → RegExp for path matching against config patterns. Supports `*`
|
|
7
|
+
* (matches within a path segment), `**` (across segments), and a leading `** /`
|
|
8
|
+
* that also matches the repo root (zero segments). Anchored to the whole path.
|
|
9
|
+
* Not a full glob implementation — enough for the allow/ignore lists in
|
|
10
|
+
* pr-checkmate.json (e.g. "assets/**", "*.png", "** / *.d.ts").
|
|
11
|
+
*/
|
|
12
|
+
function globToRegExp(glob) {
|
|
13
|
+
const re = glob
|
|
14
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape regex specials (not * or /)
|
|
15
|
+
.replace(/\*\*\//g, ' dir ') // `**/` → optional leading dirs (matches root too)
|
|
16
|
+
.replace(/\*\*/g, ' any ') // bare `**` → across segments
|
|
17
|
+
.replace(/\*/g, '[^/]*') // `*` → within a segment
|
|
18
|
+
.replace(/ dir /g, '(?:.*/)?')
|
|
19
|
+
.replace(/ any /g, '.*');
|
|
20
|
+
return new RegExp(`^${re}$`);
|
|
21
|
+
}
|
|
22
|
+
/** True when `file` matches any of the glob `patterns`. */
|
|
23
|
+
function matchesAnyGlob(file, patterns) {
|
|
24
|
+
return patterns.some(p => globToRegExp(p).test(file));
|
|
25
|
+
}
|
package/dist/utils/index.d.ts
CHANGED
package/dist/utils/index.js
CHANGED
|
@@ -21,11 +21,11 @@ exports.cleanupTempFile = cleanupTempFile;
|
|
|
21
21
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
22
22
|
const logger_1 = require("./logger");
|
|
23
23
|
__exportStar(require("./logger"), exports);
|
|
24
|
-
__exportStar(require("./delta"), exports);
|
|
25
24
|
__exportStar(require("./result-collector"), exports);
|
|
26
25
|
__exportStar(require("./resolve-bin"), exports);
|
|
27
26
|
__exportStar(require("./own-deps"), exports);
|
|
28
27
|
__exportStar(require("./detect-language"), exports);
|
|
28
|
+
__exportStar(require("./glob"), exports);
|
|
29
29
|
/**
|
|
30
30
|
*
|
|
31
31
|
*/
|