commit-cop 1.0.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 (64) hide show
  1. package/README.md +62 -0
  2. package/dist/checks/consoleLogCheck.d.ts +3 -0
  3. package/dist/checks/consoleLogCheck.d.ts.map +1 -0
  4. package/dist/checks/consoleLogCheck.js +33 -0
  5. package/dist/checks/consoleLogCheck.js.map +1 -0
  6. package/dist/checks/envFileCheck.d.ts +3 -0
  7. package/dist/checks/envFileCheck.d.ts.map +1 -0
  8. package/dist/checks/envFileCheck.js +20 -0
  9. package/dist/checks/envFileCheck.js.map +1 -0
  10. package/dist/checks/focusedTestCheck.d.ts +3 -0
  11. package/dist/checks/focusedTestCheck.d.ts.map +1 -0
  12. package/dist/checks/focusedTestCheck.js +36 -0
  13. package/dist/checks/focusedTestCheck.js.map +1 -0
  14. package/dist/checks/generatedFolderCheck.d.ts +3 -0
  15. package/dist/checks/generatedFolderCheck.d.ts.map +1 -0
  16. package/dist/checks/generatedFolderCheck.js +28 -0
  17. package/dist/checks/generatedFolderCheck.js.map +1 -0
  18. package/dist/checks/largeFileCheck.d.ts +3 -0
  19. package/dist/checks/largeFileCheck.d.ts.map +1 -0
  20. package/dist/checks/largeFileCheck.js +25 -0
  21. package/dist/checks/largeFileCheck.js.map +1 -0
  22. package/dist/checks/localHostCheck.d.ts +3 -0
  23. package/dist/checks/localHostCheck.d.ts.map +1 -0
  24. package/dist/checks/localHostCheck.js +34 -0
  25. package/dist/checks/localHostCheck.js.map +1 -0
  26. package/dist/checks/secretCheck.d.ts +3 -0
  27. package/dist/checks/secretCheck.d.ts.map +1 -0
  28. package/dist/checks/secretCheck.js +38 -0
  29. package/dist/checks/secretCheck.js.map +1 -0
  30. package/dist/git.d.ts +2 -0
  31. package/dist/git.d.ts.map +1 -0
  32. package/dist/git.js +16 -0
  33. package/dist/git.js.map +1 -0
  34. package/dist/index.d.ts +3 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +30 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/reporter.d.ts +3 -0
  39. package/dist/reporter.d.ts.map +1 -0
  40. package/dist/reporter.js +38 -0
  41. package/dist/reporter.js.map +1 -0
  42. package/dist/scanner.d.ts +3 -0
  43. package/dist/scanner.d.ts.map +1 -0
  44. package/dist/scanner.js +25 -0
  45. package/dist/scanner.js.map +1 -0
  46. package/dist/types.d.ts +18 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +2 -0
  49. package/dist/types.js.map +1 -0
  50. package/package.json +41 -0
  51. package/src/checks/consoleLogCheck.ts +39 -0
  52. package/src/checks/envFileCheck.ts +25 -0
  53. package/src/checks/focusedTestCheck.ts +41 -0
  54. package/src/checks/generatedFolderCheck.ts +37 -0
  55. package/src/checks/largeFileCheck.ts +31 -0
  56. package/src/checks/localHostCheck.ts +40 -0
  57. package/src/checks/secretCheck.ts +44 -0
  58. package/src/git.ts +16 -0
  59. package/src/index.ts +41 -0
  60. package/src/reporter.ts +48 -0
  61. package/src/scanner.ts +29 -0
  62. package/src/types.ts +20 -0
  63. package/test.ts +2 -0
  64. package/tsconfig.json +44 -0
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # CommitClean
2
+
3
+ CommitClean is a pre-commit safety checker that scans your **staged files** and warns you about common mistakes before they get pushed to GitHub.
4
+
5
+ Built for students, hackathons, and dev teams who want practical guardrails—not just formatting checks.
6
+
7
+ ## What it checks
8
+
9
+ | Check | Severity | What it catches |
10
+ | --- | --- | --- |
11
+ | Environment files | Error | `.env`, `.env.local`, and other `.env.*` files |
12
+ | Generated folders | Error | `node_modules/`, `dist/`, `build/`, `.next/`, `coverage/` |
13
+ | Secrets | Error | API keys, JWT secrets, database URLs, and similar patterns |
14
+ | Focused tests | Error | `test.only`, `it.only`, `describe.only` left in test files |
15
+ | Debug logs | Warning | `console.log` in staged JS/TS code |
16
+ | Localhost URLs | Warning | Hardcoded `localhost` or `127.0.0.1` URLs |
17
+ | Large files | Warning | Staged files over 5 MB |
18
+
19
+ Errors block the commit. Warnings are reported but do not block unless you use `--strict`.
20
+
21
+ ## Usage
22
+
23
+ Run inside a Git repository with staged changes:
24
+
25
+ ```bash
26
+ npm install
27
+ npm run commitclean
28
+ ```
29
+
30
+ Treat warnings as errors:
31
+
32
+ ```bash
33
+ npm run commitclean -- --strict
34
+ ```
35
+
36
+ Build and run the compiled CLI:
37
+
38
+ ```bash
39
+ npm run build
40
+ npm start
41
+ ```
42
+
43
+ ## Project structure
44
+
45
+ ```
46
+ src/
47
+ index.ts CLI entry point
48
+ git.ts Reads staged files from Git
49
+ scanner.ts Runs all checks
50
+ reporter.ts Prints the report
51
+ types.ts Shared types
52
+ checks/ One file per check
53
+ ```
54
+
55
+ Each check implements the same interface: receive staged files, return findings with a message and suggested fix.
56
+
57
+ ## How it works
58
+
59
+ 1. Read staged file paths with `git diff --cached --name-only`
60
+ 2. Run every check in `src/checks/`
61
+ 3. Print a summary with file locations and fix suggestions
62
+ 4. Exit with code `1` if errors are found (or warnings in strict mode)
@@ -0,0 +1,3 @@
1
+ import type { Check } from "../types.js";
2
+ export declare const consoleLogCheck: Check;
3
+ //# sourceMappingURL=consoleLogCheck.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consoleLogCheck.d.ts","sourceRoot":"","sources":["../../src/checks/consoleLogCheck.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAW,MAAM,aAAa,CAAC;AAQlD,eAAO,MAAM,eAAe,EAAE,KA6B7B,CAAC"}
@@ -0,0 +1,33 @@
1
+ import fs from "node:fs";
2
+ const codeExtensions = [".js", ".jsx", ".ts", ".tsx"];
3
+ function isCodeFile(file) {
4
+ return codeExtensions.some((ext) => file.endsWith(ext));
5
+ }
6
+ export const consoleLogCheck = {
7
+ name: "console-log-check",
8
+ async run(context) {
9
+ const findings = [];
10
+ for (const file of context.stagedFiles) {
11
+ if (!isCodeFile(file))
12
+ continue;
13
+ if (!fs.existsSync(file))
14
+ continue;
15
+ const content = fs.readFileSync(file, "utf-8");
16
+ const lines = content.split("\n");
17
+ lines.forEach((line, index) => {
18
+ if (line.includes("console.log")) {
19
+ findings.push({
20
+ severity: "warning",
21
+ checkName: this.name,
22
+ file,
23
+ line: index + 1,
24
+ message: "console.log found in staged code.",
25
+ suggestion: "Remove debug logs before committing.",
26
+ });
27
+ }
28
+ });
29
+ }
30
+ return findings;
31
+ },
32
+ };
33
+ //# sourceMappingURL=consoleLogCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consoleLogCheck.js","sourceRoot":"","sources":["../../src/checks/consoleLogCheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAGzB,MAAM,cAAc,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AAEtD,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAU;IACpC,IAAI,EAAE,mBAAmB;IAEzB,KAAK,CAAC,GAAG,CAAC,OAAO;QACf,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YACvC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,SAAS;YAChC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,SAAS;YAEnC,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAElC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;gBAC5B,IAAI,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;oBACjC,QAAQ,CAAC,IAAI,CAAC;wBACZ,QAAQ,EAAE,SAAS;wBACnB,SAAS,EAAE,IAAI,CAAC,IAAI;wBACpB,IAAI;wBACJ,IAAI,EAAE,KAAK,GAAG,CAAC;wBACf,OAAO,EAAE,mCAAmC;wBAC5C,UAAU,EAAE,sCAAsC;qBACnD,CAAC,CAAC;gBACL,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Check } from "../types.js";
2
+ export declare const envFileCheck: Check;
3
+ //# sourceMappingURL=envFileCheck.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envFileCheck.d.ts","sourceRoot":"","sources":["../../src/checks/envFileCheck.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAW,MAAM,aAAa,CAAC;AAElD,eAAO,MAAM,YAAY,EAAE,KAsB1B,CAAC"}
@@ -0,0 +1,20 @@
1
+ export const envFileCheck = {
2
+ name: "env-file-check",
3
+ async run(context) {
4
+ const findings = [];
5
+ for (const file of context.stagedFiles) {
6
+ const fileName = file.split("/").pop() ?? "";
7
+ if (fileName === ".env" || fileName.startsWith(".env.")) {
8
+ findings.push({
9
+ severity: "error",
10
+ checkName: this.name,
11
+ file,
12
+ message: "Environment file is staged and may contain secrets.",
13
+ suggestion: `Run: git restore --staged ${file}`,
14
+ });
15
+ }
16
+ }
17
+ return findings;
18
+ },
19
+ };
20
+ //# sourceMappingURL=envFileCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envFileCheck.js","sourceRoot":"","sources":["../../src/checks/envFileCheck.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,YAAY,GAAU;IACjC,IAAI,EAAE,gBAAgB;IAEtB,KAAK,CAAC,GAAG,CAAC,OAAO;QACf,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;YAE7C,IAAI,QAAQ,KAAK,MAAM,IAAI,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;gBACxD,QAAQ,CAAC,IAAI,CAAC;oBACZ,QAAQ,EAAE,OAAO;oBACjB,SAAS,EAAE,IAAI,CAAC,IAAI;oBACpB,IAAI;oBACJ,OAAO,EAAE,qDAAqD;oBAC9D,UAAU,EAAE,6BAA6B,IAAI,EAAE;iBAChD,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Check } from "../types.js";
2
+ export declare const focusedTestCheck: Check;
3
+ //# sourceMappingURL=focusedTestCheck.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"focusedTestCheck.d.ts","sourceRoot":"","sources":["../../src/checks/focusedTestCheck.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAW,MAAM,aAAa,CAAC;AAQlD,eAAO,MAAM,gBAAgB,EAAE,KA+B9B,CAAC"}
@@ -0,0 +1,36 @@
1
+ import fs from "node:fs";
2
+ const testPatterns = [
3
+ "test.only",
4
+ "it.only",
5
+ "describe.only",
6
+ ];
7
+ export const focusedTestCheck = {
8
+ name: "focused-test-check",
9
+ async run(context) {
10
+ const findings = [];
11
+ for (const file of context.stagedFiles) {
12
+ if (!file.includes("test") && !file.includes("spec"))
13
+ continue;
14
+ if (!fs.existsSync(file))
15
+ continue;
16
+ const content = fs.readFileSync(file, "utf-8");
17
+ const lines = content.split("\n");
18
+ lines.forEach((line, index) => {
19
+ for (const pattern of testPatterns) {
20
+ if (line.includes(pattern)) {
21
+ findings.push({
22
+ severity: "error",
23
+ checkName: this.name,
24
+ file,
25
+ line: index + 1,
26
+ message: `${pattern} found. This may skip the rest of the test suite.`,
27
+ suggestion: `Replace ${pattern} with the normal test function.`,
28
+ });
29
+ }
30
+ }
31
+ });
32
+ }
33
+ return findings;
34
+ },
35
+ };
36
+ //# sourceMappingURL=focusedTestCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"focusedTestCheck.js","sourceRoot":"","sources":["../../src/checks/focusedTestCheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAGzB,MAAM,YAAY,GAAG;IACnB,WAAW;IACX,SAAS;IACT,eAAe;CAChB,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAU;IACrC,IAAI,EAAE,oBAAoB;IAE1B,KAAK,CAAC,GAAG,CAAC,OAAO;QACf,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YACvC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;gBAAE,SAAS;YAC/D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,SAAS;YAEnC,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAElC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;gBAC5B,KAAK,MAAM,OAAO,IAAI,YAAY,EAAE,CAAC;oBACnC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;wBAC3B,QAAQ,CAAC,IAAI,CAAC;4BACZ,QAAQ,EAAE,OAAO;4BACjB,SAAS,EAAE,IAAI,CAAC,IAAI;4BACpB,IAAI;4BACJ,IAAI,EAAE,KAAK,GAAG,CAAC;4BACf,OAAO,EAAE,GAAG,OAAO,mDAAmD;4BACtE,UAAU,EAAE,WAAW,OAAO,iCAAiC;yBAChE,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Check } from "../types.js";
2
+ export declare const generatedFolderCheck: Check;
3
+ //# sourceMappingURL=generatedFolderCheck.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generatedFolderCheck.d.ts","sourceRoot":"","sources":["../../src/checks/generatedFolderCheck.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAW,MAAM,aAAa,CAAC;AAUlD,eAAO,MAAM,oBAAoB,EAAE,KA0BlC,CAAC"}
@@ -0,0 +1,28 @@
1
+ const blockedFolders = [
2
+ "node_modules/",
3
+ "dist/",
4
+ "build/",
5
+ ".next/",
6
+ "coverage/",
7
+ ];
8
+ export const generatedFolderCheck = {
9
+ name: "generated-folder-check",
10
+ async run(context) {
11
+ const findings = [];
12
+ for (const file of context.stagedFiles) {
13
+ const normalized = file.replaceAll("\\", "/");
14
+ const matchedFolder = blockedFolders.find((folder) => normalized.startsWith(folder));
15
+ if (matchedFolder) {
16
+ findings.push({
17
+ severity: "error",
18
+ checkName: this.name,
19
+ file,
20
+ message: `${matchedFolder} is staged but is usually generated and should not be committed.`,
21
+ suggestion: `Add ${matchedFolder} to .gitignore and run: git restore --staged ${file}`,
22
+ });
23
+ }
24
+ }
25
+ return findings;
26
+ },
27
+ };
28
+ //# sourceMappingURL=generatedFolderCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generatedFolderCheck.js","sourceRoot":"","sources":["../../src/checks/generatedFolderCheck.ts"],"names":[],"mappings":"AAEA,MAAM,cAAc,GAAG;IACrB,eAAe;IACf,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,WAAW;CACZ,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAU;IACzC,IAAI,EAAE,wBAAwB;IAE9B,KAAK,CAAC,GAAG,CAAC,OAAO;QACf,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YACvC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;YAE9C,MAAM,aAAa,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CACnD,UAAU,CAAC,UAAU,CAAC,MAAM,CAAC,CAC9B,CAAC;YAEF,IAAI,aAAa,EAAE,CAAC;gBAClB,QAAQ,CAAC,IAAI,CAAC;oBACZ,QAAQ,EAAE,OAAO;oBACjB,SAAS,EAAE,IAAI,CAAC,IAAI;oBACpB,IAAI;oBACJ,OAAO,EAAE,GAAG,aAAa,kEAAkE;oBAC3F,UAAU,EAAE,OAAO,aAAa,gDAAgD,IAAI,EAAE;iBACvF,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Check } from "../types.js";
2
+ export declare const largeFileCheck: Check;
3
+ //# sourceMappingURL=largeFileCheck.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"largeFileCheck.d.ts","sourceRoot":"","sources":["../../src/checks/largeFileCheck.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAW,MAAM,aAAa,CAAC;AAIlD,eAAO,MAAM,cAAc,EAAE,KAyB5B,CAAC"}
@@ -0,0 +1,25 @@
1
+ import fs from "node:fs";
2
+ const MAX_SIZE_MB = 5;
3
+ export const largeFileCheck = {
4
+ name: "large-file-check",
5
+ async run(context) {
6
+ const findings = [];
7
+ for (const file of context.stagedFiles) {
8
+ if (!fs.existsSync(file))
9
+ continue;
10
+ const stats = fs.statSync(file);
11
+ const sizeMb = stats.size / 1024 / 1024;
12
+ if (sizeMb > MAX_SIZE_MB) {
13
+ findings.push({
14
+ severity: "warning",
15
+ checkName: this.name,
16
+ file,
17
+ message: `Large file staged: ${sizeMb.toFixed(2)} MB.`,
18
+ suggestion: "Consider removing this file from Git or using Git LFS.",
19
+ });
20
+ }
21
+ }
22
+ return findings;
23
+ },
24
+ };
25
+ //# sourceMappingURL=largeFileCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"largeFileCheck.js","sourceRoot":"","sources":["../../src/checks/largeFileCheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAGzB,MAAM,WAAW,GAAG,CAAC,CAAC;AAEtB,MAAM,CAAC,MAAM,cAAc,GAAU;IACnC,IAAI,EAAE,kBAAkB;IAExB,KAAK,CAAC,GAAG,CAAC,OAAO;QACf,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YACvC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,SAAS;YAEnC,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAChC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;YAExC,IAAI,MAAM,GAAG,WAAW,EAAE,CAAC;gBACzB,QAAQ,CAAC,IAAI,CAAC;oBACZ,QAAQ,EAAE,SAAS;oBACnB,SAAS,EAAE,IAAI,CAAC,IAAI;oBACpB,IAAI;oBACJ,OAAO,EAAE,sBAAsB,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM;oBACtD,UAAU,EAAE,wDAAwD;iBACrE,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Check } from "../types.ts";
2
+ export declare const localHostCheck: Check;
3
+ //# sourceMappingURL=localHostCheck.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localHostCheck.d.ts","sourceRoot":"","sources":["../../src/checks/localHostCheck.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAW,MAAM,aAAa,CAAC;AAQlD,eAAO,MAAM,cAAc,EAAE,KA8B5B,CAAC"}
@@ -0,0 +1,34 @@
1
+ import fs from "node:fs";
2
+ const patterns = [
3
+ "http://localhost",
4
+ "https://localhost",
5
+ "127.0.0.1",
6
+ ];
7
+ export const localHostCheck = {
8
+ name: "localhost-check",
9
+ async run(context) {
10
+ const findings = [];
11
+ for (const file of context.stagedFiles) {
12
+ if (!fs.existsSync(file))
13
+ continue;
14
+ const content = fs.readFileSync(file, "utf-8");
15
+ const lines = content.split("\n");
16
+ lines.forEach((line, index) => {
17
+ for (const pattern of patterns) {
18
+ if (line.includes(pattern)) {
19
+ findings.push({
20
+ severity: "warning",
21
+ checkName: this.name,
22
+ file,
23
+ line: index + 1,
24
+ message: `Hardcoded local URL found: ${pattern}`,
25
+ suggestion: "Use an environment variable instead of a hardcoded localhost URL.",
26
+ });
27
+ }
28
+ }
29
+ });
30
+ }
31
+ return findings;
32
+ },
33
+ };
34
+ //# sourceMappingURL=localHostCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localHostCheck.js","sourceRoot":"","sources":["../../src/checks/localHostCheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAGzB,MAAM,QAAQ,GAAG;IACf,kBAAkB;IAClB,mBAAmB;IACnB,WAAW;CACZ,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAU;IACnC,IAAI,EAAE,iBAAiB;IAEvB,KAAK,CAAC,GAAG,CAAC,OAAO;QACf,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YACvC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,SAAS;YAEnC,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAElC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;gBAC5B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;oBAC/B,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;wBAC3B,QAAQ,CAAC,IAAI,CAAC;4BACZ,QAAQ,EAAE,SAAS;4BACnB,SAAS,EAAE,IAAI,CAAC,IAAI;4BACpB,IAAI;4BACJ,IAAI,EAAE,KAAK,GAAG,CAAC;4BACf,OAAO,EAAE,8BAA8B,OAAO,EAAE;4BAChD,UAAU,EAAE,mEAAmE;yBAChF,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Check } from "../types.js";
2
+ export declare const secretCheck: Check;
3
+ //# sourceMappingURL=secretCheck.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secretCheck.d.ts","sourceRoot":"","sources":["../../src/checks/secretCheck.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAW,MAAM,aAAa,CAAC;AAYlD,eAAO,MAAM,WAAW,EAAE,KA8BzB,CAAC"}
@@ -0,0 +1,38 @@
1
+ import fs from "node:fs";
2
+ const secretPatterns = [
3
+ /OPENAI_API_KEY\s*=/i,
4
+ /DATABASE_URL\s*=/i,
5
+ /JWT_SECRET\s*=/i,
6
+ /AUTH_SECRET\s*=/i,
7
+ /PRIVATE_KEY\s*=/i,
8
+ /SECRET_KEY\s*=/i,
9
+ /sk-[A-Za-z0-9_-]{20,}/,
10
+ ];
11
+ export const secretCheck = {
12
+ name: "secret-check",
13
+ async run(context) {
14
+ const findings = [];
15
+ for (const file of context.stagedFiles) {
16
+ if (!fs.existsSync(file))
17
+ continue;
18
+ const content = fs.readFileSync(file, "utf-8");
19
+ const lines = content.split("\n");
20
+ lines.forEach((line, index) => {
21
+ for (const pattern of secretPatterns) {
22
+ if (pattern.test(line)) {
23
+ findings.push({
24
+ severity: "error",
25
+ checkName: this.name,
26
+ file,
27
+ line: index + 1,
28
+ message: "Possible secret found in staged file.",
29
+ suggestion: "Remove the secret and rotate it if it was already pushed.",
30
+ });
31
+ }
32
+ }
33
+ });
34
+ }
35
+ return findings;
36
+ },
37
+ };
38
+ //# sourceMappingURL=secretCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secretCheck.js","sourceRoot":"","sources":["../../src/checks/secretCheck.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AAGzB,MAAM,cAAc,GAAG;IACrB,qBAAqB;IACrB,mBAAmB;IACnB,iBAAiB;IACjB,kBAAkB;IAClB,kBAAkB;IAClB,iBAAiB;IACjB,uBAAuB;CACxB,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAU;IAChC,IAAI,EAAE,cAAc;IAEpB,KAAK,CAAC,GAAG,CAAC,OAAO;QACf,MAAM,QAAQ,GAAc,EAAE,CAAC;QAE/B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YACvC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,SAAS;YAEnC,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAElC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;gBAC5B,KAAK,MAAM,OAAO,IAAI,cAAc,EAAE,CAAC;oBACrC,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;wBACvB,QAAQ,CAAC,IAAI,CAAC;4BACZ,QAAQ,EAAE,OAAO;4BACjB,SAAS,EAAE,IAAI,CAAC,IAAI;4BACpB,IAAI;4BACJ,IAAI,EAAE,KAAK,GAAG,CAAC;4BACf,OAAO,EAAE,uCAAuC;4BAChD,UAAU,EAAE,2DAA2D;yBACxE,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC"}
package/dist/git.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function getStagedFiles(): string[];
2
+ //# sourceMappingURL=git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAEA,wBAAgB,cAAc,IAAI,MAAM,EAAE,CAazC"}
package/dist/git.js ADDED
@@ -0,0 +1,16 @@
1
+ import { execSync } from "node:child_process";
2
+ export function getStagedFiles() {
3
+ try {
4
+ const output = execSync("git diff --cached --name-only", {
5
+ encoding: "utf-8",
6
+ });
7
+ return output
8
+ .split("\n")
9
+ .map((file) => file.trim())
10
+ .filter(Boolean);
11
+ }
12
+ catch {
13
+ throw new Error("CommitClean must be run inside a Git repository.");
14
+ }
15
+ }
16
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,MAAM,UAAU,cAAc;IAC5B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,QAAQ,CAAC,+BAA+B,EAAE;YACvD,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC;QAEH,OAAO,MAAM;aACV,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;aAC1B,MAAM,CAAC,OAAO,CAAC,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { getStagedFiles } from "./git.js";
4
+ import { runChecks } from "./scanner.js";
5
+ import { printReport } from "./reporter.js";
6
+ const program = new Command();
7
+ program
8
+ .name("commitclean")
9
+ .description("Pre-commit safety checker for staged files")
10
+ .option("--strict", "Treat warnings as errors")
11
+ .action(async (options) => {
12
+ const stagedFiles = getStagedFiles();
13
+ if (stagedFiles.length === 0) {
14
+ console.log("No staged files found.");
15
+ return;
16
+ }
17
+ const findings = await runChecks({
18
+ stagedFiles,
19
+ strict: Boolean(options.strict),
20
+ });
21
+ printReport(findings, stagedFiles.length);
22
+ const hasErrors = findings.some((finding) => finding.severity === "error");
23
+ const hasWarnings = findings.some((finding) => finding.severity === "warning");
24
+ if (hasErrors || (options.strict && hasWarnings)) {
25
+ process.exit(1);
26
+ }
27
+ process.exit(0);
28
+ });
29
+ program.parse();
30
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,aAAa,CAAC;KACnB,WAAW,CAAC,4CAA4C,CAAC;KACzD,MAAM,CAAC,UAAU,EAAE,0BAA0B,CAAC;KAC9C,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,MAAM,WAAW,GAAG,cAAc,EAAE,CAAC;IAErC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;QACtC,OAAO;IACT,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC;QAC/B,WAAW;QACX,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC;KAChC,CAAC,CAAC;IAEH,WAAW,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAE1C,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;IAC3E,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI,CAC/B,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,CAC5C,CAAC;IAEF,IAAI,SAAS,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,WAAW,CAAC,EAAE,CAAC;QACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Finding } from "./types.js";
2
+ export declare function printReport(findings: Finding[], scannedCount: number): void;
3
+ //# sourceMappingURL=reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,wBAAgB,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,YAAY,EAAE,MAAM,QA+BpE"}
@@ -0,0 +1,38 @@
1
+ import chalk from "chalk";
2
+ export function printReport(findings, scannedCount) {
3
+ console.log(chalk.bold("\nCommitClean Report\n"));
4
+ console.log(`Scanned ${scannedCount} staged file(s).\n`);
5
+ const errors = findings.filter((f) => f.severity === "error");
6
+ const warnings = findings.filter((f) => f.severity === "warning");
7
+ console.log(chalk.red(`Errors: ${errors.length}`));
8
+ console.log(chalk.yellow(`Warnings: ${warnings.length}`));
9
+ console.log("");
10
+ if (findings.length === 0) {
11
+ console.log(chalk.green("✅ No issues found. Commit looks clean.\n"));
12
+ return;
13
+ }
14
+ if (errors.length > 0) {
15
+ console.log(chalk.red.bold("❌ Errors"));
16
+ for (const finding of errors) {
17
+ printFinding(finding);
18
+ }
19
+ }
20
+ if (warnings.length > 0) {
21
+ console.log(chalk.yellow.bold("\n⚠️ Warnings"));
22
+ for (const finding of warnings) {
23
+ printFinding(finding);
24
+ }
25
+ }
26
+ console.log("");
27
+ }
28
+ function printFinding(finding) {
29
+ const location = finding.line
30
+ ? `${finding.file}:${finding.line}`
31
+ : finding.file;
32
+ console.log(`- ${chalk.bold(location)}`);
33
+ console.log(` ${finding.message}`);
34
+ if (finding.suggestion) {
35
+ console.log(chalk.gray(` Fix: ${finding.suggestion}`));
36
+ }
37
+ }
38
+ //# sourceMappingURL=reporter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporter.js","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,MAAM,UAAU,WAAW,CAAC,QAAmB,EAAE,YAAoB;IACnE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC,CAAC;IAClD,OAAO,CAAC,GAAG,CAAC,WAAW,YAAY,oBAAoB,CAAC,CAAC;IAEzD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC;IAC9D,MAAM,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC;IAElE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IACnD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC1D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC,CAAC;QACrE,OAAO;IACT,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QACxC,KAAK,MAAM,OAAO,IAAI,MAAM,EAAE,CAAC;YAC7B,YAAY,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC;QAChD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,YAAY,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAClB,CAAC;AAED,SAAS,YAAY,CAAC,OAAgB;IACpC,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI;QAC3B,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,EAAE;QACnC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;IAEjB,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;IACzC,OAAO,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAEpC,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IAC1D,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { CheckContext, Finding } from "./types.js";
2
+ export declare function runChecks(context: CheckContext): Promise<Finding[]>;
3
+ //# sourceMappingURL=scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAmBxD,wBAAsB,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CASzE"}
@@ -0,0 +1,25 @@
1
+ import { envFileCheck } from "./checks/envFileCheck.js";
2
+ import { generatedFolderCheck } from "./checks/generatedFolderCheck.js";
3
+ import { consoleLogCheck } from "./checks/consoleLogCheck.js";
4
+ import { focusedTestCheck } from "./checks/focusedTestCheck.js";
5
+ import { localHostCheck } from "./checks/localHostCheck.js";
6
+ import { largeFileCheck } from "./checks/largeFileCheck.js";
7
+ import { secretCheck } from "./checks/secretCheck.js";
8
+ const checks = [
9
+ envFileCheck,
10
+ generatedFolderCheck,
11
+ secretCheck,
12
+ focusedTestCheck,
13
+ consoleLogCheck,
14
+ localHostCheck,
15
+ largeFileCheck,
16
+ ];
17
+ export async function runChecks(context) {
18
+ const allFindings = [];
19
+ for (const check of checks) {
20
+ const findings = await check.run(context);
21
+ allFindings.push(...findings);
22
+ }
23
+ return allFindings;
24
+ }
25
+ //# sourceMappingURL=scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kCAAkC,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,MAAM,MAAM,GAAG;IACb,YAAY;IACZ,oBAAoB;IACpB,WAAW;IACX,gBAAgB;IAChB,eAAe;IACf,cAAc;IACd,cAAc;CACf,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAqB;IACnD,MAAM,WAAW,GAAc,EAAE,CAAC;IAElC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC1C,WAAW,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC;IAChC,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -0,0 +1,18 @@
1
+ export type Severity = "error" | "warning" | "info";
2
+ export type Finding = {
3
+ severity: Severity;
4
+ checkName: string;
5
+ file: string;
6
+ line?: number;
7
+ message: string;
8
+ suggestion?: string;
9
+ };
10
+ export type CheckContext = {
11
+ stagedFiles: string[];
12
+ strict: boolean;
13
+ };
14
+ export type Check = {
15
+ name: string;
16
+ run: (context: CheckContext) => Promise<Finding[]>;
17
+ };
18
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAEpD,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,EAAE,QAAQ,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG;IACzB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,KAAK,GAAG;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;CACpD,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "commit-cop",
3
+ "version": "1.0.0",
4
+ "description": "A pre-commit safety checker that scans staged files for risky commits.",
5
+ "type": "module",
6
+ "bin": {
7
+ "commitclean": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "dev": "tsx src/index.ts",
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "commitclean": "tsx src/index.ts"
14
+ },
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/KhalidM23/UhackathonTheDinhs.git"
18
+ },
19
+ "keywords": [
20
+ "cli",
21
+ "git",
22
+ "pre-commit",
23
+ "developer-tools",
24
+ "commit-checker"
25
+ ],
26
+ "author": "",
27
+ "license": "ISC",
28
+ "bugs": {
29
+ "url": "https://github.com/KhalidM23/UhackathonTheDinhs/issues"
30
+ },
31
+ "homepage": "https://github.com/KhalidM23/UhackathonTheDinhs#readme",
32
+ "devDependencies": {
33
+ "@types/node": "^25.9.1",
34
+ "tsx": "^4.22.3",
35
+ "typescript": "^6.0.3"
36
+ },
37
+ "dependencies": {
38
+ "chalk": "^5.6.2",
39
+ "commander": "^14.0.3"
40
+ }
41
+ }
@@ -0,0 +1,39 @@
1
+ import fs from "node:fs";
2
+ import type { Check, Finding } from "../types.js";
3
+
4
+ const codeExtensions = [".js", ".jsx", ".ts", ".tsx"];
5
+
6
+ function isCodeFile(file: string): boolean {
7
+ return codeExtensions.some((ext) => file.endsWith(ext));
8
+ }
9
+
10
+ export const consoleLogCheck: Check = {
11
+ name: "console-log-check",
12
+
13
+ async run(context) {
14
+ const findings: Finding[] = [];
15
+
16
+ for (const file of context.stagedFiles) {
17
+ if (!isCodeFile(file)) continue;
18
+ if (!fs.existsSync(file)) continue;
19
+
20
+ const content = fs.readFileSync(file, "utf-8");
21
+ const lines = content.split("\n");
22
+
23
+ lines.forEach((line, index) => {
24
+ if (line.includes("console.log")) {
25
+ findings.push({
26
+ severity: "warning",
27
+ checkName: this.name,
28
+ file,
29
+ line: index + 1,
30
+ message: "console.log found in staged code.",
31
+ suggestion: "Remove debug logs before committing.",
32
+ });
33
+ }
34
+ });
35
+ }
36
+
37
+ return findings;
38
+ },
39
+ };
@@ -0,0 +1,25 @@
1
+ import type { Check, Finding } from "../types.js";
2
+
3
+ export const envFileCheck: Check = {
4
+ name: "env-file-check",
5
+
6
+ async run(context) {
7
+ const findings: Finding[] = [];
8
+
9
+ for (const file of context.stagedFiles) {
10
+ const fileName = file.split("/").pop() ?? "";
11
+
12
+ if (fileName === ".env" || fileName.startsWith(".env.")) {
13
+ findings.push({
14
+ severity: "error",
15
+ checkName: this.name,
16
+ file,
17
+ message: "Environment file is staged and may contain secrets.",
18
+ suggestion: `Run: git restore --staged ${file}`,
19
+ });
20
+ }
21
+ }
22
+
23
+ return findings;
24
+ },
25
+ };
@@ -0,0 +1,41 @@
1
+ import fs from "node:fs";
2
+ import type { Check, Finding } from "../types.js";
3
+
4
+ const testPatterns = [
5
+ "test.only",
6
+ "it.only",
7
+ "describe.only",
8
+ ];
9
+
10
+ export const focusedTestCheck: Check = {
11
+ name: "focused-test-check",
12
+
13
+ async run(context) {
14
+ const findings: Finding[] = [];
15
+
16
+ for (const file of context.stagedFiles) {
17
+ if (!file.includes("test") && !file.includes("spec")) continue;
18
+ if (!fs.existsSync(file)) continue;
19
+
20
+ const content = fs.readFileSync(file, "utf-8");
21
+ const lines = content.split("\n");
22
+
23
+ lines.forEach((line, index) => {
24
+ for (const pattern of testPatterns) {
25
+ if (line.includes(pattern)) {
26
+ findings.push({
27
+ severity: "error",
28
+ checkName: this.name,
29
+ file,
30
+ line: index + 1,
31
+ message: `${pattern} found. This may skip the rest of the test suite.`,
32
+ suggestion: `Replace ${pattern} with the normal test function.`,
33
+ });
34
+ }
35
+ }
36
+ });
37
+ }
38
+
39
+ return findings;
40
+ },
41
+ };
@@ -0,0 +1,37 @@
1
+ import type { Check, Finding } from "../types.js";
2
+
3
+ const blockedFolders = [
4
+ "node_modules/",
5
+ "dist/",
6
+ "build/",
7
+ ".next/",
8
+ "coverage/",
9
+ ];
10
+
11
+ export const generatedFolderCheck: Check = {
12
+ name: "generated-folder-check",
13
+
14
+ async run(context) {
15
+ const findings: Finding[] = [];
16
+
17
+ for (const file of context.stagedFiles) {
18
+ const normalized = file.replaceAll("\\", "/");
19
+
20
+ const matchedFolder = blockedFolders.find((folder) =>
21
+ normalized.startsWith(folder)
22
+ );
23
+
24
+ if (matchedFolder) {
25
+ findings.push({
26
+ severity: "error",
27
+ checkName: this.name,
28
+ file,
29
+ message: `${matchedFolder} is staged but is usually generated and should not be committed.`,
30
+ suggestion: `Add ${matchedFolder} to .gitignore and run: git restore --staged ${file}`,
31
+ });
32
+ }
33
+ }
34
+
35
+ return findings;
36
+ },
37
+ };
@@ -0,0 +1,31 @@
1
+ import fs from "node:fs";
2
+ import type { Check, Finding } from "../types.js";
3
+
4
+ const MAX_SIZE_MB = 5;
5
+
6
+ export const largeFileCheck: Check = {
7
+ name: "large-file-check",
8
+
9
+ async run(context) {
10
+ const findings: Finding[] = [];
11
+
12
+ for (const file of context.stagedFiles) {
13
+ if (!fs.existsSync(file)) continue;
14
+
15
+ const stats = fs.statSync(file);
16
+ const sizeMb = stats.size / 1024 / 1024;
17
+
18
+ if (sizeMb > MAX_SIZE_MB) {
19
+ findings.push({
20
+ severity: "warning",
21
+ checkName: this.name,
22
+ file,
23
+ message: `Large file staged: ${sizeMb.toFixed(2)} MB.`,
24
+ suggestion: "Consider removing this file from Git or using Git LFS.",
25
+ });
26
+ }
27
+ }
28
+
29
+ return findings;
30
+ },
31
+ };
@@ -0,0 +1,40 @@
1
+ import fs from "node:fs";
2
+ import type { Check, Finding } from "../types.ts";
3
+
4
+ const patterns = [
5
+ "http://localhost",
6
+ "https://localhost",
7
+ "127.0.0.1",
8
+ ];
9
+
10
+ export const localHostCheck: Check = {
11
+ name: "localhost-check",
12
+
13
+ async run(context) {
14
+ const findings: Finding[] = [];
15
+
16
+ for (const file of context.stagedFiles) {
17
+ if (!fs.existsSync(file)) continue;
18
+
19
+ const content = fs.readFileSync(file, "utf-8");
20
+ const lines = content.split("\n");
21
+
22
+ lines.forEach((line, index) => {
23
+ for (const pattern of patterns) {
24
+ if (line.includes(pattern)) {
25
+ findings.push({
26
+ severity: "warning",
27
+ checkName: this.name,
28
+ file,
29
+ line: index + 1,
30
+ message: `Hardcoded local URL found: ${pattern}`,
31
+ suggestion: "Use an environment variable instead of a hardcoded localhost URL.",
32
+ });
33
+ }
34
+ }
35
+ });
36
+ }
37
+
38
+ return findings;
39
+ },
40
+ };
@@ -0,0 +1,44 @@
1
+ import fs from "node:fs";
2
+ import type { Check, Finding } from "../types.js";
3
+
4
+ const secretPatterns = [
5
+ /OPENAI_API_KEY\s*=/i,
6
+ /DATABASE_URL\s*=/i,
7
+ /JWT_SECRET\s*=/i,
8
+ /AUTH_SECRET\s*=/i,
9
+ /PRIVATE_KEY\s*=/i,
10
+ /SECRET_KEY\s*=/i,
11
+ /sk-[A-Za-z0-9_-]{20,}/,
12
+ ];
13
+
14
+ export const secretCheck: Check = {
15
+ name: "secret-check",
16
+
17
+ async run(context) {
18
+ const findings: Finding[] = [];
19
+
20
+ for (const file of context.stagedFiles) {
21
+ if (!fs.existsSync(file)) continue;
22
+
23
+ const content = fs.readFileSync(file, "utf-8");
24
+ const lines = content.split("\n");
25
+
26
+ lines.forEach((line, index) => {
27
+ for (const pattern of secretPatterns) {
28
+ if (pattern.test(line)) {
29
+ findings.push({
30
+ severity: "error",
31
+ checkName: this.name,
32
+ file,
33
+ line: index + 1,
34
+ message: "Possible secret found in staged file.",
35
+ suggestion: "Remove the secret and rotate it if it was already pushed.",
36
+ });
37
+ }
38
+ }
39
+ });
40
+ }
41
+
42
+ return findings;
43
+ },
44
+ };
package/src/git.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { execSync } from "node:child_process";
2
+
3
+ export function getStagedFiles(): string[] {
4
+ try {
5
+ const output = execSync("git diff --cached --name-only", {
6
+ encoding: "utf-8",
7
+ });
8
+
9
+ return output
10
+ .split("\n")
11
+ .map((file) => file.trim())
12
+ .filter(Boolean);
13
+ } catch {
14
+ throw new Error("CommitClean must be run inside a Git repository.");
15
+ }
16
+ }
package/src/index.ts ADDED
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import { getStagedFiles } from "./git.js";
5
+ import { runChecks } from "./scanner.js";
6
+ import { printReport } from "./reporter.js";
7
+
8
+ const program = new Command();
9
+
10
+ program
11
+ .name("commitclean")
12
+ .description("Pre-commit safety checker for staged files")
13
+ .option("--strict", "Treat warnings as errors")
14
+ .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"
32
+ );
33
+
34
+ if (hasErrors || (options.strict && hasWarnings)) {
35
+ process.exit(1);
36
+ }
37
+
38
+ process.exit(0);
39
+ });
40
+
41
+ program.parse();
@@ -0,0 +1,48 @@
1
+ import chalk from "chalk";
2
+ import type { Finding } from "./types.js";
3
+
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`);
7
+
8
+ const errors = findings.filter((f) => f.severity === "error");
9
+ const warnings = findings.filter((f) => f.severity === "warning");
10
+
11
+ console.log(chalk.red(`Errors: ${errors.length}`));
12
+ console.log(chalk.yellow(`Warnings: ${warnings.length}`));
13
+ console.log("");
14
+
15
+ if (findings.length === 0) {
16
+ console.log(chalk.green("✅ No issues found. Commit looks clean.\n"));
17
+ return;
18
+ }
19
+
20
+ if (errors.length > 0) {
21
+ console.log(chalk.red.bold("❌ Errors"));
22
+ for (const finding of errors) {
23
+ printFinding(finding);
24
+ }
25
+ }
26
+
27
+ if (warnings.length > 0) {
28
+ console.log(chalk.yellow.bold("\n⚠️ Warnings"));
29
+ for (const finding of warnings) {
30
+ printFinding(finding);
31
+ }
32
+ }
33
+
34
+ console.log("");
35
+ }
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/scanner.ts ADDED
@@ -0,0 +1,29 @@
1
+ import type { CheckContext, Finding } from "./types.js";
2
+ import { envFileCheck } from "./checks/envFileCheck.js";
3
+ import { generatedFolderCheck } from "./checks/generatedFolderCheck.ts";
4
+ import { consoleLogCheck } from "./checks/consoleLogCheck.ts";
5
+ import { focusedTestCheck } from "./checks/focusedTestCheck.ts";
6
+ import { localHostCheck } from "./checks/localHostCheck.ts";
7
+ import { largeFileCheck } from "./checks/largeFileCheck.ts";
8
+ import { secretCheck } from "./checks/secretCheck.ts";
9
+
10
+ const checks = [
11
+ envFileCheck,
12
+ generatedFolderCheck,
13
+ secretCheck,
14
+ focusedTestCheck,
15
+ consoleLogCheck,
16
+ localHostCheck,
17
+ largeFileCheck,
18
+ ];
19
+
20
+ export async function runChecks(context: CheckContext): Promise<Finding[]> {
21
+ const allFindings: Finding[] = [];
22
+
23
+ for (const check of checks) {
24
+ const findings = await check.run(context);
25
+ allFindings.push(...findings);
26
+ }
27
+
28
+ return allFindings;
29
+ }
package/src/types.ts ADDED
@@ -0,0 +1,20 @@
1
+ export type Severity = "error" | "warning" | "info";
2
+
3
+ export type Finding = {
4
+ severity: Severity;
5
+ checkName: string;
6
+ file: string;
7
+ line?: number;
8
+ message: string;
9
+ suggestion?: string;
10
+ };
11
+
12
+ export type CheckContext = {
13
+ stagedFiles: string[];
14
+ strict: boolean;
15
+ };
16
+
17
+ export type Check = {
18
+ name: string;
19
+ run: (context: CheckContext) => Promise<Finding[]>;
20
+ };
package/test.ts ADDED
@@ -0,0 +1,2 @@
1
+ console.log("Hello, world!");
2
+ console.log("Debug log");
package/tsconfig.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ // Visit https://aka.ms/tsconfig to read more about this file
3
+ "compilerOptions": {
4
+ // File Layout
5
+ "rootDir": "./src",
6
+ "outDir": "./dist",
7
+
8
+ // Environment Settings
9
+ // See also https://aka.ms/tsconfig/module
10
+ "module": "nodenext",
11
+ "allowImportingTsExtensions": true,
12
+ "rewriteRelativeImportExtensions": true,
13
+ "target": "esnext",
14
+ "lib": ["esnext"],
15
+ "types": ["node"],
16
+
17
+ // Other Outputs
18
+ "sourceMap": true,
19
+ "declaration": true,
20
+ "declarationMap": true,
21
+
22
+ // Stricter Typechecking Options
23
+ "noUncheckedIndexedAccess": true,
24
+ "exactOptionalPropertyTypes": true,
25
+
26
+ // Style Options
27
+ // "noImplicitReturns": true,
28
+ // "noImplicitOverride": true,
29
+ // "noUnusedLocals": true,
30
+ // "noUnusedParameters": true,
31
+ // "noFallthroughCasesInSwitch": true,
32
+ // "noPropertyAccessFromIndexSignature": true,
33
+
34
+ // Recommended Options
35
+ "strict": true,
36
+ "jsx": "react-jsx",
37
+ "verbatimModuleSyntax": true,
38
+ "isolatedModules": true,
39
+ "noUncheckedSideEffectImports": true,
40
+ "moduleDetection": "force",
41
+ "skipLibCheck": true,
42
+ },
43
+ "include": ["src"]
44
+ }