commit-cop 1.0.0 → 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 +98 -20
- package/dist/checks/binaryFileCheck.d.ts +3 -0
- package/dist/checks/binaryFileCheck.d.ts.map +1 -0
- package/dist/checks/binaryFileCheck.js +53 -0
- package/dist/checks/binaryFileCheck.js.map +1 -0
- package/dist/checks/debuggerCheck.d.ts +3 -0
- package/dist/checks/debuggerCheck.d.ts.map +1 -0
- package/dist/checks/debuggerCheck.js +28 -0
- package/dist/checks/debuggerCheck.js.map +1 -0
- package/dist/checks/generatedFolderCheck.d.ts.map +1 -1
- package/dist/checks/generatedFolderCheck.js +7 -2
- package/dist/checks/generatedFolderCheck.js.map +1 -1
- package/dist/checks/junkFileCheck.d.ts +3 -0
- package/dist/checks/junkFileCheck.d.ts.map +1 -0
- package/dist/checks/junkFileCheck.js +30 -0
- package/dist/checks/junkFileCheck.js.map +1 -0
- package/dist/checks/lockfileDriftCheck.d.ts +3 -0
- package/dist/checks/lockfileDriftCheck.d.ts.map +1 -0
- package/dist/checks/lockfileDriftCheck.js +32 -0
- package/dist/checks/lockfileDriftCheck.js.map +1 -0
- package/dist/checks/mergeConflictCheck.d.ts +3 -0
- package/dist/checks/mergeConflictCheck.d.ts.map +1 -0
- package/dist/checks/mergeConflictCheck.js +33 -0
- package/dist/checks/mergeConflictCheck.js.map +1 -0
- package/dist/checks/secretCheck.d.ts.map +1 -1
- package/dist/checks/secretCheck.js +15 -4
- package/dist/checks/secretCheck.js.map +1 -1
- package/dist/checks/sensitiveFilenameCheck.d.ts +3 -0
- package/dist/checks/sensitiveFilenameCheck.d.ts.map +1 -0
- package/dist/checks/sensitiveFilenameCheck.js +41 -0
- package/dist/checks/sensitiveFilenameCheck.js.map +1 -0
- package/dist/checks/utils.d.ts +9 -0
- package/dist/checks/utils.d.ts.map +1 -0
- package/dist/checks/utils.js +48 -0
- package/dist/checks/utils.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +14 -2
- package/dist/scanner.js.map +1 -1
- package/package.json +6 -5
- package/src/brand.ts +3 -0
- package/src/checks/binaryFileCheck.ts +64 -0
- package/src/checks/consoleLogCheck.ts +3 -2
- package/src/checks/debuggerCheck.ts +33 -0
- package/src/checks/envFileCheck.ts +3 -2
- package/src/checks/focusedTestCheck.ts +2 -2
- package/src/checks/generatedFolderCheck.ts +13 -5
- package/src/checks/junkFileCheck.ts +40 -0
- package/src/checks/largeFileCheck.ts +2 -2
- package/src/checks/localHostCheck.ts +2 -2
- package/src/checks/lockfileDriftCheck.ts +40 -0
- package/src/checks/mergeConflictCheck.ts +41 -0
- package/src/checks/secretCheck.ts +33 -17
- package/src/checks/sensitiveFilenameCheck.ts +51 -0
- package/src/checks/utils.ts +62 -0
- 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 +19 -4
- package/src/types.ts +5 -0
- package/test.ts +5 -1
- package/testing.ts +3 -0
package/src/index.ts
CHANGED
|
@@ -1,41 +1,59 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { CLI_NAME, PRODUCT_NAME, TAGLINE } from "./brand.js";
|
|
3
4
|
import { Command } from "commander";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { runWipFix } from "./fix/runFix.js";
|
|
6
|
+
import { installHook, uninstallHook } from "./hook.js";
|
|
7
|
+
import { runScan } from "./runScan.js";
|
|
7
8
|
|
|
8
9
|
const program = new Command();
|
|
9
10
|
|
|
10
11
|
program
|
|
11
|
-
.name(
|
|
12
|
-
.description(
|
|
12
|
+
.name(CLI_NAME)
|
|
13
|
+
.description(`${PRODUCT_NAME} — ${TAGLINE}`);
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.command("scan", { isDefault: true })
|
|
17
|
+
.description("Scan staged files for risky commits")
|
|
13
18
|
.option("--strict", "Treat warnings as errors")
|
|
19
|
+
.option(
|
|
20
|
+
"--allow-console-log",
|
|
21
|
+
"Skip console.log warnings (for projects that keep console.log on purpose)"
|
|
22
|
+
)
|
|
14
23
|
.action(async (options) => {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
console.log("No staged files found.");
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const findings = await runChecks({
|
|
23
|
-
stagedFiles,
|
|
24
|
-
strict: Boolean(options.strict),
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
printReport(findings, stagedFiles.length);
|
|
28
|
-
|
|
29
|
-
const hasErrors = findings.some((finding) => finding.severity === "error");
|
|
30
|
-
const hasWarnings = findings.some(
|
|
31
|
-
(finding) => finding.severity === "warning"
|
|
24
|
+
const exitCode = await runScan(
|
|
25
|
+
Boolean(options.strict),
|
|
26
|
+
Boolean(options.allowConsoleLog)
|
|
32
27
|
);
|
|
28
|
+
process.exit(exitCode);
|
|
29
|
+
});
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
program
|
|
32
|
+
.command("wip-fix")
|
|
33
|
+
.description(
|
|
34
|
+
"Apply common repo fixes matching commit-cop checks (.gitignore, debug code, unstaging, etc.)"
|
|
35
|
+
)
|
|
36
|
+
.option(
|
|
37
|
+
"--fix-console-log",
|
|
38
|
+
"Also remove standalone console.log lines from staged files (debugger lines are always removed)"
|
|
39
|
+
)
|
|
40
|
+
.action((options) => {
|
|
41
|
+
runWipFix({ fixConsoleLog: Boolean(options.fixConsoleLog) });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
program
|
|
45
|
+
.command("install")
|
|
46
|
+
.description("Install a Git pre-commit hook that runs Commit Cop on every commit")
|
|
47
|
+
.option("--strict", "Treat warnings as errors in the hook")
|
|
48
|
+
.action((options) => {
|
|
49
|
+
installHook(Boolean(options.strict));
|
|
50
|
+
});
|
|
37
51
|
|
|
38
|
-
|
|
52
|
+
program
|
|
53
|
+
.command("uninstall")
|
|
54
|
+
.description("Remove the Commit Cop pre-commit hook")
|
|
55
|
+
.action(() => {
|
|
56
|
+
uninstallHook();
|
|
39
57
|
});
|
|
40
58
|
|
|
41
|
-
program.parse();
|
|
59
|
+
program.parse();
|
package/src/reporter.ts
CHANGED
|
@@ -1,48 +1,88 @@
|
|
|
1
|
+
import { PRODUCT_NAME } from "./brand.js";
|
|
1
2
|
import chalk from "chalk";
|
|
2
|
-
import type { Finding } from "./types.js";
|
|
3
|
+
import type { Finding, Severity } from "./types.js";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const WIDTH = 52;
|
|
6
|
+
|
|
7
|
+
function line(char = "─"): string {
|
|
8
|
+
return chalk.dim(char.repeat(WIDTH));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function printHeader(title: string): void {
|
|
12
|
+
console.log("");
|
|
13
|
+
console.log(line("═"));
|
|
14
|
+
console.log(chalk.bold(` ${title}`));
|
|
15
|
+
console.log(line("═"));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function printSection(title: string, color: (text: string) => string): void {
|
|
19
|
+
console.log("");
|
|
20
|
+
console.log(line());
|
|
21
|
+
console.log(color(chalk.bold(` ${title}`)));
|
|
22
|
+
console.log(line());
|
|
23
|
+
console.log("");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function printSummary(scannedCount: number, errors: number, warnings: number): void {
|
|
27
|
+
console.log("");
|
|
28
|
+
console.log(chalk.dim(` Scanned ${scannedCount} staged file(s)`));
|
|
29
|
+
console.log(
|
|
30
|
+
` ${chalk.red(`Errors: ${errors}`)}${chalk.dim(" │ ")}${chalk.yellow(`Warnings: ${warnings}`)}`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function printFinding(finding: Finding, index: number): void {
|
|
35
|
+
const location = finding.line
|
|
36
|
+
? `${finding.file}:${finding.line}`
|
|
37
|
+
: finding.file;
|
|
38
|
+
|
|
39
|
+
const checkLabel = chalk.dim(`[${finding.checkName}]`);
|
|
7
40
|
|
|
8
|
-
|
|
9
|
-
|
|
41
|
+
console.log(chalk.bold(` ${index}.`) + ` ${checkLabel}`);
|
|
42
|
+
console.log(` ${chalk.cyan(location)}`);
|
|
43
|
+
console.log(` ${finding.message}`);
|
|
44
|
+
|
|
45
|
+
if (finding.suggestion) {
|
|
46
|
+
console.log(chalk.gray(` → ${finding.suggestion}`));
|
|
47
|
+
}
|
|
10
48
|
|
|
11
|
-
console.log(chalk.red(`Errors: ${errors.length}`));
|
|
12
|
-
console.log(chalk.yellow(`Warnings: ${warnings.length}`));
|
|
13
49
|
console.log("");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function printFindings(findings: Finding[], severity: Severity): void {
|
|
53
|
+
const filtered = findings.filter((finding) => finding.severity === severity);
|
|
54
|
+
|
|
55
|
+
filtered.forEach((finding, index) => {
|
|
56
|
+
printFinding(finding, index + 1);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function printReport(findings: Finding[], scannedCount: number) {
|
|
61
|
+
const errors = findings.filter((finding) => finding.severity === "error");
|
|
62
|
+
const warnings = findings.filter((finding) => finding.severity === "warning");
|
|
63
|
+
|
|
64
|
+
printHeader(`${PRODUCT_NAME} Report`);
|
|
65
|
+
printSummary(scannedCount, errors.length, warnings.length);
|
|
14
66
|
|
|
15
67
|
if (findings.length === 0) {
|
|
16
|
-
console.log(
|
|
68
|
+
console.log("");
|
|
69
|
+
console.log(chalk.green(" ✅ All clear — no issues found in staged files."));
|
|
70
|
+
console.log("");
|
|
71
|
+
console.log(line("═"));
|
|
72
|
+
console.log("");
|
|
17
73
|
return;
|
|
18
74
|
}
|
|
19
75
|
|
|
20
76
|
if (errors.length > 0) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
printFinding(finding);
|
|
24
|
-
}
|
|
77
|
+
printSection(`ERRORS (${errors.length})`, chalk.red);
|
|
78
|
+
printFindings(findings, "error");
|
|
25
79
|
}
|
|
26
80
|
|
|
27
81
|
if (warnings.length > 0) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
printFinding(finding);
|
|
31
|
-
}
|
|
82
|
+
printSection(`WARNINGS (${warnings.length})`, chalk.yellow);
|
|
83
|
+
printFindings(findings, "warning");
|
|
32
84
|
}
|
|
33
85
|
|
|
86
|
+
console.log(line("═"));
|
|
34
87
|
console.log("");
|
|
35
88
|
}
|
|
36
|
-
|
|
37
|
-
function printFinding(finding: Finding) {
|
|
38
|
-
const location = finding.line
|
|
39
|
-
? `${finding.file}:${finding.line}`
|
|
40
|
-
: finding.file;
|
|
41
|
-
|
|
42
|
-
console.log(`- ${chalk.bold(location)}`);
|
|
43
|
-
console.log(` ${finding.message}`);
|
|
44
|
-
|
|
45
|
-
if (finding.suggestion) {
|
|
46
|
-
console.log(chalk.gray(` Fix: ${finding.suggestion}`));
|
|
47
|
-
}
|
|
48
|
-
}
|
package/src/runScan.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { PRODUCT_NAME } from "./brand.js";
|
|
2
|
+
import { getStagedFiles } from "./git.js";
|
|
3
|
+
import { runChecks } from "./scanner.js";
|
|
4
|
+
import { printReport } from "./reporter.js";
|
|
5
|
+
|
|
6
|
+
export async function runScan(
|
|
7
|
+
strict = false,
|
|
8
|
+
allowConsoleLog = false
|
|
9
|
+
): Promise<number> {
|
|
10
|
+
const stagedFiles = getStagedFiles();
|
|
11
|
+
|
|
12
|
+
if (stagedFiles.length === 0) {
|
|
13
|
+
console.log(`${PRODUCT_NAME}: No staged files found.`);
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const findings = await runChecks({
|
|
18
|
+
stagedFiles,
|
|
19
|
+
strict,
|
|
20
|
+
allowConsoleLog,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
printReport(findings, stagedFiles.length);
|
|
24
|
+
|
|
25
|
+
const hasErrors = findings.some((finding) => finding.severity === "error");
|
|
26
|
+
const hasWarnings = findings.some(
|
|
27
|
+
(finding) => finding.severity === "warning"
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
if (hasErrors || (strict && hasWarnings)) {
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
package/src/scanner.ts
CHANGED
|
@@ -1,29 +1,44 @@
|
|
|
1
1
|
import type { CheckContext, Finding } from "./types.js";
|
|
2
2
|
import { envFileCheck } from "./checks/envFileCheck.js";
|
|
3
3
|
import { generatedFolderCheck } from "./checks/generatedFolderCheck.ts";
|
|
4
|
-
import {
|
|
4
|
+
import { mergeConflictCheck } from "./checks/mergeConflictCheck.ts";
|
|
5
|
+
import { sensitiveFilenameCheck } from "./checks/sensitiveFilenameCheck.ts";
|
|
6
|
+
import { secretCheck } from "./checks/secretCheck.ts";
|
|
5
7
|
import { focusedTestCheck } from "./checks/focusedTestCheck.ts";
|
|
8
|
+
import { consoleLogCheck } from "./checks/consoleLogCheck.ts";
|
|
9
|
+
import { debuggerCheck } from "./checks/debuggerCheck.ts";
|
|
6
10
|
import { localHostCheck } from "./checks/localHostCheck.ts";
|
|
11
|
+
import { junkFileCheck } from "./checks/junkFileCheck.ts";
|
|
12
|
+
import { lockfileDriftCheck } from "./checks/lockfileDriftCheck.ts";
|
|
7
13
|
import { largeFileCheck } from "./checks/largeFileCheck.ts";
|
|
8
|
-
import {
|
|
14
|
+
import { binaryFileCheck } from "./checks/binaryFileCheck.ts";
|
|
9
15
|
|
|
10
16
|
const checks = [
|
|
17
|
+
mergeConflictCheck,
|
|
11
18
|
envFileCheck,
|
|
19
|
+
sensitiveFilenameCheck,
|
|
12
20
|
generatedFolderCheck,
|
|
13
21
|
secretCheck,
|
|
14
22
|
focusedTestCheck,
|
|
15
23
|
consoleLogCheck,
|
|
24
|
+
debuggerCheck,
|
|
16
25
|
localHostCheck,
|
|
26
|
+
junkFileCheck,
|
|
27
|
+
lockfileDriftCheck,
|
|
17
28
|
largeFileCheck,
|
|
29
|
+
binaryFileCheck,
|
|
18
30
|
];
|
|
19
31
|
|
|
20
32
|
export async function runChecks(context: CheckContext): Promise<Finding[]> {
|
|
21
33
|
const allFindings: Finding[] = [];
|
|
34
|
+
const activeChecks = context.allowConsoleLog
|
|
35
|
+
? checks.filter((check) => check !== consoleLogCheck)
|
|
36
|
+
: checks;
|
|
22
37
|
|
|
23
|
-
for (const check of
|
|
38
|
+
for (const check of activeChecks) {
|
|
24
39
|
const findings = await check.run(context);
|
|
25
40
|
allFindings.push(...findings);
|
|
26
41
|
}
|
|
27
42
|
|
|
28
43
|
return allFindings;
|
|
29
|
-
}
|
|
44
|
+
}
|
package/src/types.ts
CHANGED
package/test.ts
CHANGED
package/testing.ts
ADDED