pr-checkmate 1.21.15 → 1.22.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.
@@ -4,6 +4,7 @@ exports.sensitiveFileGuardCheck = void 0;
4
4
  const execa_1 = require("execa");
5
5
  const utils_1 = require("../../utils");
6
6
  const types_1 = require("../types");
7
+ const findings_1 = require("./findings");
7
8
  const SENSITIVE_CATEGORIES = [
8
9
  {
9
10
  label: 'Environment files (.env)',
@@ -53,6 +54,21 @@ const SENSITIVE_CATEGORIES = [
53
54
  severity: 'warn',
54
55
  },
55
56
  ];
57
+ /** Bucket changed files by category severity, keyed by category label. */
58
+ function categorizeFiles(changedFiles) {
59
+ var _a;
60
+ const errors = {};
61
+ const warnings = {};
62
+ for (const filePath of changedFiles) {
63
+ for (const category of SENSITIVE_CATEGORIES) {
64
+ if (category.patterns.some(p => p.test(filePath))) {
65
+ const bucket = category.severity === 'error' ? errors : warnings;
66
+ (bucket[_a = category.label] ?? (bucket[_a] = [])).push(filePath);
67
+ }
68
+ }
69
+ }
70
+ return { errors, warnings };
71
+ }
56
72
  exports.sensitiveFileGuardCheck = {
57
73
  name: 'Sensitive Files',
58
74
  phase: 'informational',
@@ -60,40 +76,18 @@ exports.sensitiveFileGuardCheck = {
60
76
  return ctx.config.sensitiveFileGuard?.enabled !== false && Boolean(ctx.baseSha);
61
77
  },
62
78
  async run(ctx) {
63
- var _a;
64
79
  try {
65
80
  const { stdout } = await (0, execa_1.execa)('git', ['diff', '--name-only', '--diff-filter=ACMRD', ctx.baseSha, 'HEAD'], { cwd: ctx.cwd });
66
81
  const changedFiles = stdout.split('\n').filter(Boolean);
67
82
  if (changedFiles.length === 0)
68
83
  return (0, types_1.pass)();
69
- const errors = {};
70
- const warnings = {};
71
- for (const filePath of changedFiles) {
72
- for (const category of SENSITIVE_CATEGORIES) {
73
- if (category.patterns.some(p => p.test(filePath))) {
74
- const bucket = category.severity === 'error' ? errors : warnings;
75
- (bucket[_a = category.label] ?? (bucket[_a] = [])).push(filePath);
76
- }
77
- }
78
- }
79
- const totalErrors = Object.values(errors).reduce((s, a) => s + a.length, 0);
80
- const totalWarnings = Object.values(warnings).reduce((s, a) => s + a.length, 0);
81
- if (totalErrors === 0 && totalWarnings === 0)
82
- return (0, types_1.pass)();
83
- for (const [label, files] of Object.entries(errors)) {
84
- utils_1.logger.error(`[Sensitive Files]: ❌ ${label}:`);
85
- files.forEach(f => utils_1.logger.error(` ${f}`));
86
- }
87
- for (const [label, files] of Object.entries(warnings)) {
88
- utils_1.logger.warn(`[Sensitive Files]: ⚠️ ${label}:`);
89
- files.forEach(f => utils_1.logger.warn(` ${f}`));
90
- }
91
- const parts = [];
92
- if (totalErrors > 0)
93
- parts.push(`${totalErrors} sensitive file(s) changed`);
94
- if (totalWarnings > 0)
95
- parts.push(`${totalWarnings} notable file(s) changed`);
96
- return (0, types_1.warn)(parts.join(', '));
84
+ const { errors, warnings } = categorizeFiles(changedFiles);
85
+ return (0, findings_1.reportSeverityBuckets)(errors, warnings, {
86
+ prefix: 'Sensitive Files',
87
+ errorSummary: n => `${n} sensitive file(s) changed`,
88
+ warningSummary: n => `${n} notable file(s) changed`,
89
+ showCount: false,
90
+ });
97
91
  }
98
92
  catch (err) {
99
93
  utils_1.logger.warn(`[Sensitive Files]: ⚠️ Could not check file changes\n${err}`);
@@ -7,27 +7,27 @@ const commitlint_1 = require("./checks/commitlint");
7
7
  const cpp_format_1 = require("./checks/cpp-format");
8
8
  const dead_code_1 = require("./checks/dead-code");
9
9
  const dependency_check_1 = require("./checks/dependency-check");
10
+ const diff_security_1 = require("./checks/diff-security");
10
11
  const diff_smell_1 = require("./checks/diff-smell");
12
+ const duplicate_check_1 = require("./checks/duplicate-check");
13
+ const kotlin_lint_1 = require("./checks/kotlin-lint");
11
14
  const license_check_1 = require("./checks/license-check");
12
15
  const lint_1 = require("./checks/lint");
16
+ const markdown_lint_1 = require("./checks/markdown-lint");
13
17
  const npm_audit_1 = require("./checks/npm-audit");
14
18
  // informational — warn/skip only, never fail the run
15
- const diff_security_1 = require("./checks/diff-security");
16
19
  const outdated_deps_1 = require("./checks/outdated-deps");
17
20
  const pr_body_1 = require("./checks/pr-body");
18
21
  const pr_size_check_1 = require("./checks/pr-size-check");
19
22
  const pr_title_lint_1 = require("./checks/pr-title-lint");
23
+ const prettier_1 = require("./checks/prettier");
24
+ const python_format_1 = require("./checks/python-format");
25
+ const python_lint_1 = require("./checks/python-lint");
26
+ const security_1 = require("./checks/security");
20
27
  const sensitive_file_guard_1 = require("./checks/sensitive-file-guard");
21
- const duplicate_check_1 = require("./checks/duplicate-check");
22
- const markdown_lint_1 = require("./checks/markdown-lint");
23
28
  const spellcheck_1 = require("./checks/spellcheck");
24
- const python_lint_1 = require("./checks/python-lint");
25
29
  const swift_lint_1 = require("./checks/swift-lint");
26
- const kotlin_lint_1 = require("./checks/kotlin-lint");
27
30
  // format — runs last, may write files
28
- const prettier_1 = require("./checks/prettier");
29
- const python_format_1 = require("./checks/python-format");
30
- const security_1 = require("./checks/security");
31
31
  const typecheck_1 = require("./checks/typecheck");
32
32
  /**
33
33
  * Every check, in declaration order. The orchestrator groups them by `phase`
@@ -14,6 +14,8 @@ const STATUS_ICON = {
14
14
  };
15
15
  // Unique marker so we can find and update the comment on re-runs.
16
16
  const COMMENT_MARKER = '<!-- pr-checkmate-summary -->';
17
+ const SUMMARY_BORDER = '═══════════════════════════════════════════════════════════════';
18
+ const SUMMARY_RULE = '───────────────────────────────────────────────────────────────';
17
19
  function getPrContext() {
18
20
  const token = process.env.GITHUB_TOKEN;
19
21
  const repository = process.env.GITHUB_REPOSITORY;
@@ -57,16 +59,16 @@ function buildMarkdownReport(report) {
57
59
  function printStdout(report) {
58
60
  const { passed, warned, failed, skipped } = report.summary;
59
61
  utils_1.logger.info('');
60
- utils_1.logger.info('═══════════════════════════════════════════════════════════════');
62
+ utils_1.logger.info(SUMMARY_BORDER);
61
63
  utils_1.logger.info(' PR CheckMate Summary');
62
- utils_1.logger.info('═══════════════════════════════════════════════════════════════');
64
+ utils_1.logger.info(SUMMARY_BORDER);
63
65
  for (const r of report.results) {
64
66
  const details = r.details ? `: ${r.details}` : '';
65
67
  utils_1.logger.info(` ${STATUS_ICON[r.status]} ${r.name}${details}`);
66
68
  }
67
- utils_1.logger.info('───────────────────────────────────────────────────────────────');
69
+ utils_1.logger.info(SUMMARY_RULE);
68
70
  utils_1.logger.info(` ✅ ${passed} passed ⚠️ ${warned} warnings ❌ ${failed} failed ⏭️ ${skipped} skipped`);
69
- utils_1.logger.info('═══════════════════════════════════════════════════════════════');
71
+ utils_1.logger.info(SUMMARY_BORDER);
70
72
  }
71
73
  async function upsertGithubComment(body) {
72
74
  const ctx = getPrContext();
package/dist/init.js CHANGED
@@ -59,105 +59,124 @@ const DEFAULT_CONFIG = {
59
59
  },
60
60
  documentation: config_1.documentation,
61
61
  };
62
- /**
63
- *
64
- */
65
- async function init() {
66
- const root = process.cwd();
67
- if (!fs_1.default.existsSync(exports.CONFIG_FILE)) {
68
- const answers = await inquirer_1.default.prompt([
69
- {
70
- type: 'list',
71
- name: 'sourcePath',
72
- message: 'Where is your project source code?',
73
- choices: ['src', 'app', 'Enter manually'],
74
- },
75
- {
76
- type: 'input',
77
- name: 'manualPath',
78
- message: 'Enter path:',
79
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
- when: (a) => a.sourcePath === 'Enter manually',
81
- validate: (input) => input.trim().length > 0 || 'Cannot be empty',
82
- },
83
- ]);
84
- const sourcePath = answers.sourcePath === 'Enter manually' ? answers.manualPath : answers.sourcePath;
62
+ const MANUAL_ENTRY = 'Enter manually';
63
+ /** Prompt the user for the project source path, allowing a manual entry. */
64
+ async function promptSourcePath() {
65
+ const answers = await inquirer_1.default.prompt([
66
+ {
67
+ type: 'list',
68
+ name: 'sourcePath',
69
+ message: 'Where is your project source code?',
70
+ choices: ['src', 'app', MANUAL_ENTRY],
71
+ },
72
+ {
73
+ type: 'input',
74
+ name: 'manualPath',
75
+ message: 'Enter path:',
76
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
+ when: (a) => a.sourcePath === MANUAL_ENTRY,
78
+ validate: (input) => input.trim().length > 0 || 'Cannot be empty',
79
+ },
80
+ ]);
81
+ return answers.sourcePath === MANUAL_ENTRY ? answers.manualPath : answers.sourcePath;
82
+ }
83
+ /** Copy the bundled GitHub Actions workflow into the consumer repo. */
84
+ function setupWorkflow(root) {
85
+ const workflowSource = path_1.default.join(__dirname, 'config', 'pr-checkmate-workflow.yml');
86
+ const workflowDir = path_1.default.join(root, '.github', 'workflows');
87
+ const workflowDest = path_1.default.join(workflowDir, 'pr-checkmate.yml');
88
+ if (!fs_1.default.existsSync(workflowSource)) {
89
+ utils_1.logger.warn('[init]: ⚠️ Workflow template not found, skipping workflow setup');
90
+ return;
91
+ }
92
+ if (!fs_1.default.existsSync(workflowDir)) {
93
+ fs_1.default.mkdirSync(workflowDir, { recursive: true });
94
+ }
95
+ fs_1.default.copyFileSync(workflowSource, workflowDest);
96
+ utils_1.logger.info('[init]: ✅ Created .github/workflows/pr-checkmate.yml');
97
+ utils_1.logger.info('[init]: GitHub Actions workflow is ready for your PRs!');
98
+ }
99
+ /** Create a tsconfig.json scaffold if the consumer repo lacks one. */
100
+ function setupTsConfig(root, sourcePath) {
101
+ const tsconfigPath = path_1.default.join(root, 'tsconfig.json');
102
+ if (fs_1.default.existsSync(tsconfigPath))
103
+ return;
104
+ const newTsConfig = {
105
+ compilerOptions: {
106
+ target: 'ES2020',
107
+ module: 'NodeNext',
108
+ outDir: 'dist',
109
+ strict: true,
110
+ rootDir: sourcePath,
111
+ moduleResolution: 'NodeNext',
112
+ esModuleInterop: true,
113
+ resolveJsonModule: true,
114
+ forceConsistentCasingInFileNames: true,
115
+ skipLibCheck: true,
116
+ types: ['node'],
117
+ },
118
+ include: [`${sourcePath}/**/*.ts`],
119
+ exclude: ['node_modules', 'dist'],
120
+ };
121
+ fs_1.default.writeFileSync(tsconfigPath, JSON.stringify(newTsConfig, null, 2));
122
+ utils_1.logger.info('[init]: ✅ Created tsconfig.json using your sourcePath');
123
+ }
124
+ /** First-run setup: prompt, write config, and scaffold workflow + tsconfig. */
125
+ async function createConfig(root) {
126
+ const sourcePath = await promptSourcePath();
127
+ const languages = await (0, utils_1.detectLanguages)(root);
128
+ utils_1.logger.info(`[init]: 🔍 Detected languages: ${languages.length ? languages.join(', ') : 'none'}`);
129
+ const config = {
130
+ ...DEFAULT_CONFIG,
131
+ sourcePath,
132
+ languages,
133
+ };
134
+ fs_1.default.writeFileSync(exports.CONFIG_FILE, JSON.stringify(config, null, 2));
135
+ utils_1.logger.info(`[init]: ✅ Created pr-checkmate.json with sourcePath: ${sourcePath}`);
136
+ utils_1.logger.info(`[init]: 💡 You can now customize ESLint rules, Prettier settings,
137
+ and cspell words in pr-checkmate.json`);
138
+ setupWorkflow(root);
139
+ setupTsConfig(root, sourcePath);
140
+ }
141
+ /** Re-run setup: merge the existing config with current defaults. */
142
+ async function updateConfig(root) {
143
+ utils_1.logger.info('[init]: pr-checkmate.json already exists — updating it.');
144
+ try {
145
+ const raw = fs_1.default.readFileSync(exports.CONFIG_FILE, 'utf-8');
146
+ const json = JSON.parse(raw);
85
147
  const languages = await (0, utils_1.detectLanguages)(root);
86
148
  utils_1.logger.info(`[init]: 🔍 Detected languages: ${languages.length ? languages.join(', ') : 'none'}`);
87
- const config = {
149
+ // Merge with defaults to add new fields
150
+ const updated = {
88
151
  ...DEFAULT_CONFIG,
89
- sourcePath,
152
+ ...json,
153
+ // Refresh detected languages on every init so new source dirs are picked up
90
154
  languages,
155
+ eslint: { ...DEFAULT_CONFIG.eslint, ...(json.eslint || {}) },
156
+ prettier: { ...DEFAULT_CONFIG.prettier, ...(json.prettier || {}) },
157
+ cspell: { ...DEFAULT_CONFIG.cspell, ...(json.cspell || {}) },
158
+ typecheck: { ...DEFAULT_CONFIG.typecheck, ...(json.typecheck || {}) },
159
+ diffSmell: { ...DEFAULT_CONFIG.diffSmell, ...(json.diffSmell || {}) },
160
+ prBody: { ...DEFAULT_CONFIG.prBody, ...(json.prBody || {}) },
161
+ duplicate: { ...DEFAULT_CONFIG.duplicate, ...(json.duplicate || {}) },
91
162
  };
92
- fs_1.default.writeFileSync(exports.CONFIG_FILE, JSON.stringify(config, null, 2));
93
- utils_1.logger.info(`[init]: ✅ Created pr-checkmate.json with sourcePath: ${sourcePath}`);
94
- utils_1.logger.info(`[init]: 💡 You can now customize ESLint rules, Prettier settings,
95
- and cspell words in pr-checkmate.json`);
96
- // Copy workflow file to .github/workflows/
97
- const workflowSource = path_1.default.join(__dirname, 'config', 'pr-checkmate-workflow.yml');
98
- const workflowDir = path_1.default.join(root, '.github', 'workflows');
99
- const workflowDest = path_1.default.join(workflowDir, 'pr-checkmate.yml');
100
- if (fs_1.default.existsSync(workflowSource)) {
101
- if (!fs_1.default.existsSync(workflowDir)) {
102
- fs_1.default.mkdirSync(workflowDir, { recursive: true });
103
- }
104
- fs_1.default.copyFileSync(workflowSource, workflowDest);
105
- utils_1.logger.info('[init]: ✅ Created .github/workflows/pr-checkmate.yml');
106
- utils_1.logger.info('[init]: GitHub Actions workflow is ready for your PRs!');
107
- }
108
- else {
109
- utils_1.logger.warn('[init]: ⚠️ Workflow template not found, skipping workflow setup');
110
- }
111
- const tsconfigPath = path_1.default.join(root, 'tsconfig.json');
112
- if (!fs_1.default.existsSync(tsconfigPath)) {
113
- const newTsConfig = {
114
- compilerOptions: {
115
- target: 'ES2020',
116
- module: 'NodeNext',
117
- outDir: 'dist',
118
- strict: true,
119
- rootDir: sourcePath,
120
- moduleResolution: 'NodeNext',
121
- esModuleInterop: true,
122
- resolveJsonModule: true,
123
- forceConsistentCasingInFileNames: true,
124
- skipLibCheck: true,
125
- types: ['node'],
126
- },
127
- include: [`${sourcePath}/**/*.ts`],
128
- exclude: ['node_modules', 'dist'],
129
- };
130
- fs_1.default.writeFileSync(tsconfigPath, JSON.stringify(newTsConfig, null, 2));
131
- utils_1.logger.info('[init]: ✅ Created tsconfig.json using your sourcePath');
132
- }
163
+ fs_1.default.writeFileSync(exports.CONFIG_FILE, JSON.stringify(updated, null, 2));
164
+ utils_1.logger.info('[init]: 🔧 Updated pr-checkmate.json with customization options.');
165
+ utils_1.logger.info('[init]: 💡 You can now add custom ESLint rules, cspell words, and more!');
166
+ }
167
+ catch (e) {
168
+ utils_1.logger.error(`[init]: ❌ Failed to update pr-checkmate.json: ${e}`);
169
+ }
170
+ }
171
+ /**
172
+ * Initialize (or refresh) pr-checkmate configuration in the current project.
173
+ */
174
+ async function init() {
175
+ const root = process.cwd();
176
+ if (fs_1.default.existsSync(exports.CONFIG_FILE)) {
177
+ await updateConfig(root);
133
178
  }
134
179
  else {
135
- utils_1.logger.info('[init]: pr-checkmate.json already exists — updating it.');
136
- try {
137
- const raw = fs_1.default.readFileSync(exports.CONFIG_FILE, 'utf-8');
138
- const json = JSON.parse(raw);
139
- const languages = await (0, utils_1.detectLanguages)(root);
140
- utils_1.logger.info(`[init]: 🔍 Detected languages: ${languages.length ? languages.join(', ') : 'none'}`);
141
- // Merge with defaults to add new fields
142
- const updated = {
143
- ...DEFAULT_CONFIG,
144
- ...json,
145
- // Refresh detected languages on every init so new source dirs are picked up
146
- languages,
147
- eslint: { ...DEFAULT_CONFIG.eslint, ...(json.eslint || {}) },
148
- prettier: { ...DEFAULT_CONFIG.prettier, ...(json.prettier || {}) },
149
- cspell: { ...DEFAULT_CONFIG.cspell, ...(json.cspell || {}) },
150
- typecheck: { ...DEFAULT_CONFIG.typecheck, ...(json.typecheck || {}) },
151
- diffSmell: { ...DEFAULT_CONFIG.diffSmell, ...(json.diffSmell || {}) },
152
- prBody: { ...DEFAULT_CONFIG.prBody, ...(json.prBody || {}) },
153
- duplicate: { ...DEFAULT_CONFIG.duplicate, ...(json.duplicate || {}) },
154
- };
155
- fs_1.default.writeFileSync(exports.CONFIG_FILE, JSON.stringify(updated, null, 2));
156
- utils_1.logger.info('[init]: 🔧 Updated pr-checkmate.json with customization options.');
157
- utils_1.logger.info('[init]: 💡 You can now add custom ESLint rules, cspell words, and more!');
158
- }
159
- catch (e) {
160
- utils_1.logger.error(`[init]: ❌ Failed to update pr-checkmate.json: ${e}`);
161
- }
180
+ await createConfig(root);
162
181
  }
163
182
  }
@@ -24,6 +24,7 @@ __exportStar(require("./logger"), exports);
24
24
  __exportStar(require("./delta"), exports);
25
25
  __exportStar(require("./result-collector"), exports);
26
26
  __exportStar(require("./resolve-bin"), exports);
27
+ __exportStar(require("./own-deps"), exports);
27
28
  __exportStar(require("./detect-language"), exports);
28
29
  /**
29
30
  *
@@ -0,0 +1,113 @@
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.getOwnDependencyClosure = getOwnDependencyClosure;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const OWN_PACKAGE_NAME = 'pr-checkmate';
10
+ const PACKAGE_JSON = 'package.json';
11
+ let cachedClosure;
12
+ /** Read a package.json's `dependencies` keys, tolerating missing/broken files. */
13
+ function readDependencyNames(pkgDir) {
14
+ try {
15
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(pkgDir, PACKAGE_JSON), 'utf-8'));
16
+ return Object.keys(pkg.dependencies ?? {});
17
+ }
18
+ catch {
19
+ return [];
20
+ }
21
+ }
22
+ /** Walk up from this module until we find pr-checkmate's own package root. */
23
+ function findOwnPackageRoot() {
24
+ let dir = __dirname;
25
+ while (dir !== node_path_1.default.dirname(dir)) {
26
+ const pkgJsonPath = node_path_1.default.join(dir, PACKAGE_JSON);
27
+ if (node_fs_1.default.existsSync(pkgJsonPath)) {
28
+ try {
29
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgJsonPath, 'utf-8'));
30
+ if (pkg.name === OWN_PACKAGE_NAME)
31
+ return dir;
32
+ }
33
+ catch { }
34
+ }
35
+ dir = node_path_1.default.dirname(dir);
36
+ }
37
+ return null;
38
+ }
39
+ /**
40
+ * Resolve a dependency's install directory by name, starting the search from
41
+ * `fromDir`. Relies on Node's upward node_modules walk so hoisted packages are
42
+ * found regardless of physical layout.
43
+ */
44
+ function resolvePackageDir(name, fromDir) {
45
+ // Prefer the explicit package.json subpath.
46
+ try {
47
+ return node_path_1.default.dirname(require.resolve(`${name}/package.json`, { paths: [fromDir] }));
48
+ }
49
+ catch { }
50
+ // Fallback for packages whose exports map hides ./package.json: resolve the
51
+ // entry point and walk up to the package's own root.
52
+ try {
53
+ let dir = node_path_1.default.dirname(require.resolve(name, { paths: [fromDir] }));
54
+ while (dir !== node_path_1.default.dirname(dir)) {
55
+ const pkgJsonPath = node_path_1.default.join(dir, PACKAGE_JSON);
56
+ if (node_fs_1.default.existsSync(pkgJsonPath)) {
57
+ try {
58
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(pkgJsonPath, 'utf-8'));
59
+ if (pkg.name === name)
60
+ return dir;
61
+ }
62
+ catch { }
63
+ }
64
+ dir = node_path_1.default.dirname(dir);
65
+ }
66
+ }
67
+ catch { }
68
+ return null;
69
+ }
70
+ /**
71
+ * Compute the full set of package names in pr-checkmate's own dependency
72
+ * closure (direct + transitive runtime dependencies), plus pr-checkmate itself.
73
+ *
74
+ * pr-checkmate installs into the consumer's node_modules, so its own runtime
75
+ * deps (eslint plugins, cspell dictionaries, …) become part of the consumer's
76
+ * production dependency tree. Some carry copyleft licenses that would otherwise
77
+ * trip pr-checkmate's own license check against an innocent client. These are
78
+ * pr-checkmate's dependencies, not the client's, so they must be exempt.
79
+ *
80
+ * Result is cached for the process. Returns a set containing only pr-checkmate
81
+ * itself if the package root cannot be located (defensive; never throws).
82
+ */
83
+ function getOwnDependencyClosure() {
84
+ if (cachedClosure)
85
+ return cachedClosure;
86
+ const closure = new Set([OWN_PACKAGE_NAME]);
87
+ const root = findOwnPackageRoot();
88
+ if (!root) {
89
+ cachedClosure = closure;
90
+ return closure;
91
+ }
92
+ // BFS over the dependency graph, resolving each package relative to the parent
93
+ // that declared it so nested (non-hoisted) installs resolve correctly.
94
+ const queue = readDependencyNames(root).map(name => ({
95
+ name,
96
+ fromDir: root,
97
+ }));
98
+ while (queue.length > 0) {
99
+ const { name, fromDir } = queue.shift();
100
+ if (closure.has(name))
101
+ continue;
102
+ closure.add(name);
103
+ const depDir = resolvePackageDir(name, fromDir);
104
+ if (!depDir)
105
+ continue;
106
+ for (const child of readDependencyNames(depDir)) {
107
+ if (!closure.has(child))
108
+ queue.push({ name: child, fromDir: depDir });
109
+ }
110
+ }
111
+ cachedClosure = closure;
112
+ return closure;
113
+ }
@@ -14,6 +14,9 @@ const node_path_1 = __importDefault(require("node:path"));
14
14
  * 2. Via package.json — for packages that export ./package.json but not the bin subpath (e.g. eslint)
15
15
  * 3. Walk up from main entry — for packages that export neither (e.g. cspell)
16
16
  *
17
+ * @param pkgName - The package whose binary to resolve (e.g. `eslint`).
18
+ * @param binRelPath - The bin path relative to the package root (e.g. `bin/eslint.js`).
19
+ * @param options - Resolution options.
17
20
  * @param options.paths - Directories to resolve from (passed to `require.resolve`).
18
21
  * Use `[process.cwd()]` to prefer the consumer project's copy of the package.
19
22
  */
package/eslint.config.mjs CHANGED
@@ -4,7 +4,7 @@ import tsPlugin from '@typescript-eslint/eslint-plugin';
4
4
  import tsParser from '@typescript-eslint/parser';
5
5
  import prettierPlugin from 'eslint-plugin-prettier';
6
6
  import sonarjsPlugin from 'eslint-plugin-sonarjs';
7
- import importPlugin from 'eslint-plugin-import';
7
+ import importPlugin from 'eslint-plugin-import-x';
8
8
  import jsdocPlugin from 'eslint-plugin-jsdoc';
9
9
 
10
10
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-checkmate",
3
- "version": "1.21.15",
3
+ "version": "1.22.0",
4
4
  "description": "Automated PR quality checks: linting, formatting, dependency analysis, and spellcheck",
5
5
  "keywords": [
6
6
  "github-actions",
@@ -68,27 +68,27 @@
68
68
  "@cspell/dict-ru_ru": "^2.2.1",
69
69
  "@cspell/dict-uk-ua": "^4.0.0",
70
70
  "@types/node": "^26.1.0",
71
- "@typescript-eslint/eslint-plugin": "^8.47.0",
72
- "@typescript-eslint/parser": "^8.47.0",
71
+ "@typescript-eslint/eslint-plugin": "^8.62.1",
72
+ "@typescript-eslint/parser": "^8.62.1",
73
73
  "@wasm-fmt/clang-format": "^22.1.8",
74
- "cspell": "^9.3.2",
74
+ "cspell": "^10.0.1",
75
75
  "depcheck": "^1.4.7",
76
- "eslint": "^9.26.0",
76
+ "eslint": "^10.6.0",
77
77
  "eslint-config-prettier": "^10.1.8",
78
- "eslint-plugin-import": "^2.29.0",
78
+ "eslint-plugin-import-x": "^4.17.1",
79
79
  "eslint-plugin-jsdoc": "^63.0.12",
80
80
  "eslint-plugin-prettier": "^5.5.4",
81
81
  "eslint-plugin-sonarjs": "^4.1.0",
82
82
  "execa": "^9.6.0",
83
83
  "gitleaks-secret-scanner-fixed": "^2.1.3",
84
- "inquirer": "^13.0.1",
85
- "jscpd": "^3.5.10",
84
+ "inquirer": "^14.0.2",
85
+ "jscpd": "^5.0.11",
86
86
  "license-checker": "^25.0.1",
87
87
  "markdownlint-cli2": "^0.23.0",
88
88
  "prettier": ">=3.0.0",
89
89
  "ts-node": "^10.9.1",
90
90
  "ts-unused-exports": "^11.0.1",
91
- "typescript": "^5.9.3"
91
+ "typescript": "^6.0.3"
92
92
  },
93
93
  "devDependencies": {
94
94
  "standard-version": "^9.5.0"
@@ -41,7 +41,7 @@ module.exports = {
41
41
  sourceType: 'module',
42
42
  project: './tsconfig.json',
43
43
  },
44
- plugins: ['@typescript-eslint', 'prettier', 'import', 'jsdoc'],
44
+ plugins: ['@typescript-eslint', 'prettier', 'import-x', 'jsdoc'],
45
45
  extends: [
46
46
  'eslint:recommended',
47
47
  'plugin:@typescript-eslint/recommended',
@@ -83,14 +83,14 @@ module.exports = {
83
83
  // Complexity analysis (#61)
84
84
  complexity: ['warn', 15],
85
85
  // Import ordering (#65)
86
- 'import/order': [
86
+ 'import-x/order': [
87
87
  'warn',
88
88
  {
89
89
  groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
90
90
  alphabetize: { order: 'asc', caseInsensitive: true },
91
91
  },
92
92
  ],
93
- 'import/no-duplicates': 'error',
93
+ 'import-x/no-duplicates': 'error',
94
94
  // Documentation (#62)
95
95
  'jsdoc/require-jsdoc': [
96
96
  'warn',