pr-checkmate 1.22.7 → 1.22.8

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.
@@ -1,5 +1,20 @@
1
1
  /**
2
- * Common patterns to ignore in all checks
2
+ * Build output, dependency, tooling, and report directories that are never
3
+ * product code. Every check excludes them by directory-name segment match (see
4
+ * `targets.ts`): a match on any segment of a path drops the file. Untracked
5
+ * build dirs are already excluded because checks read git-tracked files only —
6
+ * this list additionally covers dirs that repos commonly *commit* (`.github`,
7
+ * `.vscode`, `.idea`, `.claude`) and any checked-in build artifacts.
8
+ *
9
+ * Covers the common ecosystems: Node/JS/web, Python, Java/Kotlin/Gradle/Android,
10
+ * Swift/iOS/Xcode, and C/C++/CMake. Add more here.
11
+ */
12
+ export declare const IGNORED_DIRS: string[];
13
+ /**
14
+ * Glob form of the ignore list, for tools that take glob ignores directly
15
+ * (ESLint `--ignore-pattern`, cspell `--exclude`, the generated
16
+ * `.prettierignore`). Combines {@link IGNORED_DIRS} with a few tool-specific
17
+ * nested report globs that are not plain directory names.
3
18
  */
4
19
  export declare const IGNORE_PATTERNS: string[];
5
20
  /**
@@ -3,57 +3,109 @@ 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.CONFIG_FILE = exports.documentation = exports.DEFAULT_ALLOW_PACKAGES = exports.DEFAULT_COMMANDS = exports.SOURCE_EXTENSIONS = exports.DUPLICATE_CODE_FORMATS = exports.CONFIG_FILE_PATTERNS = exports.DEAD_CODE_IGNORE_DEFAULTS = exports.DUPLICATE_IGNORE_DEFAULTS = exports.IGNORE_PATTERNS = void 0;
6
+ exports.CONFIG_FILE = exports.documentation = exports.DEFAULT_ALLOW_PACKAGES = exports.DEFAULT_COMMANDS = exports.SOURCE_EXTENSIONS = exports.DUPLICATE_CODE_FORMATS = exports.CONFIG_FILE_PATTERNS = exports.DEAD_CODE_IGNORE_DEFAULTS = exports.DUPLICATE_IGNORE_DEFAULTS = exports.IGNORE_PATTERNS = exports.IGNORED_DIRS = void 0;
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
8
  /**
9
- * Common patterns to ignore in all checks
9
+ * Build output, dependency, tooling, and report directories that are never
10
+ * product code. Every check excludes them by directory-name segment match (see
11
+ * `targets.ts`): a match on any segment of a path drops the file. Untracked
12
+ * build dirs are already excluded because checks read git-tracked files only —
13
+ * this list additionally covers dirs that repos commonly *commit* (`.github`,
14
+ * `.vscode`, `.idea`, `.claude`) and any checked-in build artifacts.
15
+ *
16
+ * Covers the common ecosystems: Node/JS/web, Python, Java/Kotlin/Gradle/Android,
17
+ * Swift/iOS/Xcode, and C/C++/CMake. Add more here.
18
+ */
19
+ exports.IGNORED_DIRS = [
20
+ // VCS, editors, and CI/tooling config
21
+ '.git',
22
+ '.github',
23
+ '.gitlab',
24
+ '.vscode',
25
+ '.vs',
26
+ '.idea',
27
+ '.claude',
28
+ '.cursor',
29
+ '.husky',
30
+ '.circleci',
31
+ // Node / JS / web — build output & caches
32
+ 'node_modules',
33
+ 'dist',
34
+ 'build',
35
+ 'build-release',
36
+ 'out',
37
+ 'coverage',
38
+ '.next',
39
+ '.nuxt',
40
+ '.svelte-kit',
41
+ '.turbo',
42
+ '.cache',
43
+ '.parcel-cache',
44
+ '.vercel',
45
+ '.wrangler',
46
+ '.serverless',
47
+ '.terraform',
48
+ // Python
49
+ '__pycache__',
50
+ '.pytest_cache',
51
+ '.mypy_cache',
52
+ '.ruff_cache',
53
+ '.tox',
54
+ '.venv',
55
+ 'venv',
56
+ '.eggs',
57
+ 'htmlcov',
58
+ // Java / Kotlin / Gradle / Android
59
+ '.gradle',
60
+ '.kotlin',
61
+ 'target',
62
+ 'gen',
63
+ 'captures',
64
+ // Swift / iOS / Xcode
65
+ '.build',
66
+ '.swiftpm',
67
+ 'DerivedData',
68
+ 'Pods',
69
+ 'Carthage',
70
+ // C / C++ / CMake
71
+ 'CMakeFiles',
72
+ 'cmake-build-debug',
73
+ 'cmake-build-release',
74
+ '_deps',
75
+ // Test reports / results / logs / release artifacts
76
+ 'reports',
77
+ 'report',
78
+ 'test-reports',
79
+ 'test-report',
80
+ 'test-output',
81
+ 'test-results',
82
+ 'results',
83
+ 'result',
84
+ 'logs',
85
+ 'allure-report',
86
+ 'allure-results',
87
+ 'playwright-report',
88
+ '.wdio',
89
+ ];
90
+ /**
91
+ * Glob form of the ignore list, for tools that take glob ignores directly
92
+ * (ESLint `--ignore-pattern`, cspell `--exclude`, the generated
93
+ * `.prettierignore`). Combines {@link IGNORED_DIRS} with a few tool-specific
94
+ * nested report globs that are not plain directory names.
10
95
  */
11
96
  exports.IGNORE_PATTERNS = [
12
- '**/node_modules/**',
13
- '**/dist/**',
14
- '**/.git/**',
15
- '**/build/**',
16
- '**/coverage/**',
17
- '**/.next/**',
18
- '**/.nuxt/**',
19
- '**/out/**',
20
- '**/__pycache__/**',
21
- '**/.pytest_cache/**',
22
- '**/.venv/**',
23
- '**/venv/**',
24
- '**/allure-report/**',
25
- '**/allure-results/**',
26
- '**/reports/**',
27
- '**/report/**',
28
- '**/test-reports/**',
29
- '**/test-report/**',
30
- '**/test-output/**',
31
- '**/test-results/**',
32
- '**/results/**',
33
- '**/result/**',
34
- '**/logs/**',
35
- // Jest
97
+ ...exports.IGNORED_DIRS.map(dir => `**/${dir}/**`),
98
+ // Tool-specific nested report dirs / files (not bare directory names)
36
99
  '**/jest-html-reporters-*/**',
37
100
  '**/jest-stare/**',
38
- // Cypress
39
101
  '**/cypress/reports/**',
40
102
  '**/cypress/videos/**',
41
103
  '**/cypress/screenshots/**',
42
- // Playwright
43
- '**/playwright-report/**',
44
- // WebdriverIO
45
- '**/.wdio/**',
46
- '**/wdio-logs/**',
47
- // Mocha / Jasmine
48
104
  '**/mochawesome-report/**',
49
105
  '**/mochawesome-reports/**',
50
- // JUnit / Surefire / Maven
51
106
  '**/target/surefire-reports/**',
52
107
  '**/target/failsafe-reports/**',
53
- // Python testing
54
- '**/.pytest_cache/**',
55
- '**/htmlcov/**',
56
- // Misc reports
108
+ '**/wdio-logs/**',
57
109
  '**/lint-report/**',
58
110
  '**/audit-report/**',
59
111
  ];
