pr-checkmate 1.20.7 → 1.21.1

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.
@@ -0,0 +1,10 @@
1
+ {
2
+ "MD013": {
3
+ "line_length": 500
4
+ },
5
+ "MD024": {
6
+ "siblings_only": true
7
+ },
8
+ "MD033": false,
9
+ "MD060": false
10
+ }
package/README.md CHANGED
@@ -2,64 +2,67 @@
2
2
  ![ESLint](https://img.shields.io/npm/v/eslint?color=yellow&label=ESLint)
3
3
  ![Prettier](https://img.shields.io/npm/v/prettier?color=orange&label=Prettier)
4
4
 
5
-
6
5
  # ♟️ PR CheckMate
6
+ >
7
7
  > **PR CheckMate** is an npm package for automating Pull Request checks.
8
- > It helps teams maintain code quality by automatically validating linting, formatting, dependencies, and spelling before merging to `main`.
8
+ > It helps teams maintain code quality by automatically validating linting, formatting, dependencies, security, and spelling before merging to `main`.
9
9
 
10
10
  ## 🔍 Why PR CheckMate?
11
+
11
12
  * ✅ Automatic checks on every PR without extra setup.
12
13
  * ✅ No need to install ESLint, Prettier, or cspell in your project — all included.
13
14
  * ✅ Enforces a unified code style across repositories.
14
- * ✅ Checks `package.json` / `package-lock.json` for dependency changes.
15
- * ✅ Spellcheck for code, documentation, and JSON files with multilingual support (EN, RU, UK, HE, FR).
16
- * ✅ Package Vulnerabilities Check
17
- * ✅ Scans for secrets using gitleaks
15
+ * ✅ Detects unused and missing dependencies via `depcheck`.
16
+ * ✅ Finds circular dependencies before they reach production.
17
+ * ✅ Blocks GPL/AGPL/LGPL licensed packages from slipping in.
18
+ * ✅ Validates PR title against Conventional Commits.
19
+ * ✅ Warns about oversized PRs and major-behind packages.
20
+ * ✅ Spellcheck for code, docs, and JSON with multilingual support (EN, RU, UK, HE, FR).
21
+ * ✅ Package vulnerability check via `npm audit`.
22
+ * ✅ Secret scanning via `gitleaks`.
23
+ * ✅ **Delta mode** — in GitHub PR context only changed files are checked, not the entire project.
24
+ * ✅ **Parallel execution** — all independent checks run concurrently, cutting CI time ~4×.
18
25
 
19
26
  <br>
20
27
  > ⚠️ **Note:** Currently, PR CheckMate only supports **Node.js projects**.
21
28
 
22
-
23
- - [PR CheckMate](https://www.npmjs.com/package/pr-checkmate) – Zero-config CLI that automates ESLint, Prettier, spellcheck, dependency checks, and npm audit for pull requests.
29
+ * [PR CheckMate](https://www.npmjs.com/package/pr-checkmate) – Zero-config CLI that automates ESLint, Prettier, spellcheck, dependency checks, and npm audit for pull requests.
24
30
 
25
31
  ## 🚀 Zero-Config by Default
32
+
26
33
  You don't need to install or configure:
27
- - ESLint
28
- - Prettier
29
- - cspell (with multilingual dictionaries)
30
- - Security scanning tools
34
+
35
+ * ESLint
36
+ * Prettier
37
+ * cspell (with multilingual dictionaries)
38
+ * Security scanning tools
31
39
 
32
40
  > Everything works out of the box.
33
41
  <br>
34
42
 
35
43
  ### 🧩 Fully Customizable
44
+
36
45
  Users can override:
37
- - ESLint rules and ignore patterns
38
- - Prettier rules
39
- - CSpell dictionary and ignore lists
40
- - Which checks run
41
- - Source code path
46
+
47
+ * ESLint rules and ignore patterns
48
+ * Prettier rules
49
+ * CSpell dictionary and ignore lists
50
+ * Which checks run (skip flags)
51
+ * Source code path
42
52
 
43
53
  User configuration is stored in:
54
+
44
55
  ```
45
56
  <project-root>/pr-checkmate.json
46
57
  ```
58
+
47
59
  <br>
48
60
 
49
61
  ### ⚙️ Example: pr-checkmate.json
62
+
50
63
  ```json
51
64
  {
52
65
  "sourcePath": "src",
53
- "commands": {
54
- "npx pr-checkmate init": "Update pr-checkmate.json",
55
- "npx pr-checkmate all": "Run all checks",
56
- "npx pr-checkmate lint": "Lint code using ESLint",
57
- "npx pr-checkmate prettier": "Format code using Prettier",
58
- "npx pr-checkmate deps": "Check project dependencies",
59
- "npx pr-checkmate npm-audit": "Run package vulnerabilities check",
60
- "npx pr-checkmate security": "Security scan",
61
- "npx pr-checkmate spellcheck": "Run spellcheck via cspell"
62
- },
63
66
  "eslint": {
64
67
  "rules": {},
65
68
  "ignorePatterns": []
@@ -79,11 +82,12 @@ User configuration is stored in:
79
82
  "cspell": "https://cspell.org/configuration/"
80
83
  }
81
84
  }
82
-
83
85
  ```
86
+
84
87
  <br>
85
88
 
86
89
  ## 🛠 How It Works
90
+
87
91
  ### 1. Install
88
92
 
89
93
  ```bash
@@ -91,28 +95,47 @@ npm install --save-dev pr-checkmate
91
95
  ```
92
96
 
93
97
  ### 2. Initialize
98
+
94
99
  ```bash
95
100
  npx pr-checkmate init
96
101
  ```
102
+
97
103
  > This creates:
98
- > - `pr-checkmate.json` - Configuration file where you can specify the source code path and customize rules
99
- > - `.github/workflows/pr-checkmate.yml` - GitHub Actions workflow for automated PR checks with auto-commit support
104
+ >
105
+ > * `pr-checkmate.json` — configuration file for source path and custom rules
106
+ > * `.github/workflows/pr-checkmate.yml` — GitHub Actions workflow with per-job checks and auto-commit support
100
107
 
101
108
  ### 3. Run Checks
109
+
102
110
  ```bash
103
111
  npx pr-checkmate <job>
104
112
  ```
105
113
 
106
114
  #### ⚡ Jobs
107
- | Job | Description |
108
- |------------|-------------|
109
- | `all` | Run all checks: ESLint, dependency check, Prettier, spellcheck, security scan |
110
- | `lint` | Lint code using ESLint |
111
- | `prettier` | Format code using Prettier |
112
- | `deps` | Check project dependencies |
113
- | `security` | Run security scan for secrets |
114
- | `spellcheck` | Run spellcheck via cspell (supports EN, RU, UK, HE, FR) |
115
- | `audit` | Run `npm audit --audit-level=moderate` to check for vulnerable packages |
115
+
116
+ | Job | Description |
117
+ |----------------|-------------|
118
+ | `all` | Run all checks in parallel (lint, deps, audit, security, circular, license, then spellcheck, pr-size, pr-title, outdated, markdown, then prettier) |
119
+ | `lint` | Lint code using ESLint |
120
+ | `prettier` | Format code using Prettier |
121
+ | `deps` | Check unused / missing dependencies via depcheck |
122
+ | `security` | Scan for secrets using gitleaks |
123
+ | `spellcheck` | Spellcheck via cspell (EN, RU, UK, HE, FR) |
124
+ | `audit` | Run `npm audit --audit-level=moderate` |
125
+ | `circular` | Detect circular dependencies via dpdm |
126
+ | `license` | Block GPL / AGPL / LGPL licensed packages |
127
+ | `pr-size` | Warn if PR exceeds 50 files or 1 000 changed lines |
128
+ | `pr-title` | Validate PR title against Conventional Commits format |
129
+ | `outdated` | Report packages that are major versions behind |
130
+ | `markdown` | Lint `.md`/`.mdx` files via markdownlint (warning-only, delta mode) |
131
+
132
+ <br>
133
+
134
+ ### ⚡ Delta Mode
135
+
136
+ In a GitHub Actions PR context, `lint`, `prettier`, `spellcheck`, and `markdown` automatically switch to **delta mode** — only the files changed in the PR are checked instead of the entire project. This is transparent and requires no configuration.
137
+
138
+ Outside of a PR context (local runs, push workflows) the checks fall back to the full project scan.
116
139
 
117
140
  <br>
118
141
 
@@ -133,28 +156,149 @@ permissions:
133
156
  pull-requests: write
134
157
 
135
158
  jobs:
136
- pr-checkmate-all:
137
- name: PR Checkmate All Checks
159
+ lint:
160
+ name: ESLint
161
+ runs-on: ubuntu-latest
162
+ steps:
163
+ - uses: actions/checkout@v4
164
+ with: { ref: "${{ github.head_ref }}", fetch-depth: 0 }
165
+ - uses: actions/setup-node@v4
166
+ with: { node-version: 20 }
167
+ - run: npm ci
168
+ - run: npx pr-checkmate lint
169
+ continue-on-error: true
170
+
171
+ deps:
172
+ name: Dependency Check
173
+ runs-on: ubuntu-latest
174
+ steps:
175
+ - uses: actions/checkout@v4
176
+ with: { ref: "${{ github.head_ref }}", fetch-depth: 0 }
177
+ - uses: actions/setup-node@v4
178
+ with: { node-version: 20 }
179
+ - run: npm ci
180
+ - run: npx pr-checkmate deps
181
+ continue-on-error: true
182
+
183
+ circular-deps:
184
+ name: Circular Dependencies
185
+ runs-on: ubuntu-latest
186
+ steps:
187
+ - uses: actions/checkout@v4
188
+ with: { ref: "${{ github.head_ref }}", fetch-depth: 0 }
189
+ - uses: actions/setup-node@v4
190
+ with: { node-version: 20 }
191
+ - run: npm ci
192
+ - run: npx pr-checkmate circular
193
+ continue-on-error: true
194
+
195
+ license-check:
196
+ name: License Check
138
197
  runs-on: ubuntu-latest
139
198
  steps:
140
- - name: Checkout code
141
- uses: actions/checkout@v4
142
- with:
143
- ref: ${{ github.head_ref }}
144
- fetch-depth: 0
145
-
146
- - name: Setup Node.js
147
- uses: actions/setup-node@v4
148
- with:
149
- node-version: 20
150
-
151
- - name: Install dependencies
152
- run: npm ci
153
-
154
- - name: Run PR Checkmate All Checks
155
- run: npx pr-checkmate all
199
+ - uses: actions/checkout@v4
200
+ with: { ref: "${{ github.head_ref }}", fetch-depth: 0 }
201
+ - uses: actions/setup-node@v4
202
+ with: { node-version: 20 }
203
+ - run: npm ci
204
+ - run: npx pr-checkmate license
156
205
  continue-on-error: true
157
206
 
207
+ pr-size:
208
+ name: PR Size
209
+ runs-on: ubuntu-latest
210
+ steps:
211
+ - uses: actions/checkout@v4
212
+ with: { ref: "${{ github.head_ref }}", fetch-depth: 0 }
213
+ - uses: actions/setup-node@v4
214
+ with: { node-version: 20 }
215
+ - run: npm ci
216
+ - run: npx pr-checkmate pr-size
217
+ continue-on-error: true
218
+
219
+ pr-title:
220
+ name: PR Title Lint
221
+ runs-on: ubuntu-latest
222
+ steps:
223
+ - uses: actions/checkout@v4
224
+ with: { ref: "${{ github.head_ref }}", fetch-depth: 0 }
225
+ - uses: actions/setup-node@v4
226
+ with: { node-version: 20 }
227
+ - run: npm ci
228
+ - run: npx pr-checkmate pr-title
229
+ continue-on-error: true
230
+
231
+ outdated-deps:
232
+ name: Outdated Dependencies
233
+ runs-on: ubuntu-latest
234
+ steps:
235
+ - uses: actions/checkout@v4
236
+ with: { ref: "${{ github.head_ref }}", fetch-depth: 0 }
237
+ - uses: actions/setup-node@v4
238
+ with: { node-version: 20 }
239
+ - run: npm ci
240
+ - run: npx pr-checkmate outdated
241
+ continue-on-error: true
242
+
243
+ markdown-lint:
244
+ name: Markdown Lint
245
+ runs-on: ubuntu-latest
246
+ steps:
247
+ - uses: actions/checkout@v4
248
+ with: { ref: "${{ github.head_ref }}", fetch-depth: 0 }
249
+ - uses: actions/setup-node@v4
250
+ with: { node-version: 20 }
251
+ - run: npm ci
252
+ - run: npx pr-checkmate markdown
253
+ continue-on-error: true
254
+
255
+ spellcheck:
256
+ name: Spellcheck
257
+ runs-on: ubuntu-latest
258
+ steps:
259
+ - uses: actions/checkout@v4
260
+ with: { ref: "${{ github.head_ref }}", fetch-depth: 0 }
261
+ - uses: actions/setup-node@v4
262
+ with: { node-version: 20 }
263
+ - run: npm ci
264
+ - run: npx pr-checkmate spellcheck
265
+ continue-on-error: true
266
+
267
+ security:
268
+ name: Security Scan
269
+ runs-on: ubuntu-latest
270
+ steps:
271
+ - uses: actions/checkout@v4
272
+ with: { ref: "${{ github.head_ref }}", fetch-depth: 0 }
273
+ - uses: actions/setup-node@v4
274
+ with: { node-version: 20 }
275
+ - run: npm ci
276
+ - run: npx pr-checkmate security
277
+ continue-on-error: true
278
+
279
+ npm-audit:
280
+ name: NPM Audit
281
+ runs-on: ubuntu-latest
282
+ steps:
283
+ - uses: actions/checkout@v4
284
+ with: { ref: "${{ github.head_ref }}", fetch-depth: 0 }
285
+ - uses: actions/setup-node@v4
286
+ with: { node-version: 20 }
287
+ - run: npm ci
288
+ - run: npx pr-checkmate audit
289
+ continue-on-error: true
290
+
291
+ prettier:
292
+ name: Prettier
293
+ runs-on: ubuntu-latest
294
+ steps:
295
+ - uses: actions/checkout@v4
296
+ with: { ref: "${{ github.head_ref }}", fetch-depth: 0 }
297
+ - uses: actions/setup-node@v4
298
+ with: { node-version: 20 }
299
+ - run: npm ci
300
+ - run: npx pr-checkmate prettier
301
+ continue-on-error: true
158
302
  - name: Auto-commit formatting changes
159
303
  run: |
160
304
  git config user.name "github-actions[bot]"
@@ -163,9 +307,6 @@ jobs:
163
307
  if ! git diff --cached --quiet; then
164
308
  git commit -m "style: auto-format code with Prettier"
165
309
  git push origin HEAD:${{ github.head_ref }}
166
- echo "Formatting changes committed and pushed"
167
- else
168
- echo "No formatting changes needed"
169
310
  fi
170
311
  env:
171
312
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -186,14 +327,15 @@ For the auto-commit feature to work, ensure your repository has the correct perm
186
327
  ## 🌍 Multilingual Spellcheck Support
187
328
 
188
329
  PR CheckMate includes built-in dictionaries for:
189
- - 🇬🇧 English (en)
190
- - 🇪🇸 Spanish (es)
191
- - 🇫🇷 French (fr)
192
- - 🇷🇺 Russian (ru)
193
- - 🇺🇦 Ukrainian (uk)
194
- - 🇮🇱 Hebrew (he)
195
330
 
196
- No additional configuration needed - just use `npx pr-checkmate spellcheck` and it will check spelling in all supported languages!
331
+ * 🇬🇧 English (en)
332
+ * 🇪🇸 Spanish (es)
333
+ * 🇫🇷 French (fr)
334
+ * 🇷🇺 Russian (ru)
335
+ * 🇺🇦 Ukrainian (uk)
336
+ * 🇮🇱 Hebrew (he)
337
+
338
+ No additional configuration needed — just use `npx pr-checkmate spellcheck` and it will check spelling in all supported languages.
197
339
 
198
340
  <br>
199
341
 
@@ -205,9 +347,11 @@ No additional configuration needed - just use `npx pr-checkmate spellcheck` and
205
347
  * ✅ Automatic formatting and commits when needed
206
348
  * ✅ Multilingual spellcheck support out of the box
207
349
  * ✅ Minimal setup — just `init` and `npx pr-checkmate all`
208
-
350
+ * ✅ Delta mode keeps CI fast — only changed files are scanned
351
+ * ✅ License guard prevents copyleft packages from entering your project
209
352
 
210
353
  ## 🧰 Tech Stack
354
+
211
355
  * **Node.js 20** — executes CLI scripts
212
356
  * **TypeScript** — fully typed code
213
357
  * **ESLint + @typescript-eslint** — code linting
@@ -215,7 +359,10 @@ No additional configuration needed - just use `npx pr-checkmate spellcheck` and
215
359
  * **cspell** — spellchecking with multilingual dictionaries
216
360
  * **execa** — running CLI commands from Node.js
217
361
  * **gitleaks** — secret scanning
362
+ * **depcheck** — unused and missing dependency analysis
363
+ * **dpdm** — circular dependency detection
364
+ * **license-checker** — license compliance validation
218
365
 
366
+ ## 📜 License
219
367
 
220
- ## 📜 License
221
- LicenseRef Proprietary © 2025
368
+ LicenseRef Proprietary © 2025
@@ -242,3 +242,24 @@ jobs:
242
242
  - name: Check Outdated Dependencies
243
243
  run: npx pr-checkmate outdated
244
244
  continue-on-error: true
245
+
246
+ markdown-lint:
247
+ name: Markdown Lint
248
+ runs-on: ubuntu-latest
249
+ steps:
250
+ - name: Checkout code
251
+ uses: actions/checkout@v4
252
+ with:
253
+ fetch-depth: 0
254
+
255
+ - name: Setup Node.js
256
+ uses: actions/setup-node@v4
257
+ with:
258
+ node-version: 20
259
+
260
+ - name: Install dependencies
261
+ run: npm ci
262
+
263
+ - name: Run Markdown Lint
264
+ run: npx pr-checkmate markdown
265
+ continue-on-error: true
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.runOutdatedDeps = exports.runPrTitleLint = exports.runPrSizeCheck = exports.runLicenseCheck = exports.runCircularDeps = exports.runNpmAudit = exports.runSecurityScan = exports.runSpellcheck = exports.runPrettier = exports.runDependencyCheck = exports.runLint = exports.hasLocalConfig = exports.getPackageConfigPath = exports.getSourcePath = void 0;
3
+ exports.runMarkdownLint = exports.runOutdatedDeps = exports.runPrTitleLint = exports.runPrSizeCheck = exports.runLicenseCheck = exports.runCircularDeps = exports.runNpmAudit = exports.runSecurityScan = exports.runSpellcheck = exports.runPrettier = exports.runDependencyCheck = exports.runLint = exports.hasLocalConfig = exports.getPackageConfigPath = exports.getSourcePath = void 0;
4
4
  exports.runAllChecks = runAllChecks;
5
5
  exports.runChecks = runChecks;
6
6
  /**
@@ -19,6 +19,7 @@ const license_check_1 = require("./scripts/license-check");
19
19
  const pr_size_check_1 = require("./scripts/pr-size-check");
20
20
  const pr_title_lint_1 = require("./scripts/pr-title-lint");
21
21
  const outdated_deps_1 = require("./scripts/outdated-deps");
22
+ const markdown_lint_1 = require("./scripts/markdown-lint");
22
23
  const utils_1 = require("./utils");
23
24
  var config_1 = require("./config");
24
25
  Object.defineProperty(exports, "getSourcePath", { enumerable: true, get: function () { return config_1.getSourcePath; } });
@@ -46,6 +47,8 @@ var pr_title_lint_2 = require("./scripts/pr-title-lint");
46
47
  Object.defineProperty(exports, "runPrTitleLint", { enumerable: true, get: function () { return pr_title_lint_2.runPrTitleLint; } });
47
48
  var outdated_deps_2 = require("./scripts/outdated-deps");
48
49
  Object.defineProperty(exports, "runOutdatedDeps", { enumerable: true, get: function () { return outdated_deps_2.runOutdatedDeps; } });
50
+ var markdown_lint_2 = require("./scripts/markdown-lint");
51
+ Object.defineProperty(exports, "runMarkdownLint", { enumerable: true, get: function () { return markdown_lint_2.runMarkdownLint; } });
49
52
  /**
50
53
  * Run all checks with parallel execution where possible.
51
54
  * Blocking: lint, deps, npm-audit, security, circular-deps, license.
@@ -67,6 +70,7 @@ async function runAllChecks() {
67
70
  (0, pr_size_check_1.runPrSizeCheck)(),
68
71
  (0, pr_title_lint_1.runPrTitleLint)(),
69
72
  (0, outdated_deps_1.runOutdatedDeps)(),
73
+ (0, markdown_lint_1.runMarkdownLint)(),
70
74
  (0, spellcheck_1.runSpellcheck)().catch(() => utils_1.logger.warn('⚠️ Spellcheck found issues, continuing...')),
71
75
  ]);
72
76
  // Phase 3: prettier last — writes files
@@ -94,6 +98,8 @@ async function runChecks(config = {}) {
94
98
  await (0, pr_title_lint_1.runPrTitleLint)();
95
99
  if (!config.skipOutdated)
96
100
  await (0, outdated_deps_1.runOutdatedDeps)();
101
+ if (!config.skipMarkdown)
102
+ await (0, markdown_lint_1.runMarkdownLint)();
97
103
  if (!config.skipSpell)
98
104
  await (0, spellcheck_1.runSpellcheck)();
99
105
  if (!config.skipFormat)
package/dist/init.js CHANGED
@@ -32,6 +32,11 @@ const DEFAULT_CONFIG = {
32
32
  ignorePaths: [],
33
33
  ignoreRegExpList: [],
34
34
  },
35
+ // Markdownlint customization
36
+ markdownlint: {
37
+ rules: {},
38
+ ignores: [],
39
+ },
35
40
  documentation: config_1.documentation,
36
41
  };
37
42
  async function init() {
@@ -72,7 +77,7 @@ async function init() {
72
77
  }
73
78
  fs_1.default.copyFileSync(workflowSource, workflowDest);
74
79
  utils_1.logger.info('[init]: ✅ Created .github/workflows/pr-checkmate.yml');
75
- utils_1.logger.info('[init]: 🚀 GitHub Actions workflow is ready for your PRs!');
80
+ utils_1.logger.info('[init]: GitHub Actions workflow is ready for your PRs!');
76
81
  }
77
82
  else {
78
83
  utils_1.logger.warn('[init]: ⚠️ Workflow template not found, skipping workflow setup');
@@ -9,7 +9,7 @@ const utils_1 = require("../utils");
9
9
  const config_1 = require("../config");
10
10
  const node_path_1 = __importDefault(require("node:path"));
11
11
  async function runCircularDeps() {
12
- utils_1.logger.info('[runCircularDeps]: 🔄 Checking for circular dependencies...');
12
+ utils_1.logger.info('[runCircularDeps]: Checking for circular dependencies...');
13
13
  const sourcePath = (0, config_1.getSourcePath)();
14
14
  const pattern = node_path_1.default.join(process.cwd(), sourcePath, '**/*.{ts,tsx,js,jsx}');
15
15
  try {
@@ -17,7 +17,10 @@ async function runCircularDeps() {
17
17
  reject: false,
18
18
  });
19
19
  const output = (result.stdout + result.stderr).trim();
20
- if (output.toLowerCase().includes('circular')) {
20
+ // dpdm success message also contains "circular" — match only its error patterns:
21
+ // "Circular Dependencies:" header or numbered entry "[1] src/a.ts -> src/b.ts"
22
+ const hasCircular = /circular dependencies:/i.test(output) || /^\s*\[\d+\]/m.test(output);
23
+ if (hasCircular) {
21
24
  utils_1.logger.error('[runCircularDeps]: ❌ Circular dependencies detected:');
22
25
  utils_1.logger.error(output);
23
26
  throw new Error('Circular dependencies found');
@@ -18,7 +18,7 @@ const configFiles = [
18
18
  ];
19
19
  function copyConfigs() {
20
20
  const root = node_path_1.default.resolve(__dirname, '../..');
21
- utils_1.logger.log('[copyConfigs]: 📦 Copying config files to package root...\n');
21
+ utils_1.logger.log('[copyConfigs]: Copying config files to package root...\n');
22
22
  configFiles.forEach(({ source, dest }) => {
23
23
  const srcPath = node_path_1.default.join(root, source);
24
24
  const destPath = node_path_1.default.join(root, dest);
@@ -9,37 +9,49 @@ const utils_1 = require("../utils");
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
10
  const node_fs_1 = __importDefault(require("node:fs"));
11
11
  async function runDependencyCheck() {
12
- utils_1.logger.info('[runDependencyCheck]: 📦 Checking dependencies...');
12
+ utils_1.logger.info('[runDependencyCheck]: Checking dependencies...');
13
13
  const projectRoot = process.cwd();
14
14
  if (!node_fs_1.default.existsSync(node_path_1.default.join(projectRoot, 'package.json'))) {
15
15
  utils_1.logger.warn('[runDependencyCheck]: ⚠️ No package.json found, skipping');
16
16
  return;
17
17
  }
18
+ // execa v9 always throws on non-zero exit; depcheck exits 255 when it finds issues
19
+ // but the JSON result is still in error.stdout — extract it either way
20
+ let stdout;
18
21
  try {
19
- const { stdout } = await (0, execa_1.execa)('npx', ['depcheck', '--json'], { cwd: projectRoot });
20
- const result = JSON.parse(stdout);
21
- const unused = result.dependencies ?? [];
22
- const missing = Object.keys(result.missing ?? {});
23
- if (unused.length === 0 && missing.length === 0) {
24
- utils_1.logger.info('[runDependencyCheck]: ✅ Dependencies are clean');
25
- return;
26
- }
27
- if (unused.length > 0) {
28
- utils_1.logger.warn(`[runDependencyCheck]: ⚠️ ${unused.length} unused dependency(ies):`);
29
- unused.forEach(dep => utils_1.logger.warn(` ${dep}`));
30
- }
31
- if (missing.length > 0) {
32
- utils_1.logger.error(`[runDependencyCheck]: ❌ ${missing.length} missing dependency(ies):`);
33
- missing.forEach(dep => utils_1.logger.error(` ${dep} (used in: ${result.missing[dep]?.join(', ')})`));
34
- throw new Error(`Missing dependencies: ${missing.join(', ')}`);
35
- }
36
- // Unused deps are warnings — don't block the build
22
+ ({ stdout } = await (0, execa_1.execa)('npx', ['depcheck', '--json'], { cwd: projectRoot }));
37
23
  }
38
24
  catch (err) {
39
- const error = err;
40
- if (error.message?.startsWith('Missing dependencies'))
25
+ if (err instanceof execa_1.ExecaError && err.stdout) {
26
+ stdout = err.stdout;
27
+ }
28
+ else {
29
+ utils_1.logger.error(`[runDependencyCheck]: ❌ Dependency check failed\n${err}`);
41
30
  throw err;
42
- utils_1.logger.error(`[runDependencyCheck]: ❌ Dependency check failed\n${err}`);
43
- throw err;
31
+ }
32
+ }
33
+ let result;
34
+ try {
35
+ result = JSON.parse(stdout);
36
+ }
37
+ catch {
38
+ utils_1.logger.error('[runDependencyCheck]: ❌ Failed to parse depcheck output');
39
+ throw new Error('depcheck produced invalid JSON');
40
+ }
41
+ const unused = result.dependencies ?? [];
42
+ const missing = Object.keys(result.missing ?? {});
43
+ if (unused.length === 0 && missing.length === 0) {
44
+ utils_1.logger.info('[runDependencyCheck]: ✅ Dependencies are clean');
45
+ return;
46
+ }
47
+ if (unused.length > 0) {
48
+ utils_1.logger.warn(`[runDependencyCheck]: ⚠️ ${unused.length} unused dependency(ies):`);
49
+ unused.forEach(dep => utils_1.logger.warn(` ${dep}`));
50
+ }
51
+ if (missing.length > 0) {
52
+ utils_1.logger.error(`[runDependencyCheck]: ❌ ${missing.length} missing dependency(ies):`);
53
+ missing.forEach(dep => utils_1.logger.error(` ${dep} (used in: ${result.missing[dep]?.join(', ')})`));
54
+ throw new Error(`Missing dependencies: ${missing.join(', ')}`);
44
55
  }
56
+ // Unused deps are warnings — don't block the build
45
57
  }
@@ -25,6 +25,7 @@ const license_check_1 = require("./license-check");
25
25
  const pr_size_check_1 = require("./pr-size-check");
26
26
  const pr_title_lint_1 = require("./pr-title-lint");
27
27
  const outdated_deps_1 = require("./outdated-deps");
28
+ const markdown_lint_1 = require("./markdown-lint");
28
29
  const utils_1 = require("../utils");
29
30
  const lint_1 = require("./lint");
30
31
  __exportStar(require("./dependency-check"), exports);
@@ -37,6 +38,7 @@ __exportStar(require("./license-check"), exports);
37
38
  __exportStar(require("./pr-size-check"), exports);
38
39
  __exportStar(require("./pr-title-lint"), exports);
39
40
  __exportStar(require("./outdated-deps"), exports);
41
+ __exportStar(require("./markdown-lint"), exports);
40
42
  async function runJob(jobName) {
41
43
  utils_1.logger.info(`[runJob]: 🚀 Running job: ${jobName}`);
42
44
  try {
@@ -56,6 +58,7 @@ async function runJob(jobName) {
56
58
  (0, pr_size_check_1.runPrSizeCheck)(),
57
59
  (0, pr_title_lint_1.runPrTitleLint)(),
58
60
  (0, outdated_deps_1.runOutdatedDeps)(),
61
+ (0, markdown_lint_1.runMarkdownLint)(),
59
62
  (0, spellcheck_1.runSpellcheck)().catch(() => utils_1.logger.warn('⚠️ Spellcheck found issues, continuing...')),
60
63
  ]);
61
64
  // Phase 3: prettier last — writes files
@@ -100,8 +103,11 @@ async function runJob(jobName) {
100
103
  case 'outdated':
101
104
  await (0, outdated_deps_1.runOutdatedDeps)();
102
105
  break;
106
+ case 'markdown':
107
+ await (0, markdown_lint_1.runMarkdownLint)();
108
+ break;
103
109
  default:
104
- utils_1.logger.warn(`[runJob]: Unknown job "${jobName}". Available: all, lint, deps, npm-audit, prettier, spellcheck, security, circular, license, pr-size, pr-title, outdated`);
110
+ utils_1.logger.warn(`[runJob]: Unknown job "${jobName}". Available: all, lint, deps, npm-audit, prettier, spellcheck, security, circular, license, pr-size, pr-title, outdated, markdown`);
105
111
  }
106
112
  }
107
113
  catch (err) {
@@ -10,7 +10,7 @@ const node_path_1 = __importDefault(require("node:path"));
10
10
  const node_fs_1 = __importDefault(require("node:fs"));
11
11
  const FORBIDDEN_LICENSES = ['GPL-2.0', 'GPL-3.0', 'AGPL-3.0', 'LGPL-2.0', 'LGPL-2.1', 'LGPL-3.0'];
12
12
  async function runLicenseCheck() {
13
- utils_1.logger.info('[runLicenseCheck]: 📄 Checking dependency licenses...');
13
+ utils_1.logger.info('[runLicenseCheck]: Checking dependency licenses...');
14
14
  if (!node_fs_1.default.existsSync(node_path_1.default.join(process.cwd(), 'package.json'))) {
15
15
  utils_1.logger.warn('[runLicenseCheck]: ⚠️ No package.json found, skipping');
16
16
  return;
@@ -22,7 +22,8 @@ async function runLicenseCheck() {
22
22
  const licenses = JSON.parse(stdout);
23
23
  const violations = [];
24
24
  for (const [pkg, info] of Object.entries(licenses)) {
25
- const license = info.licenses ?? '';
25
+ const raw = info.licenses ?? '';
26
+ const license = Array.isArray(raw) ? raw.join(' OR ') : raw;
26
27
  if (FORBIDDEN_LICENSES.some(f => license.includes(f))) {
27
28
  violations.push(` ${pkg}: ${license}`);
28
29
  }
@@ -38,16 +38,16 @@ async function runLint() {
38
38
  ]);
39
39
  let args;
40
40
  if (hasLocal) {
41
- utils_1.logger.info(`[runLint]: 📋 Using local ESLint config (checking: ${sourcePath})`);
41
+ utils_1.logger.info(`[runLint]: Using local ESLint config (checking: ${sourcePath})`);
42
42
  args = ['eslint', ...targets, '--ext', '.ts,.tsx,.js,.jsx', '--fix'];
43
43
  }
44
44
  else {
45
- utils_1.logger.info(`[runLint]: 📦 Using pr-checkmate ESLint config (checking: ${sourcePath})`);
45
+ utils_1.logger.info(`[runLint]: Using pr-checkmate ESLint config (checking: ${sourcePath})`);
46
46
  // Detect ESLint version and choose appropriate config
47
47
  const eslintVersion = await getESLintMajorVersion();
48
48
  const configFile = eslintVersion >= 9 ? 'eslint.config.mjs' : '.eslintrc.js';
49
49
  const configPath = (0, config_1.getPackageConfigPath)(configFile);
50
- utils_1.logger.info(`[runLint]: 📋 Detected ESLint v${eslintVersion}, using ${configFile}`);
50
+ utils_1.logger.info(`[runLint]: Detected ESLint v${eslintVersion}, using ${configFile}`);
51
51
  args = ['eslint', ...targets, '--ext', '.ts,.tsx,.js,.jsx', '--fix', '--config', configPath];
52
52
  }
53
53
  config_1.IGNORE_PATTERNS.forEach(p => args.push('--ignore-pattern', p));
@@ -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.runMarkdownLint = runMarkdownLint;
7
+ const execa_1 = require("execa");
8
+ const utils_1 = require("../utils");
9
+ const delta_1 = require("../utils/delta");
10
+ const config_1 = require("../config");
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const node_fs_1 = __importDefault(require("node:fs"));
13
+ function loadUserMarkdownConfig() {
14
+ const configPath = node_path_1.default.join(process.cwd(), 'pr-checkmate.json');
15
+ if (!node_fs_1.default.existsSync(configPath)) {
16
+ return {};
17
+ }
18
+ try {
19
+ const config = JSON.parse(node_fs_1.default.readFileSync(configPath, 'utf-8'));
20
+ return config.markdownlint || {};
21
+ }
22
+ catch (err) {
23
+ utils_1.logger.warn(`[loadUserMarkdownConfig]: ⚠️ Failed to read markdownlint config from pr-checkmate.json: ${err}`);
24
+ return {};
25
+ }
26
+ }
27
+ async function runMarkdownLint() {
28
+ utils_1.logger.info('[runMarkdownLint]: 📝 Running Markdown lint...');
29
+ const root = process.cwd();
30
+ const changedFiles = await (0, delta_1.getChangedFiles)(['.md', '.mdx']);
31
+ const targets = changedFiles ?? [`${root}/**/*.md`];
32
+ const userConfig = loadUserMarkdownConfig();
33
+ // Prefer local .markdownlint.json → else fall back to bundled one
34
+ let configArg;
35
+ if ((0, config_1.hasLocalConfig)(['.markdownlint.json', '.markdownlint.jsonc', '.markdownlint.yaml', '.markdownlint.yml'])) {
36
+ utils_1.logger.info('[runMarkdownLint]: 📋 Using local .markdownlint config');
37
+ configArg = [];
38
+ }
39
+ else {
40
+ const bundledConfig = (0, config_1.getPackageConfigPath)('.markdownlint.json');
41
+ utils_1.logger.info('[runMarkdownLint]: 📋 Using pr-checkmate bundled markdownlint config');
42
+ configArg = ['--config', bundledConfig];
43
+ }
44
+ const extraIgnores = (userConfig.ignores ?? []).flatMap(p => ['--ignore', p]);
45
+ const args = [
46
+ 'markdownlint',
47
+ ...targets,
48
+ ...configArg,
49
+ '--ignore', 'node_modules',
50
+ '--ignore', 'dist',
51
+ '--ignore', 'coverage',
52
+ ...extraIgnores,
53
+ ];
54
+ const result = await (0, execa_1.execa)('npx', args, { reject: false, stdio: 'inherit' });
55
+ if (result.exitCode === 0) {
56
+ utils_1.logger.info('[runMarkdownLint]: ✅ Markdown lint passed');
57
+ }
58
+ else {
59
+ utils_1.logger.warn('[runMarkdownLint]: ⚠️ Markdown lint found issues (see above)');
60
+ // warning only — never block
61
+ }
62
+ }
@@ -12,7 +12,7 @@ const execa_1 = require("execa");
12
12
  * Run npm audit on the current project
13
13
  */
14
14
  async function runNpmAudit() {
15
- utils_1.logger.info('🛡️ Running npm audit...');
15
+ utils_1.logger.info('Running npm audit...');
16
16
  const projectRoot = process.cwd();
17
17
  const packageJsonPath = node_path_1.default.join(projectRoot, 'package.json');
18
18
  if (!node_fs_1.default.existsSync(packageJsonPath)) {
@@ -9,7 +9,7 @@ const utils_1 = require("../utils");
9
9
  const node_path_1 = __importDefault(require("node:path"));
10
10
  const node_fs_1 = __importDefault(require("node:fs"));
11
11
  async function runOutdatedDeps() {
12
- utils_1.logger.info('[runOutdatedDeps]: 📅 Checking for outdated dependencies...');
12
+ utils_1.logger.info('[runOutdatedDeps]: Checking for outdated dependencies...');
13
13
  if (!node_fs_1.default.existsSync(node_path_1.default.join(process.cwd(), 'package.json'))) {
14
14
  utils_1.logger.warn('[runOutdatedDeps]: ⚠️ No package.json found, skipping');
15
15
  return;
@@ -46,7 +46,7 @@ async function runOutdatedDeps() {
46
46
  });
47
47
  }
48
48
  if (minorBehind > 0) {
49
- utils_1.logger.info(`[runOutdatedDeps]: ℹ️ ${minorBehind} package(s) have minor/patch updates available`);
49
+ utils_1.logger.info(`[runOutdatedDeps]: ${minorBehind} package(s) have minor/patch updates available`);
50
50
  }
51
51
  // informational only — never throws
52
52
  }
@@ -24,10 +24,10 @@ function readBaseSha() {
24
24
  }
25
25
  }
26
26
  async function runPrSizeCheck() {
27
- utils_1.logger.info('[runPrSizeCheck]: 📏 Checking PR size...');
27
+ utils_1.logger.info('[runPrSizeCheck]: Checking PR size...');
28
28
  const baseSha = readBaseSha();
29
29
  if (!baseSha) {
30
- utils_1.logger.info('[runPrSizeCheck]: ℹ️ Not in GitHub PR context, skipping');
30
+ utils_1.logger.info('[runPrSizeCheck]: Not in GitHub PR context, skipping');
31
31
  return;
32
32
  }
33
33
  try {
@@ -38,7 +38,7 @@ async function runPrSizeCheck() {
38
38
  const deletions = stdout.match(/(\d+) deletion/);
39
39
  const fileCount = filesMatch ? parseInt(filesMatch[1]) : 0;
40
40
  const lines = (insertions ? parseInt(insertions[1]) : 0) + (deletions ? parseInt(deletions[1]) : 0);
41
- utils_1.logger.info(`[runPrSizeCheck]: 📊 ${fileCount} files changed, ${lines} lines`);
41
+ utils_1.logger.info(`[runPrSizeCheck]: ${fileCount} files changed, ${lines} lines`);
42
42
  if (fileCount > WARN_FILES) {
43
43
  utils_1.logger.warn(`[runPrSizeCheck]: ⚠️ Large PR: ${fileCount} files changed (recommended < ${WARN_FILES})`);
44
44
  }
@@ -8,10 +8,10 @@ const utils_1 = require("../utils");
8
8
  const node_fs_1 = __importDefault(require("node:fs"));
9
9
  const CONVENTIONAL_PREFIX = /^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?!?: .+/;
10
10
  async function runPrTitleLint() {
11
- utils_1.logger.info('[runPrTitleLint]: 📝 Checking PR title...');
11
+ utils_1.logger.info('[runPrTitleLint]: Checking PR title...');
12
12
  const eventPath = process.env.GITHUB_EVENT_PATH;
13
13
  if (!eventPath || !node_fs_1.default.existsSync(eventPath)) {
14
- utils_1.logger.info('[runPrTitleLint]: ℹ️ Not in GitHub CI, skipping');
14
+ utils_1.logger.info('[runPrTitleLint]: Not in GitHub CI, skipping');
15
15
  return;
16
16
  }
17
17
  let event;
@@ -24,7 +24,7 @@ async function runPrTitleLint() {
24
24
  }
25
25
  const pr = event.pull_request;
26
26
  if (!pr?.title) {
27
- utils_1.logger.info('[runPrTitleLint]: ℹ️ No PR title found, skipping');
27
+ utils_1.logger.info('[runPrTitleLint]: No PR title found, skipping');
28
28
  return;
29
29
  }
30
30
  const title = pr.title;
@@ -42,7 +42,7 @@ function createTempPrettierConfig() {
42
42
  // Log customizations
43
43
  const customRulesCount = Object.keys(userConfig).length;
44
44
  if (customRulesCount > 0) {
45
- utils_1.logger.info(`[createTempPrettierConfig]: 🎨 Applied ${customRulesCount}
45
+ utils_1.logger.info(`[createTempPrettierConfig]: Applied ${customRulesCount}
46
46
  custom Prettier rules from pr-checkmate.json`);
47
47
  utils_1.logger.debug(`[createTempPrettierConfig]: Custom rules: ${JSON.stringify(userConfig)}`);
48
48
  }
@@ -60,7 +60,7 @@ function createTempIgnoreFile() {
60
60
  return file;
61
61
  }
62
62
  async function runPrettier() {
63
- utils_1.logger.info('[runPrettier]: 🎨 Running Prettier...');
63
+ utils_1.logger.info('[runPrettier]: Running Prettier...');
64
64
  const root = process.cwd();
65
65
  const sourcePath = (0, config_1.getSourcePath)();
66
66
  const changedFiles = await (0, delta_1.getChangedFiles)(['.ts', '.tsx', '.js', '.jsx', '.json', '.md']);
@@ -88,11 +88,11 @@ async function runPrettier() {
88
88
  let args;
89
89
  let tempConfig = null;
90
90
  if (hasLocal) {
91
- utils_1.logger.info(`[runPrettier]: 📋 Using local Prettier config (checking: ${sourcePath})`);
91
+ utils_1.logger.info(`[runPrettier]: Using local Prettier config (checking: ${sourcePath})`);
92
92
  args = ['prettier', '--write', ...targets, '--ignore-path', ignorePath];
93
93
  }
94
94
  else {
95
- utils_1.logger.info(`[runPrettier]: 📦 Using pr-checkmate Prettier config (checking: ${sourcePath})`);
95
+ utils_1.logger.info(`[runPrettier]: Using pr-checkmate Prettier config (checking: ${sourcePath})`);
96
96
  // Create temporary config with merged settings
97
97
  tempConfig = createTempPrettierConfig();
98
98
  args = ['prettier', '--write', ...targets, '--config', tempConfig, '--ignore-path', ignorePath];
@@ -32,7 +32,7 @@ async function getBaseSha() {
32
32
  const event = JSON.parse(node_fs_1.default.readFileSync(eventPath, 'utf-8'));
33
33
  const baseSha = event?.pull_request?.base?.sha;
34
34
  if (baseSha) {
35
- utils_1.logger.info(`[getBaseSha]: ℹ️ Using PR base SHA: ${baseSha}`);
35
+ utils_1.logger.info(`[getBaseSha]: Using PR base SHA: ${baseSha}`);
36
36
  return baseSha;
37
37
  }
38
38
  }
@@ -55,10 +55,10 @@ async function getBaseSha() {
55
55
  function getGitleaksCommand() {
56
56
  const localPath = node_path_1.default.join(process.cwd(), 'node_modules', '.bin', 'gitleaks-secret-scanner');
57
57
  if (node_fs_1.default.existsSync(localPath)) {
58
- utils_1.logger.info('[getGitleaksCommand]: 📦 Using local gitleaks-secret-scanner');
58
+ utils_1.logger.info('[getGitleaksCommand]: Using local gitleaks-secret-scanner');
59
59
  return localPath;
60
60
  }
61
- utils_1.logger.info('[getGitleaksCommand]: 📥 Using npx gitleaks-secret-scanner');
61
+ utils_1.logger.info('[getGitleaksCommand]: Using npx gitleaks-secret-scanner');
62
62
  return 'npx';
63
63
  }
64
64
  /**
@@ -45,7 +45,7 @@ function createTempCspellConfig() {
45
45
  const customWordsCount = (userConfig.words || []).length;
46
46
  const customIgnoresCount = (userConfig.ignorePaths || []).length;
47
47
  if (customWordsCount > 0) {
48
- utils_1.logger.info(`[createTempCspellConfig]: 📖 Added ${customWordsCount} custom words from pr-checkmate.json`);
48
+ utils_1.logger.info(`[createTempCspellConfig]: Added ${customWordsCount} custom words from pr-checkmate.json`);
49
49
  }
50
50
  if (customIgnoresCount > 0) {
51
51
  utils_1.logger.info(`[createTempCspellConfig]: 🚫 Added ${customIgnoresCount}
@@ -65,7 +65,7 @@ function buildSpellcheckPatterns(projectRoot, sourcePath) {
65
65
  return [base, ...excludes];
66
66
  }
67
67
  async function runSpellcheck() {
68
- utils_1.logger.info('[runSpellcheck]: 🔤 Running Spellcheck...');
68
+ utils_1.logger.info('[runSpellcheck]: Running Spellcheck...');
69
69
  const projectRoot = process.cwd();
70
70
  const sourcePath = (0, config_1.getSourcePath)();
71
71
  const changedFiles = await (0, delta_1.getChangedFiles)([
@@ -86,7 +86,7 @@ async function runSpellcheck() {
86
86
  args = ['cspell', ...patterns, '--no-progress'];
87
87
  }
88
88
  else {
89
- utils_1.logger.info(`[runSpellcheck]: 📦 Using pr-checkmate cspell config (checking: ${sourcePath})`);
89
+ utils_1.logger.info(`[runSpellcheck]: Using pr-checkmate cspell config (checking: ${sourcePath})`);
90
90
  // Create temporary config with merged settings
91
91
  tempConfigPath = createTempCspellConfig();
92
92
  args = ['cspell', ...patterns, '--config', tempConfigPath, '--no-progress'];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pr-checkmate",
3
- "version": "1.20.7",
3
+ "version": "1.21.1",
4
4
  "description": "Automated PR quality checks: linting, formatting, dependency analysis, and spellcheck",
5
5
  "keywords": [
6
6
  "github-actions",
@@ -26,6 +26,7 @@
26
26
  ".eslintignore",
27
27
  "cspell.json",
28
28
  "src/config/pr-checkmate-workflow.yml",
29
+ ".markdownlint.json",
29
30
  "LICENSE"
30
31
  ],
31
32
  "scripts": {
@@ -73,6 +74,7 @@
73
74
  "eslint-plugin-prettier": "^5.5.4",
74
75
  "execa": "^9.6.0",
75
76
  "gitleaks-secret-scanner-fixed": "^2.1.3",
77
+ "markdownlint-cli": "^0.45.0",
76
78
  "inquirer": "^13.0.1",
77
79
  "prettier": ">=3.0.0",
78
80
  "ts-node": "^10.9.1",
@@ -242,3 +242,24 @@ jobs:
242
242
  - name: Check Outdated Dependencies
243
243
  run: npx pr-checkmate outdated
244
244
  continue-on-error: true
245
+
246
+ markdown-lint:
247
+ name: Markdown Lint
248
+ runs-on: ubuntu-latest
249
+ steps:
250
+ - name: Checkout code
251
+ uses: actions/checkout@v4
252
+ with:
253
+ fetch-depth: 0
254
+
255
+ - name: Setup Node.js
256
+ uses: actions/setup-node@v4
257
+ with:
258
+ node-version: 20
259
+
260
+ - name: Install dependencies
261
+ run: npm ci
262
+
263
+ - name: Run Markdown Lint
264
+ run: npx pr-checkmate markdown
265
+ continue-on-error: true