pr-checkmate 1.21.11 → 1.21.12

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/dist/cli.js CHANGED
@@ -27,6 +27,8 @@ const COMMAND_TO_CHECK = {
27
27
  duplicate: 'Duplicate Code',
28
28
  commitlint: 'Commitlint',
29
29
  'cpp-format': 'C++ Format',
30
+ 'python-lint': 'Ruff Lint',
31
+ 'python-format': 'Ruff Format',
30
32
  };
31
33
  const cmd = process.argv[2];
32
34
  void (async () => {
@@ -61,6 +61,11 @@ function mergeWithDefaults(userConfig) {
61
61
  ...userConfig.cpp,
62
62
  clangFormat: { ...defaults.cpp?.clangFormat, ...userConfig.cpp?.clangFormat },
63
63
  },
64
+ python: {
65
+ ...defaults.python,
66
+ ...userConfig.python,
67
+ ruff: { ...defaults.python?.ruff, ...userConfig.python?.ruff },
68
+ },
64
69
  diffSmell: { ...defaults.diffSmell, ...userConfig.diffSmell },
65
70
  prBody: { ...defaults.prBody, ...userConfig.prBody },
66
71
  duplicate: { ...defaults.duplicate, ...userConfig.duplicate },
@@ -133,6 +138,7 @@ function getDefaultConfig() {
133
138
  },
134
139
  typecheck: { enabled: true },
135
140
  cpp: { enabled: true, clangFormat: { style: 'LLVM' } },
141
+ python: { enabled: true, ruff: { lint: true, format: true } },
136
142
  diffSmell: { enabled: true },
137
143
  prBody: { enabled: true, minLength: 20, requireTicket: true },
138
144
  duplicate: { enabled: true, minLines: 5, minTokens: 50 },
@@ -0,0 +1,68 @@
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.pythonFormatCheck = void 0;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const utils_1 = require("../../utils");
10
+ const targets_1 = require("../targets");
11
+ const types_1 = require("../types");
12
+ const PY_GLOBS = ['*.py'];
13
+ /**
14
+ * Formats changed Python files with Ruff's formatter (WASM, no Python/pip needed).
15
+ * Read-only in check mode (`warn` when files differ from formatted output); rewrites
16
+ * files in place when `write` so CI can auto-commit — mirrors the Prettier/C++ checks.
17
+ */
18
+ exports.pythonFormatCheck = {
19
+ name: 'Ruff Format',
20
+ phase: 'format',
21
+ applies(ctx) {
22
+ return (ctx.config.python?.enabled !== false &&
23
+ ctx.config.python?.ruff?.format !== false &&
24
+ ctx.languages.includes('python'));
25
+ },
26
+ async run(ctx) {
27
+ const targets = await (0, targets_1.resolveTargetFiles)(ctx, PY_GLOBS);
28
+ if (targets === null)
29
+ return (0, types_1.skip)('git unavailable');
30
+ if (targets.length === 0)
31
+ return (0, types_1.pass)('no Python files changed');
32
+ const { Workspace, PositionEncoding } = require('@astral-sh/ruff-wasm-nodejs');
33
+ const workspace = new Workspace(Workspace.defaultSettings(), PositionEncoding.Utf16);
34
+ let unformatted = 0;
35
+ for (const rel of targets) {
36
+ const abs = node_path_1.default.join(ctx.cwd, rel);
37
+ let content;
38
+ try {
39
+ content = node_fs_1.default.readFileSync(abs, 'utf-8');
40
+ }
41
+ catch {
42
+ continue; // renamed/removed between diff and read — skip
43
+ }
44
+ let formatted;
45
+ try {
46
+ formatted = workspace.format(content);
47
+ }
48
+ catch (err) {
49
+ // A syntax error makes Ruff throw — that's a lint concern, not a format failure.
50
+ utils_1.logger.warn(`[Ruff Format]: ⚠️ Could not format ${rel} (syntax error?)\n${err}`);
51
+ continue;
52
+ }
53
+ if (formatted === content)
54
+ continue;
55
+ unformatted += 1;
56
+ if (ctx.write)
57
+ node_fs_1.default.writeFileSync(abs, formatted);
58
+ }
59
+ if (unformatted === 0)
60
+ return (0, types_1.pass)();
61
+ if (!ctx.write) {
62
+ utils_1.logger.warn(`[Ruff Format]: ⚠️ ${unformatted} Python file(s) need formatting`);
63
+ return (0, types_1.warn)(`${unformatted} file(s) need formatting (run with write to fix)`);
64
+ }
65
+ utils_1.logger.info(`[Ruff Format]: ✅ Reformatted ${unformatted} Python file(s)`);
66
+ return (0, types_1.pass)(`reformatted ${unformatted} file(s)`);
67
+ },
68
+ };
@@ -0,0 +1,62 @@
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.pythonLintCheck = void 0;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const utils_1 = require("../../utils");
10
+ const targets_1 = require("../targets");
11
+ const types_1 = require("../types");
12
+ const PY_GLOBS = ['*.py'];
13
+ /**
14
+ * Lints changed Python files with Ruff, bundled as a self-contained WASM module
15
+ * (`@astral-sh/ruff-wasm-nodejs`) so it runs on a bare Node image with no Python
16
+ * or pip install. Informational by design — reports issues without failing the
17
+ * build, matching how formatting checks roll out; promote to blocking per repo
18
+ * once teams have adopted it. Uses Ruff's default rule set (pyflakes + pycodestyle).
19
+ */
20
+ exports.pythonLintCheck = {
21
+ name: 'Ruff Lint',
22
+ phase: 'informational',
23
+ applies(ctx) {
24
+ return (ctx.config.python?.enabled !== false &&
25
+ ctx.config.python?.ruff?.lint !== false &&
26
+ ctx.languages.includes('python'));
27
+ },
28
+ async run(ctx) {
29
+ const targets = await (0, targets_1.resolveTargetFiles)(ctx, PY_GLOBS);
30
+ if (targets === null)
31
+ return (0, types_1.skip)('git unavailable');
32
+ if (targets.length === 0)
33
+ return (0, types_1.pass)('no Python files changed');
34
+ const { Workspace, PositionEncoding } = require('@astral-sh/ruff-wasm-nodejs');
35
+ const workspace = new Workspace(Workspace.defaultSettings(), PositionEncoding.Utf16);
36
+ let totalIssues = 0;
37
+ let filesWithIssues = 0;
38
+ for (const rel of targets) {
39
+ let content;
40
+ try {
41
+ content = node_fs_1.default.readFileSync(node_path_1.default.join(ctx.cwd, rel), 'utf-8');
42
+ }
43
+ catch {
44
+ continue; // renamed/removed between diff and read — skip
45
+ }
46
+ const diagnostics = workspace.check(content);
47
+ if (diagnostics.length === 0)
48
+ continue;
49
+ filesWithIssues += 1;
50
+ totalIssues += diagnostics.length;
51
+ utils_1.logger.warn(`[Ruff Lint]: ⚠️ ${rel}: ${diagnostics.length} issue(s)`);
52
+ diagnostics
53
+ .slice(0, 5)
54
+ .forEach((d) => utils_1.logger.warn(` ${rel}:${d.start_location.row} ${d.code ?? '?'} ${d.message}`));
55
+ if (diagnostics.length > 5)
56
+ utils_1.logger.warn(` ... and ${diagnostics.length - 5} more`);
57
+ }
58
+ if (totalIssues === 0)
59
+ return (0, types_1.pass)();
60
+ return (0, types_1.warn)(`${totalIssues} issue(s) in ${filesWithIssues} file(s)`);
61
+ },
62
+ };
@@ -21,9 +21,11 @@ const pr_title_lint_1 = require("./checks/pr-title-lint");
21
21
  const outdated_deps_1 = require("./checks/outdated-deps");
22
22
  const markdown_lint_1 = require("./checks/markdown-lint");
23
23
  const spellcheck_1 = require("./checks/spellcheck");
24
+ const python_lint_1 = require("./checks/python-lint");
24
25
  // format — runs last, may write files
25
26
  const prettier_1 = require("./checks/prettier");
26
27
  const cpp_format_1 = require("./checks/cpp-format");
28
+ const python_format_1 = require("./checks/python-format");
27
29
  /**
28
30
  * Every check, in declaration order. The orchestrator groups them by `phase`
29
31
  * (blocking → informational → format) and runs each phase's checks in parallel.
@@ -49,7 +51,9 @@ exports.REGISTRY = [
49
51
  outdated_deps_1.outdatedDepsCheck,
50
52
  markdown_lint_1.markdownLintCheck,
51
53
  spellcheck_1.spellcheckCheck,
54
+ python_lint_1.pythonLintCheck,
52
55
  // format
53
56
  prettier_1.prettierCheck,
54
57
  cpp_format_1.cppFormatCheck,
58
+ python_format_1.pythonFormatCheck,
55
59
  ];
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveTargetFiles = resolveTargetFiles;
4
+ const execa_1 = require("execa");
5
+ const utils_1 = require("../utils");
6
+ /**
7
+ * Lists the files a language check should operate on, matched by pathspec `globs`
8
+ * (e.g. `['*.py']`). Uses the PR diff range when available (delta mode), else
9
+ * scans all tracked files. Returns an empty array when nothing matches, and
10
+ * `null` when git itself is unusable (caller should `skip`).
11
+ *
12
+ * Only ACMR files are returned (Added/Copied/Modified/Renamed) — deletions are
13
+ * excluded, so every returned path exists on disk and is safe to read.
14
+ */
15
+ async function resolveTargetFiles(ctx, globs) {
16
+ try {
17
+ const args = ctx.baseSha
18
+ ? [
19
+ 'diff',
20
+ '--name-only',
21
+ '--diff-filter=ACMR',
22
+ ctx.baseSha,
23
+ ctx.headSha ?? 'HEAD',
24
+ '--',
25
+ ...globs,
26
+ ]
27
+ : ['ls-files', ...globs];
28
+ const { stdout } = await (0, execa_1.execa)('git', args, { cwd: ctx.cwd, reject: false });
29
+ const files = stdout
30
+ .split('\n')
31
+ .map(f => f.trim())
32
+ .filter(Boolean);
33
+ utils_1.logger.info(`[targets]: ${ctx.baseSha ? '📂 Delta' : 'Full scan'}: ${files.length} file(s) for ${globs.join(', ')}`);
34
+ return files;
35
+ }
36
+ catch (err) {
37
+ utils_1.logger.warn(`[targets]: ⚠️ Could not list files\n${err}`);
38
+ return null;
39
+ }
40
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-checkmate",
3
- "version": "1.21.11",
3
+ "version": "1.21.12",
4
4
  "description": "Automated PR quality checks: linting, formatting, dependency analysis, and spellcheck",
5
5
  "keywords": [
6
6
  "github-actions",
@@ -59,6 +59,7 @@
59
59
  "author": "Artur Polishchuk",
60
60
  "license": "LicenseRef-Proprietary",
61
61
  "dependencies": {
62
+ "@astral-sh/ruff-wasm-nodejs": "^0.15.20",
62
63
  "@commitlint/cli": "^21.2.0",
63
64
  "@commitlint/config-conventional": "^21.2.0",
64
65
  "@cspell/dict-es-es": "^3.0.8",