pr-checkmate 2.1.0 → 2.1.2

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 CHANGED
@@ -143,11 +143,12 @@ machine — ShellCheck among them — are inside, so they stop silently skipping
143
143
  version tag (`:2.0.0`) for a reproducible pipeline; `--user` keeps files that formatters
144
144
  rewrite owned by you rather than by root.
145
145
 
146
- Verify what you pulled — the image carries build provenance signed by the workflow
147
- that produced it:
146
+ Verify what you pulled — the image carries SLSA provenance and an SBOM, attached to
147
+ the image itself in the registry:
148
148
 
149
149
  ```bash
150
- gh attestation verify oci://ghcr.io/mybono/pr-checkmate:2 --owner Mybono
150
+ docker buildx imagetools inspect ghcr.io/mybono/pr-checkmate:2 \
151
+ --format '{{ json .Provenance }}'
151
152
  ```
152
153
 
153
154
  [All tags →](https://github.com/users/Mybono/packages/container/package/pr-checkmate)
@@ -70,6 +70,13 @@ export declare const CONFIG_FILE_PATTERNS: string[];
70
70
  * config/CI YAML and JSON out of the report — they are data, not code, and their
71
71
  * repetition is not meaningful duplication. This mirrors the code-only file set the
72
72
  * check already uses when scanning a git diff, so full-tree and diff scans agree.
73
+ *
74
+ * One entry per language pr-checkmate detects, all of them verified against the
75
+ * jscpd binary rather than assumed: the list used to stop at C++ while the check
76
+ * only ever handed jscpd `.ts`/`.js` files, so the Go, Rust, C#, Ruby and PHP
77
+ * halves were unreachable and the Python one was decoration. Copy-paste is a
78
+ * language-independent smell, and the tokenizer behind this check has always been
79
+ * able to see it — the file list was the only thing in the way.
73
80
  */
74
81
  export declare const DUPLICATE_CODE_FORMATS: string[];
75
82
  /**
@@ -192,6 +192,13 @@ exports.CONFIG_FILE_PATTERNS = [
192
192
  * config/CI YAML and JSON out of the report — they are data, not code, and their
193
193
  * repetition is not meaningful duplication. This mirrors the code-only file set the
194
194
  * check already uses when scanning a git diff, so full-tree and diff scans agree.
195
+ *
196
+ * One entry per language pr-checkmate detects, all of them verified against the
197
+ * jscpd binary rather than assumed: the list used to stop at C++ while the check
198
+ * only ever handed jscpd `.ts`/`.js` files, so the Go, Rust, C#, Ruby and PHP
199
+ * halves were unreachable and the Python one was decoration. Copy-paste is a
200
+ * language-independent smell, and the tokenizer behind this check has always been
201
+ * able to see it — the file list was the only thing in the way.
195
202
  */
196
203
  exports.DUPLICATE_CODE_FORMATS = [
197
204
  'javascript',
@@ -203,6 +210,11 @@ exports.DUPLICATE_CODE_FORMATS = [
203
210
  'swift',
204
211
  'c',
205
212
  'cpp',
213
+ 'csharp',
214
+ 'go',
215
+ 'php',
216
+ 'ruby',
217
+ 'rust',
206
218
  ];
207
219
  /**
208
220
  * File extensions to check
@@ -106,7 +106,7 @@ exports.dependencyCheck = {
106
106
  applies(ctx) {
107
107
  if (ctx.config.dependency?.enabled === false)
108
108
  return false;
109
- return node_fs_1.default.existsSync(node_path_1.default.join(ctx.cwd, 'package.json'));
109
+ return (0, types_1.isNpmProject)(ctx);
110
110
  },
111
111
  async run(ctx) {
112
112
  // Resolve the bundled knip binary and run it via the current node — never
@@ -1,11 +1,6 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.licenseCheck = void 0;
7
- const node_fs_1 = __importDefault(require("node:fs"));
8
- const node_path_1 = __importDefault(require("node:path"));
9
4
  const utils_1 = require("../../../utils");
10
5
  const execa_1 = require("../../../utils/execa");
11
6
  const types_1 = require("../../types");
@@ -19,7 +14,7 @@ exports.licenseCheck = {
19
14
  name: 'License Check',
20
15
  phase: 'blocking',
21
16
  applies(ctx) {
22
- return node_fs_1.default.existsSync(node_path_1.default.join(ctx.cwd, 'package.json'));
17
+ return (0, types_1.isNpmProject)(ctx);
23
18
  },
24
19
  async run(ctx) {
25
20
  const allowPackages = new Set(ctx.config.licenseCheck?.allowPackages ?? []);
@@ -34,7 +34,7 @@ exports.npmAuditCheck = {
34
34
  applies(ctx) {
35
35
  if (ctx.config.security?.['npm-audit']?.enabled === false)
36
36
  return false;
37
- return node_fs_1.default.existsSync(node_path_1.default.join(ctx.cwd, 'package.json'));
37
+ return (0, types_1.isNpmProject)(ctx);
38
38
  },
39
39
  async run(ctx) {
40
40
  // `high`, not `moderate`. A moderate CVE in a direct dependency often has no
@@ -1,11 +1,6 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.outdatedDepsCheck = void 0;
7
- const node_fs_1 = __importDefault(require("node:fs"));
8
- const node_path_1 = __importDefault(require("node:path"));
9
4
  const utils_1 = require("../../../utils");
10
5
  const execa_1 = require("../../../utils/execa");
11
6
  const types_1 = require("../../types");
@@ -16,7 +11,7 @@ exports.outdatedDepsCheck = {
16
11
  applies(ctx) {
17
12
  if (ctx.config.outdatedDeps?.enabled === false)
18
13
  return false;
19
- return node_fs_1.default.existsSync(node_path_1.default.join(ctx.cwd, 'package.json'));
14
+ return (0, types_1.isNpmProject)(ctx);
20
15
  },
21
16
  async run(ctx) {
22
17
  // `npm outdated` queries the registry for every dependency and can take a
@@ -7,6 +7,37 @@ const utils_1 = require("../../../utils");
7
7
  const execa_1 = require("../../../utils/execa");
8
8
  const targets_1 = require("../../targets");
9
9
  const types_1 = require("../../types");
10
+ /**
11
+ * The files to hand jscpd, per language.
12
+ *
13
+ * This map is why the check reads `ctx.languages` at all. It used to pass a fixed
14
+ * `*.ts,*.tsx,*.js,*.jsx` and gate itself on TypeScript/JavaScript, so a Python
15
+ * repository got `⏭️ Duplicate Code: no relevant files` — not because it had no
16
+ * duplication, but because nothing ever looked. The formats list in `constants.ts`
17
+ * named Python and C++ all along, which made the report's own claim untrue.
18
+ *
19
+ * Extensions mirror `detect-language.ts` so a language that decides the check runs
20
+ * is a language whose files it actually scans, with one deliberate addition: `.c`
21
+ * and `.h` under `cpp`, since a C project detects as `cpp` and the `c` format is
22
+ * one jscpd supports.
23
+ */
24
+ const LANGUAGE_GLOBS = {
25
+ typescript: ['*.ts', '*.tsx'],
26
+ javascript: ['*.js', '*.jsx', '*.mjs', '*.cjs'],
27
+ python: ['*.py'],
28
+ cpp: ['*.cpp', '*.cc', '*.cxx', '*.c++', '*.hpp', '*.hh', '*.hxx', '*.c', '*.h'],
29
+ swift: ['*.swift'],
30
+ kotlin: ['*.kt', '*.kts', '*.java'],
31
+ go: ['*.go'],
32
+ rust: ['*.rs'],
33
+ csharp: ['*.cs'],
34
+ ruby: ['*.rb'],
35
+ php: ['*.php'],
36
+ };
37
+ /** The scan globs for one run: every detected language jscpd can tokenise. */
38
+ function globsFor(languages) {
39
+ return [...new Set(languages.flatMap(lang => LANGUAGE_GLOBS[lang] ?? []))];
40
+ }
10
41
  // Always excluded regardless of client config — scanning these never yields
11
42
  // meaningful production duplication. Config files (eslint/*.config.*) are data,
12
43
  // not logic, and are commonly shipped in two formats (e.g. .eslintrc + flat
@@ -36,12 +67,12 @@ exports.duplicateCheck = {
36
67
  applies(ctx) {
37
68
  if (ctx.config.duplicate?.enabled === false)
38
69
  return false;
39
- return ctx.languages.includes('typescript') || ctx.languages.includes('javascript');
70
+ return globsFor(ctx.languages).length > 0;
40
71
  },
41
72
  async run(ctx) {
42
73
  const minLines = ctx.config.duplicate?.minLines ?? 5;
43
74
  const minTokens = ctx.config.duplicate?.minTokens ?? 50;
44
- const targetPaths = await (0, targets_1.resolveScopedTargetFiles)(ctx, ['*.ts', '*.tsx', '*.js', '*.jsx'], (0, config_1.getSourcePaths)());
75
+ const targetPaths = await (0, targets_1.resolveScopedTargetFiles)(ctx, globsFor(ctx.languages), (0, config_1.getSourcePaths)());
45
76
  if (targetPaths === null)
46
77
  return (0, types_1.skip)('git unavailable');
47
78
  if (targetPaths.length === 0)
@@ -1,8 +1,8 @@
1
1
  import { type LanguageUsage } from '../../utils/detect-language';
2
+ import { packageVersion } from '../../utils/package-version';
2
3
  import { type EnsureResult } from './ensure';
3
4
  import { type ProfileDecision } from './profiles';
4
- /** Our own version, read from the package manifest that ships with us. */
5
- export declare function packageVersion(): string;
5
+ export { packageVersion };
6
6
  /**
7
7
  * Everything the install layer knows about this repository, in one shot.
8
8
  *
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.packageVersion = packageVersion;
6
+ exports.packageVersion = void 0;
7
7
  exports.planInstall = planInstall;
8
8
  exports.installProfiles = installProfiles;
9
9
  exports.runDoctor = runDoctor;
@@ -13,19 +13,11 @@ const node_path_1 = __importDefault(require("node:path"));
13
13
  const loader_1 = require("../../config/loader");
14
14
  const detect_language_1 = require("../../utils/detect-language");
15
15
  const logger_1 = require("../../utils/logger");
16
+ const package_version_1 = require("../../utils/package-version");
17
+ Object.defineProperty(exports, "packageVersion", { enumerable: true, get: function () { return package_version_1.packageVersion; } });
16
18
  const cache_1 = require("./cache");
17
19
  const ensure_1 = require("./ensure");
18
20
  const profiles_1 = require("./profiles");
19
- /** Our own version, read from the package manifest that ships with us. */
20
- function packageVersion() {
21
- try {
22
- const manifest = node_path_1.default.resolve(__dirname, '../../..', 'package.json');
23
- return JSON.parse(node_fs_1.default.readFileSync(manifest, 'utf-8')).version ?? '0.0.0';
24
- }
25
- catch {
26
- return '0.0.0';
27
- }
28
- }
29
21
  /**
30
22
  * Everything the install layer knows about this repository, in one shot.
31
23
  *
@@ -36,7 +28,7 @@ function packageVersion() {
36
28
  async function planInstall(cwd = process.cwd(), usage) {
37
29
  const config = (0, loader_1.loadProjectConfig)();
38
30
  const detected = usage ?? (await (0, detect_language_1.detectLanguageUsage)(cwd, config.ignoreDirs, config.detect));
39
- const location = (0, cache_1.resolveCacheDir)(packageVersion(), config.install?.cacheDir);
31
+ const location = (0, cache_1.resolveCacheDir)((0, package_version_1.packageVersion)(), config.install?.cacheDir);
40
32
  return { decisions: (0, profiles_1.planProfiles)(detected, config), cacheDir: location?.dir ?? null };
41
33
  }
42
34
  /** Whether a profile's tooling is present in the cache right now. */
@@ -71,7 +63,7 @@ async function installProfiles(cwd = process.cwd(), all = false) {
71
63
  // packages fight over npm's cache and gain nothing; the wall-clock cost lands
72
64
  // once, on a cold cache, and clarity of the log matters more there.
73
65
  for (const profile of wanted) {
74
- results.push(await (0, ensure_1.ensureProfile)(profile, { cwd, version: packageVersion(), install }));
66
+ results.push(await (0, ensure_1.ensureProfile)(profile, { cwd, version: (0, package_version_1.packageVersion)(), install }));
75
67
  }
76
68
  return results;
77
69
  }
@@ -90,7 +82,7 @@ async function runDoctor(cwd = process.cwd()) {
90
82
  const lines = [];
91
83
  lines.push('PR CheckMate — install doctor');
92
84
  lines.push('');
93
- lines.push(` version ${packageVersion()}`);
85
+ lines.push(` version ${(0, package_version_1.packageVersion)()}`);
94
86
  lines.push(` repository ${cwd}`);
95
87
  lines.push(` cache ${cacheDir ?? 'unavailable (no writable directory)'}`);
96
88
  lines.push(` strategy ${config.install?.strategy ?? 'adaptive'}`);
@@ -116,7 +108,7 @@ async function runDoctor(cwd = process.cwd()) {
116
108
  lines.push(` ${missing.length} profile(s) not installed yet. They are fetched on the next`);
117
109
  lines.push(' run, or now with: npx pr-checkmate install');
118
110
  }
119
- const stale = (0, cache_1.stalePaths)(packageVersion(), config.install?.cacheDir);
111
+ const stale = (0, cache_1.stalePaths)((0, package_version_1.packageVersion)(), config.install?.cacheDir);
120
112
  if (stale.length > 0) {
121
113
  lines.push('');
122
114
  lines.push(` ${stale.length} directory(ies) from other versions: npx pr-checkmate cache-prune`);
@@ -126,7 +118,7 @@ async function runDoctor(cwd = process.cwd()) {
126
118
  /** Remove cached tooling belonging to other versions of pr-checkmate. */
127
119
  function pruneCache() {
128
120
  const config = (0, loader_1.loadProjectConfig)();
129
- const stale = (0, cache_1.stalePaths)(packageVersion(), config.install?.cacheDir);
121
+ const stale = (0, cache_1.stalePaths)((0, package_version_1.packageVersion)(), config.install?.cacheDir);
130
122
  if (stale.length === 0) {
131
123
  logger_1.logger.print('Nothing to prune — no tooling from other versions is cached.');
132
124
  return;
@@ -8,6 +8,7 @@ exports.report = report;
8
8
  const node_fs_1 = __importDefault(require("node:fs"));
9
9
  const utils_1 = require("../utils");
10
10
  const http_1 = require("../utils/http");
11
+ const package_version_1 = require("../utils/package-version");
11
12
  const STATUS_ICON = {
12
13
  pass: '✅',
13
14
  warn: '⚠️',
@@ -74,7 +75,7 @@ function buildMarkdownReport(report, cfg = {}) {
74
75
  // Say so rather than letting the table quietly disagree with the counts above.
75
76
  ...(hidden > 0 ? ['', `_${hidden} row(s) hidden by \`reporter.include\`._`] : []),
76
77
  '',
77
- '_Generated by [PR CheckMate](https://www.npmjs.com/package/pr-checkmate)_',
78
+ `_Generated by [PR CheckMate](https://www.npmjs.com/package/pr-checkmate) v${(0, package_version_1.packageVersion)()}_`,
78
79
  ].join('\n');
79
80
  }
80
81
  /**
@@ -89,7 +90,12 @@ function printStdout(report, cfg) {
89
90
  const { passed, warned, failed, skipped } = report.summary;
90
91
  utils_1.logger.print('');
91
92
  utils_1.logger.print(SUMMARY_BORDER);
92
- utils_1.logger.print(' PR CheckMate Summary');
93
+ // The version belongs in the headline, not in a footnote. Which copy of
94
+ // pr-checkmate produced a report is the first thing to establish when the
95
+ // report disagrees with what someone expects, and it is not otherwise
96
+ // recoverable from the log: a stale global install answers `pr-checkmate` on
97
+ // `PATH` ahead of the project's own, and reads the same config while doing it.
98
+ utils_1.logger.print(` PR CheckMate Summary · v${(0, package_version_1.packageVersion)()}`);
93
99
  utils_1.logger.print(SUMMARY_BORDER);
94
100
  for (const r of report.results) {
95
101
  const details = r.details ? `: ${r.details}` : '';
@@ -164,6 +164,24 @@ export declare const pass: (details?: string) => CheckOutcome;
164
164
  export declare const warn: (details: string) => CheckOutcome;
165
165
  export declare const fail: (details: string) => CheckOutcome;
166
166
  export declare const skip: (details: string) => CheckOutcome;
167
+ /**
168
+ * Whether this repository is an npm project — the gate for every check that reads
169
+ * the npm dependency tree (NPM Audit, Dependencies, License Check, Outdated Deps).
170
+ *
171
+ * A `package.json` on its own is not the answer, and this is the one predicate
172
+ * where that matters most: installing pr-checkmate as a devDependency *creates*
173
+ * that file. Gate on it alone and a Python service gets four rows auditing the
174
+ * dependency tree of the tool auditing it — `npm audit` over our own transitive
175
+ * packages, licences of our own `node_modules`, "outdated" versions of tooling
176
+ * the team never chose. All four are unactionable by construction: nothing in the
177
+ * repository's own code can change any of them.
178
+ *
179
+ * So both halves must hold — a manifest to read, and JavaScript or TypeScript
180
+ * actually written here. A polyglot repository (a Django backend with a JS
181
+ * frontend) satisfies both and keeps every check; a Python repository whose only
182
+ * `package.json` is the one that installed us satisfies neither.
183
+ */
184
+ export declare function isNpmProject(ctx: CheckContext): boolean;
167
185
  /**
168
186
  * Identity helper for authoring a `Check` with full type inference and checking.
169
187
  * Purely ergonomic — `defineCheck({ ... })` is equivalent to `{ ... } satisfies Check`.
@@ -1,6 +1,12 @@
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.defineCheck = exports.skip = exports.fail = exports.warn = exports.pass = exports.PHASES = void 0;
7
+ exports.isNpmProject = isNpmProject;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
4
10
  exports.PHASES = ['blocking', 'informational', 'format'];
5
11
  /** Outcome constructors — keep check bodies terse. */
6
12
  const pass = (details) => ({
@@ -14,6 +20,28 @@ const fail = (details) => ({ status: 'fail', details });
14
20
  exports.fail = fail;
15
21
  const skip = (details) => ({ status: 'skip', details });
16
22
  exports.skip = skip;
23
+ /**
24
+ * Whether this repository is an npm project — the gate for every check that reads
25
+ * the npm dependency tree (NPM Audit, Dependencies, License Check, Outdated Deps).
26
+ *
27
+ * A `package.json` on its own is not the answer, and this is the one predicate
28
+ * where that matters most: installing pr-checkmate as a devDependency *creates*
29
+ * that file. Gate on it alone and a Python service gets four rows auditing the
30
+ * dependency tree of the tool auditing it — `npm audit` over our own transitive
31
+ * packages, licences of our own `node_modules`, "outdated" versions of tooling
32
+ * the team never chose. All four are unactionable by construction: nothing in the
33
+ * repository's own code can change any of them.
34
+ *
35
+ * So both halves must hold — a manifest to read, and JavaScript or TypeScript
36
+ * actually written here. A polyglot repository (a Django backend with a JS
37
+ * frontend) satisfies both and keeps every check; a Python repository whose only
38
+ * `package.json` is the one that installed us satisfies neither.
39
+ */
40
+ function isNpmProject(ctx) {
41
+ if (!ctx.languages.includes('javascript') && !ctx.languages.includes('typescript'))
42
+ return false;
43
+ return node_fs_1.default.existsSync(node_path_1.default.join(ctx.cwd, 'package.json'));
44
+ }
17
45
  /**
18
46
  * Identity helper for authoring a `Check` with full type inference and checking.
19
47
  * Purely ergonomic — `defineCheck({ ... })` is equivalent to `{ ... } satisfies Check`.
@@ -18,7 +18,17 @@ const logger_1 = require("./logger");
18
18
  */
19
19
  const MARKER_FILES = {
20
20
  typescript: ['tsconfig.json'],
21
- javascript: ['package.json'],
21
+ // Deliberately empty. `package.json` used to sit here, and it is the one marker
22
+ // this package creates itself: installing pr-checkmate through npm puts a
23
+ // `package.json` in a repository that may not hold a single line of JavaScript.
24
+ // A Python service then detected as JavaScript and got the whole JS half of the
25
+ // report — ESLint, Prettier, NPM Audit, License Check — every row of it about
26
+ // the file that installed us. `install/profiles.ts` already refused to buy
27
+ // 152 MB of toolchain off this marker (ticket 064); the checks now refuse to
28
+ // run off it for the same reason. Real JavaScript is detected by its `.js`,
29
+ // `.jsx`, `.mjs` and `.cjs` files, and a repository that has none loses
30
+ // nothing: every JS check it lost would have reported "no files" anyway.
31
+ javascript: [],
22
32
  python: ['pyproject.toml', 'setup.py', 'setup.cfg', 'requirements.txt', 'Pipfile'],
23
33
  cpp: ['CMakeLists.txt', 'compile_commands.json', 'conanfile.txt', 'conanfile.py'],
24
34
  swift: ['Package.swift', 'Package.resolved'],
@@ -6,6 +6,7 @@ export * from './direct-deps';
6
6
  export * from './detect-language';
7
7
  export * from './glob';
8
8
  export * from './update-notifier';
9
+ export * from './package-version';
9
10
  export * from './ci';
10
11
  export * from './git';
11
12
  /**
@@ -28,6 +28,7 @@ __exportStar(require("./direct-deps"), exports);
28
28
  __exportStar(require("./detect-language"), exports);
29
29
  __exportStar(require("./glob"), exports);
30
30
  __exportStar(require("./update-notifier"), exports);
31
+ __exportStar(require("./package-version"), exports);
31
32
  __exportStar(require("./ci"), exports);
32
33
  __exportStar(require("./git"), exports);
33
34
  /**
@@ -0,0 +1,4 @@
1
+ /** The version of pr-checkmate that is executing right now. */
2
+ export declare function packageVersion(): string;
3
+ /** The package name that is executing right now — the one to query npm for. */
4
+ export declare function packageName(): string;
@@ -0,0 +1,41 @@
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.packageVersion = packageVersion;
7
+ exports.packageName = packageName;
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ /**
11
+ * Our own name and version, read from the manifest that ships inside the package.
12
+ *
13
+ * Read from disk rather than baked in at build time so it is the *running* copy
14
+ * that answers. That distinction is the whole point: a stale global install
15
+ * (`npm i -g pr-checkmate`) shadows the project's own copy on `PATH`, and a repo
16
+ * pinned to the latest version can still be checked by a year-old binary without
17
+ * anything in the log saying so. One client spent a while reading an upgrade
18
+ * notice for a version they had already installed, because nothing printed which
19
+ * version was speaking.
20
+ *
21
+ * Falls back to placeholders rather than throwing: no caller here is important
22
+ * enough to fail a run over a manifest it could not read.
23
+ */
24
+ function readManifest() {
25
+ try {
26
+ const manifest = node_path_1.default.resolve(__dirname, '../..', 'package.json');
27
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(manifest, 'utf-8'));
28
+ return { name: parsed.name ?? 'pr-checkmate', version: parsed.version ?? '0.0.0' };
29
+ }
30
+ catch {
31
+ return { name: 'pr-checkmate', version: '0.0.0' };
32
+ }
33
+ }
34
+ /** The version of pr-checkmate that is executing right now. */
35
+ function packageVersion() {
36
+ return readManifest().version;
37
+ }
38
+ /** The package name that is executing right now — the one to query npm for. */
39
+ function packageName() {
40
+ return readManifest().name;
41
+ }
@@ -1,16 +1,12 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.isMajorUpgrade = isMajorUpgrade;
7
4
  exports.getUpdateNotice = getUpdateNotice;
8
- const node_fs_1 = __importDefault(require("node:fs"));
9
- const node_path_1 = __importDefault(require("node:path"));
10
5
  // Imported from the module rather than the `../config` barrel on purpose:
11
6
  // `config/index.ts` imports `../utils`, so going through the barrel would make
12
7
  // utils ⇄ config circular. `constants.ts` itself pulls in nothing from utils.
13
8
  const constants_1 = require("../config/constants");
9
+ const package_version_1 = require("./package-version");
14
10
  // Same widths as the run summary in `reporters.ts`, so the two boxes line up
15
11
  // when both are printed at the end of a run.
16
12
  const NOTICE_BORDER = '═'.repeat(63);
@@ -72,8 +68,10 @@ async function getUpdateNotice(timeoutMs = 2500) {
72
68
  if (process.env.PR_CHECKMATE_NO_UPDATE_CHECK)
73
69
  return null;
74
70
  try {
75
- const pkgPath = node_path_1.default.resolve(__dirname, '../..', 'package.json');
76
- const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgPath, 'utf-8'));
71
+ // The *running* copy's identity, not the project's declared dependency. A
72
+ // stale global install shadows the local one on `PATH`, and it is the stale
73
+ // copy that must own the notice it prints.
74
+ const pkg = { name: (0, package_version_1.packageName)(), version: (0, package_version_1.packageVersion)() };
77
75
  const controller = new AbortController();
78
76
  const timer = setTimeout(() => controller.abort(), timeoutMs);
79
77
  let latest;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-checkmate",
3
- "version": "2.1.0",
3
+ "version": "2.1.2",
4
4
  "description": "Security-first PR gate for CI: secret scanning, grype dependency scanning, SBOM — plus linting and formatting across 11 languages",
5
5
  "keywords": [
6
6
  "github-actions",