@@ -1,10 +1,23 @@
1
1
  export * from './constants';
2
2
  export { loadProjectConfig } from '../config/loader';
3
3
  /**
4
- * Get SOURCE_PATH dynamically
5
- * This ensures fresh config is always read
4
+ * Get the configured source path(s) dynamically as a normalized list.
5
+ * Accepts a single string or an array in pr-checkmate.json; empty/invalid
6
+ * values fall back to `['.']` (the whole repo). Clients narrow scanning to a
7
+ * subdirectory by setting `sourcePath` in their pr-checkmate.json. Always reads
8
+ * fresh config.
9
+ */
10
+ export declare function getSourcePaths(): string[];
11
+ /**
12
+ * Get a single source path (first configured entry) for callers that only
13
+ * support one directory. Prefer `getSourcePaths()` for scan targets.
6
14
  */
7
15
  export declare function getSourcePath(): string;
16
+ /**
17
+ * Get the effective list of directory names excluded from every check
18
+ * (client `ignoreDirs` in pr-checkmate.json, or the curated default).
19
+ */
20
+ export declare function getIgnoredDirs(): string[];
8
21
  /**
9
22
  * Get path to package config files
10
23
  */
@@ -18,16 +18,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
20
  exports.loadProjectConfig = void 0;
21
+ exports.getSourcePaths = getSourcePaths;
21
22
  exports.getSourcePath = getSourcePath;
23
+ exports.getIgnoredDirs = getIgnoredDirs;
22
24
  exports.getPackageConfigPath = getPackageConfigPath;
23
25
  exports.hasLocalConfig = hasLocalConfig;
24
26
  const node_fs_1 = __importDefault(require("node:fs"));
25
27
  const node_path_1 = __importDefault(require("node:path"));
28
+ const loader_1 = require("../config/loader");
26
29
  const utils_1 = require("../utils");
27
30
  const constants_1 = require("./constants");
28
31
  __exportStar(require("./constants"), exports);
29
- var loader_1 = require("../config/loader");
30
- Object.defineProperty(exports, "loadProjectConfig", { enumerable: true, get: function () { return loader_1.loadProjectConfig; } });
32
+ var loader_2 = require("../config/loader");
33
+ Object.defineProperty(exports, "loadProjectConfig", { enumerable: true, get: function () { return loader_2.loadProjectConfig; } });
31
34
  /**
32
35
  * Load configuration dynamically (always reads fresh config)
33
36
  */
@@ -41,15 +44,33 @@ function loadConfig() {
41
44
  catch (err) {
42
45
  utils_1.logger.warn(`⚠️ Failed to read config from ${constants_1.CONFIG_FILE}, using defaults${err}`);
43
46
  }
44
- return { sourcePath: 'src' };
47
+ return { sourcePath: '.' };
45
48
  }
46
49
  /**
47
- * Get SOURCE_PATH dynamically
48
- * This ensures fresh config is always read
50
+ * Get the configured source path(s) dynamically as a normalized list.
51
+ * Accepts a single string or an array in pr-checkmate.json; empty/invalid
52
+ * values fall back to `['.']` (the whole repo). Clients narrow scanning to a
53
+ * subdirectory by setting `sourcePath` in their pr-checkmate.json. Always reads
54
+ * fresh config.
55
+ */
56
+ function getSourcePaths() {
57
+ const { sourcePath } = loadConfig();
58
+ const paths = (Array.isArray(sourcePath) ? sourcePath : [sourcePath]).filter((p) => typeof p === 'string' && p.trim().length > 0);
59
+ return paths.length > 0 ? paths : ['.'];
60
+ }
61
+ /**
62
+ * Get a single source path (first configured entry) for callers that only
63
+ * support one directory. Prefer `getSourcePaths()` for scan targets.
49
64
  */
50
65
  function getSourcePath() {
51
- const config = loadConfig();
52
- return config.sourcePath;
66
+ return getSourcePaths()[0];
67
+ }
68
+ /**
69
+ * Get the effective list of directory names excluded from every check
70
+ * (client `ignoreDirs` in pr-checkmate.json, or the curated default).
71
+ */
72
+ function getIgnoredDirs() {
73
+ return (0, loader_1.loadProjectConfig)().ignoreDirs ?? [];
53
74
  }
54
75
  /**
55
76
  * Get path to package config files
@@ -34,6 +34,9 @@ function mergeWithDefaults(userConfig) {
34
34
  const defaults = getDefaultConfig();
35
35
  return {
36
36
  sourcePath: userConfig.sourcePath || defaults.sourcePath,
37
+ // Replace (not merge): the client sees the full default list in their
38
+ // generated config and edits it directly, so their list is authoritative.
39
+ ignoreDirs: userConfig.ignoreDirs ?? defaults.ignoreDirs,
37
40
  lint: {
38
41
  rules: { ...defaults.lint?.rules, ...userConfig.lint?.rules },
39
42
  ignorePatterns: [
@@ -90,7 +93,8 @@ function mergeWithDefaults(userConfig) {
90
93
  */
