pr-checkmate 3.2.2 → 3.3.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.
Files changed (73) hide show
  1. package/.eslintrc.js +3 -9
  2. package/README.md +1 -1
  3. package/dist/cjs/cli.js +13 -0
  4. package/dist/cjs/config/pr-checkmate.template.json +393 -0
  5. package/dist/cjs/core/checks/dependencies/grype-scan.js +18 -0
  6. package/dist/cjs/core/checks/languages/lint.js +12 -0
  7. package/dist/cjs/core/checks/languages/ruff-shared.js +6 -1
  8. package/dist/cjs/core/checks/pr/commitlint.js +4 -2
  9. package/dist/cjs/core/checks/quality/package-size.d.ts +23 -1
  10. package/dist/cjs/core/checks/quality/package-size.js +20 -4
  11. package/dist/cjs/core/checks/quality/spellcheck.js +8 -5
  12. package/dist/cjs/core/install/doctor.js +12 -0
  13. package/dist/cjs/core/install/grype-db.d.ts +25 -0
  14. package/dist/cjs/core/install/grype-db.js +45 -0
  15. package/dist/cjs/core/install/profiles.d.ts +1 -1
  16. package/dist/cjs/core/install/profiles.js +1 -1
  17. package/dist/cjs/core/install/reuse.js +6 -3
  18. package/dist/cjs/init.js +48 -2
  19. package/dist/cjs/scripts/copy-configs.js +9 -0
  20. package/dist/cjs/utils/own-deps.js +8 -1
  21. package/dist/cjs/utils/resolve-bin.d.ts +2 -0
  22. package/dist/cjs/utils/resolve-bin.js +5 -0
  23. package/dist/esm/cli.js +14 -1
  24. package/dist/esm/config/pr-checkmate.template.json +393 -0
  25. package/dist/esm/core/checks/dependencies/dependency-check.js +2 -2
  26. package/dist/esm/core/checks/dependencies/grype-scan.js +18 -0
  27. package/dist/esm/core/checks/dependencies/license-check.js +2 -2
  28. package/dist/esm/core/checks/dependencies/npm-audit.js +2 -2
  29. package/dist/esm/core/checks/git/diff-smell.js +1 -1
  30. package/dist/esm/core/checks/languages/cpp-format.js +1 -1
  31. package/dist/esm/core/checks/languages/lint.js +12 -3
  32. package/dist/esm/core/checks/languages/prettier.js +3 -3
  33. package/dist/esm/core/checks/languages/python-format.js +2 -2
  34. package/dist/esm/core/checks/languages/python-lint.js +2 -2
  35. package/dist/esm/core/checks/languages/ruff-shared.js +6 -1
  36. package/dist/esm/core/checks/languages/typecheck.js +1 -1
  37. package/dist/esm/core/checks/pr/commitlint.js +4 -2
  38. package/dist/esm/core/checks/pr/pr-body.js +1 -1
  39. package/dist/esm/core/checks/pr/pr-size-check.js +1 -1
  40. package/dist/esm/core/checks/pr/pr-title-lint.js +1 -1
  41. package/dist/esm/core/checks/quality/dead-code.js +1 -1
  42. package/dist/esm/core/checks/quality/duplicate-check.js +1 -1
  43. package/dist/esm/core/checks/quality/markdown-lint.js +1 -1
  44. package/dist/esm/core/checks/quality/package-size.d.ts +23 -1
  45. package/dist/esm/core/checks/quality/package-size.js +19 -4
  46. package/dist/esm/core/checks/quality/spellcheck.js +11 -8
  47. package/dist/esm/core/checks/security/diff-security.js +1 -1
  48. package/dist/esm/core/checks/security/security.js +1 -1
  49. package/dist/esm/core/install/doctor.js +12 -0
  50. package/dist/esm/core/install/grype-db.d.ts +25 -0
  51. package/dist/esm/core/install/grype-db.js +41 -0
  52. package/dist/esm/core/install/prepare.js +1 -1
  53. package/dist/esm/core/install/profiles.d.ts +1 -1
  54. package/dist/esm/core/install/profiles.js +1 -1
  55. package/dist/esm/core/install/reuse.js +6 -3
  56. package/dist/esm/core/run-checks.js +2 -2
  57. package/dist/esm/init.js +50 -4
  58. package/dist/esm/scripts/copy-configs.js +9 -0
  59. package/dist/esm/utils/own-deps.js +8 -1
  60. package/dist/esm/utils/resolve-bin.d.ts +2 -0
  61. package/dist/esm/utils/resolve-bin.js +4 -0
  62. package/eslint-default-rules.json +2 -8
  63. package/eslint.config.mjs +37 -8
  64. package/install-manifests/commitlint/package-lock.json +5 -4
  65. package/install-manifests/core/package-lock.json +5 -3
  66. package/install-manifests/js/package-lock.json +250 -571
  67. package/install-manifests/js/package.json +10 -8
  68. package/install-manifests/markdown/package-lock.json +16 -13
  69. package/install-manifests/markdown/package.json +3 -0
  70. package/install-manifests/spellcheck/package-lock.json +3 -3
  71. package/package.json +19 -15
  72. package/src/config/.eslintrc.js +3 -9
  73. package/src/config/eslint-default-rules.json +2 -8
