commit-cop 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -7
- package/package.json +4 -3
- package/src/brand.ts +3 -0
- package/src/checks/binaryFileCheck.ts +3 -3
- package/src/checks/consoleLogCheck.ts +3 -2
- package/src/checks/debuggerCheck.ts +3 -2
- package/src/checks/envFileCheck.ts +3 -2
- package/src/checks/focusedTestCheck.ts +2 -2
- package/src/checks/generatedFolderCheck.ts +2 -2
- package/src/checks/junkFileCheck.ts +3 -2
- package/src/checks/largeFileCheck.ts +2 -2
- package/src/checks/localHostCheck.ts +2 -2
- package/src/checks/lockfileDriftCheck.ts +6 -4
- package/src/checks/mergeConflictCheck.ts +3 -2
- package/src/checks/secretCheck.ts +19 -19
- package/src/checks/sensitiveFilenameCheck.ts +3 -2
- package/src/fix/debugCode.ts +74 -0
- package/src/fix/focusedTests.ts +26 -0
- package/src/fix/gitignore.ts +108 -0
- package/src/fix/junkFiles.ts +16 -0
- package/src/fix/lockfile.ts +23 -0
- package/src/fix/matchers.ts +141 -0
- package/src/fix/runFix.ts +96 -0
- package/src/fix/unstage.ts +25 -0
- package/src/fix/utils.ts +50 -0
- package/src/git.ts +2 -1
- package/src/hook.ts +98 -0
- package/src/index.ts +45 -27
- package/src/reporter.ts +70 -30
- package/src/runScan.ts +35 -0
- package/src/scanner.ts +4 -1
- package/src/types.ts +5 -0
- package/test.ts +5 -1
- package/testing.ts +3 -0
package/README.md
CHANGED
|
@@ -4,13 +4,39 @@ Commit Cop is a pre-commit safety checker that scans your **staged files** and w
|
|
|
4
4
|
|
|
5
5
|
Built for students, hackathons, and dev teams who want practical guardrails—not just formatting checks.
|
|
6
6
|
|
|
7
|
+
> **Commit Cop** (`commit-cop`) — catch bad commits before they hit GitHub.
|
|
8
|
+
|
|
7
9
|
## Install
|
|
8
10
|
|
|
9
11
|
```bash
|
|
10
12
|
npm install commit-cop
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
Run
|
|
15
|
+
### Run automatically on every commit (recommended)
|
|
16
|
+
|
|
17
|
+
From your Git repo:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx commit-cop install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This adds a pre-commit hook. After that, **Commit Cop runs whenever you `git commit`**. If it finds errors, the commit is blocked.
|
|
24
|
+
|
|
25
|
+
Treat warnings as errors in the hook:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx commit-cop install --strict
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Remove the hook:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx commit-cop uninstall
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Run manually
|
|
38
|
+
|
|
39
|
+
Scan staged changes once:
|
|
14
40
|
|
|
15
41
|
```bash
|
|
16
42
|
npx commit-cop
|
|
@@ -22,6 +48,39 @@ Treat warnings as errors:
|
|
|
22
48
|
npx commit-cop --strict
|
|
23
49
|
```
|
|
24
50
|
|
|
51
|
+
Allow `console.log` in staged code (skips the console.log check):
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx commit-cop --allow-console-log
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Auto-fix (work in progress)
|
|
58
|
+
|
|
59
|
+
`wip-fix` is a placeholder command name that applies common repo fixes. More fixes may be added over time.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npx commit-cop wip-fix
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
By default, `wip-fix` does **not** remove `console.log` lines. Pass `--fix-console-log` to include that fix. `debugger` lines are still removed from staged files.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npx commit-cop wip-fix --fix-console-log
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| Check | Fix |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| Generated folders / env files | Adds missing `.gitignore` entries (`.env`, `node_modules/`, `dist/`, `build/`, `.next/`, `coverage/`, junk patterns) |
|
|
74
|
+
| Focused tests | Replaces `test.only`, `it.only`, `describe.only` in test/spec files |
|
|
75
|
+
| Debug logs | Removes standalone `console.log(...)` from **staged** JS/TS files only with `--fix-console-log` |
|
|
76
|
+
| Debugger | Always removes standalone `debugger` lines from **staged** JS/TS files |
|
|
77
|
+
| Junk files | Deletes `.DS_Store`, `Thumbs.db`, swap/backup files found on disk |
|
|
78
|
+
| Env / sensitive / generated / junk / binary / large (staged) | Runs `git restore --staged` on matching staged files |
|
|
79
|
+
| Lockfile drift | Runs `npm install` when `package-lock.json` is missing or older than `package.json` |
|
|
80
|
+
| Merge conflicts, secrets, localhost | **Manual only** — reported at the end; not auto-fixed |
|
|
81
|
+
|
|
82
|
+
Review all changes before committing. `wip-fix` may run `npm install` and unstaging commands against your Git index.
|
|
83
|
+
|
|
25
84
|
## What it checks
|
|
26
85
|
|
|
27
86
|
| Check | Severity | What it catches |
|
|
@@ -32,7 +91,7 @@ npx commit-cop --strict
|
|
|
32
91
|
| Generated folders | Error | `node_modules/`, `dist/`, `build/`, `.next/`, `coverage/` (including nested paths) |
|
|
33
92
|
| Secrets | Error | API keys, GitHub/AWS/Stripe tokens, JWT secrets, database URLs |
|
|
34
93
|
| Focused tests | Error | `test.only`, `it.only`, `describe.only` left in test files |
|
|
35
|
-
| Debug logs | Warning | `console.log` in staged JS/TS code |
|
|
94
|
+
| Debug logs | Warning | `console.log` in staged JS/TS code (skip with `--allow-console-log`) |
|
|
36
95
|
| Debugger statements | Warning | `debugger` in staged JS/TS code |
|
|
37
96
|
| Localhost URLs | Warning | Hardcoded `localhost` or `127.0.0.1` URLs |
|
|
38
97
|
| Junk files | Warning | `.DS_Store`, `Thumbs.db`, swap/backup files |
|
|
@@ -47,6 +106,7 @@ Errors block the commit. Warnings are reported but do not block unless you use `
|
|
|
47
106
|
```bash
|
|
48
107
|
npm install
|
|
49
108
|
npm run commit-cop
|
|
109
|
+
npm run commit-cop -- wip-fix
|
|
50
110
|
```
|
|
51
111
|
|
|
52
112
|
Build and run the compiled CLI:
|
|
@@ -60,19 +120,21 @@ npm start
|
|
|
60
120
|
|
|
61
121
|
```
|
|
62
122
|
src/
|
|
63
|
-
index.ts CLI entry point
|
|
123
|
+
index.ts CLI entry point (scan + wip-fix subcommand)
|
|
64
124
|
git.ts Reads staged files from Git
|
|
65
125
|
scanner.ts Runs all checks
|
|
66
126
|
reporter.ts Prints the report
|
|
67
127
|
types.ts Shared types
|
|
68
128
|
checks/ One file per check
|
|
129
|
+
fix/ Auto-fix helpers used by wip-fix
|
|
69
130
|
```
|
|
70
131
|
|
|
71
132
|
Each check implements the same interface: receive staged files, return findings with a message and suggested fix.
|
|
72
133
|
|
|
73
134
|
## How it works
|
|
74
135
|
|
|
75
|
-
1.
|
|
76
|
-
2.
|
|
77
|
-
3.
|
|
78
|
-
4.
|
|
136
|
+
1. Install the hook with `npx commit-cop install` (or run manually)
|
|
137
|
+
2. Read staged file paths with `git diff --cached --name-only`
|
|
138
|
+
3. Run every check in `src/checks/`
|
|
139
|
+
4. Print a summary with file locations and fix suggestions
|
|
140
|
+
5. Exit with code `1` if errors are found (or warnings in strict mode), which blocks the commit
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "commit-cop",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Commit Cop — a pre-commit safety checker that scans staged files for risky commits.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"commit-cop": "./dist/index.js"
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"dev": "tsx src/index.ts",
|
|
11
11
|
"build": "tsc",
|
|
12
12
|
"start": "node dist/index.js",
|
|
13
|
-
"commit-cop": "tsx src/index.ts"
|
|
13
|
+
"commit-cop": "tsx src/index.ts",
|
|
14
|
+
"demo:setup": "node scripts/setup-demo.mjs"
|
|
14
15
|
},
|
|
15
16
|
"repository": {
|
|
16
17
|
"type": "git",
|
package/src/brand.ts
ADDED
|
@@ -52,9 +52,9 @@ export const binaryFileCheck: Check = {
|
|
|
52
52
|
severity: "warning",
|
|
53
53
|
checkName: this.name,
|
|
54
54
|
file,
|
|
55
|
-
message:
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
message:
|
|
56
|
+
"Binary file detected — archives, executables, and media don't belong in source control.",
|
|
57
|
+
suggestion: "Remove it from the commit or store it with Git LFS.",
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
60
|
}
|
|
@@ -27,8 +27,9 @@ export const consoleLogCheck: Check = {
|
|
|
27
27
|
checkName: this.name,
|
|
28
28
|
file,
|
|
29
29
|
line: index + 1,
|
|
30
|
-
message:
|
|
31
|
-
|
|
30
|
+
message:
|
|
31
|
+
"Debug log left in code — easy to miss and clutters production output.",
|
|
32
|
+
suggestion: "Delete the console.log before committing.",
|
|
32
33
|
});
|
|
33
34
|
}
|
|
34
35
|
});
|
|
@@ -20,8 +20,9 @@ export const debuggerCheck: Check = {
|
|
|
20
20
|
checkName: this.name,
|
|
21
21
|
file,
|
|
22
22
|
line: index + 1,
|
|
23
|
-
message:
|
|
24
|
-
|
|
23
|
+
message:
|
|
24
|
+
"debugger statement left in code — pauses execution and breaks CI/headless runs.",
|
|
25
|
+
suggestion: "Delete the debugger statement before committing.",
|
|
25
26
|
});
|
|
26
27
|
}
|
|
27
28
|
});
|
|
@@ -14,8 +14,9 @@ export const envFileCheck: Check = {
|
|
|
14
14
|
severity: "error",
|
|
15
15
|
checkName: this.name,
|
|
16
16
|
file,
|
|
17
|
-
message:
|
|
18
|
-
|
|
17
|
+
message:
|
|
18
|
+
".env file detected — these often hold API keys, passwords, and tokens.",
|
|
19
|
+
suggestion: `Unstage it: git restore --staged ${file}`,
|
|
19
20
|
});
|
|
20
21
|
}
|
|
21
22
|
}
|
|
@@ -28,8 +28,8 @@ export const focusedTestCheck: Check = {
|
|
|
28
28
|
checkName: this.name,
|
|
29
29
|
file,
|
|
30
30
|
line: index + 1,
|
|
31
|
-
message: `${pattern}
|
|
32
|
-
suggestion: `
|
|
31
|
+
message: `${pattern} detected — only that test will run, hiding failures in the rest of the suite.`,
|
|
32
|
+
suggestion: `Change ${pattern} back to ${pattern.replace(".only", "")}.`,
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -34,8 +34,8 @@ export const generatedFolderCheck: Check = {
|
|
|
34
34
|
severity: "error",
|
|
35
35
|
checkName: this.name,
|
|
36
36
|
file,
|
|
37
|
-
message:
|
|
38
|
-
suggestion: `Add ${matchedFolder} to .gitignore
|
|
37
|
+
message: `Generated folder (${matchedFolder}) — auto-built files bloat the repo and cause merge pain.`,
|
|
38
|
+
suggestion: `Add ${matchedFolder} to .gitignore, then: git restore --staged ${file}`,
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -28,8 +28,9 @@ export const junkFileCheck: Check = {
|
|
|
28
28
|
severity: "warning",
|
|
29
29
|
checkName: this.name,
|
|
30
30
|
file,
|
|
31
|
-
message:
|
|
32
|
-
|
|
31
|
+
message:
|
|
32
|
+
"OS or editor junk file — adds noise and has no place in the repo.",
|
|
33
|
+
suggestion: `Unstage it: git restore --staged ${file}`,
|
|
33
34
|
});
|
|
34
35
|
}
|
|
35
36
|
}
|
|
@@ -20,8 +20,8 @@ export const largeFileCheck: Check = {
|
|
|
20
20
|
severity: "warning",
|
|
21
21
|
checkName: this.name,
|
|
22
22
|
file,
|
|
23
|
-
message: `Large file
|
|
24
|
-
suggestion: "
|
|
23
|
+
message: `Large file (${sizeMb.toFixed(2)} MB) — slows clones and may hit GitHub's size limits.`,
|
|
24
|
+
suggestion: "Remove it from Git or use Git LFS for big assets.",
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -27,8 +27,8 @@ export const localHostCheck: Check = {
|
|
|
27
27
|
checkName: this.name,
|
|
28
28
|
file,
|
|
29
29
|
line: index + 1,
|
|
30
|
-
message: `Hardcoded local URL
|
|
31
|
-
suggestion: "
|
|
30
|
+
message: `Hardcoded local URL (${pattern}) — won't work in production or for teammates.`,
|
|
31
|
+
suggestion: "Move the URL to an environment variable (e.g. process.env.API_URL).",
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -18,8 +18,9 @@ export const lockfileDriftCheck: Check = {
|
|
|
18
18
|
severity: "warning",
|
|
19
19
|
checkName: this.name,
|
|
20
20
|
file: "package.json",
|
|
21
|
-
message:
|
|
22
|
-
|
|
21
|
+
message:
|
|
22
|
+
"package.json changed without its lockfile — teammates may get different dependency versions.",
|
|
23
|
+
suggestion: "Run npm install, then stage package-lock.json.",
|
|
23
24
|
});
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -28,8 +29,9 @@ export const lockfileDriftCheck: Check = {
|
|
|
28
29
|
severity: "warning",
|
|
29
30
|
checkName: this.name,
|
|
30
31
|
file: "package-lock.json",
|
|
31
|
-
message:
|
|
32
|
-
|
|
32
|
+
message:
|
|
33
|
+
"Lockfile changed without package.json — the lockfile may not match your declared dependencies.",
|
|
34
|
+
suggestion: "Stage package.json too, or unstage package-lock.json.",
|
|
33
35
|
});
|
|
34
36
|
}
|
|
35
37
|
|
|
@@ -25,9 +25,10 @@ export const mergeConflictCheck: Check = {
|
|
|
25
25
|
checkName: this.name,
|
|
26
26
|
file,
|
|
27
27
|
line: index + 1,
|
|
28
|
-
message:
|
|
28
|
+
message:
|
|
29
|
+
"Unresolved merge conflict — this file won't run correctly until fixed.",
|
|
29
30
|
suggestion:
|
|
30
|
-
"Resolve the conflict, remove the markers,
|
|
31
|
+
"Resolve the conflict, remove the <<<<<<< / ======= / >>>>>>> markers, then restage.",
|
|
31
32
|
});
|
|
32
33
|
return;
|
|
33
34
|
}
|
|
@@ -6,21 +6,21 @@ import {
|
|
|
6
6
|
shouldSkipContentScan,
|
|
7
7
|
} from "./utils.js";
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
/OPENAI_API_KEY\s*=/i,
|
|
11
|
-
/DATABASE_URL\s*=/i,
|
|
12
|
-
/JWT_SECRET\s*=/i,
|
|
13
|
-
/AUTH_SECRET\s*=/i,
|
|
14
|
-
/PRIVATE_KEY\s*=/i,
|
|
15
|
-
/SECRET_KEY\s*=/i,
|
|
16
|
-
/sk-[A-Za-z0-9_-]{20,}/,
|
|
17
|
-
/ghp_[A-Za-z0-9]{36,}/,
|
|
18
|
-
/github_pat_[A-Za-z0-9_]+/,
|
|
19
|
-
/AKIA[0-9A-Z]{16}/,
|
|
20
|
-
/sk_live_[A-Za-z0-9]+/,
|
|
21
|
-
/AIza[0-9A-Za-z_-]{35}/,
|
|
22
|
-
/hooks\.slack\.com\/services\//,
|
|
23
|
-
/password\s*=\s*['"][^'"]{8,}['"]/i,
|
|
9
|
+
const secretRules: { pattern: RegExp; label: string }[] = [
|
|
10
|
+
{ pattern: /OPENAI_API_KEY\s*=/i, label: "OpenAI API key" },
|
|
11
|
+
{ pattern: /DATABASE_URL\s*=/i, label: "database connection string" },
|
|
12
|
+
{ pattern: /JWT_SECRET\s*=/i, label: "JWT secret" },
|
|
13
|
+
{ pattern: /AUTH_SECRET\s*=/i, label: "auth secret" },
|
|
14
|
+
{ pattern: /PRIVATE_KEY\s*=/i, label: "private key" },
|
|
15
|
+
{ pattern: /SECRET_KEY\s*=/i, label: "secret key" },
|
|
16
|
+
{ pattern: /sk-[A-Za-z0-9_-]{20,}/, label: "API key" },
|
|
17
|
+
{ pattern: /ghp_[A-Za-z0-9]{36,}/, label: "GitHub personal access token" },
|
|
18
|
+
{ pattern: /github_pat_[A-Za-z0-9_]+/, label: "GitHub personal access token" },
|
|
19
|
+
{ pattern: /AKIA[0-9A-Z]{16}/, label: "AWS access key" },
|
|
20
|
+
{ pattern: /sk_live_[A-Za-z0-9]+/, label: "Stripe live secret key" },
|
|
21
|
+
{ pattern: /AIza[0-9A-Za-z_-]{35}/, label: "Google API key" },
|
|
22
|
+
{ pattern: /hooks\.slack\.com\/services\//, label: "Slack webhook URL" },
|
|
23
|
+
{ pattern: /password\s*=\s*['"][^'"]{8,}['"]/i, label: "hardcoded password" },
|
|
24
24
|
];
|
|
25
25
|
|
|
26
26
|
export const secretCheck: Check = {
|
|
@@ -38,16 +38,16 @@ export const secretCheck: Check = {
|
|
|
38
38
|
lines.forEach((line, index) => {
|
|
39
39
|
if (isCommentLine(line) || isPlaceholderValue(line)) return;
|
|
40
40
|
|
|
41
|
-
for (const
|
|
42
|
-
if (pattern.test(line)) {
|
|
41
|
+
for (const rule of secretRules) {
|
|
42
|
+
if (rule.pattern.test(line)) {
|
|
43
43
|
findings.push({
|
|
44
44
|
severity: "error",
|
|
45
45
|
checkName: this.name,
|
|
46
46
|
file,
|
|
47
47
|
line: index + 1,
|
|
48
|
-
message:
|
|
48
|
+
message: `Possible ${rule.label} — credentials pushed to GitHub can be scraped instantly.`,
|
|
49
49
|
suggestion:
|
|
50
|
-
"Remove the
|
|
50
|
+
"Remove it from the code, unstage the file, and rotate the credential if it was ever pushed.",
|
|
51
51
|
});
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
@@ -39,8 +39,9 @@ export const sensitiveFilenameCheck: Check = {
|
|
|
39
39
|
severity: "error",
|
|
40
40
|
checkName: this.name,
|
|
41
41
|
file,
|
|
42
|
-
message:
|
|
43
|
-
|
|
42
|
+
message:
|
|
43
|
+
"Credential or key file detected — private keys and auth config should not be in Git.",
|
|
44
|
+
suggestion: `Unstage it: git restore --staged ${file}`,
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
47
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { isCodeFile } from "../checks/utils.js";
|
|
4
|
+
import { getStagedFiles } from "../git.js";
|
|
5
|
+
import type { WipFixOptions } from "../types.js";
|
|
6
|
+
|
|
7
|
+
const CONSOLE_LOG_LINE = /^\s*console\.log\([^)]*\);?\s*$/;
|
|
8
|
+
const DEBUGGER_LINE = /^\s*debugger;?\s*$/;
|
|
9
|
+
|
|
10
|
+
function stripDebugLines(
|
|
11
|
+
content: string,
|
|
12
|
+
options: { removeConsoleLog: boolean; removeDebugger: boolean }
|
|
13
|
+
): { updated: string; removed: number } {
|
|
14
|
+
const lines = content.split("\n");
|
|
15
|
+
const kept: string[] = [];
|
|
16
|
+
let removed = 0;
|
|
17
|
+
|
|
18
|
+
for (const line of lines) {
|
|
19
|
+
const isConsoleLog = CONSOLE_LOG_LINE.test(line);
|
|
20
|
+
const isDebugger = DEBUGGER_LINE.test(line);
|
|
21
|
+
|
|
22
|
+
if (
|
|
23
|
+
(options.removeConsoleLog && isConsoleLog) ||
|
|
24
|
+
(options.removeDebugger && isDebugger)
|
|
25
|
+
) {
|
|
26
|
+
removed += 1;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
kept.push(line);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { updated: kept.join("\n"), removed };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function fixDebugCode(
|
|
36
|
+
cwd = process.cwd(),
|
|
37
|
+
options: WipFixOptions = {}
|
|
38
|
+
): string[] {
|
|
39
|
+
const removeConsoleLog = Boolean(options.fixConsoleLog);
|
|
40
|
+
const removeDebugger = true;
|
|
41
|
+
|
|
42
|
+
if (!removeConsoleLog && !removeDebugger) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let stagedFiles: string[];
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
stagedFiles = getStagedFiles();
|
|
50
|
+
} catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const fixed: string[] = [];
|
|
55
|
+
|
|
56
|
+
for (const file of stagedFiles) {
|
|
57
|
+
if (!isCodeFile(file)) continue;
|
|
58
|
+
|
|
59
|
+
const absolutePath = path.resolve(cwd, file);
|
|
60
|
+
if (!fs.existsSync(absolutePath)) continue;
|
|
61
|
+
|
|
62
|
+
const content = fs.readFileSync(absolutePath, "utf-8");
|
|
63
|
+
const { updated, removed } = stripDebugLines(content, {
|
|
64
|
+
removeConsoleLog,
|
|
65
|
+
removeDebugger,
|
|
66
|
+
});
|
|
67
|
+
if (removed === 0) continue;
|
|
68
|
+
|
|
69
|
+
fs.writeFileSync(absolutePath, updated, "utf-8");
|
|
70
|
+
fixed.push(file);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return fixed;
|
|
74
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { walkCodeFiles } from "./utils.js";
|
|
4
|
+
|
|
5
|
+
const FOCUSED_TEST_PATTERN = /\b(test|it|describe)\.only\b/g;
|
|
6
|
+
|
|
7
|
+
function isTestFile(filePath: string): boolean {
|
|
8
|
+
return filePath.includes("test") || filePath.includes("spec");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function fixFocusedTests(cwd = process.cwd()): string[] {
|
|
12
|
+
const files = walkCodeFiles(cwd, isTestFile);
|
|
13
|
+
const fixed: string[] = [];
|
|
14
|
+
|
|
15
|
+
for (const absolutePath of files) {
|
|
16
|
+
const content = fs.readFileSync(absolutePath, "utf-8");
|
|
17
|
+
if (!FOCUSED_TEST_PATTERN.test(content)) continue;
|
|
18
|
+
|
|
19
|
+
FOCUSED_TEST_PATTERN.lastIndex = 0;
|
|
20
|
+
const updated = content.replace(FOCUSED_TEST_PATTERN, "$1");
|
|
21
|
+
fs.writeFileSync(absolutePath, updated, "utf-8");
|
|
22
|
+
fixed.push(path.relative(cwd, absolutePath));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return fixed;
|
|
26
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const GITIGNORE_PATH = ".gitignore";
|
|
5
|
+
|
|
6
|
+
type GitignoreEntry = {
|
|
7
|
+
line: string;
|
|
8
|
+
isPresent: (lines: string[]) => boolean;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const entries: GitignoreEntry[] = [
|
|
12
|
+
{
|
|
13
|
+
line: ".env",
|
|
14
|
+
isPresent: (lines) =>
|
|
15
|
+
lines.some((line) => line === ".env" || line.startsWith(".env")),
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
line: "node_modules/",
|
|
19
|
+
isPresent: (lines) =>
|
|
20
|
+
lines.some(
|
|
21
|
+
(line) => line === "node_modules" || line === "node_modules/"
|
|
22
|
+
),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
line: "dist/",
|
|
26
|
+
isPresent: (lines) =>
|
|
27
|
+
lines.some((line) => line === "dist" || line === "dist/"),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
line: "build/",
|
|
31
|
+
isPresent: (lines) =>
|
|
32
|
+
lines.some((line) => line === "build" || line === "build/"),
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
line: ".next/",
|
|
36
|
+
isPresent: (lines) =>
|
|
37
|
+
lines.some((line) => line === ".next" || line === ".next/"),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
line: "coverage/",
|
|
41
|
+
isPresent: (lines) =>
|
|
42
|
+
lines.some((line) => line === "coverage" || line === "coverage/"),
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
line: ".DS_Store",
|
|
46
|
+
isPresent: (lines) =>
|
|
47
|
+
lines.some((line) => line.toLowerCase() === ".ds_store"),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
line: "Thumbs.db",
|
|
51
|
+
isPresent: (lines) =>
|
|
52
|
+
lines.some((line) => line.toLowerCase() === "thumbs.db"),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
line: "desktop.ini",
|
|
56
|
+
isPresent: (lines) =>
|
|
57
|
+
lines.some((line) => line.toLowerCase() === "desktop.ini"),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
line: "*.swp",
|
|
61
|
+
isPresent: (lines) => lines.includes("*.swp"),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
line: "*.bak",
|
|
65
|
+
isPresent: (lines) => lines.includes("*.bak"),
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
line: "*.tmp",
|
|
69
|
+
isPresent: (lines) => lines.includes("*.tmp"),
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
function parseGitignoreLines(content: string): string[] {
|
|
74
|
+
return content
|
|
75
|
+
.split("\n")
|
|
76
|
+
.map((line) => line.trim())
|
|
77
|
+
.filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function fixGitignore(cwd = process.cwd()): string[] {
|
|
81
|
+
const filePath = path.join(cwd, GITIGNORE_PATH);
|
|
82
|
+
const applied: string[] = [];
|
|
83
|
+
|
|
84
|
+
const existingContent = fs.existsSync(filePath)
|
|
85
|
+
? fs.readFileSync(filePath, "utf-8")
|
|
86
|
+
: "";
|
|
87
|
+
const existingLines = parseGitignoreLines(existingContent);
|
|
88
|
+
|
|
89
|
+
const missing = entries.filter((entry) => !entry.isPresent(existingLines));
|
|
90
|
+
if (missing.length === 0) {
|
|
91
|
+
return applied;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const additions = missing.map((entry) => entry.line);
|
|
95
|
+
const needsLeadingNewline =
|
|
96
|
+
existingContent.length > 0 && !existingContent.endsWith("\n");
|
|
97
|
+
const block =
|
|
98
|
+
(needsLeadingNewline ? "\n" : "") +
|
|
99
|
+
(existingContent.length > 0 ? "\n" : "") +
|
|
100
|
+
"# Added by commit-cop wip-fix\n" +
|
|
101
|
+
additions.join("\n") +
|
|
102
|
+
"\n";
|
|
103
|
+
|
|
104
|
+
fs.writeFileSync(filePath, existingContent + block, "utf-8");
|
|
105
|
+
applied.push(...additions);
|
|
106
|
+
|
|
107
|
+
return applied;
|
|
108
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { isJunkFile } from "./matchers.js";
|
|
3
|
+
import { walkRepo } from "./utils.js";
|
|
4
|
+
|
|
5
|
+
export function fixJunkFiles(cwd = process.cwd()): string[] {
|
|
6
|
+
const removed: string[] = [];
|
|
7
|
+
|
|
8
|
+
walkRepo(cwd, (absolutePath, relativePath) => {
|
|
9
|
+
if (!isJunkFile(relativePath)) return;
|
|
10
|
+
|
|
11
|
+
fs.unlinkSync(absolutePath);
|
|
12
|
+
removed.push(relativePath);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
return removed;
|
|
16
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
export function fixLockfile(cwd = process.cwd()): boolean {
|
|
6
|
+
const packageJson = path.join(cwd, "package.json");
|
|
7
|
+
const packageLock = path.join(cwd, "package-lock.json");
|
|
8
|
+
|
|
9
|
+
if (!fs.existsSync(packageJson)) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const needsInstall =
|
|
14
|
+
!fs.existsSync(packageLock) ||
|
|
15
|
+
fs.statSync(packageJson).mtimeMs > fs.statSync(packageLock).mtimeMs;
|
|
16
|
+
|
|
17
|
+
if (!needsInstall) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
execSync("npm install", { cwd, stdio: "inherit" });
|
|
22
|
+
return true;
|
|
23
|
+
}
|