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.
- package/README.md +62 -0
- package/dist/checks/consoleLogCheck.d.ts +3 -0
- package/dist/checks/consoleLogCheck.d.ts.map +1 -0
- package/dist/checks/consoleLogCheck.js +33 -0
- package/dist/checks/consoleLogCheck.js.map +1 -0
- package/dist/checks/envFileCheck.d.ts +3 -0
- package/dist/checks/envFileCheck.d.ts.map +1 -0
- package/dist/checks/envFileCheck.js +20 -0
- package/dist/checks/envFileCheck.js.map +1 -0
- package/dist/checks/focusedTestCheck.d.ts +3 -0
- package/dist/checks/focusedTestCheck.d.ts.map +1 -0
- package/dist/checks/focusedTestCheck.js +36 -0
- package/dist/checks/focusedTestCheck.js.map +1 -0
- package/dist/checks/generatedFolderCheck.d.ts +3 -0
- package/dist/checks/generatedFolderCheck.d.ts.map +1 -0
- package/dist/checks/generatedFolderCheck.js +28 -0
- package/dist/checks/generatedFolderCheck.js.map +1 -0
- package/dist/checks/largeFileCheck.d.ts +3 -0
- package/dist/checks/largeFileCheck.d.ts.map +1 -0
- package/dist/checks/largeFileCheck.js +25 -0
- package/dist/checks/largeFileCheck.js.map +1 -0
- package/dist/checks/localHostCheck.d.ts +3 -0
- package/dist/checks/localHostCheck.d.ts.map +1 -0
- package/dist/checks/localHostCheck.js +34 -0
- package/dist/checks/localHostCheck.js.map +1 -0
- package/dist/checks/secretCheck.d.ts +3 -0
- package/dist/checks/secretCheck.d.ts.map +1 -0
- package/dist/checks/secretCheck.js +38 -0
- package/dist/checks/secretCheck.js.map +1 -0
- package/dist/git.d.ts +2 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +16 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/reporter.d.ts +3 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +38 -0
- package/dist/reporter.js.map +1 -0
- package/dist/scanner.d.ts +3 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +25 -0
- package/dist/scanner.js.map +1 -0
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +41 -0
- package/src/checks/consoleLogCheck.ts +39 -0
- package/src/checks/envFileCheck.ts +25 -0
- package/src/checks/focusedTestCheck.ts +41 -0
- package/src/checks/generatedFolderCheck.ts +37 -0
- package/src/checks/largeFileCheck.ts +31 -0
- package/src/checks/localHostCheck.ts +40 -0
- package/src/checks/secretCheck.ts +44 -0
- package/src/git.ts +16 -0
- package/src/index.ts +41 -0
- package/src/reporter.ts +48 -0
- package/src/scanner.ts +29 -0
- package/src/types.ts +20 -0
- package/test.ts +2 -0
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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
|
package/dist/git.js.map
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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 @@
|
|
|
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"}
|
package/dist/reporter.js
ADDED
|
@@ -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 @@
|
|
|
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"}
|
package/dist/scanner.js
ADDED
|
@@ -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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
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();
|
package/src/reporter.ts
ADDED
|
@@ -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
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
|
+
}
|