pr-checkmate 1.23.0 → 1.23.1

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/cspell.json CHANGED
@@ -701,7 +701,22 @@
701
701
  "depcheckrc",
702
702
  "gofmt",
703
703
  "dotnet",
704
- "rubocop"
704
+ "rubocop",
705
+ "precommit",
706
+ "swiftpm",
707
+ "recognises",
708
+ "vulnerab",
709
+ "numstat",
710
+ "bpprint",
711
+ "pprint",
712
+ "bpdb",
713
+ "setext",
714
+ "Confict",
715
+ "bhashlib",
716
+ "bfmt",
717
+ "dockerfiles",
718
+ "footguns",
719
+ "materialises"
705
720
  ],
706
721
  "ignorePaths": [
707
722
  "**/node_modules/**",
package/dist/cli.js CHANGED
@@ -63,7 +63,15 @@ void (async () => {
63
63
  // Full run: auto-detect the CI platform (GitHub/GitLab/Bitbucket), post the
64
64
  // summary to its PR/MR, and allow formatters to write.
65
65
  if (!cmd || cmd === 'all') {
66
+ // Fetch the update notice concurrently (best-effort, never blocks/affects
67
+ // the exit code), but print it LAST — after the summary — so it's noticeable.
68
+ const noticePromise = (0, utils_1.getUpdateNotice)();
66
69
  const report = await (0, core_1.runChecks)({ reporter: 'auto', write: true });
70
+ const notice = await noticePromise.catch(() => null);
71
+ if (notice) {
72
+ utils_1.logger.info('');
73
+ notice.forEach(line => utils_1.logger.info(line));
74
+ }
67
75
  process.exit(report.ok ? 0 : 1);
68
76
  }
69
77
  // Local pre-commit: check the staged changes (index vs HEAD), print to
@@ -9,6 +9,7 @@ const node_path_1 = __importDefault(require("node:path"));
9
9
  const utils_1 = require("../../../utils");
10
10
  const execa_1 = require("../../../utils/execa");
11
11
  const types_1 = require("../../types");
12
+ const DEFAULT_TIMEOUT_MS = 30000;
12
13
  exports.outdatedDepsCheck = {
13
14
  name: 'Outdated Deps',
14
15
  phase: 'informational',
@@ -18,8 +19,20 @@ exports.outdatedDepsCheck = {
18
19
  return node_fs_1.default.existsSync(node_path_1.default.join(ctx.cwd, 'package.json'));
19
20
  },
20
21
  async run(ctx) {
22
+ // `npm outdated` queries the registry for every dependency and can take a
23
+ // long time on big dependency trees — cap it so an advisory check never
24
+ // holds up CI. On timeout, skip rather than fail.
25
+ const timeout = ctx.config.outdatedDeps?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
21
26
  // npm outdated exits 1 when outdated packages exist — use reject:false.
22
- const result = await (0, execa_1.execa)('npm', ['outdated', '--json'], { cwd: ctx.cwd, reject: false });
27
+ const result = await (0, execa_1.execa)('npm', ['outdated', '--json'], {
28
+ cwd: ctx.cwd,
29
+ reject: false,
30
+ timeout,
31
+ });
32
+ if (result.timedOut) {
33
+ utils_1.logger.warn(`[Outdated Deps]: ⚠️ npm outdated timed out after ${timeout}ms — skipping`);
34
+ return (0, types_1.skip)('npm outdated timed out');
35
+ }
23
36
  if (!result.stdout?.trim())
24
37
  return (0, types_1.pass)();
25
38
  let outdated;
@@ -12,6 +12,29 @@ const targets_1 = require("../../targets");
12
12
  const types_1 = require("../../types");
13
13
  const findings_1 = require("../findings");
14
14
  const DEFAULT_MAX_KB = 500;
15
+ /**
16
+ * Lockfiles are legitimately large and always generated, never accidental — so
17
+ * they're exempt from the size/binary guard by default (a 600 KB
18
+ * package-lock.json is normal, not bloat). Their changes are still surfaced by
19
+ * the Lockfile Drift and Sensitive Files checks. Merged with the client's
20
+ * `largeFiles.allow`.
21
+ */
22
+ const LOCKFILE_ALLOW = [
23
+ '**/package-lock.json',
24
+ '**/npm-shrinkwrap.json',
25
+ '**/yarn.lock',
26
+ '**/pnpm-lock.yaml',
27
+ '**/bun.lockb',
28
+ '**/Cargo.lock',
29
+ '**/poetry.lock',
30
+ '**/uv.lock',
31
+ '**/Pipfile.lock',
32
+ '**/pdm.lock',
33
+ '**/Gemfile.lock',
34
+ '**/composer.lock',
35
+ '**/go.sum',
36
+ '**/Package.resolved',
37
+ ];
15
38
  /**
16
39
  * Parse `git diff --numstat` and collect files git reports as binary — those
17
40
  * rows carry `-` in both the additions and deletions columns.
@@ -45,7 +68,7 @@ exports.largeFilesCheck = {
45
68
  return (0, types_1.pass)();
46
69
  const maxKb = ctx.config.largeFiles?.maxSizeKb ?? DEFAULT_MAX_KB;
47
70
  const maxBytes = maxKb * 1024;
48
- const allow = ctx.config.largeFiles?.allow ?? [];
71
+ const allow = [...LOCKFILE_ALLOW, ...(ctx.config.largeFiles?.allow ?? [])];
49
72
  let binary;
50
73
  try {
51
74
  const { stdout } = await (0, execa_1.execa)('git', ['diff', '--numstat', '--diff-filter=ACM', ctx.baseSha, ctx.headSha ?? 'HEAD'], { cwd: ctx.cwd });
@@ -57,7 +80,7 @@ exports.largeFilesCheck = {
57
80
  }
58
81
  const findings = [];
59
82
  for (const file of files) {
60
- if (allow.length > 0 && (0, utils_1.matchesAnyGlob)(file, allow))
83
+ if ((0, utils_1.matchesAnyGlob)(file, allow))
61
84
  continue;
62
85
  let size;
63
86
  try {
@@ -23,7 +23,12 @@ exports.securityCheck = {
23
23
  'gitleaks.toml',
24
24
  '.gitleaks/config.toml',
25
25
  ]);
26
- const args = ['--diff-mode', 'ci'];
26
+ // Pin the gitleaks binary version so the scanner never reaches out to the
27
+ // GitHub API for the "latest" version — that network call adds latency and
28
+ // times out in locked-down/offline CI (the scanner already falls back to
29
+ // this exact version). Overridable via `security.gitleaks.version`.
30
+ const gitleaksVersion = ctx.config.security?.gitleaks?.version ?? '8.30.0';
31
+ const args = ['--diff-mode', 'ci', '--gitleaks-version', gitleaksVersion];
27
32
  if (!hasClientConfig) {
28
33
  args.push('--config', (0, config_1.getPackageConfigPath)('gitleaks.toml'));
29
34
  }
@@ -69,6 +69,8 @@ function printStdout(report) {
69
69
  utils_1.logger.info(SUMMARY_RULE);
70
70
  utils_1.logger.info(` ✅ ${passed} passed ⚠️ ${warned} warnings ❌ ${failed} failed ⏭️ ${skipped} skipped`);
71
71
  utils_1.logger.info(SUMMARY_BORDER);
72
+ // Always shown under the report — the docs cover every check and config key.
73
+ utils_1.logger.info(' DOCUMENTATION: https://pr-checkmate-docs.pages.dev/');
72
74
  }
73
75
  async function upsertGithubComment(body) {
74
76
  const ctx = getPrContext();
package/dist/init.js CHANGED
@@ -232,6 +232,25 @@ async function createConfig(root) {
232
232
  sourcePath,
233
233
  languages,
234
234
  };
235
+ // Omit config blocks for languages not present in the repo — a cleaner,
236
+ // tailored file. This does NOT disable them: the key is simply absent, which
237
+ // still defaults to enabled, and runtime language detection gates whether each
238
+ // check runs. So a language added later auto-activates without editing config.
239
+ const LANGUAGE_TOGGLE_KEYS = [
240
+ 'python',
241
+ 'cpp',
242
+ 'swift',
243
+ 'kotlin',
244
+ 'go',
245
+ 'rust',
246
+ 'csharp',
247
+ 'ruby',
248
+ 'php',
249
+ ];
250
+ for (const key of LANGUAGE_TOGGLE_KEYS) {
251
+ if (!languages.includes(key))
252
+ delete config[key];
253
+ }
235
254
  fs_1.default.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
236
255
  utils_1.logger.info(`[init]: ✅ Created pr-checkmate.json with sourcePath: ${sourcePath}`);
237
256
  utils_1.logger.info(`[init]: 💡 You can now customize ESLint rules, Prettier settings,
@@ -49,6 +49,7 @@ export interface PRCheckMateConfig {
49
49
  security?: {
50
50
  gitleaks?: {
51
51
  enabled: boolean;
52
+ version?: string;
52
53
  };
53
54
  'npm-audit'?: {
54
55
  enabled: boolean;
@@ -153,6 +154,7 @@ export interface PRCheckMateConfig {
153
154
  };
154
155
  outdatedDeps?: {
155
156
  enabled?: boolean;
157
+ timeoutMs?: number;
156
158
  /**
157
159
  * Exact package names to exclude from the outdated-deps warning, e.g.
158
160
  * "commander". Use for dependencies deliberately pinned behind their latest
@@ -4,6 +4,7 @@ export * from './resolve-bin';
4
4
  export * from './own-deps';
5
5
  export * from './detect-language';
6
6
  export * from './glob';
7
+ export * from './update-notifier';
7
8
  /**
8
9
  *
9
10
  */
@@ -26,6 +26,7 @@ __exportStar(require("./resolve-bin"), exports);
26
26
  __exportStar(require("./own-deps"), exports);
27
27
  __exportStar(require("./detect-language"), exports);
28
28
  __exportStar(require("./glob"), exports);
29
+ __exportStar(require("./update-notifier"), exports);
29
30
  /**
30
31
  *
31
32
  */
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Best-effort check for a newer published version. Returns the notice lines when
3
+ * an update exists, or `null` (up-to-date, offline, timed out, or opted out).
4
+ * Non-blocking and fully silent on any failure: short timeout, swallowed errors,
5
+ * opt-out via `PR_CHECKMATE_NO_UPDATE_CHECK`. Returns lines instead of logging so
6
+ * the caller can print them at the very end — after the summary — where they're
7
+ * noticeable. Deliberately called ONLY from the CLI (not `runChecks`) so
8
+ * programmatic consumers (e.g. a wrapping SDK) never trigger a network call.
9
+ */
10
+ export declare function getUpdateNotice(timeoutMs?: number): Promise<string[] | null>;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getUpdateNotice = getUpdateNotice;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ /** Compare dotted versions; true when `latest` is strictly greater than `current`. */
10
+ function isNewer(latest, current) {
11
+ const a = latest.split('.').map(n => parseInt(n, 10) || 0);
12
+ const b = current.split('.').map(n => parseInt(n, 10) || 0);
13
+ for (let i = 0; i < 3; i++) {
14
+ if ((a[i] ?? 0) !== (b[i] ?? 0))
15
+ return (a[i] ?? 0) > (b[i] ?? 0);
16
+ }
17
+ return false;
18
+ }
19
+ /**
20
+ * Best-effort check for a newer published version. Returns the notice lines when
21
+ * an update exists, or `null` (up-to-date, offline, timed out, or opted out).
22
+ * Non-blocking and fully silent on any failure: short timeout, swallowed errors,
23
+ * opt-out via `PR_CHECKMATE_NO_UPDATE_CHECK`. Returns lines instead of logging so
24
+ * the caller can print them at the very end — after the summary — where they're
25
+ * noticeable. Deliberately called ONLY from the CLI (not `runChecks`) so
26
+ * programmatic consumers (e.g. a wrapping SDK) never trigger a network call.
27
+ */
28
+ async function getUpdateNotice(timeoutMs = 2500) {
29
+ if (process.env.PR_CHECKMATE_NO_UPDATE_CHECK)
30
+ return null;
31
+ try {
32
+ const pkgPath = node_path_1.default.resolve(__dirname, '../..', 'package.json');
33
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, 'utf-8'));
34
+ const controller = new AbortController();
35
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
36
+ let latest;
37
+ try {
38
+ const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(pkg.name)}/latest`, {
39
+ signal: controller.signal,
40
+ });
41
+ if (res.ok)
42
+ latest = (await res.json()).version;
43
+ }
44
+ finally {
45
+ clearTimeout(timer);
46
+ }
47
+ if (latest && isNewer(latest, pkg.version)) {
48
+ return [
49
+ `⬆️ A new version of ${pkg.name} is available: ${pkg.version} → ${latest}`,
50
+ ` Update: npm install ${pkg.name}@latest`,
51
+ ' Details: https://www.npmjs.com/package/pr-checkmate',
52
+ ];
53
+ }
54
+ return null;
55
+ }
56
+ catch {
57
+ // Best-effort only — never let an update check slow or break a run.
58
+ return null;
59
+ }
60
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-checkmate",
3
- "version": "1.23.0",
3
+ "version": "1.23.1",
4
4
  "description": "Automated PR quality checks: linting, formatting, dependency analysis, and spellcheck",
5
5
  "keywords": [
6
6
  "github-actions",
@@ -701,7 +701,22 @@
701
701
  "depcheckrc",
702
702
  "gofmt",
703
703
  "dotnet",
704
- "rubocop"
704
+ "rubocop",
705
+ "precommit",
706
+ "swiftpm",
707
+ "recognises",
708
+ "vulnerab",
709
+ "numstat",
710
+ "bpprint",
711
+ "pprint",
712
+ "bpdb",
713
+ "setext",
714
+ "Confict",
715
+ "bhashlib",
716
+ "bfmt",
717
+ "dockerfiles",
718
+ "footguns",
719
+ "materialises"
705
720
  ],
706
721
  "ignorePaths": [
707
722
  "**/node_modules/**",