package/.eslintrc.js CHANGED
@@ -41,7 +41,7 @@ module.exports = {
41
41
  sourceType: 'module',
42
42
  project: './tsconfig.json',
43
43
  },
44
- plugins: ['@typescript-eslint', 'prettier', 'import-x', 'jsdoc'],
44
+ plugins: ['@typescript-eslint', 'prettier', 'simple-import-sort', 'jsdoc'],
45
45
  extends: [
46
46
  'eslint:recommended',
47
47
  'plugin:@typescript-eslint/recommended',
@@ -85,14 +85,8 @@ module.exports = {
85
85
  // Complexity analysis (#61)
86
86
  complexity: ['warn', 30],
87
87
  // Import ordering (#65)
88
- 'import-x/order': [
89
- 'warn',
90
- {
91
- groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
92
- alphabetize: { order: 'asc', caseInsensitive: true },
93
- },
94
- ],
95
- 'import-x/no-duplicates': 'error',
88
+ 'simple-import-sort/imports': 'warn',
89
+ 'no-duplicate-imports': ['error', { allowSeparateTypeImports: true }],
96
90
  // Documentation (#62). `jsdoc/require-jsdoc` is deliberately absent — see the
97
91
  // note in eslint-flat-config.mjs. These three check docblocks that exist and
98
92
  // fix only inside them; they never fabricate one.
package/README.md CHANGED
@@ -72,7 +72,7 @@ tool installed; the check skips gracefully when the tool is absent.
72
72
  | Language | Checks | Toolchain |
73
73
  |---|---|---|
74
74
  | TypeScript / JavaScript | ESLint, Prettier, `tsc --noEmit` | Bundled |
75
- | Python | Ruff (lint + format), mypy/pyright (types) | Bundled — Ruff, and pyright for types; the runner's own mypy is preferred when it has one |
75
+ | Python | Ruff (lint + format), mypy/pyright (types) | Fetched on first use — Ruff, and pyright for types; the runner's own mypy is preferred when it has one |
76
76
  | C++ | clang-format | Bundled |
77
77
  | Swift | SwiftLint | `swiftlint` on the runner |
78
78
  | Kotlin / Java | ktlint | `ktlint` on the runner |
package/dist/cjs/cli.js CHANGED
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
+ const loader_js_1 = require("./config/loader.js");
4
5
  const commands_js_1 = require("./core/commands.js");
5
6
  const index_js_1 = require("./core/index.js");
6
7
  const doctor_js_1 = require("./core/install/doctor.js");
8
+ const grype_db_js_1 = require("./core/install/grype-db.js");
7
9
  const init_js_1 = require("./init.js");
8
10
  const index_js_2 = require("./utils/index.js");
9
11
  const argv = process.argv.slice(2);
@@ -90,6 +92,17 @@ void (async () => {
90
92
  for (const result of failed) {
91
93
  index_js_2.logger.error(`[install]: ❌ ${result.profiles.join(', ') || 'tooling'}: ${result.reason}`);
92
94
  }
95
+ // Warm grype's vulnerability database too, with no time cap: the download is
96
+ // hundreds of MB and the scan itself would only be allowed a couple of minutes.
97
+ // Best-effort — an absent grype or a failed download is a warning, not a
98
+ // failed install, since Grype Scan skips cleanly without it.
99
+ if ((0, loader_js_1.loadProjectConfig)().grypeScan?.enabled !== false) {
100
+ const why = await (0, grype_db_js_1.ensureGrypeDb)(process.cwd());
101
+ if (why && why !== 'ENOENT')
102
+ index_js_2.logger.warn(`[install]: ⚠️ grype: ${why}`);
103
+ else if (!why)
104
+ index_js_2.logger.info('[install]: grype vulnerability database is up to date');
105
+ }
93
106
  process.exit(failed.length > 0 ? 1 : 0);
94
107
  }
95
108
  if (cmd === 'doctor') {
@@ -0,0 +1,393 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/Mybono/pr_checkmate_public/main/pr-checkmate.schema.json",
3
+ "//": "PR CheckMate configuration template. Place this file at your repository root as 'pr-checkmate.json'. Every value below is the built-in DEFAULT — delete any block you do not want to change, because the file is deep-merged with the defaults. Keys starting with '//' are comments and are ignored. Full per-check reference: https://github.com/Mybono/pr_checkmate_public",
4
+ "//excludePaths": "FILES AND FOLDERS NO CHECK EVER LOOKS AT, as globs: \"src/generated/**\" for a whole folder, \"vendor/legacy.ts\" for one file. Applied where every check gets its file list, so the rule is written once rather than per check. Use this before reaching for a per-check ignore; use 'ignoreDirs' only when you mean a directory NAME wherever it appears.",
5
+ "excludePaths": [],
6
+ "//1-scope": "WHERE CHECKS LOOK. 'sourcePath' may be a string or an array. 'ignoreDirs' REPLACES the default ignore list rather than extending it, so copy the defaults you still want — the defaults cover build output, dependency and report directories, plus generated 'graphify-out'. Test directories are deliberately NOT in it: test code is still linted, type-checked and formatted, and is excluded per-check where it is noise (see 'duplicate', 'deadCode', 'diffSecurity' and the bundled cspell ignores).",
7
+ "sourcePath": ".",
8
+ "//2-severity": "UNIVERSAL ON/OFF SWITCH, keyed by a check's DISPLAY NAME (see //check-names below). 'off' removes the check before it runs; 'error' promotes a warning to a failure; 'warn' demotes a failure to a warning. This works for every check, including those with no 'enabled' flag of their own.",
9
+ "severity": {},
10
+ "//check-names": [
11
+ "Banned Imports",
12
+ "Circular Deps",
13
+ "Dependencies",
14
+ "Grype Scan",
15
+ "License Check",
16
+ "NPM Audit",
17
+ "Outdated Deps",
18
+ "Vuln Scan",
19
+ "Custom Rules",
20
+ "Diff Smells",
21
+ "Large Files",
22
+ "Leftover Debug",
23
+ "Lockfile Drift",
24
+ "Merge Conflict",
25
+ "Missing Tests",
26
+ "TODO/FIXME",
27
+ "ESLint",
28
+ "TypeScript",
29
+ "Prettier",
30
+ "Ruff Lint",
31
+ "Ruff Format",
32
+ "Python Types",
33
+ "C++ Format",
34
+ "SwiftLint",
35
+ "ktlint",
36
+ "Go Vet",
37
+ "Go Format",
38
+ "Clippy",
39
+ "Rustfmt",
40
+ "C# Format",
41
+ "RuboCop",
42
+ "PHP CS Fixer",
43
+ "Commitlint",
44
+ "PR Body",
45
+ "PR Size",
46
+ "PR Title",
47
+ "Case Collision",
48
+ "Config Validation",
49
+ "Coverage",
50
+ "Dead Code",
51
+ "Duplicate Code",
52
+ "License Header",
53
+ "Markdown",
54
+ "Spellcheck",
55
+ "YAML Lint",
56
+ "Broken Symlinks",
57
+ "ShellCheck",
58
+ "Security Scan",
59
+ "Diff Security",
60
+ "Dockerfile Security",
61
+ "Migration Safety",
62
+ "Sensitive Files",
63
+ "Workflow Security"
64
+ ],
65
+ "//3-security": "Secret scanning and dependency vulnerabilities.",
66
+ "security": {
67
+ "gitleaks": {
68
+ "enabled": true
69
+ },
70
+ "npm-audit": {
71
+ "enabled": true,
72
+ "//auditLevel": "info | low | moderate | high | critical. Only DIRECT dependencies at or above this level block. Default 'high': a moderate CVE often has no fix available, and blocking on it stops releases indefinitely for a risk most teams have already accepted. Set 'moderate' for a stricter line.",
73
+ "auditLevel": "high",
74
+ "//ignore": "Package names that never block, but are still reported as warnings.",
75
+ "ignore": []
76
+ }
77
+ },
78
+ "//diffSecurity": "'ignorePaths' skips whole files by glob; setting it REPLACES the test-path defaults shown here, which exist because a security test's job is to contain the exact constructs this check hunts for. 'ignore' silences a finding LABEL everywhere instead. 'dataflowWindow' is how many added lines a value stays tainted for, so `const q = `...${x}`` on one line and `db.query(q)` on the next are still connected — a heuristic with no scopes and no reassignment tracking, so it only ever advises; 0 switches it off.",
79
+ "diffSecurity": {
80
+ "enabled": true,
81
+ "ignore": [],
82
+ "dataflowWindow": 10,
83
+ "ignorePaths": [
84
+ "**/__tests__/**",
85
+ "**/__mocks__/**",
86
+ "**/*.test.*",
87
+ "**/*.spec.*",
88
+ "**/test/**",
89
+ "**/tests/**"
90
+ ]
91
+ },
92
+ "workflowSecurity": {
93
+ "enabled": true,
94
+ "ignore": []
95
+ },
96
+ "dockerfileSecurity": {
97
+ "enabled": true,
98
+ "ignore": []
99
+ },
100
+ "//sensitiveFileGuard": "Flags .env files, private keys, CI config and lockfiles. 'ignorePaths' excuses ONE file by glob (a .pem test fixture); 'ignore' silences a whole CATEGORY and would excuse the next real key too — reach for ignorePaths first.",
101
+ "sensitiveFileGuard": {
102
+ "enabled": true,
103
+ "ignorePaths": []
104
+ },
105
+ "migrationSafety": {
106
+ "enabled": true,
107
+ "severity": "warn",
108
+ "//paths": "Globs treated as migrations. Setting this REPLACES the defaults listed here.",
109
+ "paths": ["**/migrations/**", "**/migrate/**", "**/db/migrate/**", "**/versions/**", "**/*.sql"]
110
+ },
111
+ "//4-dependencies": "Manifest, licence, and vulnerability checks.",
112
+ "dependency": {
113
+ "enabled": true,
114
+ "//ignore": "Dependency names, or globs over the whole name (\"eslint-plugin-*\"), that knip should ignore. Only applied when non-empty, and never when the repo ships its own knip config — that file stays the source of truth.",
115
+ "ignore": []
116
+ },
117
+ "circularDeps": {
118
+ "//": "Circular imports (dpdm, bundled — nothing needed on the runner). Uses the project's own dpdm when it has one. When the analyzer cannot run, the check SKIPS rather than passing or failing.",
119
+ "enabled": true,
120
+ "//severity": "Advisory by default: most repos carry long-standing cycles through barrel files. Set 'error' to gate merges on them.",
121
+ "severity": "warn",
122
+ "//ignore": "Globs over repo-relative paths to keep out of the analysis (generated or vendored code).",
123
+ "ignore": [],
124
+ "//timeoutMs": "Cap on the analysis. On timeout the check skips rather than fails.",
125
+ "timeoutMs": 120000
126
+ },
127
+ "outdatedDeps": {
128
+ "enabled": true,
129
+ "ignore": [],
130
+ "//timeoutMs": "Cap on 'npm outdated'. On timeout the check skips rather than fails.",
131
+ "timeoutMs": 30000
132
+ },
133
+ "licenseCheck": {
134
+ "//": "GPL/LGPL/AGPL in PRODUCTION dependencies fail the run. Both lists below are APPENDED to the defaults.",
135
+ "allowPackages": [],
136
+ "allowLicenses": []
137
+ },
138
+ "vulnScan": {
139
+ "//": "osv-scanner, cross-ecosystem. OPT-IN: must be exactly true. Requires osv-scanner on the runner.",
140
+ "enabled": false
141
+ },
142
+ "grypeScan": {
143
+ "//": "Anchore grype, cross-ecosystem. ON by default but skips when grype is absent from the runner.",
144
+ "enabled": true,
145
+ "minSeverity": "medium",
146
+ "//ignore": "Suppress by CVE/GHSA id or by package name.",
147
+ "ignore": [],
148
+ "//timeoutMs": "Cap on the grype scan. On timeout the check skips rather than fails. The scan never walks 'ignoreDirs' (node_modules, dist, …), which is what keeps it quick.",
149
+ "timeoutMs": 120000,
150
+ "//ci": "Optional per-environment overrides (enabled, minSeverity, ignore, timeoutMs): 'ci' applies on CI builds, 'local' elsewhere. Example: \"local\": { \"enabled\": false }"
151
+ },
152
+ "//bannedImports": "Packages that must not be ADDED. Empty means the check is inert. severity defaults to 'warn'; use 'error' to fail the run.",
153
+ "bannedImports": [],
154
+ "//5-git-and-pr": "Checks on the diff and on pull-request metadata.",
155
+ "mergeConflict": {
156
+ "enabled": true,
157
+ "severity": "error"
158
+ },
159
+ "//packageSize": "Growth of the published npm package vs the last release. Advisory — a bigger tarball is usually earned. Only runs where package.json names a non-private package.",
160
+ "packageSize": {
161
+ "enabled": true,
162
+ "maxGrowthPercent": 20,
163
+ "compareWith": "latest"
164
+ },
165
+ "diffSmell": {
166
+ "enabled": true
167
+ },
168
+ "leftoverDebug": {
169
+ "enabled": true,
170
+ "severity": "warn"
171
+ },
172
+ "todoFixme": {
173
+ "enabled": true,
174
+ "severity": "warn",
175
+ "//requireTicket": "When true, a TODO must reference a ticket id — '#123' or 'PROJ-456'.",
176
+ "requireTicket": false,
177
+ "keywords": ["TODO", "FIXME", "XXX", "HACK"]
178
+ },
179
+ "lockfileDrift": {
180
+ "enabled": true,
181
+ "severity": "warn"
182
+ },
183
+ "largeFiles": {
184
+ "enabled": true,
185
+ "severity": "warn",
186
+ "maxSizeKb": 500,
187
+ "//allow": "Globs exempted from the size limit. APPENDED to a built-in lockfile allow-list.",
188
+ "allow": []
189
+ },
190
+ "missingTests": {
191
+ "enabled": true,
192
+ "severity": "warn",
193
+ "//ignore": "Globs that never require an accompanying test. Setting this REPLACES the defaults, which cover *.d.ts, index files and *.config.*.",
194
+ "ignore": [
195
+ "**/*.d.ts",
196
+ "**/index.ts",
197
+ "**/index.tsx",
198
+ "**/index.js",
199
+ "**/index.jsx",
200
+ "**/*.config.*"
201
+ ]
202
+ },
203
+ "caseCollision": {
204
+ "enabled": true
205
+ },
206
+ "//customRules": "Your own regex rules run against added diff lines. Each needs 'name' and 'pattern'; optional 'message', 'severity' (error|warn), 'include', 'exclude', 'flags'.",
207
+ "customRules": [],
208
+ "prBody": {
209
+ "enabled": true,
210
+ "minLength": 20,
211
+ "requireTicket": true
212
+ },
213
+ "prSize": {
214
+ "enabled": true,
215
+ "maxFiles": 50,
216
+ "maxLines": 1000
217
+ },
218
+ "commitlint": {
219
+ "enabled": true,
220
+ "types": [
221
+ "feat",
222
+ "fix",
223
+ "docs",
224
+ "chore",
225
+ "ci",
226
+ "refactor",
227
+ "perf",
228
+ "style",
229
+ "revert",
230
+ "test",
231
+ "build"
232
+ ],
233
+ "//ignores": "Regex sources for titles to skip, e.g. release PRs.",
234
+ "ignores": ["^Release\\b", "^chore\\(release\\):"]
235
+ },
236
+ "//6-quality": "Cross-language quality gates.",
237
+ "yamlLint": {
238
+ "enabled": true,
239
+ "severity": "error",
240
+ "ignore": []
241
+ },
242
+ "symlinks": {
243
+ "//": "Whole-repo scan for tracked symlinks whose target does not exist.",
244
+ "enabled": true,
245
+ "//severity": "Advisory by default. Set 'error' to gate merges on a broken link.",
246
+ "severity": "warn",
247
+ "//ignore": "Globs for links intentionally left broken (e.g. a fixture testing another tool's own broken-symlink handling).",
248
+ "ignore": []
249
+ },
250
+ "typecheck": {
251
+ "enabled": true
252
+ },
253
+ "duplicate": {
254
+ "enabled": true,
255
+ "minLines": 5,
256
+ "minTokens": 50,
257
+ "//ignore": "Test and mock paths are excluded by default because test setup legitimately repeats.",
258
+ "ignore": [
259
+ "**/__tests__/**",
260
+ "**/__mocks__/**",
261
+ "**/*.test.*",
262
+ "**/*.spec.*",
263
+ "**/test/**",
264
+ "**/tests/**"
265
+ ]
266
+ },
267
+ "deadCode": {
268
+ "enabled": true,
269
+ "//ignoreFiles": "REGEX sources (not globs), matched against reported file paths. Setting this REPLACES the defaults.",
270
+ "ignoreFiles": [
271
+ "/__tests__/",
272
+ "/__mocks__/",
273
+ "/__mock__/",
274
+ "/tests?/",
275
+ "\\.test\\.",
276
+ "\\.spec\\."
277
+ ]
278
+ },
279
+ "coverage": {
280
+ "//": "OPT-IN: must be exactly true. Reads an existing coverage summary; it does not run your tests.",
281
+ "enabled": false,
282
+ "severity": "warn",
283
+ "//reportPath": "Path or array of paths to a coverage summary.",
284
+ "reportPath": []
285
+ },
286
+ "licenseHeader": {
287
+ "//": "OPT-IN: must be exactly true. Requires a header matching 'pattern' within the first 'lines' lines of each changed source file.",
288
+ "enabled": false,
289
+ "pattern": "",
290
+ "lines": 10
291
+ },
292
+ "configValidation": {
293
+ "enabled": true
294
+ },
295
+ "//7-linters": "ESLint, Prettier, cspell and markdownlint. 'rules' blocks are MERGED with the defaults; 'ignorePatterns' and 'ignorePaths' are APPENDED.",
296
+ "eslint": {
297
+ "rules": {},
298
+ "ignorePatterns": []
299
+ },
300
+ "prettier": {
301
+ "rules": {}
302
+ },
303
+ "cspell": {
304
+ "//": "Only \"enabled\" always applies. The rest configures the cspell config pr-checkmate generates, so it is ignored when the repository has its own cspell.json — that file wins.",
305
+ "//enabled": "Set false to turn the Spellcheck check off entirely.",
306
+ "enabled": true,
307
+ "//words": "Project vocabulary that should not be reported as misspelled. Appended to the bundled list.",
308
+ "words": [],
309
+ "//ignorePaths": "Globs to skip, APPENDED to the bundled list — which already covers CHANGELOG.md (assembled from commit subjects, so its typos are unfixable), test and mock paths, lockfiles and minified bundles. Anchored to the repository root, so a pattern never matches a directory ABOVE the checkout.",
310
+ "ignorePaths": [],
311
+ "ignoreRegExpList": [],
312
+ "//dictionaries": "Extra cspell dictionaries to enable, on top of the bundled fr, ru, uk, he, es. Unknown names are ignored.",
313
+ "dictionaries": [],
314
+ "//language": "Locales to spellcheck against. Omit it to keep the bundled \"en,ru,uk,he,fr,es\" — a value here REPLACES that list, so \"en\" alone reports every non-English word as a typo."
315
+ },
316
+ "markdownlint": {
317
+ "rules": {},
318
+ "//ignores": "APPENDED to the built-in list: node_modules, dist, coverage, graphify-out, .claude and CHANGELOG.md.",
319
+ "ignores": []
320
+ },
321
+ "//7b-install": "WHICH TOOLING IS FETCHED. The tooling totals ~233 MB and no repository needs all of it. \"adaptive\" (default) fetches only what this repository turns out to need; \"eager\" fetches everything up front, which is what a closed network or a prebuilt image wants and is also the exact previous behaviour; \"manual\" fetches nothing because the tools are already on the runner. A missing tool never fails silently: the affected checks skip with a reason in the report, or fail the run outright with onMissingTool: \"error\".",
322
+ "install": {
323
+ "strategy": "adaptive",
324
+ "//profiles": "Extra profiles beyond what detection concluded, for generated code or a monorepo layout detection cannot see through. Names: core, js, python, cpp, spellcheck, markdown, commitlint, duplicate.",
325
+ "profiles": [],
326
+ "//excludeProfiles": "Never install these, even when the language is detected. Wins over 'profiles'.",
327
+ "excludeProfiles": [],
328
+ "//minFilesPerLanguage": "Source files a language needs before its toolchain is worth downloading. Separate from detect.minFiles: running a check that then skips is free, downloading a toolchain is not.",
329
+ "minFilesPerLanguage": 1,
330
+ "//reuseLocal": "compatible | always | off. Run the tool the REPOSITORY already has instead of our copy. \"compatible\" (default) does so when the major matches ours, or when the tool reads the repository's own config — a legacy .eslintrc can only be read by the ESLint it was written for, and a tsconfig.json by its own compiler. \"always\" reuses across a major gap at your own risk: our generated flat config does not load in ESLint 8. \"off\" always uses our pinned copy, for reproducibility.",
331
+ "reuseLocal": "compatible",
332
+ "//onMissingTool": "auto | skip | error. In a closed contour a silent skip is not acceptable — use 'error'.",
333
+ "onMissingTool": "auto",
334
+ "//offline": "Forbid every network install at run time. Pre-warm with: npx pr-checkmate install --all",
335
+ "offline": false,
336
+ "//cacheDir": "Where fetched tooling is kept; the path includes the pr-checkmate version. Cache it in CI or the tooling is fetched on every run.",
337
+ "cacheDir": "~/.cache/pr-checkmate",
338
+ "//deny": "Packages never fetched, whatever profile asks for them.",
339
+ "deny": [],
340
+ "//prewarm": "NOT YET IMPLEMENTED — accepted and ignored. Warming the cache needs a postinstall hook, and there is nothing to warm while every profile's tooling ships inside the package. Documented as inert rather than quietly doing nothing.",
341
+ "prewarm": true
342
+ },
343
+ "//8-languages": "Language is auto-detected. A check skips when its language is absent, and runner-dependency tools skip when the binary is missing — so leaving everything enabled is safe. TypeScript and JavaScript have no block of their own: configure them through 'eslint', 'prettier' and 'typecheck' above.",
344
+ "//detect": "HOW that auto-detection reads the tree, when 'languages' is not set explicitly. Generated reports, vendored code and marker files are already excluded; these knobs are for the cases the built-in rules get wrong in your repo. 'minFiles' is how many source files a language needs to count (a marker file such as pyproject.toml includes it regardless). 'excludePaths' ADDS to the built-in exclusions rather than replacing them. 'disable' drops a language outright.",
345
+ "detect": {
346
+ "minFiles": 1,
347
+ "excludePaths": [],
348
+ "disable": []
349
+ },
350
+ "python": {
351
+ "enabled": true,
352
+ "//typeChecker": "mypy | pyright — pins one checker. Leave unset: the runner's own mypy is used when it has one, our bundled pyright otherwise.",
353
+ "typecheck": true,
354
+ "ruff": {
355
+ "lint": true,
356
+ "format": true
357
+ }
358
+ },
359
+ "cpp": {
360
+ "enabled": true,
361
+ "clangFormat": {
362
+ "style": "LLVM"
363
+ }
364
+ },
365
+ "swift": {
366
+ "enabled": true
367
+ },
368
+ "kotlin": {
369
+ "enabled": true
370
+ },
371
+ "go": {
372
+ "enabled": true
373
+ },
374
+ "rust": {
375
+ "enabled": true
376
+ },
377
+ "csharp": {
378
+ "enabled": true
379
+ },
380
+ "ruby": {
381
+ "enabled": true
382
+ },
383
+ "php": {
384
+ "enabled": true
385
+ },
386
+ "shellcheck": {
387
+ "//": "Needs the `shellcheck` binary on the runner; skips when absent.",
388
+ "enabled": true,
389
+ "//minSeverity": "error | warning | info | style. Omit to keep ShellCheck's own default (every level).",
390
+ "//ignore": "ShellCheck codes to suppress, e.g. \"SC2086\".",
391
+ "ignore": []
392
+ }
393
+ }
@@ -4,6 +4,7 @@ exports.grypeScanCheck = void 0;
4
4
  exports.resolveGrypeScan = resolveGrypeScan;
5
5
  const execa_js_1 = require("../../../utils/execa.js");
6
6
  const index_js_1 = require("../../../utils/index.js");
7
+ const grype_db_js_1 = require("../../install/grype-db.js");
7
8
  const types_js_1 = require("../../types.js");
8
9
  const tool_install_js_1 = require("../tool-install.js");
9
10
  const SEVERITY_ORDER = ['negligible', 'low', 'medium', 'high', 'critical'];
@@ -21,6 +22,14 @@ const TYPE_TO_ECOSYSTEM = {
21
22
  * looks like a hung commit — an advisory check must never be able to do that.
22
23
  */
23
24
  const DEFAULT_TIMEOUT_MS = 120000;
25
+ /**
26
+ * Cap for refreshing grype's vulnerability database. A stale database (grype
27
+ * refuses one older than 5 days) is re-downloaded, hundreds of MB, which takes
28
+ * longer than the scan's own cap. Kept below the per-check timeout so the check
29
+ * itself reports the outcome instead of being cut off by the runner. `npx
30
+ * pr-checkmate install` refreshes it without any cap, ahead of time.
31
+ */
32
+ const DB_UPDATE_TIMEOUT_MS = 240000;
24
33
  function severityRank(severity) {
25
34
  return SEVERITY_ORDER.indexOf((severity ?? '').toLowerCase());
26
35
  }
@@ -76,10 +85,19 @@ exports.grypeScanCheck = {
76
85
  // third-party code is not something the client could fix anyway (see below).
77
86
  const excludes = (ctx.config.ignoreDirs ?? []).flatMap(dir => ['--exclude', `**/${dir}/**`]);
78
87
  const timeout = settings.timeoutMs ?? DEFAULT_TIMEOUT_MS;
88
+ const unusable = await (0, grype_db_js_1.ensureGrypeDb)(ctx.cwd, DB_UPDATE_TIMEOUT_MS);
89
+ if (unusable === 'ENOENT')
90
+ return (0, tool_install_js_1.notInstalled)('grype');
91
+ if (unusable) {
92
+ index_js_1.logger.warn(`[Grype Scan]: ⚠️ ${unusable} — skipping`);
93
+ return (0, types_js_1.skip)(unusable);
94
+ }
95
+ // The database was just checked, so the scan must not try to fetch one itself.
79
96
  const result = await (0, execa_js_1.execa)('grype', ['dir:.', '-o', 'json', '-q', ...excludes], {
80
97
  cwd: ctx.cwd,
81
98
  reject: false,
82
99
  timeout,
100
+ env: { GRYPE_DB_AUTO_UPDATE: 'false' },
83
101
  });
84
102
  // execa v9 with reject:false resolves (not throws) with `code: 'ENOENT'`
85
103
  // when the binary is absent — detect the missing scanner on the result.
@@ -1,6 +1,10 @@
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.lintCheck = void 0;
7
+ const node_path_1 = __importDefault(require("node:path"));
4
8
  const index_js_1 = require("../../../config/index.js");
5
9
  const execa_js_1 = require("../../../utils/execa.js");
6
10
  const index_js_2 = require("../../../utils/index.js");
@@ -76,10 +80,18 @@ exports.lintCheck = {
76
80
  index_js_2.logger.info(`[ESLint]: Using pr-checkmate config (ESLint v${version}, ${configFile})`);
77
81
  }
78
82
  index_js_1.IGNORE_PATTERNS.forEach(p => args.push('--ignore-pattern', p));
83
+ // Our bundled config loads its plugins through these roots, and an ESLint
84
+ // older than 9 resolves legacy-config plugins relative to the path below — in
85
+ // both cases the plugins are in a fetched profile rather than beside us.
86
+ const toolRoots = (0, index_js_2.registeredToolPaths)();
87
+ if (!hasLocal && version < 9 && toolRoots.length > 0) {
88
+ args.push('--resolve-plugins-relative-to', toolRoots[0]);
89
+ }
79
90
  const result = await (0, execa_js_1.execa)(process.execPath, [eslintBin, ...args], {
80
91
  cwd: ctx.cwd,
81
92
  stdio: 'inherit',
82
93
  reject: false,
94
+ env: { PR_CHECKMATE_TOOL_ROOTS: toolRoots.join(node_path_1.default.delimiter) },
83
95
  });
84
96
  if (result.exitCode === 0)
85
97
  return (0, types_js_1.pass)();
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PY_GLOBS = void 0;
4
4
  exports.createRuffWorkspace = createRuffWorkspace;
5
+ const index_js_1 = require("../../../utils/index.js");
5
6
  exports.PY_GLOBS = ['*.py'];
6
7
  /**
7
8
  * The rules we lint Python with — pinned here rather than left to Ruff's defaults.
@@ -28,7 +29,11 @@ const SELECTED_RULES = ['E4', 'E7', 'E9', 'F'];
28
29
  * singleton) so the WASM module loads only when a check runs.
29
30
  */
30
31
  function createRuffWorkspace() {
31
- const { Workspace, PositionEncoding } = require('@astral-sh/ruff-wasm-nodejs');
32
+ // Resolved rather than required by name: the package lives in the `python`
33
+ // profile, which is fetched into the cache directory when this repository
34
+ // needs it, and a bare specifier only searches our own tree.
35
+ const entry = (0, index_js_1.resolvePackageBin)('@astral-sh/ruff-wasm-nodejs', 'ruff_wasm.js');
36
+ const { Workspace, PositionEncoding } = require(entry);
32
37
  const settings = {
33
38
  ...Workspace.defaultSettings(),
34
39
  lint: { select: SELECTED_RULES },
@@ -40,8 +40,10 @@ const DEFAULT_IGNORES = ['^Release\\b', '^chore\\(release\\):'];
40
40
  function buildTempConfig(types, ignores) {
41
41
  // Emit a CJS module so `ignores` can hold functions — commitlint requires
42
42
  // predicate functions there, which a JSON config cannot express.
43
- // Resolve config-conventional via Node module resolution — robust to hoisting.
44
- const configConventional = require.resolve('@commitlint/config-conventional');
43
+ // Resolved through the same roots as the binary, not by bare specifier: both
44
+ // live in the `commitlint` profile, which is fetched into the cache directory,
45
+ // and `require.resolve` alone only searches our own tree.
46
+ const configConventional = (0, index_js_2.resolvePackageBin)('@commitlint/config-conventional', 'lib/index.js');
45
47
  const content = `const patterns = ${JSON.stringify(ignores)}.map((p) => new RegExp(p, 'i'));
46
48
  module.exports = {
47
49
  extends: [${JSON.stringify(configConventional)}],
@@ -1,6 +1,27 @@
1
1
  import { Check } from '../../types.js';
2
+ /** The slice of `npm pack --dry-run --json` this check reads. */
3
+ interface PackedFile {
4
+ path: string;
5
+ size: number;
6
+ }
7
+ interface PackManifest {
8
+ name: string;
9
+ version: string;
10
+ size: number;
11
+ unpackedSize: number;
12
+ entryCount: number;
13
+ files: PackedFile[];
14
+ }
15
+ /**
16
+ * The manifest out of whatever `npm pack --json` printed.
17
+ *
18
+ * npm 10 prints `[ {manifest} ]`; npm 11 prints `{ "<name>": {manifest} }`. Reading
19
+ * only the first form turned every npm 11 runner into "could not describe this
20
+ * package" and the check into a permanent skip.
21
+ */
22
+ export declare function pickManifest(parsed: unknown): PackManifest | null;
2
23
  /** Why we could not read a published version. */
3
- export type QueryFailure = 'absent' | 'unauthenticated' | 'unreadable';
24
+ type QueryFailure = 'absent' | 'unauthenticated' | 'unreadable';
4
25
  /**
5
26
  * Tell "never published" apart from "we were not let in".
6
27
  *
@@ -34,3 +55,4 @@ export declare function classifyQueryFailure(stderr: string): QueryFailure;
34
55
  * { "Package Size": "error" }` makes it a gate for a repository that wants one.
35
56
  */
36
57
  export declare const packageSizeCheck: Check;
58
+ export {};
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.packageSizeCheck = void 0;
7
+ exports.pickManifest = pickManifest;
7
8
  exports.classifyQueryFailure = classifyQueryFailure;
8
9
  const node_fs_1 = __importDefault(require("node:fs"));
9
10
  const node_path_1 = __importDefault(require("node:path"));
@@ -15,6 +16,22 @@ const findings_js_1 = require("../findings.js");
15
16
  const DEFAULT_MAX_GROWTH = 20;
16
17
  /** How many of the files behind the growth are named. */
17
18
  const NAMED_FILES = 5;
19
+ /**
20
+ * The manifest out of whatever `npm pack --json` printed.
21
+ *
22
+ * npm 10 prints `[ {manifest} ]`; npm 11 prints `{ "<name>": {manifest} }`. Reading
23
+ * only the first form turned every npm 11 runner into "could not describe this
24
+ * package" and the check into a permanent skip.
25
+ */
26
+ function pickManifest(parsed) {
27
+ const entries = Array.isArray(parsed)
28
+ ? parsed
29
+ : parsed !== null && typeof parsed === 'object'
30
+ ? Object.values(parsed)
31
+ : [];
32
+ const first = entries[0];
33
+ return first !== null && typeof first === 'object' ? first : null;
34
+ }
18
35
  /**
19
36
  * Ask npm what a tarball would contain, without producing one.
20
37
  *
@@ -48,10 +65,9 @@ async function packManifest(cwd, spec, registry) {
48
65
  if (result.exitCode !== 0)
49
66
  return { failure: classifyQueryFailure(result.stderr ?? '') };
50
67
  try {
51
- const parsed = JSON.parse(result.stdout);
52
- if (Array.isArray(parsed) && parsed.length > 0) {
53
- return { manifest: parsed[0] };
54
- }
68
+ const manifest = pickManifest(JSON.parse(result.stdout));
69
+ if (manifest)
70
+ return { manifest };
55
71
  }
56
72
  catch {
57
73
  // Fall through: npm answered with something that is not a manifest.