pr-checkmate 1.23.18 → 1.23.20
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 +0 -3
- package/cspell.json +19 -17
- package/dist/config/constants.js +1 -0
- package/dist/core/checks/dependencies/circular-deps.js +132 -14
- package/dist/core/checks/dependencies/dependency-check.d.ts +29 -0
- package/dist/core/checks/dependencies/dependency-check.js +38 -12
- package/dist/core/checks/dependencies/npm-audit.js +22 -1
- package/dist/core/checks/languages/lint.js +10 -1
- package/dist/core/checks/languages/typecheck.js +7 -0
- package/dist/core/checks/quality/markdown-lint.js +10 -1
- package/dist/core/checks/quality/spellcheck.d.ts +25 -0
- package/dist/core/checks/quality/spellcheck.js +71 -11
- package/dist/core/checks/security/security.js +12 -1
- package/dist/init.js +20 -1
- package/dist/interfaces/index.d.ts +17 -0
- package/package.json +2 -1
- package/pr-checkmate.schema.json +21 -0
- package/src/config/cspell.json +19 -17
- package/src/config/pr-checkmate.schema.json +21 -0
package/README.md
CHANGED
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|

|
|
11
11
|
[](https://pr-checkmate-docs.pages.dev)
|
|
12
12
|
[](https://github.com/Mybono/pr_checkmate_public/releases/latest)
|
|
13
|
-
[](https://scorecard.dev/viewer/?uri=github.com/Mybono/pr_checkmate_public)
|
|
14
13
|
|
|
15
14
|
> PR CheckMate is a universal PR quality checker: one `npm install`, 51 checks across
|
|
16
15
|
> 11 languages, running in CI on every pull request. The bundled languages need no
|
|
@@ -20,8 +19,6 @@ Every release is published with a CycloneDX SBOM and an independent
|
|
|
20
19
|
[VirusTotal scan report](https://github.com/Mybono/pr_checkmate_public/releases/latest)
|
|
21
20
|
of the exact npm tarball, attached to that version's
|
|
22
21
|
[GitHub release notes](https://github.com/Mybono/pr_checkmate_public/releases/latest).
|
|
23
|
-
The public repo also runs an [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/Mybono/pr_checkmate_public)
|
|
24
|
-
analysis of its own supply-chain hygiene.
|
|
25
22
|
|
|
26
23
|
## Why PR CheckMate
|
|
27
24
|
|
package/cspell.json
CHANGED
|
@@ -309,6 +309,7 @@
|
|
|
309
309
|
"dont",
|
|
310
310
|
"Dorg",
|
|
311
311
|
"dotenv",
|
|
312
|
+
"dotenvx",
|
|
312
313
|
"dotnet",
|
|
313
314
|
"DPARALLEL",
|
|
314
315
|
"dpdm",
|
|
@@ -343,6 +344,7 @@
|
|
|
343
344
|
"endtable",
|
|
344
345
|
"engineio",
|
|
345
346
|
"ENOENT",
|
|
347
|
+
"ENOLOCK",
|
|
346
348
|
"eoilist",
|
|
347
349
|
"Escaneá",
|
|
348
350
|
"Escaneás",
|
|
@@ -1235,23 +1237,23 @@
|
|
|
1235
1237
|
"רטינול"
|
|
1236
1238
|
],
|
|
1237
1239
|
"ignorePaths": [
|
|
1238
|
-
"
|
|
1239
|
-
"
|
|
1240
|
-
"
|
|
1241
|
-
"
|
|
1242
|
-
"
|
|
1243
|
-
"
|
|
1244
|
-
"
|
|
1245
|
-
"
|
|
1246
|
-
"
|
|
1247
|
-
"
|
|
1248
|
-
"
|
|
1249
|
-
"
|
|
1250
|
-
"
|
|
1251
|
-
"
|
|
1252
|
-
"
|
|
1253
|
-
"
|
|
1254
|
-
"
|
|
1240
|
+
"/**/node_modules/**",
|
|
1241
|
+
"/**/dist/**",
|
|
1242
|
+
"/**/.git/**",
|
|
1243
|
+
"/**/build/**",
|
|
1244
|
+
"/**/coverage/**",
|
|
1245
|
+
"/**/.next/**",
|
|
1246
|
+
"/**/.nuxt/**",
|
|
1247
|
+
"/**/out/**",
|
|
1248
|
+
"/**/__pycache__/**",
|
|
1249
|
+
"/**/.pytest_cache/**",
|
|
1250
|
+
"/**/.venv/**",
|
|
1251
|
+
"/**/venv/**",
|
|
1252
|
+
"/**/package-lock.json",
|
|
1253
|
+
"/**/yarn.lock",
|
|
1254
|
+
"/**/pnpm-lock.yaml",
|
|
1255
|
+
"/**/*.min.js",
|
|
1256
|
+
"/**/*.min.css"
|
|
1255
1257
|
],
|
|
1256
1258
|
"enableFiletypes": ["typescript", "javascript", "markdown", "json"],
|
|
1257
1259
|
"ignoreRegExpList": ["/https?:\\/\\/[^\\s]+/g", "/\\b[0-9a-f]{7,40}\\b/gi"]
|
package/dist/config/constants.js
CHANGED
|
@@ -1,33 +1,151 @@
|
|
|
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.circularDepsCheck = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
4
9
|
const config_1 = require("../../../config");
|
|
5
10
|
const utils_1 = require("../../../utils");
|
|
6
11
|
const execa_1 = require("../../../utils/execa");
|
|
7
12
|
const targets_1 = require("../../targets");
|
|
8
13
|
const types_1 = require("../../types");
|
|
14
|
+
const findings_1 = require("../findings");
|
|
15
|
+
/**
|
|
16
|
+
* Cap for the analysis. dpdm builds the whole module graph, which on a large repo
|
|
17
|
+
* is the slowest part of a run — an uncapped analysis looks like a hung commit.
|
|
18
|
+
*/
|
|
19
|
+
const DEFAULT_TIMEOUT_MS = 120000;
|
|
20
|
+
/**
|
|
21
|
+
* Ceiling for the assembled argument list. Passing every tracked source file as a
|
|
22
|
+
* separate argument overflows the OS argument limit (`E2BIG`) on a large repo, and
|
|
23
|
+
* a spawn that never starts writes no report. Past the ceiling dpdm gets globs
|
|
24
|
+
* instead — it expands them itself, over the same source paths.
|
|
25
|
+
*/
|
|
26
|
+
const MAX_ARGV_CHARS = 100000;
|
|
27
|
+
/** Extensions dpdm resolves for us; also the globs used in the fallback. */
|
|
28
|
+
const SOURCE_GLOBS = ['*.ts', '*.tsx', '*.js', '*.jsx'];
|
|
29
|
+
/**
|
|
30
|
+
* Resolve dpdm, preferring the consumer project's own copy (so a repo pinned to a
|
|
31
|
+
* different major keeps its own behaviour) and falling back to the copy bundled
|
|
32
|
+
* with pr-checkmate — the order `lint` and `typecheck` use for eslint/tsc.
|
|
33
|
+
*
|
|
34
|
+
* Returns null instead of throwing: an unresolvable analyzer is a skip, never a
|
|
35
|
+
* failed PR. This check used to shell out to `npx dpdm`, which silently fetched
|
|
36
|
+
* dpdm from the registry on first use — and when that fetch could not happen
|
|
37
|
+
* (offline or locked-down CI, private registry, exit 127) nothing was analyzed.
|
|
38
|
+
*/
|
|
39
|
+
function resolveDpdm(cwd) {
|
|
40
|
+
for (const options of [{ paths: [cwd] }, undefined]) {
|
|
41
|
+
try {
|
|
42
|
+
return (0, utils_1.resolvePackageBin)('dpdm', 'lib/bin/dpdm.js', options);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Try the next location; both failing is a skip, handled by the caller.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build the file arguments for dpdm, falling back to globs when the explicit list
|
|
52
|
+
* would overflow the OS argument limit. `sourcePaths` scopes the globs the same way
|
|
53
|
+
* `listTrackedFiles` scopes the explicit list.
|
|
54
|
+
*/
|
|
55
|
+
function toFileArgs(files, sourcePaths) {
|
|
56
|
+
const explicitLength = files.reduce((sum, file) => sum + file.length + 1, 0);
|
|
57
|
+
if (explicitLength <= MAX_ARGV_CHARS)
|
|
58
|
+
return files;
|
|
59
|
+
const roots = sourcePaths.length > 0 ? sourcePaths : ['.'];
|
|
60
|
+
const globs = roots.flatMap(root => SOURCE_GLOBS.map(ext => `${root.replace(/\/+$/, '')}/**/${ext}`));
|
|
61
|
+
utils_1.logger.info(`[Circular Deps]: ${files.length} files exceed the argument limit — analyzing via globs (${globs.join(', ')})`);
|
|
62
|
+
return globs;
|
|
63
|
+
}
|
|
64
|
+
/** Read dpdm's report file; null when it is absent or unparseable. */
|
|
65
|
+
function readReport(reportPath) {
|
|
66
|
+
try {
|
|
67
|
+
return JSON.parse(node_fs_1.default.readFileSync(reportPath, 'utf-8'));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
9
73
|
exports.circularDepsCheck = {
|
|
10
74
|
name: 'Circular Deps',
|
|
11
75
|
phase: 'blocking',
|
|
12
76
|
applies(ctx) {
|
|
77
|
+
if (ctx.config.circularDeps?.enabled === false)
|
|
78
|
+
return false;
|
|
13
79
|
return ctx.languages.includes('typescript') || ctx.languages.includes('javascript');
|
|
14
80
|
},
|
|
15
81
|
async run(ctx) {
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
82
|
+
const allFiles = await (0, targets_1.listTrackedFiles)(ctx, SOURCE_GLOBS, (0, config_1.getSourcePaths)());
|
|
83
|
+
if (allFiles.length === 0)
|
|
18
84
|
return (0, types_1.pass)();
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
28
|
-
utils_1.logger.
|
|
29
|
-
return (0, types_1.
|
|
85
|
+
// Files the client exempted (generated code, vendored bundles) are dropped
|
|
86
|
+
// before analysis — and again from the cycles below, since the graph can reach
|
|
87
|
+
// an ignored file by following imports from one that is analyzed.
|
|
88
|
+
const ignore = ctx.config.circularDeps?.ignore ?? [];
|
|
89
|
+
const files = ignore.length > 0 ? allFiles.filter(f => !(0, utils_1.matchesAnyGlob)(f, ignore)) : allFiles;
|
|
90
|
+
if (files.length === 0)
|
|
91
|
+
return (0, types_1.skip)('all files ignored');
|
|
92
|
+
const dpdmBin = resolveDpdm(ctx.cwd);
|
|
93
|
+
if (!dpdmBin) {
|
|
94
|
+
utils_1.logger.warn('[Circular Deps]: ⚠️ dpdm could not be resolved — skipping');
|
|
95
|
+
return (0, types_1.skip)('dpdm unavailable');
|
|
96
|
+
}
|
|
97
|
+
const timeout = ctx.config.circularDeps?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
98
|
+
const reportPath = node_path_1.default.join(ctx.cwd, '.dpdm.temp.json');
|
|
99
|
+
try {
|
|
100
|
+
// Read the cycles from dpdm's JSON report rather than its console output.
|
|
101
|
+
// The console shapes this check used to grep for ("Circular Dependencies:",
|
|
102
|
+
// "[1] a -> b") are not what dpdm 4 prints ("• Circular Dependencies",
|
|
103
|
+
// "1) a -> b"), so no cycle ever matched and the check passed every repo,
|
|
104
|
+
// including one built solely out of a cycle. `--exit-code circular:1` gives
|
|
105
|
+
// the same answer independently: dpdm otherwise exits 0 even when it finds
|
|
106
|
+
// cycles, so the exit code alone cannot tell a clean repo from a broken run.
|
|
107
|
+
const result = await (0, execa_1.execa)(process.execPath, [
|
|
108
|
+
dpdmBin,
|
|
109
|
+
'--no-warning',
|
|
110
|
+
'--no-tree',
|
|
111
|
+
'--no-circular',
|
|
112
|
+
'--exit-code',
|
|
113
|
+
'circular:1',
|
|
114
|
+
'-o',
|
|
115
|
+
reportPath,
|
|
116
|
+
...toFileArgs(files, (0, config_1.getSourcePaths)()),
|
|
117
|
+
], { cwd: ctx.cwd, reject: false, timeout });
|
|
118
|
+
if (result.timedOut) {
|
|
119
|
+
utils_1.logger.warn(`[Circular Deps]: ⚠️ dpdm timed out after ${timeout}ms — skipping`);
|
|
120
|
+
return (0, types_1.skip)('dpdm timed out');
|
|
121
|
+
}
|
|
122
|
+
const report = readReport(reportPath);
|
|
123
|
+
// No report means dpdm never got far enough to write one (a tsconfig it
|
|
124
|
+
// cannot read, a file it cannot parse, a killed process). Skip: reporting
|
|
125
|
+
// that as "no cycles" hides a check that did not run, and failing on it
|
|
126
|
+
// blocks a PR that may have no cycle at all.
|
|
127
|
+
if (!report) {
|
|
128
|
+
const output = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
|
|
129
|
+
utils_1.logger.warn(`[Circular Deps]: ⚠️ dpdm wrote no report (exit ${result.exitCode ?? 'killed'})${output ? `:\n${output}` : ''}`);
|
|
130
|
+
return (0, types_1.skip)(`dpdm failed to run (exit ${result.exitCode ?? 'killed'})`);
|
|
131
|
+
}
|
|
132
|
+
const cycles = (report.circulars ?? []).filter(cycle => ignore.length === 0 || !cycle.some(file => (0, utils_1.matchesAnyGlob)(file, ignore)));
|
|
133
|
+
if (cycles.length === 0)
|
|
134
|
+
return (0, types_1.pass)();
|
|
135
|
+
// Advisory by default. This check reported nothing for its whole existence
|
|
136
|
+
// (see the note above), so switching it on turns every long-standing cycle
|
|
137
|
+
// in every client repo into a finding at once — barrel files alone account
|
|
138
|
+
// for most of them. Warning first lets a repo see its cycles before they
|
|
139
|
+
// gate anything; promote with `circularDeps.severity: "error"`.
|
|
140
|
+
const sev = (0, findings_1.severityEmitter)(ctx.config.circularDeps?.severity, 'warn');
|
|
141
|
+
sev.log(`[Circular Deps]: ${sev.icon} ${cycles.length} circular dependency(ies):`);
|
|
142
|
+
cycles.slice(0, 10).forEach((cycle, i) => sev.log(` ${i + 1}) ${cycle.join(' -> ')}`));
|
|
143
|
+
if (cycles.length > 10)
|
|
144
|
+
sev.log(` ... and ${cycles.length - 10} more`);
|
|
145
|
+
return sev.emit(`${cycles.length} circular dependency(ies) found`);
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
(0, utils_1.cleanupTempFile)(reportPath);
|
|
30
149
|
}
|
|
31
|
-
return (0, types_1.pass)();
|
|
32
150
|
},
|
|
33
151
|
};
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import { Check } from '../../types';
|
|
2
|
+
/** One entry of knip's JSON reporter — a file plus the issues found for it. */
|
|
3
|
+
interface KnipIssue {
|
|
4
|
+
file: string;
|
|
5
|
+
dependencies?: {
|
|
6
|
+
name: string;
|
|
7
|
+
}[];
|
|
8
|
+
unlisted?: {
|
|
9
|
+
name: string;
|
|
10
|
+
}[];
|
|
11
|
+
}
|
|
12
|
+
interface KnipReport {
|
|
13
|
+
issues?: KnipIssue[];
|
|
14
|
+
}
|
|
2
15
|
/**
|
|
3
16
|
* Compile one `dependency.ignore` entry into a pattern knip matches the way this
|
|
4
17
|
* config documents it — a glob over the whole dependency name.
|
|
@@ -11,4 +24,20 @@ import { Check } from '../../types';
|
|
|
11
24
|
* only `eslint-…`, and keeps a plain name matching exactly that name.
|
|
12
25
|
*/
|
|
13
26
|
export declare function toKnipIgnorePattern(entry: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Pull knip's report out of a stdout stream that may carry unrelated output.
|
|
29
|
+
*
|
|
30
|
+
* knip imports the project's own config files while analyzing (a `wdio.conf.ts`
|
|
31
|
+
* for its WebdriverIO plugin, say), so any top-level side effect in them —
|
|
32
|
+
* a dotenv banner, a `console.log` in a config module — is printed before the
|
|
33
|
+
* report. Slicing from the first `{` to the last `}` therefore captured that
|
|
34
|
+
* noise instead: a dotenvx tip ending in `{ path: '…/.env' }` made the whole
|
|
35
|
+
* check fail on a repo whose report was perfectly valid.
|
|
36
|
+
*
|
|
37
|
+
* The json reporter writes the report as a single line (`JSON.stringify` with no
|
|
38
|
+
* indentation), so the last line that parses AND carries `issues` is the report.
|
|
39
|
+
* Anything else on stdout is noise, whatever braces it happens to contain.
|
|
40
|
+
*/
|
|
41
|
+
export declare function parseKnipReport(stdout: string): KnipReport | null;
|
|
14
42
|
export declare const dependencyCheck: Check;
|
|
43
|
+
export {};
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.dependencyCheck = void 0;
|
|
7
7
|
exports.toKnipIgnorePattern = toKnipIgnorePattern;
|
|
8
|
+
exports.parseKnipReport = parseKnipReport;
|
|
8
9
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
11
|
const config_1 = require("../../../config");
|
|
@@ -57,6 +58,37 @@ function hasPackageJsonConfig(cwd) {
|
|
|
57
58
|
return false;
|
|
58
59
|
}
|
|
59
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Pull knip's report out of a stdout stream that may carry unrelated output.
|
|
63
|
+
*
|
|
64
|
+
* knip imports the project's own config files while analyzing (a `wdio.conf.ts`
|
|
65
|
+
* for its WebdriverIO plugin, say), so any top-level side effect in them —
|
|
66
|
+
* a dotenv banner, a `console.log` in a config module — is printed before the
|
|
67
|
+
* report. Slicing from the first `{` to the last `}` therefore captured that
|
|
68
|
+
* noise instead: a dotenvx tip ending in `{ path: '…/.env' }` made the whole
|
|
69
|
+
* check fail on a repo whose report was perfectly valid.
|
|
70
|
+
*
|
|
71
|
+
* The json reporter writes the report as a single line (`JSON.stringify` with no
|
|
72
|
+
* indentation), so the last line that parses AND carries `issues` is the report.
|
|
73
|
+
* Anything else on stdout is noise, whatever braces it happens to contain.
|
|
74
|
+
*/
|
|
75
|
+
function parseKnipReport(stdout) {
|
|
76
|
+
const lines = stdout.split('\n');
|
|
77
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
78
|
+
const line = lines[i].trim();
|
|
79
|
+
if (!line.startsWith('{'))
|
|
80
|
+
continue;
|
|
81
|
+
try {
|
|
82
|
+
const parsed = JSON.parse(line);
|
|
83
|
+
if (Array.isArray(parsed.issues))
|
|
84
|
+
return parsed;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// A noise line that merely looks like JSON — keep scanning upwards.
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
60
92
|
/** Collect the names reported for one issue type across every file in the report. */
|
|
61
93
|
function collectNames(issues, key) {
|
|
62
94
|
return [...new Set(issues.flatMap(issue => (issue[key] ?? []).map(entry => entry.name)))];
|
|
@@ -122,18 +154,12 @@ exports.dependencyCheck = {
|
|
|
122
154
|
cwd: ctx.cwd,
|
|
123
155
|
reject: false,
|
|
124
156
|
});
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
try {
|
|
132
|
-
result = JSON.parse(stdout.slice(start, end + 1));
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
return (0, types_1.fail)('knip produced invalid JSON');
|
|
136
|
-
}
|
|
157
|
+
const result = parseKnipReport(stdout);
|
|
158
|
+
// No report on stdout → the tool couldn't produce a result; skip rather than
|
|
159
|
+
// fail the client's PR. Never fail here: a check that blocks a PR because a
|
|
160
|
+
// tool misbehaved reports our problem as the client's.
|
|
161
|
+
if (!result)
|
|
162
|
+
return (0, types_1.skip)('knip produced no report');
|
|
137
163
|
const issues = result.issues ?? [];
|
|
138
164
|
const unused = collectNames(issues, 'dependencies');
|
|
139
165
|
const missing = collectNames(issues, 'unlisted');
|
|
@@ -41,7 +41,28 @@ exports.npmAuditCheck = {
|
|
|
41
41
|
const ignore = new Set((ctx.config.security?.['npm-audit']?.ignore ?? []).map(name => name.toLowerCase()));
|
|
42
42
|
const ownName = getOwnPackageName();
|
|
43
43
|
const { stdout } = await (0, execa_1.execa)('npm', ['audit', '--json'], { cwd: ctx.cwd, reject: false });
|
|
44
|
-
|
|
44
|
+
// `npm audit` reports registry/lockfile problems as JSON on stdout, but not
|
|
45
|
+
// always: an absent npm (execa resolves with `code: 'ENOENT'`), a killed
|
|
46
|
+
// process or a proxy that answers with HTML all leave stdout unparseable.
|
|
47
|
+
// Parsing it bare made those cases throw, and a throw in a blocking check is
|
|
48
|
+
// a failed PR — the client saw "NPM Audit threw: Unexpected end of JSON
|
|
49
|
+
// input" and a red gate for an environment problem they did not cause.
|
|
50
|
+
let report;
|
|
51
|
+
try {
|
|
52
|
+
report = JSON.parse(stdout);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
utils_1.logger.warn('[NPM Audit]: ⚠️ npm audit produced no parseable report — skipping');
|
|
56
|
+
return (0, types_1.skip)('npm audit produced no parseable report');
|
|
57
|
+
}
|
|
58
|
+
// A repo with no lockfile, or a registry npm cannot reach, yields
|
|
59
|
+
// `{"error":{"code":"ENOLOCK",…}}` — no `vulnerabilities` key at all. Passing
|
|
60
|
+
// on that reads as "no vulnerabilities" when nothing was actually audited.
|
|
61
|
+
if (report.error) {
|
|
62
|
+
const detail = report.error.summary ?? report.error.code ?? 'unknown error';
|
|
63
|
+
utils_1.logger.warn(`[NPM Audit]: ⚠️ npm could not audit this project — ${detail}`);
|
|
64
|
+
return (0, types_1.skip)(`npm audit unavailable: ${report.error.code ?? 'error'}`);
|
|
65
|
+
}
|
|
45
66
|
const relevant = Object.values(report.vulnerabilities ?? {}).filter(v => v.isDirect && v.name !== ownName && isSeverityAtLeast(v.severity, auditLevel));
|
|
46
67
|
const [ignored, directVulns] = relevant.reduce(([skip, block], v) => {
|
|
47
68
|
(ignore.has(v.name.toLowerCase()) ? skip : block).push(v);
|
|
@@ -74,6 +74,15 @@ exports.lintCheck = {
|
|
|
74
74
|
stdio: 'inherit',
|
|
75
75
|
reject: false,
|
|
76
76
|
});
|
|
77
|
-
|
|
77
|
+
if (result.exitCode === 0)
|
|
78
|
+
return (0, types_1.pass)();
|
|
79
|
+
// ESLint exits 1 for lint errors and 2 for a fatal error — a config it cannot
|
|
80
|
+
// load, an unresolvable plugin, a parser it cannot construct. Only the
|
|
81
|
+
// former is the client's code. Treating exit 2 as "lint errors found" turned
|
|
82
|
+
// a config incompatibility (often between our bundled config and the client's
|
|
83
|
+
// toolchain) into a red gate on their PR, and named the wrong cause.
|
|
84
|
+
if (result.exitCode === 1)
|
|
85
|
+
return (0, types_1.fail)('lint errors found');
|
|
86
|
+
return (0, types_1.warn)(`eslint failed to run (exit ${result.exitCode ?? 'killed'})${hasLocal ? '' : ' — using the pr-checkmate config'}`);
|
|
78
87
|
},
|
|
79
88
|
};
|
|
@@ -52,6 +52,13 @@ exports.typecheckCheck = {
|
|
|
52
52
|
const errorCount = (output.match(/error TS\d+/g) ?? []).length;
|
|
53
53
|
if (output)
|
|
54
54
|
utils_1.logger.warn(output);
|
|
55
|
+
// A non-zero exit with no diagnostic in it is not a type error — tsc was
|
|
56
|
+
// killed (OOM on a large project), crashed, or never started. Reporting that
|
|
57
|
+
// as `0 type error(s)` blocked PRs with a message naming nothing to fix.
|
|
58
|
+
if (errorCount === 0) {
|
|
59
|
+
utils_1.logger.warn(`[TypeScript]: ⚠️ tsc exited ${result.exitCode ?? 'abnormally'} without reporting a type error — treating as a tooling failure, not a type error`);
|
|
60
|
+
return (0, types_1.warn)(`tsc failed to run (exit ${result.exitCode ?? 'killed'})`);
|
|
61
|
+
}
|
|
55
62
|
return (0, types_1.fail)(`${errorCount} type error(s)`);
|
|
56
63
|
},
|
|
57
64
|
};
|
|
@@ -91,7 +91,16 @@ exports.markdownLintCheck = {
|
|
|
91
91
|
reject: false,
|
|
92
92
|
stdio: 'inherit',
|
|
93
93
|
});
|
|
94
|
-
|
|
94
|
+
if (result.exitCode === 0)
|
|
95
|
+
return (0, types_1.pass)();
|
|
96
|
+
// markdownlint-cli2 exits 1 for lint findings and 2 when it could not run
|
|
97
|
+
// (an unreadable config, a bad glob). Reporting the latter as "markdown lint
|
|
98
|
+
// issues found" names the wrong cause — and under `severity: error` it
|
|
99
|
+
// becomes a gate on a tooling problem.
|
|
100
|
+
if (result.exitCode === 2) {
|
|
101
|
+
return (0, types_1.warn)('markdownlint failed to run (exit 2) — check the markdownlint config');
|
|
102
|
+
}
|
|
103
|
+
return (0, types_1.warn)('markdown lint issues found');
|
|
95
104
|
}
|
|
96
105
|
finally {
|
|
97
106
|
if (tempConfigFile && node_fs_1.default.existsSync(tempConfigFile))
|
|
@@ -1,2 +1,27 @@
|
|
|
1
|
+
import { CspellConfig } from '../../../interfaces';
|
|
1
2
|
import { Check } from '../../types';
|
|
3
|
+
/**
|
|
4
|
+
* Anchors an ignore glob to the repository root.
|
|
5
|
+
*
|
|
6
|
+
* cspell matches `ignorePaths` against each file's ABSOLUTE path, so an unanchored
|
|
7
|
+
* `**\/build/**` also matches every ancestor directory of the workspace. On Bitbucket
|
|
8
|
+
* Pipelines the clone lives in `/opt/atlassian/pipelines/agent/build`, so that one
|
|
9
|
+
* pattern excluded the entire repository and the check silently spellchecked nothing.
|
|
10
|
+
* A leading `/` anchors the pattern to the config's directory (the repo root) while
|
|
11
|
+
* `/**\/dir/**` still matches at any depth INSIDE the repo — verified against cspell 10.
|
|
12
|
+
*
|
|
13
|
+
* Patterns that are already anchored (`/…`) or plainly relative (`dir/**`,
|
|
14
|
+
* `CHANGELOG.md`) are left untouched: they cannot match an ancestor.
|
|
15
|
+
*/
|
|
16
|
+
export declare function anchorIgnoreGlob(pattern: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Folds the client's `cspell` block from pr-checkmate.json into the bundled
|
|
19
|
+
* defaults. `enabled` is deliberately not carried over — it gates the check
|
|
20
|
+
* itself (see `applies`) and is not a cspell setting.
|
|
21
|
+
*
|
|
22
|
+
* Lists are appended so a client extends the curated vocabulary instead of
|
|
23
|
+
* replacing it; `language` is the one exception and replaces the default, because
|
|
24
|
+
* a client writing `"en"` means exactly that and appending would be meaningless.
|
|
25
|
+
*/
|
|
26
|
+
export declare function mergeCspellConfig(defaultConfig: CspellConfig, userConfig: Partial<CspellConfig>): CspellConfig;
|
|
2
27
|
export declare const spellcheckCheck: Check;
|
|
@@ -4,6 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.spellcheckCheck = void 0;
|
|
7
|
+
exports.anchorIgnoreGlob = anchorIgnoreGlob;
|
|
8
|
+
exports.mergeCspellConfig = mergeCspellConfig;
|
|
7
9
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
11
|
const config_1 = require("../../../config");
|
|
@@ -23,6 +25,47 @@ function loadUserCspellConfig(cwd) {
|
|
|
23
25
|
return {};
|
|
24
26
|
}
|
|
25
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Anchors an ignore glob to the repository root.
|
|
30
|
+
*
|
|
31
|
+
* cspell matches `ignorePaths` against each file's ABSOLUTE path, so an unanchored
|
|
32
|
+
* `**\/build/**` also matches every ancestor directory of the workspace. On Bitbucket
|
|
33
|
+
* Pipelines the clone lives in `/opt/atlassian/pipelines/agent/build`, so that one
|
|
34
|
+
* pattern excluded the entire repository and the check silently spellchecked nothing.
|
|
35
|
+
* A leading `/` anchors the pattern to the config's directory (the repo root) while
|
|
36
|
+
* `/**\/dir/**` still matches at any depth INSIDE the repo — verified against cspell 10.
|
|
37
|
+
*
|
|
38
|
+
* Patterns that are already anchored (`/…`) or plainly relative (`dir/**`,
|
|
39
|
+
* `CHANGELOG.md`) are left untouched: they cannot match an ancestor.
|
|
40
|
+
*/
|
|
41
|
+
function anchorIgnoreGlob(pattern) {
|
|
42
|
+
return pattern.startsWith('**/') ? `/${pattern}` : pattern;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Folds the client's `cspell` block from pr-checkmate.json into the bundled
|
|
46
|
+
* defaults. `enabled` is deliberately not carried over — it gates the check
|
|
47
|
+
* itself (see `applies`) and is not a cspell setting.
|
|
48
|
+
*
|
|
49
|
+
* Lists are appended so a client extends the curated vocabulary instead of
|
|
50
|
+
* replacing it; `language` is the one exception and replaces the default, because
|
|
51
|
+
* a client writing `"en"` means exactly that and appending would be meaningless.
|
|
52
|
+
*/
|
|
53
|
+
function mergeCspellConfig(defaultConfig, userConfig) {
|
|
54
|
+
const dictionaries = [...(defaultConfig.dictionaries ?? []), ...(userConfig.dictionaries ?? [])];
|
|
55
|
+
return {
|
|
56
|
+
...defaultConfig,
|
|
57
|
+
language: userConfig.language || defaultConfig.language,
|
|
58
|
+
words: [...defaultConfig.words, ...(userConfig.words || [])],
|
|
59
|
+
// Client globs are anchored too — a repo's own pattern is meant to be relative
|
|
60
|
+
// to that repo, never to whatever directories sit above the checkout.
|
|
61
|
+
ignorePaths: [...defaultConfig.ignorePaths, ...(userConfig.ignorePaths || [])].map(anchorIgnoreGlob),
|
|
62
|
+
ignoreRegExpList: [...defaultConfig.ignoreRegExpList, ...(userConfig.ignoreRegExpList || [])],
|
|
63
|
+
// The bundled dictionaries arrive through `import`, so there is nothing to
|
|
64
|
+
// override here. cspell ignores names it cannot resolve, so a client typo
|
|
65
|
+
// costs coverage but never fails the run.
|
|
66
|
+
...(dictionaries.length > 0 ? { dictionaries } : {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
26
69
|
function createTempCspellConfig(cwd) {
|
|
27
70
|
const packageConfigPath = (0, config_1.getPackageConfigPath)('cspell.json');
|
|
28
71
|
const packageConfigDir = node_path_1.default.dirname(packageConfigPath);
|
|
@@ -40,11 +83,8 @@ function createTempCspellConfig(cwd) {
|
|
|
40
83
|
}
|
|
41
84
|
});
|
|
42
85
|
const mergedConfig = {
|
|
43
|
-
...defaultConfig,
|
|
86
|
+
...mergeCspellConfig(defaultConfig, userConfig),
|
|
44
87
|
import: resolvedImports,
|
|
45
|
-
words: [...defaultConfig.words, ...(userConfig.words || [])],
|
|
46
|
-
ignorePaths: [...defaultConfig.ignorePaths, ...(userConfig.ignorePaths || [])],
|
|
47
|
-
ignoreRegExpList: [...defaultConfig.ignoreRegExpList, ...(userConfig.ignoreRegExpList || [])],
|
|
48
88
|
};
|
|
49
89
|
const tempConfigPath = node_path_1.default.join(cwd, '.cspell.temp.json');
|
|
50
90
|
node_fs_1.default.writeFileSync(tempConfigPath, JSON.stringify(mergedConfig, null, 2));
|
|
@@ -53,26 +93,46 @@ function createTempCspellConfig(cwd) {
|
|
|
53
93
|
exports.spellcheckCheck = {
|
|
54
94
|
name: 'Spellcheck',
|
|
55
95
|
phase: 'informational',
|
|
56
|
-
|
|
57
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Clients turn the check off with `cspell.enabled: false` (or the `spellcheck`
|
|
98
|
+
* alias the config interface uses). The universal `severity: { Spellcheck: "off" }`
|
|
99
|
+
* switch keeps working too — this is the per-check flag every other check exposes.
|
|
100
|
+
*/
|
|
101
|
+
applies(ctx) {
|
|
102
|
+
return ctx.config.cspell?.enabled !== false && ctx.config.spellcheck?.enabled !== false;
|
|
58
103
|
},
|
|
59
104
|
async run(ctx) {
|
|
60
|
-
const
|
|
61
|
-
if (
|
|
105
|
+
const targets = await (0, targets_1.resolveScopedTargetFiles)(ctx, ['*.ts', '*.tsx', '*.js', '*.jsx', '*.md', '*.mdx', '*.json'], (0, config_1.getSourcePaths)());
|
|
106
|
+
if (targets === null)
|
|
62
107
|
return (0, types_1.skip)('git unavailable');
|
|
108
|
+
// Committed build output and test reports are dropped here, in JS, against
|
|
109
|
+
// repo-RELATIVE paths — a client cannot fix a typo in a generated file, so
|
|
110
|
+
// reporting one is noise that buries the real findings. Neither cspell
|
|
111
|
+
// mechanism can do this safely: `--exclude` ignores glob anchoring outright
|
|
112
|
+
// (`--exclude=/**/build/**` still matches an ancestor `build` dir, which on
|
|
113
|
+
// Bitbucket Pipelines is the checkout itself), and `ignorePaths` only reaches
|
|
114
|
+
// repos that have no cspell config of their own. Filtering the list keeps one
|
|
115
|
+
// rule for every repo. `ignoreDirs` already covers plain directory names via
|
|
116
|
+
// `resolveTargetFiles`; this adds the composite globs it cannot express
|
|
117
|
+
// (`**/cypress/videos/**`, `**/jest-html-reporters-*/**`).
|
|
118
|
+
const files = targets.filter(f => !(0, utils_1.matchesAnyGlob)(f, config_1.IGNORE_PATTERNS));
|
|
63
119
|
if (files.length === 0)
|
|
64
120
|
return (0, types_1.pass)('no files to spellcheck');
|
|
65
|
-
const patterns = [...files, ...config_1.IGNORE_PATTERNS.map(p => `--exclude=${p}`)];
|
|
66
121
|
const hasLocal = (0, config_1.hasLocalConfig)(['cspell.json', '.cspell.json', 'cspell.config.json']);
|
|
67
122
|
let tempConfigPath = null;
|
|
68
123
|
const cspellBin = (0, utils_1.resolvePackageBin)('cspell', 'bin.mjs');
|
|
124
|
+
// `--no-must-find-files`: cspell exits 1 when it checked nothing, which the
|
|
125
|
+
// exit-code mapping below would report as "spelling issues found" — a warning
|
|
126
|
+
// about problems that do not exist. Every file being excluded is a pass, not a
|
|
127
|
+
// finding.
|
|
128
|
+
const baseArgs = [...files, '--no-progress', '--no-must-find-files'];
|
|
69
129
|
let args;
|
|
70
130
|
if (hasLocal) {
|
|
71
|
-
args =
|
|
131
|
+
args = baseArgs;
|
|
72
132
|
}
|
|
73
133
|
else {
|
|
74
134
|
tempConfigPath = createTempCspellConfig(ctx.cwd);
|
|
75
|
-
args = [...
|
|
135
|
+
args = [...baseArgs, '--config', tempConfigPath];
|
|
76
136
|
}
|
|
77
137
|
try {
|
|
78
138
|
const result = await (0, execa_1.execa)(process.execPath, [cspellBin, ...args], {
|
|
@@ -17,7 +17,18 @@ exports.securityCheck = {
|
|
|
17
17
|
return ctx.config.security?.gitleaks?.enabled !== false;
|
|
18
18
|
},
|
|
19
19
|
async run(ctx) {
|
|
20
|
-
|
|
20
|
+
// A binary that cannot be resolved throws, and a throw in a blocking check is
|
|
21
|
+
// a failed PR — the client would see "Security Scan threw: Cannot resolve
|
|
22
|
+
// binary" as a red gate on a repo with no secrets in it. Every other bundled
|
|
23
|
+
// tool (knip, license-checker) already skips here; this one did not.
|
|
24
|
+
let gitleaksBin;
|
|
25
|
+
try {
|
|
26
|
+
gitleaksBin = (0, utils_1.resolvePackageBin)('gitleaks-secret-scanner-fixed', 'bin/cli.js');
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
utils_1.logger.warn('[Security Scan]: ⚠️ bundled gitleaks scanner could not be resolved — skipping');
|
|
30
|
+
return (0, types_1.skip)('gitleaks unavailable');
|
|
31
|
+
}
|
|
21
32
|
const hasClientConfig = (0, config_1.hasLocalConfig)([
|
|
22
33
|
'.gitleaks.toml',
|
|
23
34
|
'gitleaks.toml',
|
package/dist/init.js
CHANGED
|
@@ -58,8 +58,14 @@ const DEFAULT_CONFIG = {
|
|
|
58
58
|
prettier: {
|
|
59
59
|
rules: {},
|
|
60
60
|
},
|
|
61
|
-
//
|
|
61
|
+
// Spellcheck (cspell). `enabled: false` turns the check off — that part always
|
|
62
|
+
// applies. The lists feed the cspell config pr-checkmate generates, so they are
|
|
63
|
+
// ignored when the repo has a cspell.json of its own; all of them are appended to
|
|
64
|
+
// the bundled defaults. `ignorePaths` takes globs to skip (e.g. "CHANGELOG.md",
|
|
65
|
+
// "generated/**"), anchored to the repo root so a pattern never matches a
|
|
66
|
+
// directory above the checkout.
|
|
62
67
|
cspell: {
|
|
68
|
+
enabled: true,
|
|
63
69
|
words: [],
|
|
64
70
|
ignorePaths: [],
|
|
65
71
|
ignoreRegExpList: [],
|
|
@@ -109,6 +115,19 @@ const DEFAULT_CONFIG = {
|
|
|
109
115
|
// which knip can't see as imports and would report as unused).
|
|
110
116
|
ignore: [],
|
|
111
117
|
},
|
|
118
|
+
// Circular-import check (dpdm, bundled — nothing needed on the runner). Uses
|
|
119
|
+
// the project's own dpdm when it has one. Advisory by default (severity
|
|
120
|
+
// "warn"): most repos have long-standing cycles through barrel files, so see
|
|
121
|
+
// yours first, then set severity "error" to gate merges on them. enabled false
|
|
122
|
+
// disables the check; `ignore` takes glob patterns kept out of the analysis
|
|
123
|
+
// (generated or vendored code, or barrels whose cycles you tolerate). When the
|
|
124
|
+
// analyzer cannot run, the check skips rather than passing or failing.
|
|
125
|
+
circularDeps: {
|
|
126
|
+
enabled: true,
|
|
127
|
+
severity: 'warn',
|
|
128
|
+
ignore: [],
|
|
129
|
+
timeoutMs: 120000,
|
|
130
|
+
},
|
|
112
131
|
// Unused-exports check (ts-unused-exports)
|
|
113
132
|
deadCode: {
|
|
114
133
|
enabled: true,
|
|
@@ -19,6 +19,8 @@ export interface CspellConfig {
|
|
|
19
19
|
ignorePaths: string[];
|
|
20
20
|
enableFiletypes: string[];
|
|
21
21
|
ignoreRegExpList: string[];
|
|
22
|
+
dictionaries?: string[];
|
|
23
|
+
enabled?: boolean;
|
|
22
24
|
}
|
|
23
25
|
export interface PRCheckMateConfig {
|
|
24
26
|
sourcePath: string | string[];
|
|
@@ -42,10 +44,19 @@ export interface PRCheckMateConfig {
|
|
|
42
44
|
config?: Record<string, any>;
|
|
43
45
|
};
|
|
44
46
|
spellcheck?: {
|
|
47
|
+
enabled?: boolean;
|
|
45
48
|
words?: string[];
|
|
46
49
|
ignoreWords?: string[];
|
|
47
50
|
ignorePaths?: string[];
|
|
48
51
|
};
|
|
52
|
+
cspell?: {
|
|
53
|
+
enabled?: boolean;
|
|
54
|
+
words?: string[];
|
|
55
|
+
ignorePaths?: string[];
|
|
56
|
+
ignoreRegExpList?: string[];
|
|
57
|
+
dictionaries?: string[];
|
|
58
|
+
language?: string;
|
|
59
|
+
};
|
|
49
60
|
security?: {
|
|
50
61
|
gitleaks?: {
|
|
51
62
|
enabled?: boolean;
|
|
@@ -204,6 +215,12 @@ export interface PRCheckMateConfig {
|
|
|
204
215
|
severity?: 'error' | 'warn';
|
|
205
216
|
ignore?: string[];
|
|
206
217
|
};
|
|
218
|
+
circularDeps?: {
|
|
219
|
+
enabled?: boolean;
|
|
220
|
+
severity?: 'error' | 'warn';
|
|
221
|
+
ignore?: string[];
|
|
222
|
+
timeoutMs?: number;
|
|
223
|
+
};
|
|
207
224
|
vulnScan?: {
|
|
208
225
|
enabled?: boolean;
|
|
209
226
|
timeoutMs?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pr-checkmate",
|
|
3
|
-
"version": "1.23.
|
|
3
|
+
"version": "1.23.20",
|
|
4
4
|
"description": "Automated PR quality checks: linting, formatting, dependency analysis, and spellcheck",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"github-actions",
|
|
@@ -83,6 +83,7 @@
|
|
|
83
83
|
"@typescript-eslint/parser": "^8.62.1",
|
|
84
84
|
"@wasm-fmt/clang-format": "^22.1.8",
|
|
85
85
|
"cspell": "^10.0.1",
|
|
86
|
+
"dpdm": "^4.2.0",
|
|
86
87
|
"eslint": "^10.6.0",
|
|
87
88
|
"eslint-config-prettier": "^10.1.8",
|
|
88
89
|
"eslint-plugin-import-x": "^4.17.1",
|
package/pr-checkmate.schema.json
CHANGED
|
@@ -149,6 +149,18 @@
|
|
|
149
149
|
"caseCollision": { "$ref": "#/$defs/toggle" },
|
|
150
150
|
"sensitiveFileGuard": { "$ref": "#/$defs/toggle" },
|
|
151
151
|
"configValidation": { "$ref": "#/$defs/toggle" },
|
|
152
|
+
"cspell": {
|
|
153
|
+
"description": "Spellcheck control. 'enabled: false' turns the check off. The other fields configure the cspell config pr-checkmate generates, so they apply only when the repository has no cspell.json of its own: 'words', 'ignorePaths', 'ignoreRegExpList' and 'dictionaries' are appended to the bundled defaults, 'language' replaces it. Ignore globs are anchored to the repository root, so '**/dist/**' never matches a directory above the checkout.",
|
|
154
|
+
"type": "object",
|
|
155
|
+
"properties": {
|
|
156
|
+
"enabled": { "type": "boolean" },
|
|
157
|
+
"language": { "type": "string" },
|
|
158
|
+
"words": { "type": "array", "items": { "type": "string" } },
|
|
159
|
+
"dictionaries": { "type": "array", "items": { "type": "string" } },
|
|
160
|
+
"ignorePaths": { "type": "array", "items": { "type": "string" } },
|
|
161
|
+
"ignoreRegExpList": { "type": "array", "items": { "type": "string" } }
|
|
162
|
+
}
|
|
163
|
+
},
|
|
152
164
|
"yamlLint": {
|
|
153
165
|
"type": "object",
|
|
154
166
|
"properties": {
|
|
@@ -157,6 +169,15 @@
|
|
|
157
169
|
"ignore": { "type": "array", "items": { "type": "string" } }
|
|
158
170
|
}
|
|
159
171
|
},
|
|
172
|
+
"circularDeps": {
|
|
173
|
+
"type": "object",
|
|
174
|
+
"properties": {
|
|
175
|
+
"enabled": { "type": "boolean" },
|
|
176
|
+
"severity": { "enum": ["error", "warn"] },
|
|
177
|
+
"ignore": { "type": "array", "items": { "type": "string" } },
|
|
178
|
+
"timeoutMs": { "type": "number" }
|
|
179
|
+
}
|
|
180
|
+
},
|
|
160
181
|
"typecheck": { "$ref": "#/$defs/toggle" },
|
|
161
182
|
"prBody": {
|
|
162
183
|
"type": "object",
|
package/src/config/cspell.json
CHANGED
|
@@ -309,6 +309,7 @@
|
|
|
309
309
|
"dont",
|
|
310
310
|
"Dorg",
|
|
311
311
|
"dotenv",
|
|
312
|
+
"dotenvx",
|
|
312
313
|
"dotnet",
|
|
313
314
|
"DPARALLEL",
|
|
314
315
|
"dpdm",
|
|
@@ -343,6 +344,7 @@
|
|
|
343
344
|
"endtable",
|
|
344
345
|
"engineio",
|
|
345
346
|
"ENOENT",
|
|
347
|
+
"ENOLOCK",
|
|
346
348
|
"eoilist",
|
|
347
349
|
"Escaneá",
|
|
348
350
|
"Escaneás",
|
|
@@ -1235,23 +1237,23 @@
|
|
|
1235
1237
|
"רטינול"
|
|
1236
1238
|
],
|
|
1237
1239
|
"ignorePaths": [
|
|
1238
|
-
"
|
|
1239
|
-
"
|
|
1240
|
-
"
|
|
1241
|
-
"
|
|
1242
|
-
"
|
|
1243
|
-
"
|
|
1244
|
-
"
|
|
1245
|
-
"
|
|
1246
|
-
"
|
|
1247
|
-
"
|
|
1248
|
-
"
|
|
1249
|
-
"
|
|
1250
|
-
"
|
|
1251
|
-
"
|
|
1252
|
-
"
|
|
1253
|
-
"
|
|
1254
|
-
"
|
|
1240
|
+
"/**/node_modules/**",
|
|
1241
|
+
"/**/dist/**",
|
|
1242
|
+
"/**/.git/**",
|
|
1243
|
+
"/**/build/**",
|
|
1244
|
+
"/**/coverage/**",
|
|
1245
|
+
"/**/.next/**",
|
|
1246
|
+
"/**/.nuxt/**",
|
|
1247
|
+
"/**/out/**",
|
|
1248
|
+
"/**/__pycache__/**",
|
|
1249
|
+
"/**/.pytest_cache/**",
|
|
1250
|
+
"/**/.venv/**",
|
|
1251
|
+
"/**/venv/**",
|
|
1252
|
+
"/**/package-lock.json",
|
|
1253
|
+
"/**/yarn.lock",
|
|
1254
|
+
"/**/pnpm-lock.yaml",
|
|
1255
|
+
"/**/*.min.js",
|
|
1256
|
+
"/**/*.min.css"
|
|
1255
1257
|
],
|
|
1256
1258
|
"enableFiletypes": ["typescript", "javascript", "markdown", "json"],
|
|
1257
1259
|
"ignoreRegExpList": ["/https?:\\/\\/[^\\s]+/g", "/\\b[0-9a-f]{7,40}\\b/gi"]
|
|
@@ -149,6 +149,18 @@
|
|
|
149
149
|
"caseCollision": { "$ref": "#/$defs/toggle" },
|
|
150
150
|
"sensitiveFileGuard": { "$ref": "#/$defs/toggle" },
|
|
151
151
|
"configValidation": { "$ref": "#/$defs/toggle" },
|
|
152
|
+
"cspell": {
|
|
153
|
+
"description": "Spellcheck control. 'enabled: false' turns the check off. The other fields configure the cspell config pr-checkmate generates, so they apply only when the repository has no cspell.json of its own: 'words', 'ignorePaths', 'ignoreRegExpList' and 'dictionaries' are appended to the bundled defaults, 'language' replaces it. Ignore globs are anchored to the repository root, so '**/dist/**' never matches a directory above the checkout.",
|
|
154
|
+
"type": "object",
|
|
155
|
+
"properties": {
|
|
156
|
+
"enabled": { "type": "boolean" },
|
|
157
|
+
"language": { "type": "string" },
|
|
158
|
+
"words": { "type": "array", "items": { "type": "string" } },
|
|
159
|
+
"dictionaries": { "type": "array", "items": { "type": "string" } },
|
|
160
|
+
"ignorePaths": { "type": "array", "items": { "type": "string" } },
|
|
161
|
+
"ignoreRegExpList": { "type": "array", "items": { "type": "string" } }
|
|
162
|
+
}
|
|
163
|
+
},
|
|
152
164
|
"yamlLint": {
|
|
153
165
|
"type": "object",
|
|
154
166
|
"properties": {
|
|
@@ -157,6 +169,15 @@
|
|
|
157
169
|
"ignore": { "type": "array", "items": { "type": "string" } }
|
|
158
170
|
}
|
|
159
171
|
},
|
|
172
|
+
"circularDeps": {
|
|
173
|
+
"type": "object",
|
|
174
|
+
"properties": {
|
|
175
|
+
"enabled": { "type": "boolean" },
|
|
176
|
+
"severity": { "enum": ["error", "warn"] },
|
|
177
|
+
"ignore": { "type": "array", "items": { "type": "string" } },
|
|
178
|
+
"timeoutMs": { "type": "number" }
|
|
179
|
+
}
|
|
180
|
+
},
|
|
160
181
|
"typecheck": { "$ref": "#/$defs/toggle" },
|
|
161
182
|
"prBody": {
|
|
162
183
|
"type": "object",
|