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.
- package/.eslintrc.js +3 -3
- package/README.md +7 -1
- package/cspell.json +151 -1
- package/dist/config/loader.js +4 -0
- package/dist/core/checks/commitlint.js +17 -9
- package/dist/core/checks/diff-security.js +24 -41
- package/dist/core/checks/diff-smell.js +4 -21
- package/dist/core/checks/duplicate-check.js +19 -3
- package/dist/core/checks/findings.js +55 -0
- package/dist/core/checks/license-check.js +7 -1
- package/dist/core/checks/lint.js +8 -2
- package/dist/core/checks/markdown-lint.js +11 -15
- package/dist/core/checks/sensitive-file-guard.js +23 -29
- package/dist/core/registry.js +8 -8
- package/dist/core/reporters.js +6 -4
- package/dist/init.js +112 -93
- package/dist/utils/index.js +1 -0
- package/dist/utils/own-deps.js +113 -0
- package/dist/utils/resolve-bin.js +3 -0
- package/eslint.config.mjs +1 -1
- package/package.json +9 -9
- package/src/config/.eslintrc.js +3 -3
- package/src/config/cspell.json +151 -1
- package/src/config/eslint.config.mjs +1 -1
|
@@ -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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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}`);
|
package/dist/core/registry.js
CHANGED
|
@@ -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`
|
package/dist/core/reporters.js
CHANGED
|
@@ -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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
149
|
+
// Merge with defaults to add new fields
|
|
150
|
+
const updated = {
|
|
88
151
|
...DEFAULT_CONFIG,
|
|
89
|
-
|
|
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(
|
|
93
|
-
utils_1.logger.info(
|
|
94
|
-
utils_1.logger.info(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/utils/index.js
CHANGED
|
@@ -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.
|
|
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.
|
|
72
|
-
"@typescript-eslint/parser": "^8.
|
|
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": "^
|
|
74
|
+
"cspell": "^10.0.1",
|
|
75
75
|
"depcheck": "^1.4.7",
|
|
76
|
-
"eslint": "^
|
|
76
|
+
"eslint": "^10.6.0",
|
|
77
77
|
"eslint-config-prettier": "^10.1.8",
|
|
78
|
-
"eslint-plugin-import": "^
|
|
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": "^
|
|
85
|
-
"jscpd": "^
|
|
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": "^
|
|
91
|
+
"typescript": "^6.0.3"
|
|
92
92
|
},
|
|
93
93
|
"devDependencies": {
|
|
94
94
|
"standard-version": "^9.5.0"
|
package/src/config/.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', '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',
|