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.
Files changed (74) hide show
  1. package/README.md +98 -20
  2. package/dist/checks/binaryFileCheck.d.ts +3 -0
  3. package/dist/checks/binaryFileCheck.d.ts.map +1 -0
  4. package/dist/checks/binaryFileCheck.js +53 -0
  5. package/dist/checks/binaryFileCheck.js.map +1 -0
  6. package/dist/checks/debuggerCheck.d.ts +3 -0
  7. package/dist/checks/debuggerCheck.d.ts.map +1 -0
  8. package/dist/checks/debuggerCheck.js +28 -0
  9. package/dist/checks/debuggerCheck.js.map +1 -0
  10. package/dist/checks/generatedFolderCheck.d.ts.map +1 -1
  11. package/dist/checks/generatedFolderCheck.js +7 -2
  12. package/dist/checks/generatedFolderCheck.js.map +1 -1
  13. package/dist/checks/junkFileCheck.d.ts +3 -0
  14. package/dist/checks/junkFileCheck.d.ts.map +1 -0
  15. package/dist/checks/junkFileCheck.js +30 -0
  16. package/dist/checks/junkFileCheck.js.map +1 -0
  17. package/dist/checks/lockfileDriftCheck.d.ts +3 -0
  18. package/dist/checks/lockfileDriftCheck.d.ts.map +1 -0
  19. package/dist/checks/lockfileDriftCheck.js +32 -0
  20. package/dist/checks/lockfileDriftCheck.js.map +1 -0
  21. package/dist/checks/mergeConflictCheck.d.ts +3 -0
  22. package/dist/checks/mergeConflictCheck.d.ts.map +1 -0
  23. package/dist/checks/mergeConflictCheck.js +33 -0
  24. package/dist/checks/mergeConflictCheck.js.map +1 -0
  25. package/dist/checks/secretCheck.d.ts.map +1 -1
  26. package/dist/checks/secretCheck.js +15 -4
  27. package/dist/checks/secretCheck.js.map +1 -1
  28. package/dist/checks/sensitiveFilenameCheck.d.ts +3 -0
  29. package/dist/checks/sensitiveFilenameCheck.d.ts.map +1 -0
  30. package/dist/checks/sensitiveFilenameCheck.js +41 -0
  31. package/dist/checks/sensitiveFilenameCheck.js.map +1 -0
  32. package/dist/checks/utils.d.ts +9 -0
  33. package/dist/checks/utils.d.ts.map +1 -0
  34. package/dist/checks/utils.js +48 -0
  35. package/dist/checks/utils.js.map +1 -0
  36. package/dist/index.js +1 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/scanner.d.ts.map +1 -1
  39. package/dist/scanner.js +14 -2
  40. package/dist/scanner.js.map +1 -1
  41. package/package.json +6 -5
  42. package/src/brand.ts +3 -0
  43. package/src/checks/binaryFileCheck.ts +64 -0
  44. package/src/checks/consoleLogCheck.ts +3 -2
  45. package/src/checks/debuggerCheck.ts +33 -0
  46. package/src/checks/envFileCheck.ts +3 -2
  47. package/src/checks/focusedTestCheck.ts +2 -2
  48. package/src/checks/generatedFolderCheck.ts +13 -5
  49. package/src/checks/junkFileCheck.ts +40 -0
  50. package/src/checks/largeFileCheck.ts +2 -2
  51. package/src/checks/localHostCheck.ts +2 -2
  52. package/src/checks/lockfileDriftCheck.ts +40 -0
  53. package/src/checks/mergeConflictCheck.ts +41 -0
  54. package/src/checks/secretCheck.ts +33 -17
  55. package/src/checks/sensitiveFilenameCheck.ts +51 -0
  56. package/src/checks/utils.ts +62 -0
  57. package/src/fix/debugCode.ts +74 -0
  58. package/src/fix/focusedTests.ts +26 -0
  59. package/src/fix/gitignore.ts +108 -0
  60. package/src/fix/junkFiles.ts +16 -0
  61. package/src/fix/lockfile.ts +23 -0
  62. package/src/fix/matchers.ts +141 -0
  63. package/src/fix/runFix.ts +96 -0
  64. package/src/fix/unstage.ts +25 -0
  65. package/src/fix/utils.ts +50 -0
  66. package/src/git.ts +2 -1
  67. package/src/hook.ts +98 -0
  68. package/src/index.ts +45 -27
  69. package/src/reporter.ts +70 -30
  70. package/src/runScan.ts +35 -0
  71. package/src/scanner.ts +19 -4
  72. package/src/types.ts +5 -0
  73. package/test.ts +5 -1
  74. 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 { getStagedFiles } from "./git.js";
5
- import { runChecks } from "./scanner.js";
6
- import { printReport } from "./reporter.js";
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("commitclean")
12
- .description("Pre-commit safety checker for staged files")
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 stagedFiles = getStagedFiles();
16
-
17
- if (stagedFiles.length === 0) {
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
- if (hasErrors || (options.strict && hasWarnings)) {
35
- process.exit(1);
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
- process.exit(0);
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
- export function printReport(findings: Finding[], scannedCount: number) {
5
- console.log(chalk.bold("\nCommitClean Report\n"));
6
- console.log(`Scanned ${scannedCount} staged file(s).\n`);
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
- const errors = findings.filter((f) => f.severity === "error");
9
- const warnings = findings.filter((f) => f.severity === "warning");
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(chalk.green("✅ No issues found. Commit looks clean.\n"));
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
- console.log(chalk.red.bold("❌ Errors"));
22
- for (const finding of errors) {
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
- console.log(chalk.yellow.bold("\n⚠️ Warnings"));
29
- for (const finding of warnings) {
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 { consoleLogCheck } from "./checks/consoleLogCheck.ts";
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 { secretCheck } from "./checks/secretCheck.ts";
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 checks) {
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
@@ -12,6 +12,11 @@ export type Finding = {
12
12
  export type CheckContext = {
13
13
  stagedFiles: string[];
14
14
  strict: boolean;
15
+ allowConsoleLog?: boolean;
16
+ };
17
+
18
+ export type WipFixOptions = {
19
+ fixConsoleLog?: boolean;
15
20
  };
16
21
 
17
22
  export type Check = {
package/test.ts CHANGED
@@ -1,2 +1,6 @@
1
+ // Add test cases here, make sure to to
2
+ // git add when you make a change to see CLI in
3
+ // action
1
4
  console.log("Hello, world!");
2
- console.log("Debug log");
5
+ console.log("Debug log");
6
+ console.log("Debug log 2");
package/testing.ts ADDED
@@ -0,0 +1,3 @@
1
+ console.log("testingFinal");
2
+ console.log("testingFinal");
3
+ console.log("testingFinal");