91
94
  function getDefaultConfig() {
92
95
  return {
93
- sourcePath: 'src',
96
+ sourcePath: '.',
97
+ ignoreDirs: [...constants_1.IGNORED_DIRS],
94
98
  lint: {
95
99
  rules: {
96
100
  'no-console': 'warn',
@@ -1,13 +1,10 @@
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.circularDepsCheck = void 0;
7
- const node_path_1 = __importDefault(require("node:path"));
8
4
  const config_1 = require("../../config");
9
5
  const utils_1 = require("../../utils");
10
6
  const execa_1 = require("../../utils/execa");
7
+ const targets_1 = require("../targets");
11
8
  const types_1 = require("../types");
12
9
  exports.circularDepsCheck = {
13
10
  name: 'Circular Deps',
@@ -16,8 +13,10 @@ exports.circularDepsCheck = {
16
13
  return ctx.languages.includes('typescript') || ctx.languages.includes('javascript');
17
14
  },
18
15
  async run(ctx) {
19
- const pattern = node_path_1.default.join(ctx.cwd, (0, config_1.getSourcePath)(), '**/*.{ts,tsx,js,jsx}');
20
- const result = await (0, execa_1.execa)('npx', ['dpdm', '--no-warning', '--no-tree', '--circular', pattern], {
16
+ const files = await (0, targets_1.listTrackedFiles)(ctx, ['*.ts', '*.tsx', '*.js', '*.jsx'], (0, config_1.getSourcePaths)());
17
+ if (files.length === 0)
18
+ return (0, types_1.pass)();
19
+ const result = await (0, execa_1.execa)('npx', ['dpdm', '--no-warning', '--no-tree', '--circular', ...files], {
21
20
  cwd: ctx.cwd,
22
21
  reject: false,
23
22
  });
@@ -1,15 +1,11 @@
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.duplicateCheck = void 0;
7
- const node_fs_1 = __importDefault(require("node:fs"));
8
- const node_path_1 = __importDefault(require("node:path"));
4
+ const config_1 = require("../../config");
9
5
  const constants_1 = require("../../config/constants");
10
6
  const utils_1 = require("../../utils");
11
- const delta_1 = require("../../utils/delta");
12
7
  const execa_1 = require("../../utils/execa");
8
+ const targets_1 = require("../targets");
13
9
  const types_1 = require("../types");
14
10
  // Always excluded regardless of client config — scanning these never yields
15
11
  // meaningful production duplication. Config files (eslint/*.config.*) are data,
@@ -44,11 +40,11 @@ exports.duplicateCheck = {
44
40
  async run(ctx) {
45
41
  const minLines = ctx.config.duplicate?.minLines ?? 5;
46
42
  const minTokens = ctx.config.duplicate?.minTokens ?? 50;
47
- const changedFiles = await (0, delta_1.getChangedFiles)(['.ts', '.tsx', '.js', '.jsx']);
48
- if (changedFiles !== null && changedFiles.length === 0)
49
- return (0, types_1.skip)('no relevant files changed');
50
- const srcPath = node_path_1.default.join(ctx.cwd, 'src');
51
- const targetPaths = changedFiles ?? (node_fs_1.default.existsSync(srcPath) ? [srcPath] : [ctx.cwd]);
43
+ const targetPaths = await (0, targets_1.resolveScopedTargetFiles)(ctx, ['*.ts', '*.tsx', '*.js', '*.jsx'], (0, config_1.getSourcePaths)());
44
+ if (targetPaths === null)
45
+ return (0, types_1.skip)('git unavailable');
46
+ if (targetPaths.length === 0)
47
+ return (0, types_1.skip)('no relevant files');
52
48
  const jscpdBin = (0, utils_1.resolvePackageBin)('jscpd', 'run-jscpd.js');
53
49
  // Client-configurable exclusions (pr-checkmate.json `duplicate.ignore`),
54
50
  // defaulting to test files/folders, appended to the always-on base ignores.
@@ -1,15 +1,24 @@
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.lintCheck = void 0;
7
- const node_path_1 = __importDefault(require("node:path"));
8
4
  const config_1 = require("../../config");
9
5
  const utils_1 = require("../../utils");
10
- const delta_1 = require("../../utils/delta");
11
6
  const execa_1 = require("../../utils/execa");
7
+ const targets_1 = require("../targets");
12
8
  const types_1 = require("../types");
9
+ /**
10
+ * Resolve the ESLint binary, preferring the consumer project's own copy so a
11
+ * repo pinned to ESLint 8 (+ legacy `.eslintrc.*`) keeps working. Falls back to
12
+ * the version bundled with pr-checkmate when the project has no local install.
13
+ */
14
+ function resolveESLintBin(cwd) {
15
+ try {
16
+ return (0, utils_1.resolvePackageBin)('eslint', 'bin/eslint.js', { paths: [cwd] });
17
+ }
18
+ catch {
19
+ return (0, utils_1.resolvePackageBin)('eslint', 'bin/eslint.js');
20
+ }
21
+ }
13
22
  async function getESLintMajorVersion(eslintBin) {
14
23
  try {
15
24
  const { stdout } = await (0, execa_1.execa)(process.execPath, [eslintBin, '--version']);
@@ -27,10 +36,12 @@ exports.lintCheck = {
27
36
  return ctx.languages.includes('typescript') || ctx.languages.includes('javascript');
28
37
  },
29
38
  async run(ctx) {
30
- const eslintBin = (0, utils_1.resolvePackageBin)('eslint', 'bin/eslint.js');
31
- const sourcePath = (0, config_1.getSourcePath)();
32
- const changedFiles = await (0, delta_1.getChangedFiles)(['.ts', '.tsx', '.js', '.jsx']);
33
- const targets = changedFiles ?? [node_path_1.default.join(ctx.cwd, sourcePath)];
39
+ const eslintBin = resolveESLintBin(ctx.cwd);
40
+ const targets = await (0, targets_1.resolveScopedTargetFiles)(ctx, ['*.ts', '*.tsx', '*.js', '*.jsx'], (0, config_1.getSourcePaths)());
41
+ if (targets === null)
42
+ return (0, types_1.skip)('git unavailable');
43
+ if (targets.length === 0)
44
+ return (0, types_1.pass)('no TypeScript/JavaScript files to lint');
34
45
  const hasLocal = (0, config_1.hasLocalConfig)([
35
46
  'eslint.config.mjs',
36
47
  'eslint.config.js',
@@ -50,7 +61,7 @@ exports.lintCheck = {
50
61
  if (ctx.write)
51
62
  args.push('--fix');
52
63
  if (hasLocal) {
53
- utils_1.logger.info(`[ESLint]: Using local config (checking: ${sourcePath})`);
64
+ utils_1.logger.info(`[ESLint]: Using local config (${targets.length} file(s))`);
54
65
  }
55
66
  else {
56
67
  const configFile = version >= 9 ? 'eslint.config.mjs' : '.eslintrc.js';
@@ -9,8 +9,8 @@ const node_os_1 = __importDefault(require("node:os"));
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
10
  const config_1 = require("../../config");
11
11
  const utils_1 = require("../../utils");
12
- const delta_1 = require("../../utils/delta");
13
12
  const execa_1 = require("../../utils/execa");
13
+ const targets_1 = require("../targets");
14
14
  const types_1 = require("../types");
15
15
  function loadUserMarkdownConfig(cwd) {
16
16
  const configPath = node_path_1.default.join(cwd, 'pr-checkmate.json');
@@ -31,21 +31,6 @@ function buildMergedConfig(bundledConfigPath, userRules) {
31
31
  node_fs_1.default.writeFileSync(tmpFile, JSON.stringify(merged, null, 2));
32
32
  return tmpFile;
33
33
  }
34
- async function resolveTargets(cwd) {
35
- const changedFiles = await (0, delta_1.getChangedFiles)(['.md', '.mdx']);
36
- if (changedFiles !== null)
37
- return changedFiles;
38
- try {
39
- const { stdout } = await (0, execa_1.execa)('git', ['ls-files', '*.md', '**/*.md'], { cwd, reject: false });
40
- return stdout
41
- .split('\n')
42
- .map(f => f.trim())
43
- .filter(f => f.endsWith('.md'));
44
- }
45
- catch {
46
- return [`${cwd}/**/*.md`];
47
- }
48
- }
49
34
  exports.markdownLintCheck = {
50
35
  name: 'Markdown',
51
36
  phase: 'informational',
@@ -53,7 +38,9 @@ exports.markdownLintCheck = {
53
38
  return true;
54
39
  },
55
40
  async run(ctx) {
56
- const targets = await resolveTargets(ctx.cwd);
41
+ const targets = await (0, targets_1.resolveTargetFiles)(ctx, ['*.md', '*.mdx']);
42
+ if (targets === null)
43
+ return (0, types_1.skip)('git unavailable');
57
44
  if (targets.length === 0)
58
45
  return (0, types_1.skip)('no markdown files');
59
46
  const userConfig = loadUserMarkdownConfig(ctx.cwd);
@@ -8,8 +8,8 @@ const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const config_1 = require("../../config");
10
10
  const utils_1 = require("../../utils");
11
- const delta_1 = require("../../utils/delta");
12
11
  const execa_1 = require("../../utils/execa");
12
+ const targets_1 = require("../targets");
13
13
  const types_1 = require("../types");
14
14
  function loadUserPrettierConfig(cwd) {
15
15
  const configPath = node_path_1.default.join(cwd, 'pr-checkmate.json');
@@ -42,11 +42,11 @@ exports.prettierCheck = {
42
42
  return ctx.languages.includes('typescript') || ctx.languages.includes('javascript');
43
43
  },
44
44
  async run(ctx) {
45
- const sourcePath = (0, config_1.getSourcePath)();
46
- const changedFiles = await (0, delta_1.getChangedFiles)(['.ts', '.tsx', '.js', '.jsx', '.json', '.md']);
47
- const targets = changedFiles ?? [
48
- `${node_path_1.default.join(ctx.cwd, sourcePath)}/**/*.{ts,tsx,js,jsx,json,md}`,
49
- ];
45
+ const targets = await (0, targets_1.resolveScopedTargetFiles)(ctx, ['*.ts', '*.tsx', '*.js', '*.jsx', '*.json', '*.md'], (0, config_1.getSourcePaths)());
46
+ if (targets === null)
47
+ return (0, types_1.skip)('git unavailable');
48
+ if (targets.length === 0)
49
+ return (0, types_1.pass)('no files to format');
50
50
  const projectIgnore = node_path_1.default.join(ctx.cwd, '.prettierignore');
51
51
  const gitignore = node_path_1.default.join(ctx.cwd, '.gitignore');
52
52
  let tempIgnore = null;
@@ -8,8 +8,8 @@ const node_fs_1 = __importDefault(require("node:fs"));
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const config_1 = require("../../config");
10
10
  const utils_1 = require("../../utils");
11
- const delta_1 = require("../../utils/delta");
12
11
  const execa_1 = require("../../utils/execa");
12
+ const targets_1 = require("../targets");
13
13
  const types_1 = require("../types");
14
14
  function loadUserCspellConfig(cwd) {
15
15
  const configPath = node_path_1.default.join(cwd, 'pr-checkmate.json');
@@ -57,20 +57,12 @@ exports.spellcheckCheck = {
57
57
  return true;
58
58
  },
59
59
  async run(ctx) {
60
- const sourcePath = (0, config_1.getSourcePath)();
61
- const changedFiles = await (0, delta_1.getChangedFiles)([
62
- '.ts',
63
- '.tsx',
64
- '.js',
65
- '.jsx',
66
- '.md',
67
- '.mdx',
68
- '.json',
69
- ]);
70
- const patterns = changedFiles ?? [
71
- node_path_1.default.join(ctx.cwd, sourcePath, '**/*.{ts,tsx,js,jsx,md,mdx,json}'),
72
- ...config_1.IGNORE_PATTERNS.map(p => `--exclude=${p}`),
73
- ];
60
+ const files = await (0, targets_1.resolveScopedTargetFiles)(ctx, ['*.ts', '*.tsx', '*.js', '*.jsx', '*.md', '*.mdx', '*.json'], (0, config_1.getSourcePaths)());
61
+ if (files === null)
62
+ return (0, types_1.skip)('git unavailable');
63
+ if (files.length === 0)
64
+ return (0, types_1.pass)('no files to spellcheck');
65
+ const patterns = [...files, ...config_1.IGNORE_PATTERNS.map(p => `--exclude=${p}`)];
74
66
  const hasLocal = (0, config_1.hasLocalConfig)(['cspell.json', '.cspell.json', 'cspell.config.json']);
75
67
  let tempConfigPath = null;
76
68
  const cspellBin = (0, utils_1.resolvePackageBin)('cspell', 'bin.mjs');
@@ -1,6 +1,10 @@
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.buildContext = buildContext;
7
+ const node_fs_1 = __importDefault(require("node:fs"));
4
8
  const loader_1 = require("../config/loader");
5
9
  const utils_1 = require("../utils");
6
10
  const detect_language_1 = require("../utils/detect-language");
@@ -10,6 +14,21 @@ const execa_1 = require("../utils/execa");
10
14
  * Prefers explicit opts, then GitHub PR event, then Bitbucket PR env, then git HEAD^.
11
15
  * Returns undefined SHAs when nothing usable is found → checks fall back to full scan.
12
16
  */
17
+ /**
18
+ * Resolve a git ref to a concrete commit SHA, or undefined when it can't be
19
+ * resolved in this clone. `--verify --quiet` emits nothing (instead of echoing
20
+ * the literal ref) and exits non-zero for a missing ref — e.g. `HEAD^` on a
21
+ * single-commit repo, or a base branch not fetched in a shallow CI clone. That
22
+ * way an unresolvable base yields `undefined` and callers fall back to a full
23
+ * scan, rather than diffing against a garbage ref and silently checking nothing.
24
+ */
25
+ async function resolveRef(cwd, ref) {
26
+ const { stdout } = await (0, execa_1.execa)('git', ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`], {
27
+ cwd,
28
+ reject: false,
29
+ });
30
+ return stdout.trim() || undefined;
31
+ }
13
32
  async function resolveDiffRange(cwd, opts) {
14
33
  if (opts.baseSha || opts.headSha) {
15
34
  return { baseSha: opts.baseSha, headSha: opts.headSha };
@@ -22,17 +41,28 @@ async function resolveDiffRange(cwd, opts) {
22
41
  catch {
23
42
  return {};
24
43
  }
25
- // Bitbucket exposes the PR destination branch; GitHub delta is handled by utils/delta.
26
- const bitbucketTarget = process.env.BITBUCKET_PR_DESTINATION_BRANCH;
27
- try {
28
- const ref = bitbucketTarget ? `origin/${bitbucketTarget}` : 'HEAD^';
29
- const { stdout } = await (0, execa_1.execa)('git', ['rev-parse', ref], { cwd, reject: false });
30
- const baseSha = stdout.trim();
31
- return { baseSha: baseSha || undefined, headSha };
32
- }
33
- catch {
34
- return { headSha };
44
+ // GitHub Actions: the PR event payload carries the base branch SHA. Read it
45
+ // directly so delta works without relying on any client-set variable
46
+ // (GITHUB_EVENT_* are injected by the runner).
47
+ if (process.env.GITHUB_EVENT_NAME === 'pull_request' && process.env.GITHUB_EVENT_PATH) {
48
+ try {
49
+ const event = JSON.parse(node_fs_1.default.readFileSync(process.env.GITHUB_EVENT_PATH, 'utf-8'));
50
+ const eventBase = event?.pull_request?.base?.sha;
51
+ // Only use it if the commit is actually present in this clone; otherwise
52
+ // fall through so a shallow clone degrades to a full scan, not 0 files.
53
+ const baseSha = eventBase ? await resolveRef(cwd, eventBase) : undefined;
54
+ if (baseSha)
55
+ return { baseSha, headSha };
56
+ }
57
+ catch {
58
+ // Malformed/unreadable event — fall through to the git-based resolution.
59
+ }
35
60
  }
61
+ // Bitbucket exposes the PR destination branch as an env var (also runner-set).
62
+ const bitbucketTarget = process.env.BITBUCKET_PR_DESTINATION_BRANCH;
63
+ const ref = bitbucketTarget ? `origin/${bitbucketTarget}` : 'HEAD^';
64
+ const baseSha = await resolveRef(cwd, ref);
65
+ return { baseSha, headSha };
36
66
  }
37
67
  /**
38
68
  * Assemble the shared CheckContext once per run. Language detection is skipped
@@ -9,3 +9,16 @@ import type { CheckContext } from './types';
9
9
  * excluded, so every returned path exists on disk and is safe to read.
10
10
  */
11
11
  export declare function resolveTargetFiles(ctx: CheckContext, globs: string[]): Promise<string[] | null>;
12
+ /**
13
+ * Like {@link resolveTargetFiles} but restricts results to the configured
14
+ * `sourcePath`(s). Preserves the semantics of the old `sourcePath` full-scan
15
+ * fallback while gaining delta support on every platform via `ctx.baseSha`.
16
+ */
17
+ export declare function resolveScopedTargetFiles(ctx: CheckContext, globs: string[], sourcePaths: string[]): Promise<string[] | null>;
18
+ /**
19
+ * List ALL tracked files matching `globs` (never delta), scoped to
20
+ * `sourcePath`(s). For whole-repo analyses like circular-dependency detection
21
+ * that need the full graph, not just the PR diff. Using `git ls-files` means
22
+ * untracked `node_modules` is excluded by construction.
23
+ */
24
+ export declare function listTrackedFiles(ctx: CheckContext, globs: string[], sourcePaths: string[]): Promise<string[]>;
@@ -1,8 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveTargetFiles = resolveTargetFiles;
4
+ exports.resolveScopedTargetFiles = resolveScopedTargetFiles;
5
+ exports.listTrackedFiles = listTrackedFiles;
6
+ const config_1 = require("../config");
4
7
  const utils_1 = require("../utils");
5
8
  const execa_1 = require("../utils/execa");
9
+ /**
10
+ * Drop files that live under any excluded directory (client `ignoreDirs` or the
11
+ * curated default), matched on any path segment so nesting depth doesn't matter.
12
+ * Applied to every check's file list so build/tooling/report dirs are never
13
+ * scanned, even when committed.
14
+ */
15
+ function excludeIgnoredDirs(files) {
16
+ const ignored = new Set((0, config_1.getIgnoredDirs)());
17
+ if (ignored.size === 0)
18
+ return files;
19
+ return files.filter(f => !f.split('/').some(segment => ignored.has(segment)));
20
+ }
6
21
  /**
7
22
  * Lists the files a language check should operate on, matched by pathspec `globs`
8
23
  * (e.g. `['*.py']`). Uses the PR diff range when available (delta mode), else
@@ -26,10 +41,10 @@ async function resolveTargetFiles(ctx, globs) {
26
41
  ]
27
42
  : ['ls-files', ...globs];
28
43
  const { stdout } = await (0, execa_1.execa)('git', args, { cwd: ctx.cwd, reject: false });
29
- const files = stdout
44
+ const files = excludeIgnoredDirs(stdout
30
45
  .split('\n')
31
46
  .map(f => f.trim())
32
- .filter(Boolean);
47
+ .filter(Boolean));
33
48
  utils_1.logger.info(`[targets]: ${ctx.baseSha ? '📂 Delta' : 'Full scan'}: ${files.length} file(s) for ${globs.join(', ')}`);
34
49
  return files;
35
50
  }
@@ -38,3 +53,45 @@ async function resolveTargetFiles(ctx, globs) {
38
53
  return null;
39
54
  }
40
55
  }
56
+ /**
57
+ * Restrict a list of repo-relative paths to the configured `sourcePath`(s).
58
+ * A source path of `.` (or one that normalizes to empty) imposes no
59
+ * restriction, so the whole repo is considered.
60
+ */
61
+ function scopeToSourcePaths(files, sourcePaths) {
62
+ const scopes = sourcePaths.map(p => p.replace(/^\.\/?/, '').replace(/\/+$/, '')).filter(Boolean);
63
+ if (scopes.length === 0)
64
+ return files;
65
+ return files.filter(f => scopes.some(s => f === s || f.startsWith(`${s}/`)));
66
+ }
67
+ /**
68
+ * Like {@link resolveTargetFiles} but restricts results to the configured
69
+ * `sourcePath`(s). Preserves the semantics of the old `sourcePath` full-scan
70
+ * fallback while gaining delta support on every platform via `ctx.baseSha`.
71
+ */
72
+ async function resolveScopedTargetFiles(ctx, globs, sourcePaths) {
73
+ const files = await resolveTargetFiles(ctx, globs);
74
+ if (files === null)
75
+ return null;
76
+ return scopeToSourcePaths(files, sourcePaths);
77
+ }
78
+ /**
79
+ * List ALL tracked files matching `globs` (never delta), scoped to
80
+ * `sourcePath`(s). For whole-repo analyses like circular-dependency detection
81
+ * that need the full graph, not just the PR diff. Using `git ls-files` means
82
+ * untracked `node_modules` is excluded by construction.
83
+ */
84
+ async function listTrackedFiles(ctx, globs, sourcePaths) {
85
+ try {
86
+ const { stdout } = await (0, execa_1.execa)('git', ['ls-files', ...globs], { cwd: ctx.cwd, reject: false });
87
+ const files = excludeIgnoredDirs(stdout
88
+ .split('\n')
89
+ .map(f => f.trim())
90
+ .filter(Boolean));
91
+ return scopeToSourcePaths(files, sourcePaths);
92
+ }
93
+ catch (err) {
94
+ utils_1.logger.warn(`[targets]: ⚠️ Could not list tracked files\n${err}`);
95
+ return [];
96
+ }
97
+ }
package/dist/index.d.ts CHANGED
@@ -6,5 +6,5 @@
6
6
  export { runChecks, buildContext, REGISTRY } from './core';
7
7
  export { pass, warn, fail, skip, defineCheck } from './core';
8
8
  export type { Check, CheckContext, CheckOutcome, CheckReport, CheckResult, CheckStatus, Language, Phase, ReporterFn, ReporterKind, RunOptions, } from './core';
9
- export { getSourcePath, getPackageConfigPath, hasLocalConfig } from './config';
9
+ export { getSourcePath, getSourcePaths, getIgnoredDirs, getPackageConfigPath, hasLocalConfig, } from './config';
10
10
  export type { PRCheckMateConfig } from './interfaces';
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * @module pr-checkmate
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.hasLocalConfig = exports.getPackageConfigPath = exports.getSourcePath = exports.defineCheck = exports.skip = exports.fail = exports.warn = exports.pass = exports.REGISTRY = exports.buildContext = exports.runChecks = void 0;
8
+ exports.hasLocalConfig = exports.getPackageConfigPath = exports.getIgnoredDirs = exports.getSourcePaths = exports.getSourcePath = exports.defineCheck = exports.skip = exports.fail = exports.warn = exports.pass = exports.REGISTRY = exports.buildContext = exports.runChecks = void 0;
9
9
  // Primary programmatic API: run checks and receive a structured report.
10
10
  // Never throws for violations, never calls process.exit — the caller decides.
11
11
  var core_1 = require("./core");
@@ -22,5 +22,7 @@ Object.defineProperty(exports, "defineCheck", { enumerable: true, get: function
22
22
  // Config helpers for consumers embedding pr-checkmate.
23
23
  var config_1 = require("./config");
24
24
  Object.defineProperty(exports, "getSourcePath", { enumerable: true, get: function () { return config_1.getSourcePath; } });
25
+ Object.defineProperty(exports, "getSourcePaths", { enumerable: true, get: function () { return config_1.getSourcePaths; } });
26
+ Object.defineProperty(exports, "getIgnoredDirs", { enumerable: true, get: function () { return config_1.getIgnoredDirs; } });
25
27
  Object.defineProperty(exports, "getPackageConfigPath", { enumerable: true, get: function () { return config_1.getPackageConfigPath; } });
26
28
  Object.defineProperty(exports, "hasLocalConfig", { enumerable: true, get: function () { return config_1.hasLocalConfig; } });
package/dist/init.js CHANGED
@@ -10,19 +10,33 @@ const inquirer_1 = __importDefault(require("inquirer"));
10
10
  const config_1 = require("./config");
11
11
  const utils_1 = require("./utils");
12
12
  const CONFIG_FILE = path_1.default.resolve(process.cwd(), 'pr-checkmate.json');
13
+ /**
14
+ * Load the canonical default ESLint rules (the same set eslint.config.mjs
15
+ * applies) so the generated pr-checkmate.json lists every rule for the user to
16
+ * change or disable. Falls back to an empty object if the file is unavailable.
17
+ */
18
+ function loadDefaultEslintRules() {
19
+ try {
20
+ return JSON.parse(fs_1.default.readFileSync((0, config_1.getPackageConfigPath)('eslint-default-rules.json'), 'utf-8'));
21
+ }
22
+ catch (err) {
23
+ utils_1.logger.warn(`[init]: ⚠️ Could not load default ESLint rules: ${err}`);
24
+ return {};
25
+ }
26
+ }
13
27
  /**
14
28
  * Default configuration structure with customization options
15
29
  */
16
30
  const DEFAULT_CONFIG = {
17
- sourcePath: 'src',
31
+ sourcePath: '.',
32
+ // Directories excluded from every check (matched on any path segment). Edit
33
+ // freely — add your own generated/vendored dirs or remove ones you want scanned.
34
+ ignoreDirs: config_1.IGNORED_DIRS,
18
35
  commands: config_1.DEFAULT_COMMANDS,
19
- // ESLint customization
36
+ // ESLint — the full default rule set is written out so every rule is visible
37
+ // and editable. Change any value, or set a rule to "off" to disable it.
20
38
  eslint: {
21
- rules: {
22
- // Off by default: its `--fix` injects empty `/** */` JSDoc stubs into
23
- // client source files. Set to 'warn'/'error' to opt into doc enforcement.
24
- 'jsdoc/require-jsdoc': 'off',
25
- },
39
+ rules: loadDefaultEslintRules(),
26
40
  ignorePatterns: [],
27
41
  },
28
42
  // Prettier customization
@@ -90,7 +104,8 @@ async function promptSourcePath() {
90
104
  type: 'list',
91
105
  name: 'sourcePath',
92
106
  message: 'Where is your project source code?',
93
- choices: ['src', 'app', MANUAL_ENTRY],
107
+ default: '.',
108
+ choices: [{ name: '. (entire repo — recommended)', value: '.' }, 'src', 'app', MANUAL_ENTRY],
94
109
  },
95
110
  {
96
111
  type: 'input',
@@ -21,7 +21,8 @@ export interface CspellConfig {
21
21
  ignoreRegExpList: string[];
22
22
  }
23
23
  export interface PRCheckMateConfig {
24
- sourcePath: string;
24
+ sourcePath: string | string[];
25
+ ignoreDirs?: string[];
25
26
  languages?: Language[];
26
27
  lint?: {
27
28
  rules?: Record<string, any>;
@@ -7,7 +7,11 @@ const node_fs_1 = __importDefault(require("node:fs"));
7
7
  const node_path_1 = __importDefault(require("node:path"));
8
8
  const utils_1 = require("../utils");
9
9
  const configFiles = [
10
- { source: 'src/config/eslint.config.mjs', dest: 'eslint.config.mjs' },
10
+ // Source is named so ESLint's auto-discovery does NOT treat it as a nested
11
+ // config for files under src/config/ — it ships to the package root as the
12
+ // canonical `eslint.config.mjs`.
13
+ { source: 'src/config/eslint-flat-config.mjs', dest: 'eslint.config.mjs' },
14
+ { source: 'src/config/eslint-default-rules.json', dest: 'eslint-default-rules.json' },
11
15
  { source: 'src/config/.eslintrc.js', dest: '.eslintrc.js' },
12
16
  { source: 'src/config/.prettierrc', dest: '.prettierrc' },
13
17
  { source: 'src/config/cspell.json', dest: 'cspell.json' },
@@ -1,5 +1,4 @@
1
1
  export * from './logger';
2
- export * from './delta';
3
2
  export * from './result-collector';
4
3
  export * from './resolve-bin';
5
4
  export * from './own-deps';
@@ -21,7 +21,6 @@ exports.cleanupTempFile = cleanupTempFile;
21
21
  const node_fs_1 = __importDefault(require("node:fs"));
22
22
  const logger_1 = require("./logger");
23
23
  __exportStar(require("./logger"), exports);
24
- __exportStar(require("./delta"), exports);
25
24
  __exportStar(require("./result-collector"), exports);
26
25
  __exportStar(require("./resolve-bin"), exports);
27
26
  __exportStar(require("./own-deps"), exports);
@@ -0,0 +1,62 @@
1
+ {
2
+ "no-console": "warn",
3
+ "no-debugger": "error",
4
+ "prefer-const": "error",
5
+ "eqeqeq": ["error", "always"],
6
+ "@typescript-eslint/no-unused-vars": [
7
+ "error",
8
+ { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }
9
+ ],
10
+ "@typescript-eslint/explicit-function-return-type": ["warn"],
11
+ "@typescript-eslint/no-explicit-any": ["warn"],
12
+ "@typescript-eslint/no-floating-promises": ["error"],
13
+ "space-before-function-paren": "off",
14
+ "operator-linebreak": "off",
15
+ "max-len": ["warn", { "code": 100 }],
16
+ "semi": ["error", "always"],
17
+ "quotes": ["error", "single", { "avoidEscape": true }],
18
+ "object-curly-spacing": ["error", "always"],
19
+ "space-infix-ops": "error",
20
+ "keyword-spacing": ["error", { "before": true, "after": true }],
21
+ "padding-line-between-statements": [
22
+ "error",
23
+ { "blankLine": "always", "prev": "block", "next": "*" },
24
+ { "blankLine": "always", "prev": "*", "next": "return" }
25
+ ],
26
+ "sonarjs/cognitive-complexity": ["warn", 30],
27
+ "sonarjs/no-duplicate-string": ["warn", { "threshold": 3 }],
28
+ "sonarjs/no-identical-functions": "warn",
29
+ "import/order": [
30
+ "warn",
31
+ {
32
+ "groups": ["builtin", "external", "internal", "parent", "sibling", "index"],
33
+ "alphabetize": { "order": "asc", "caseInsensitive": true }
34
+ }
35
+ ],
36
+ "import/no-duplicates": "error",
37
+ "jsdoc/require-jsdoc": [
38
+ "warn",
39
+ {
40
+ "publicOnly": true,
41
+ "require": {
42
+ "FunctionDeclaration": true,
43
+ "MethodDefinition": true,
44
+ "ClassDeclaration": true,
45
+ "ArrowFunctionExpression": false
46
+ }
47
+ }
48
+ ],
49
+ "jsdoc/check-param-names": "warn",
50
+ "jsdoc/check-tag-names": "warn",
51
+ "jsdoc/check-types": "warn",
52
+ "prettier/prettier": [
53
+ "error",
54
+ {
55
+ "semi": true,
56
+ "singleQuote": true,
57
+ "bracketSpacing": true,
58
+ "arrowParens": "avoid",
59
+ "printWidth": 100
60
+ }
61
+ ]
62
+ }
package/eslint.config.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import fs from 'fs';
2
+ import { createRequire } from 'module';
2
3
  import path from 'path';
3
4
  import tsPlugin from '@typescript-eslint/eslint-plugin';
4
5
  import tsParser from '@typescript-eslint/parser';
@@ -8,73 +9,12 @@ import importPlugin from 'eslint-plugin-import-x';
8
9
  import jsdocPlugin from 'eslint-plugin-jsdoc';
9
10
 
10
11
  /**
11
- * Default ESLint rules - can be exported for use in init.ts
12
+ * Default ESLint rules — single source of truth, shared with init.ts so the
13
+ * pr-checkmate.json it generates lists exactly the rules applied here. Edit the
14
+ * rules in `eslint-default-rules.json` (shipped alongside this file).
12
15
  */
13
- export const defaultRules = {
14
- 'no-console': 'warn',
15
- 'no-debugger': 'error',
16
- 'prefer-const': 'error',
17
- 'eqeqeq': ['error', 'always'],
18
- '@typescript-eslint/no-unused-vars': [
19
- 'error',
20
- { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
21
- ],
22
- '@typescript-eslint/explicit-function-return-type': ['warn'],
23
- '@typescript-eslint/no-explicit-any': ['warn'],
24
- '@typescript-eslint/no-floating-promises': ['error'],
25
- 'space-before-function-paren': 'off',
26
- 'operator-linebreak': 'off',
27
- 'max-len': ['warn', { code: 100 }],
28
- 'semi': ['error', 'always'],
29
- 'quotes': ['error', 'single', { avoidEscape: true }],
30
- 'object-curly-spacing': ['error', 'always'],
31
- 'space-infix-ops': 'error',
32
- 'keyword-spacing': ['error', { before: true, after: true }],
33
- 'padding-line-between-statements': [
34
- 'error',
35
- { blankLine: 'always', prev: 'block', next: '*' },
36
- { blankLine: 'always', prev: '*', next: 'return' },
37
- ],
38
- // Complexity analysis (#61)
39
- 'sonarjs/cognitive-complexity': ['warn', 30],
40
- 'sonarjs/no-duplicate-string': ['warn', { threshold: 3 }],
41
- 'sonarjs/no-identical-functions': 'warn',
42
- // Import ordering (#65)
43
- 'import/order': [
44
- 'warn',
45
- {
46
- groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
47
- alphabetize: { order: 'asc', caseInsensitive: true },
48
- },
49
- ],
50
- 'import/no-duplicates': 'error',
51
- // Documentation (#62)
52
- 'jsdoc/require-jsdoc': [
53
- 'warn',
54
- {
55
- publicOnly: true,
56
- require: {
57
- FunctionDeclaration: true,
58
- MethodDefinition: true,
59
- ClassDeclaration: true,
60
- ArrowFunctionExpression: false,
61
- },
62
- },
63
- ],
64
- 'jsdoc/check-param-names': 'warn',
65
- 'jsdoc/check-tag-names': 'warn',
66
- 'jsdoc/check-types': 'warn',
67
- 'prettier/prettier': [
68
- 'error',
69
- {
70
- semi: true,
71
- singleQuote: true,
72
- bracketSpacing: true,
73
- arrowParens: 'avoid',
74
- printWidth: 100,
75
- },
76
- ],
77
- };
16
+ const require = createRequire(import.meta.url);
17
+ export const defaultRules = require('./eslint-default-rules.json');
78
18
 
79
19
  /**
80
20
  * Default ignore patterns
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-checkmate",
3
- "version": "1.22.7",
3
+ "version": "1.22.8",
4
4
  "description": "Automated PR quality checks: linting, formatting, dependency analysis, and spellcheck",
5
5
  "keywords": [
6
6
  "github-actions",
@@ -29,6 +29,7 @@
29
29
  "README.md",
30
30
  "scripts/copy-configs.js",
31
31
  "eslint.config.mjs",
32
+ "eslint-default-rules.json",
32
33
  ".eslintrc.js",
33
34
  ".prettierrc",
34
35
  "cspell.json",
@@ -0,0 +1,62 @@
1
+ {
2
+ "no-console": "warn",
3
+ "no-debugger": "error",
4
+ "prefer-const": "error",
5
+ "eqeqeq": ["error", "always"],
6
+ "@typescript-eslint/no-unused-vars": [
7
+ "error",
8
+ { "argsIgnorePattern": "^_", "varsIgnorePattern": "^_" }
9
+ ],
10
+ "@typescript-eslint/explicit-function-return-type": ["warn"],
11
+ "@typescript-eslint/no-explicit-any": ["warn"],
12
+ "@typescript-eslint/no-floating-promises": ["error"],
13
+ "space-before-function-paren": "off",
14
+ "operator-linebreak": "off",
15
+ "max-len": ["warn", { "code": 100 }],
16
+ "semi": ["error", "always"],
17
+ "quotes": ["error", "single", { "avoidEscape": true }],
18
+ "object-curly-spacing": ["error", "always"],
19
+ "space-infix-ops": "error",
20
+ "keyword-spacing": ["error", { "before": true, "after": true }],
21
+ "padding-line-between-statements": [
22
+ "error",
23
+ { "blankLine": "always", "prev": "block", "next": "*" },
24
+ { "blankLine": "always", "prev": "*", "next": "return" }
25
+ ],
26
+ "sonarjs/cognitive-complexity": ["warn", 30],
27
+ "sonarjs/no-duplicate-string": ["warn", { "threshold": 3 }],
28
+ "sonarjs/no-identical-functions": "warn",
29
+ "import/order": [
30
+ "warn",
31
+ {
32
+ "groups": ["builtin", "external", "internal", "parent", "sibling", "index"],
33
+ "alphabetize": { "order": "asc", "caseInsensitive": true }
34
+ }
35
+ ],
36
+ "import/no-duplicates": "error",
37
+ "jsdoc/require-jsdoc": [
38
+ "warn",
39
+ {
40
+ "publicOnly": true,
41
+ "require": {
42
+ "FunctionDeclaration": true,
43
+ "MethodDefinition": true,
44
+ "ClassDeclaration": true,
45
+ "ArrowFunctionExpression": false
46
+ }
47
+ }
48
+ ],
49
+ "jsdoc/check-param-names": "warn",
50
+ "jsdoc/check-tag-names": "warn",
51
+ "jsdoc/check-types": "warn",
52
+ "prettier/prettier": [
53
+ "error",
54
+ {
55
+ "semi": true,
56
+ "singleQuote": true,
57
+ "bracketSpacing": true,
58
+ "arrowParens": "avoid",
59
+ "printWidth": 100
60
+ }
61
+ ]
62
+ }
@@ -1,5 +0,0 @@
1
- /**
2
- * Returns list of changed files in the current PR filtered by extension.
3
- * Returns null when not in a GitHub PR context (falls back to full-project scan).
4
- */
5
- export declare function getChangedFiles(extensions: string[]): Promise<string[] | null>;
@@ -1,53 +0,0 @@
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.getChangedFiles = getChangedFiles;
7
- const node_fs_1 = __importDefault(require("node:fs"));
8
- const node_path_1 = __importDefault(require("node:path"));
9
- const execa_1 = require("./execa");
10
- const logger_1 = require("./logger");
11
- /**
12
- * Returns list of changed files in the current PR filtered by extension.
13
- * Returns null when not in a GitHub PR context (falls back to full-project scan).
14
- */
15
- async function getChangedFiles(extensions) {
16
- const eventName = process.env.GITHUB_EVENT_NAME;
17
- const eventPath = process.env.GITHUB_EVENT_PATH;
18
- if (eventName !== 'pull_request' || !eventPath || !node_fs_1.default.existsSync(eventPath)) {
19
- return null;
20
- }
21
- let baseSha = null;
22
- try {
23
- const event = JSON.parse(node_fs_1.default.readFileSync(eventPath, 'utf-8'));
24
- baseSha = event?.pull_request?.base?.sha ?? null;
25
- }
26
- catch {
27
- return null;
28
- }
29
- if (!baseSha)
30
- return null;
31
- try {
32
- const { stdout } = await (0, execa_1.execa)('git', [
33
- 'diff',
34
- '--name-only',
35
- '--diff-filter=ACMR',
36
- baseSha,
37
- 'HEAD',
38
- ]);
39
- const files = stdout
40
- .split('\n')
41
- .map(f => f.trim())
42
- .filter(f => f && extensions.some(ext => f.endsWith(ext)))
43
- .map(f => node_path_1.default.join(process.cwd(), f));
44
- if (files.length === 0)
45
- return null;
46
- logger_1.logger.info(`[delta]: 📂 Delta mode: checking ${files.length} changed file(s)`);
47
- return files;
48
- }
49
- catch (err) {
50
- logger_1.logger.warn(`[delta]: ⚠️ Could not get changed files, falling back to full scan\n${err}`);
51
- return null;
52
- }
53
- }
@@ -1,180 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import tsPlugin from '@typescript-eslint/eslint-plugin';
4
- import tsParser from '@typescript-eslint/parser';
5
- import prettierPlugin from 'eslint-plugin-prettier';
6
- import sonarjsPlugin from 'eslint-plugin-sonarjs';
7
- import importPlugin from 'eslint-plugin-import-x';
8
- import jsdocPlugin from 'eslint-plugin-jsdoc';
9
-
10
- /**
11
- * Default ESLint rules - can be exported for use in init.ts
12
- */
13
- export const defaultRules = {
14
- 'no-console': 'warn',
15
- 'no-debugger': 'error',
16
- 'prefer-const': 'error',
17
- 'eqeqeq': ['error', 'always'],
18
- '@typescript-eslint/no-unused-vars': [
19
- 'error',
20
- { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
21
- ],
22
- '@typescript-eslint/explicit-function-return-type': ['warn'],
23
- '@typescript-eslint/no-explicit-any': ['warn'],
24
- '@typescript-eslint/no-floating-promises': ['error'],
25
- 'space-before-function-paren': 'off',
26
- 'operator-linebreak': 'off',
27
- 'max-len': ['warn', { code: 100 }],
28
- 'semi': ['error', 'always'],
29
- 'quotes': ['error', 'single', { avoidEscape: true }],
30
- 'object-curly-spacing': ['error', 'always'],
31
- 'space-infix-ops': 'error',
32
- 'keyword-spacing': ['error', { before: true, after: true }],
33
- 'padding-line-between-statements': [
34
- 'error',
35
- { blankLine: 'always', prev: 'block', next: '*' },
36
- { blankLine: 'always', prev: '*', next: 'return' },
37
- ],
38
- // Complexity analysis (#61)
39
- 'sonarjs/cognitive-complexity': ['warn', 30],
40
- 'sonarjs/no-duplicate-string': ['warn', { threshold: 3 }],
41
- 'sonarjs/no-identical-functions': 'warn',
42
- // Import ordering (#65)
43
- 'import/order': [
44
- 'warn',
45
- {
46
- groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
47
- alphabetize: { order: 'asc', caseInsensitive: true },
48
- },
49
- ],
50
- 'import/no-duplicates': 'error',
51
- // Documentation (#62)
52
- 'jsdoc/require-jsdoc': [
53
- 'warn',
54
- {
55
- publicOnly: true,
56
- require: {
57
- FunctionDeclaration: true,
58
- MethodDefinition: true,
59
- ClassDeclaration: true,
60
- ArrowFunctionExpression: false,
61
- },
62
- },
63
- ],
64
- 'jsdoc/check-param-names': 'warn',
65
- 'jsdoc/check-tag-names': 'warn',
66
- 'jsdoc/check-types': 'warn',
67
- 'prettier/prettier': [
68
- 'error',
69
- {
70
- semi: true,
71
- singleQuote: true,
72
- bracketSpacing: true,
73
- arrowParens: 'avoid',
74
- printWidth: 100,
75
- },
76
- ],
77
- };
78
-
79
- /**
80
- * Default ignore patterns
81
- */
82
- const defaultIgnores = [
83
- '**/node_modules/**',
84
- '**/dist/**',
85
- '**/.git/**',
86
- '**/build/**',
87
- '**/coverage/**',
88
- ];
89
-
90
- /**
91
- * Load user's pr-checkmate.json from project root
92
- * Works both when developing the package and when installed in node_modules
93
- */
94
- function loadUserConfig() {
95
- let currentDir = process.cwd();
96
- let configPath = path.join(currentDir, 'pr-checkmate.json');
97
-
98
- // If running from node_modules, find project root
99
- if (currentDir.includes('node_modules')) {
100
- const parts = currentDir.split(path.sep);
101
- const nodeModulesIndex = parts.lastIndexOf('node_modules');
102
- if (nodeModulesIndex > 0) {
103
- currentDir = parts.slice(0, nodeModulesIndex).join(path.sep);
104
- configPath = path.join(currentDir, 'pr-checkmate.json');
105
- }
106
- }
107
-
108
- if (fs.existsSync(configPath)) {
109
- try {
110
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
111
- console.error(`[loadUserConfig]: ✅ Loaded user config from: ${configPath}`);
112
- return config;
113
- } catch (err) {
114
- console.warn(`[loadUserConfig]: ⚠️ Failed to parse ${configPath}:`, err.message);
115
- }
116
- } else {
117
- console.error(`[loadUserConfig]: No pr-checkmate.json found, using defaults`);
118
- }
119
-
120
- return { sourcePath: 'src', eslint: {} };
121
- }
122
-
123
- const userConfig = loadUserConfig();
124
- const SRC = userConfig.sourcePath || 'src';
125
-
126
- // Merge default rules with user-defined rules
127
- const mergedRules = {
128
- ...defaultRules,
129
- ...(userConfig.eslint?.rules || {}),
130
- };
131
-
132
- // Merge default ignores with user-defined ignores
133
- const mergedIgnores = [
134
- ...defaultIgnores,
135
- '**/.eslintrc.js',
136
- '**/eslint.config.mjs',
137
- ...(userConfig.eslint?.ignorePatterns || []),
138
- ];
139
-
140
- // Log customization info
141
- const customRulesCount = Object.keys(userConfig.eslint?.rules || {}).length;
142
- const customIgnoresCount = (userConfig.eslint?.ignorePatterns || []).length;
143
-
144
- if (customRulesCount > 0) {
145
- console.error(`Applied ${customRulesCount} custom ESLint rules`);
146
- }
147
- if (customIgnoresCount > 0) {
148
- console.error(`🚫 Applied ${customIgnoresCount} custom ignore patterns`);
149
- }
150
-
151
- console.error(`ESLint checking: ${SRC}/**/*.{ts,tsx,js,jsx}`);
152
-
153
- export default [
154
- // Global ignores must be in a separate config object
155
- {
156
- ignores: mergedIgnores,
157
- },
158
- {
159
- files: [`${SRC}/**/*.{ts,tsx,js,jsx}`],
160
-
161
- languageOptions: {
162
- parser: tsParser,
163
- parserOptions: {
164
- ecmaVersion: 2020,
165
- sourceType: 'module',
166
- project: './tsconfig.json',
167
- },
168
- },
169
-
170
- plugins: {
171
- '@typescript-eslint': tsPlugin,
172
- prettier: prettierPlugin,
173
- sonarjs: sonarjsPlugin,
174
- import: importPlugin,
175
- jsdoc: jsdocPlugin,
176
- },
177
-
178
- rules: mergedRules,
179
- },
180
- ];