commit-cop 1.1.0 → 1.1.2
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 +72 -15
- package/dist/brand.d.ts +4 -0
- package/dist/brand.d.ts.map +1 -0
- package/{src/brand.ts → dist/brand.js} +4 -3
- package/dist/brand.js.map +1 -0
- package/dist/checks/binaryFileCheck.js +2 -2
- package/dist/checks/binaryFileCheck.js.map +1 -1
- package/dist/checks/consoleLogCheck.d.ts.map +1 -1
- package/dist/checks/consoleLogCheck.js +2 -2
- package/dist/checks/consoleLogCheck.js.map +1 -1
- package/dist/checks/debuggerCheck.d.ts.map +1 -1
- package/dist/checks/debuggerCheck.js +2 -2
- package/dist/checks/debuggerCheck.js.map +1 -1
- package/dist/checks/envFileCheck.d.ts.map +1 -1
- package/dist/checks/envFileCheck.js +2 -2
- package/dist/checks/envFileCheck.js.map +1 -1
- package/dist/checks/focusedTestCheck.js +2 -2
- package/dist/checks/focusedTestCheck.js.map +1 -1
- package/dist/checks/generatedFolderCheck.js +2 -2
- package/dist/checks/generatedFolderCheck.js.map +1 -1
- package/dist/checks/junkFileCheck.d.ts.map +1 -1
- package/dist/checks/junkFileCheck.js +2 -2
- package/dist/checks/junkFileCheck.js.map +1 -1
- package/dist/checks/largeFileCheck.js +2 -2
- package/dist/checks/largeFileCheck.js.map +1 -1
- package/dist/checks/localHostCheck.js +2 -2
- package/dist/checks/localHostCheck.js.map +1 -1
- package/dist/checks/lockfileDriftCheck.d.ts.map +1 -1
- package/dist/checks/lockfileDriftCheck.js +4 -4
- package/dist/checks/lockfileDriftCheck.js.map +1 -1
- package/dist/checks/mergeConflictCheck.d.ts.map +1 -1
- package/dist/checks/mergeConflictCheck.js +2 -2
- package/dist/checks/mergeConflictCheck.js.map +1 -1
- package/dist/checks/secretCheck.js +19 -19
- package/dist/checks/secretCheck.js.map +1 -1
- package/dist/checks/sensitiveFilenameCheck.d.ts.map +1 -1
- package/dist/checks/sensitiveFilenameCheck.js +2 -2
- package/dist/checks/sensitiveFilenameCheck.js.map +1 -1
- package/dist/fix/debugCode.d.ts +3 -0
- package/dist/fix/debugCode.d.ts.map +1 -0
- package/dist/fix/debugCode.js +55 -0
- package/dist/fix/debugCode.js.map +1 -0
- package/dist/fix/focusedTests.d.ts +2 -0
- package/dist/fix/focusedTests.d.ts.map +1 -0
- package/dist/fix/focusedTests.js +22 -0
- package/dist/fix/focusedTests.js.map +1 -0
- package/dist/fix/gitignore.d.ts +2 -0
- package/dist/fix/gitignore.d.ts.map +1 -0
- package/dist/fix/gitignore.js +82 -0
- package/dist/fix/gitignore.js.map +1 -0
- package/dist/fix/junkFiles.d.ts +2 -0
- package/dist/fix/junkFiles.d.ts.map +1 -0
- package/dist/fix/junkFiles.js +14 -0
- package/dist/fix/junkFiles.js.map +1 -0
- package/dist/fix/lockfile.d.ts +2 -0
- package/dist/fix/lockfile.d.ts.map +1 -0
- package/dist/fix/lockfile.js +18 -0
- package/dist/fix/lockfile.js.map +1 -0
- package/dist/fix/matchers.d.ts +9 -0
- package/dist/fix/matchers.d.ts.map +1 -0
- package/dist/fix/matchers.js +118 -0
- package/dist/fix/matchers.js.map +1 -0
- package/dist/fix/runFix.d.ts +3 -0
- package/dist/fix/runFix.d.ts.map +1 -0
- package/dist/fix/runFix.js +82 -0
- package/dist/fix/runFix.js.map +1 -0
- package/dist/fix/unstage.d.ts +2 -0
- package/dist/fix/unstage.d.ts.map +1 -0
- package/dist/fix/unstage.js +22 -0
- package/dist/fix/unstage.js.map +1 -0
- package/dist/fix/utils.d.ts +4 -0
- package/dist/fix/utils.d.ts.map +1 -0
- package/dist/fix/utils.js +39 -0
- package/dist/fix/utils.js.map +1 -0
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +2 -1
- package/dist/git.js.map +1 -1
- package/dist/hook.d.ts +3 -0
- package/dist/hook.d.ts.map +1 -0
- package/dist/hook.js +87 -0
- package/dist/hook.js.map +1 -0
- package/dist/index.js +38 -21
- package/dist/index.js.map +1 -1
- package/dist/reporter.d.ts +1 -1
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +86 -28
- package/dist/reporter.js.map +1 -1
- package/dist/runScan.d.ts +2 -0
- package/dist/runScan.d.ts.map +1 -0
- package/dist/runScan.js +18 -0
- package/dist/runScan.js.map +1 -0
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +4 -1
- package/dist/scanner.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -2
- package/src/checks/binaryFileCheck.ts +0 -64
- package/src/checks/consoleLogCheck.ts +0 -40
- package/src/checks/debuggerCheck.ts +0 -33
- package/src/checks/envFileCheck.ts +0 -26
- package/src/checks/focusedTestCheck.ts +0 -41
- package/src/checks/generatedFolderCheck.ts +0 -45
- package/src/checks/junkFileCheck.ts +0 -40
- package/src/checks/largeFileCheck.ts +0 -31
- package/src/checks/localHostCheck.ts +0 -40
- package/src/checks/lockfileDriftCheck.ts +0 -40
- package/src/checks/mergeConflictCheck.ts +0 -41
- package/src/checks/secretCheck.ts +0 -60
- package/src/checks/sensitiveFilenameCheck.ts +0 -51
- package/src/checks/utils.ts +0 -62
- package/src/fix/debugCode.ts +0 -74
- package/src/fix/focusedTests.ts +0 -26
- package/src/fix/gitignore.ts +0 -108
- package/src/fix/junkFiles.ts +0 -16
- package/src/fix/lockfile.ts +0 -23
- package/src/fix/matchers.ts +0 -141
- package/src/fix/runFix.ts +0 -96
- package/src/fix/unstage.ts +0 -25
- package/src/fix/utils.ts +0 -50
- package/src/git.ts +0 -17
- package/src/hook.ts +0 -98
- package/src/index.ts +0 -59
- package/src/reporter.ts +0 -88
- package/src/runScan.ts +0 -35
- package/src/scanner.ts +0 -44
- package/src/types.ts +0 -25
- package/test.ts +0 -6
- package/testing.ts +0 -3
- package/tsconfig.json +0 -44
package/src/fix/lockfile.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
export function fixLockfile(cwd = process.cwd()): boolean {
|
|
6
|
-
const packageJson = path.join(cwd, "package.json");
|
|
7
|
-
const packageLock = path.join(cwd, "package-lock.json");
|
|
8
|
-
|
|
9
|
-
if (!fs.existsSync(packageJson)) {
|
|
10
|
-
return false;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const needsInstall =
|
|
14
|
-
!fs.existsSync(packageLock) ||
|
|
15
|
-
fs.statSync(packageJson).mtimeMs > fs.statSync(packageLock).mtimeMs;
|
|
16
|
-
|
|
17
|
-
if (!needsInstall) {
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
execSync("npm install", { cwd, stdio: "inherit" });
|
|
22
|
-
return true;
|
|
23
|
-
}
|
package/src/fix/matchers.ts
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
getBaseName,
|
|
4
|
-
matchesAnyPattern,
|
|
5
|
-
normalizePath,
|
|
6
|
-
} from "../checks/utils.js";
|
|
7
|
-
|
|
8
|
-
export const GENERATED_FOLDERS = [
|
|
9
|
-
"node_modules/",
|
|
10
|
-
"dist/",
|
|
11
|
-
"build/",
|
|
12
|
-
".next/",
|
|
13
|
-
"coverage/",
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
const junkExactNames = new Set([
|
|
17
|
-
".ds_store",
|
|
18
|
-
"thumbs.db",
|
|
19
|
-
"desktop.ini",
|
|
20
|
-
]);
|
|
21
|
-
|
|
22
|
-
const junkNamePatterns = [/\.swp$/i, /\.bak$/i, /\.tmp$/i, /~$/];
|
|
23
|
-
|
|
24
|
-
const sensitiveExactNames = new Set([
|
|
25
|
-
"id_rsa",
|
|
26
|
-
"id_ed25519",
|
|
27
|
-
"credentials.json",
|
|
28
|
-
"serviceaccountkey.json",
|
|
29
|
-
".npmrc",
|
|
30
|
-
".pypirc",
|
|
31
|
-
]);
|
|
32
|
-
|
|
33
|
-
const sensitiveNamePatterns = [
|
|
34
|
-
/\.pem$/i,
|
|
35
|
-
/\.p12$/i,
|
|
36
|
-
/\.key$/i,
|
|
37
|
-
/^firebase-adminsdk.*\.json$/i,
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
const binaryExtensions = new Set([
|
|
41
|
-
".zip",
|
|
42
|
-
".exe",
|
|
43
|
-
".dll",
|
|
44
|
-
".mp4",
|
|
45
|
-
".mov",
|
|
46
|
-
".sqlite",
|
|
47
|
-
".db",
|
|
48
|
-
]);
|
|
49
|
-
|
|
50
|
-
const MAX_SIZE_MB = 5;
|
|
51
|
-
|
|
52
|
-
function matchesGeneratedFolder(normalizedPath: string): string | null {
|
|
53
|
-
for (const folder of GENERATED_FOLDERS) {
|
|
54
|
-
if (
|
|
55
|
-
normalizedPath.startsWith(folder) ||
|
|
56
|
-
normalizedPath.includes(`/${folder}`)
|
|
57
|
-
) {
|
|
58
|
-
return folder;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function isEnvFile(file: string): boolean {
|
|
65
|
-
const baseName = getBaseName(file);
|
|
66
|
-
return baseName === ".env" || baseName.startsWith(".env.");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function isSensitiveFile(file: string): boolean {
|
|
70
|
-
const baseName = getBaseName(file);
|
|
71
|
-
const normalizedBaseName = baseName.toLowerCase();
|
|
72
|
-
const normalizedPath = normalizePath(file).toLowerCase();
|
|
73
|
-
|
|
74
|
-
return (
|
|
75
|
-
sensitiveExactNames.has(normalizedBaseName) ||
|
|
76
|
-
matchesAnyPattern(baseName, sensitiveNamePatterns) ||
|
|
77
|
-
normalizedPath.endsWith("/credentials.json") ||
|
|
78
|
-
normalizedPath.includes("serviceaccountkey.json")
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function isGeneratedPath(file: string): boolean {
|
|
83
|
-
return matchesGeneratedFolder(normalizePath(file)) !== null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function isJunkFile(file: string): boolean {
|
|
87
|
-
const baseName = getBaseName(file);
|
|
88
|
-
const normalizedBaseName = baseName.toLowerCase();
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
junkExactNames.has(normalizedBaseName) ||
|
|
92
|
-
matchesAnyPattern(baseName, junkNamePatterns)
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function hasNullBytes(file: string): boolean {
|
|
97
|
-
const buffer = fs.readFileSync(file);
|
|
98
|
-
const sampleSize = Math.min(buffer.length, 8192);
|
|
99
|
-
|
|
100
|
-
for (let index = 0; index < sampleSize; index += 1) {
|
|
101
|
-
if (buffer[index] === 0) {
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function isBinaryFile(file: string): boolean {
|
|
110
|
-
if (!fs.existsSync(file)) return false;
|
|
111
|
-
|
|
112
|
-
const baseName = getBaseName(file);
|
|
113
|
-
const extension = baseName.includes(".")
|
|
114
|
-
? baseName.slice(baseName.lastIndexOf(".")).toLowerCase()
|
|
115
|
-
: "";
|
|
116
|
-
|
|
117
|
-
if (binaryExtensions.has(extension)) return true;
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
return hasNullBytes(file);
|
|
121
|
-
} catch {
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export function isLargeFile(file: string): boolean {
|
|
127
|
-
if (!fs.existsSync(file)) return false;
|
|
128
|
-
|
|
129
|
-
const sizeMb = fs.statSync(file).size / 1024 / 1024;
|
|
130
|
-
return sizeMb > MAX_SIZE_MB;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export function shouldUnstage(file: string): string | null {
|
|
134
|
-
if (isEnvFile(file)) return "environment file";
|
|
135
|
-
if (isSensitiveFile(file)) return "sensitive file";
|
|
136
|
-
if (isGeneratedPath(file)) return "generated path";
|
|
137
|
-
if (isJunkFile(file)) return "junk file";
|
|
138
|
-
if (isBinaryFile(file)) return "binary file";
|
|
139
|
-
if (isLargeFile(file)) return "large file";
|
|
140
|
-
return null;
|
|
141
|
-
}
|
package/src/fix/runFix.ts
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import type { WipFixOptions } from "../types.js";
|
|
3
|
-
import { fixDebugCode } from "./debugCode.js";
|
|
4
|
-
import { fixFocusedTests } from "./focusedTests.js";
|
|
5
|
-
import { fixGitignore } from "./gitignore.js";
|
|
6
|
-
import { fixJunkFiles } from "./junkFiles.js";
|
|
7
|
-
import { fixLockfile } from "./lockfile.js";
|
|
8
|
-
import { fixUnstageRiskyFiles } from "./unstage.js";
|
|
9
|
-
|
|
10
|
-
function printSection(title: string, items: string[]): void {
|
|
11
|
-
if (items.length === 0) return;
|
|
12
|
-
console.log(chalk.green(`\n${title}`));
|
|
13
|
-
for (const item of items) {
|
|
14
|
-
console.log(` ${item}`);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function printSkipped(message: string): void {
|
|
19
|
-
console.log(chalk.dim(`\n${message}`));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function runWipFix(options: WipFixOptions = {}): void {
|
|
23
|
-
const fixConsoleLog = Boolean(options.fixConsoleLog);
|
|
24
|
-
|
|
25
|
-
console.log(chalk.bold("commit-cop wip-fix"));
|
|
26
|
-
console.log(chalk.dim("(work in progress — more fixes may be added later)\n"));
|
|
27
|
-
|
|
28
|
-
const gitignoreAdded = fixGitignore();
|
|
29
|
-
const testsFixed = fixFocusedTests();
|
|
30
|
-
const debugFixed = fixDebugCode(process.cwd(), { fixConsoleLog });
|
|
31
|
-
const junkRemoved = fixJunkFiles();
|
|
32
|
-
const unstaged = fixUnstageRiskyFiles();
|
|
33
|
-
const lockfileSynced = fixLockfile();
|
|
34
|
-
|
|
35
|
-
if (gitignoreAdded.length > 0) {
|
|
36
|
-
console.log(chalk.green("Updated .gitignore:"));
|
|
37
|
-
for (const entry of gitignoreAdded) {
|
|
38
|
-
console.log(` + ${entry}`);
|
|
39
|
-
}
|
|
40
|
-
} else {
|
|
41
|
-
console.log(
|
|
42
|
-
chalk.dim(".gitignore already includes expected entries from checks")
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
printSection("Replaced focused test (.only) calls in:", testsFixed);
|
|
47
|
-
if (testsFixed.length === 0) {
|
|
48
|
-
printSkipped("No test.only / it.only / describe.only in test files");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (fixConsoleLog) {
|
|
52
|
-
printSection("Removed debug lines from staged files:", debugFixed);
|
|
53
|
-
if (debugFixed.length === 0) {
|
|
54
|
-
printSkipped("No staged standalone console.log or debugger lines");
|
|
55
|
-
}
|
|
56
|
-
} else {
|
|
57
|
-
printSkipped("Skipped console.log removal (use --fix-console-log to enable)");
|
|
58
|
-
if (debugFixed.length > 0) {
|
|
59
|
-
printSection("Removed debugger lines from staged files:", debugFixed);
|
|
60
|
-
} else {
|
|
61
|
-
printSkipped("No staged debugger lines");
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
printSection("Deleted junk files:", junkRemoved);
|
|
66
|
-
if (junkRemoved.length === 0) {
|
|
67
|
-
printSkipped("No junk files found on disk");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
printSection("Unstaged risky files:", unstaged);
|
|
71
|
-
if (unstaged.length === 0) {
|
|
72
|
-
printSkipped("No staged env, sensitive, generated, junk, binary, or large files");
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (lockfileSynced) {
|
|
76
|
-
console.log(chalk.green("\nRan npm install to sync package-lock.json"));
|
|
77
|
-
} else {
|
|
78
|
-
printSkipped("package-lock.json is up to date (or no package.json)");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
printSkipped(
|
|
82
|
-
"Cannot auto-fix: merge conflicts, secrets, or localhost URLs — resolve those manually"
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
const changed =
|
|
86
|
-
gitignoreAdded.length > 0 ||
|
|
87
|
-
testsFixed.length > 0 ||
|
|
88
|
-
debugFixed.length > 0 ||
|
|
89
|
-
junkRemoved.length > 0 ||
|
|
90
|
-
unstaged.length > 0 ||
|
|
91
|
-
lockfileSynced;
|
|
92
|
-
|
|
93
|
-
if (!changed) {
|
|
94
|
-
console.log(chalk.dim("\nNothing to fix."));
|
|
95
|
-
}
|
|
96
|
-
}
|
package/src/fix/unstage.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
2
|
-
import { getStagedFiles } from "../git.js";
|
|
3
|
-
import { shouldUnstage } from "./matchers.js";
|
|
4
|
-
|
|
5
|
-
export function fixUnstageRiskyFiles(): string[] {
|
|
6
|
-
let stagedFiles: string[];
|
|
7
|
-
|
|
8
|
-
try {
|
|
9
|
-
stagedFiles = getStagedFiles();
|
|
10
|
-
} catch {
|
|
11
|
-
return [];
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const unstaged: string[] = [];
|
|
15
|
-
|
|
16
|
-
for (const file of stagedFiles) {
|
|
17
|
-
const reason = shouldUnstage(file);
|
|
18
|
-
if (!reason) continue;
|
|
19
|
-
|
|
20
|
-
execSync(`git restore --staged -- "${file}"`, { stdio: "pipe" });
|
|
21
|
-
unstaged.push(`${file} (${reason})`);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
return unstaged;
|
|
25
|
-
}
|
package/src/fix/utils.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
export const SKIP_DIRS = new Set([
|
|
5
|
-
"node_modules",
|
|
6
|
-
"dist",
|
|
7
|
-
"build",
|
|
8
|
-
".next",
|
|
9
|
-
"coverage",
|
|
10
|
-
".git",
|
|
11
|
-
]);
|
|
12
|
-
|
|
13
|
-
export function walkRepo(
|
|
14
|
-
cwd: string,
|
|
15
|
-
onFile: (absolutePath: string, relativePath: string) => void
|
|
16
|
-
): void {
|
|
17
|
-
function walk(dir: string): void {
|
|
18
|
-
if (!fs.existsSync(dir)) return;
|
|
19
|
-
|
|
20
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
21
|
-
if (SKIP_DIRS.has(entry.name)) continue;
|
|
22
|
-
|
|
23
|
-
const absolutePath = path.join(dir, entry.name);
|
|
24
|
-
const relativePath = path.relative(cwd, absolutePath);
|
|
25
|
-
|
|
26
|
-
if (entry.isDirectory()) {
|
|
27
|
-
walk(absolutePath);
|
|
28
|
-
continue;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
onFile(absolutePath, relativePath);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
walk(cwd);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function walkCodeFiles(
|
|
39
|
-
cwd: string,
|
|
40
|
-
predicate: (relativePath: string) => boolean,
|
|
41
|
-
files: string[] = []
|
|
42
|
-
): string[] {
|
|
43
|
-
walkRepo(cwd, (absolutePath, relativePath) => {
|
|
44
|
-
if (!/\.(ts|tsx|js|jsx|mjs|cjs)$/.test(relativePath)) return;
|
|
45
|
-
if (!predicate(relativePath)) return;
|
|
46
|
-
files.push(absolutePath);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
return files;
|
|
50
|
-
}
|
package/src/git.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
2
|
-
import { PRODUCT_NAME } from "./brand.js";
|
|
3
|
-
|
|
4
|
-
export function getStagedFiles(): string[] {
|
|
5
|
-
try {
|
|
6
|
-
const output = execSync("git diff --cached --name-only", {
|
|
7
|
-
encoding: "utf-8",
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
return output
|
|
11
|
-
.split("\n")
|
|
12
|
-
.map((file) => file.trim())
|
|
13
|
-
.filter(Boolean);
|
|
14
|
-
} catch {
|
|
15
|
-
throw new Error(`${PRODUCT_NAME} must be run inside a Git repository.`);
|
|
16
|
-
}
|
|
17
|
-
}
|
package/src/hook.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { CLI_NAME, PRODUCT_NAME } from "./brand.js";
|
|
5
|
-
|
|
6
|
-
const HOOK_MARKER = "# Generated by Commit Cop";
|
|
7
|
-
const BACKUP_SUFFIX = ".commit-cop.backup";
|
|
8
|
-
|
|
9
|
-
function getGitHooksDir(): string {
|
|
10
|
-
try {
|
|
11
|
-
const gitPath = execSync("git rev-parse --git-path hooks", {
|
|
12
|
-
encoding: "utf-8",
|
|
13
|
-
}).trim();
|
|
14
|
-
|
|
15
|
-
return path.resolve(gitPath);
|
|
16
|
-
} catch {
|
|
17
|
-
throw new Error(`${PRODUCT_NAME} install must be run inside a Git repository.`);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function buildHookScript(strict: boolean): string {
|
|
22
|
-
const strictFlag = strict ? " --strict" : "";
|
|
23
|
-
|
|
24
|
-
return `#!/bin/sh
|
|
25
|
-
${HOOK_MARKER}
|
|
26
|
-
# Re-run: npx ${CLI_NAME} install
|
|
27
|
-
|
|
28
|
-
cd "$(git rev-parse --show-toplevel)" || exit 1
|
|
29
|
-
|
|
30
|
-
if [ -x "./node_modules/.bin/${CLI_NAME}" ]; then
|
|
31
|
-
exec ./node_modules/.bin/${CLI_NAME}${strictFlag}
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
if command -v ${CLI_NAME} >/dev/null 2>&1; then
|
|
35
|
-
exec ${CLI_NAME}${strictFlag}
|
|
36
|
-
fi
|
|
37
|
-
|
|
38
|
-
exec npx ${CLI_NAME}${strictFlag}
|
|
39
|
-
`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function installHook(strict = false): void {
|
|
43
|
-
const hooksDir = getGitHooksDir();
|
|
44
|
-
const hookPath = path.join(hooksDir, "pre-commit");
|
|
45
|
-
const backupPath = `${hookPath}${BACKUP_SUFFIX}`;
|
|
46
|
-
|
|
47
|
-
fs.mkdirSync(hooksDir, { recursive: true });
|
|
48
|
-
|
|
49
|
-
if (fs.existsSync(hookPath)) {
|
|
50
|
-
const existing = fs.readFileSync(hookPath, "utf-8");
|
|
51
|
-
|
|
52
|
-
if (existing.includes(HOOK_MARKER)) {
|
|
53
|
-
console.log(`${PRODUCT_NAME}: Updating existing pre-commit hook.`);
|
|
54
|
-
} else {
|
|
55
|
-
fs.copyFileSync(hookPath, backupPath);
|
|
56
|
-
console.log(
|
|
57
|
-
`${PRODUCT_NAME}: Backed up existing pre-commit hook to ${path.basename(backupPath)}.`
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
fs.writeFileSync(hookPath, buildHookScript(strict), { mode: 0o755 });
|
|
63
|
-
|
|
64
|
-
console.log(`${PRODUCT_NAME}: Pre-commit hook installed.`);
|
|
65
|
-
console.log(` ${hookPath}`);
|
|
66
|
-
console.log("");
|
|
67
|
-
console.log("Commit Cop will now run automatically on git commit.");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function uninstallHook(): void {
|
|
71
|
-
const hooksDir = getGitHooksDir();
|
|
72
|
-
const hookPath = path.join(hooksDir, "pre-commit");
|
|
73
|
-
const backupPath = `${hookPath}${BACKUP_SUFFIX}`;
|
|
74
|
-
|
|
75
|
-
if (!fs.existsSync(hookPath)) {
|
|
76
|
-
console.log(`${PRODUCT_NAME}: No pre-commit hook found.`);
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const existing = fs.readFileSync(hookPath, "utf-8");
|
|
81
|
-
|
|
82
|
-
if (!existing.includes(HOOK_MARKER)) {
|
|
83
|
-
throw new Error(
|
|
84
|
-
`${PRODUCT_NAME} did not install this pre-commit hook. Remove it manually if needed.`
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
fs.unlinkSync(hookPath);
|
|
89
|
-
|
|
90
|
-
if (fs.existsSync(backupPath)) {
|
|
91
|
-
fs.copyFileSync(backupPath, hookPath);
|
|
92
|
-
fs.unlinkSync(backupPath);
|
|
93
|
-
console.log(`${PRODUCT_NAME}: Pre-commit hook removed and previous hook restored.`);
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
console.log(`${PRODUCT_NAME}: Pre-commit hook removed.`);
|
|
98
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { CLI_NAME, PRODUCT_NAME, TAGLINE } from "./brand.js";
|
|
4
|
-
import { Command } from "commander";
|
|
5
|
-
import { runWipFix } from "./fix/runFix.js";
|
|
6
|
-
import { installHook, uninstallHook } from "./hook.js";
|
|
7
|
-
import { runScan } from "./runScan.js";
|
|
8
|
-
|
|
9
|
-
const program = new Command();
|
|
10
|
-
|
|
11
|
-
program
|
|
12
|
-
.name(CLI_NAME)
|
|
13
|
-
.description(`${PRODUCT_NAME} — ${TAGLINE}`);
|
|
14
|
-
|
|
15
|
-
program
|
|
16
|
-
.command("scan", { isDefault: true })
|
|
17
|
-
.description("Scan staged files for risky commits")
|
|
18
|
-
.option("--strict", "Treat warnings as errors")
|
|
19
|
-
.option(
|
|
20
|
-
"--allow-console-log",
|
|
21
|
-
"Skip console.log warnings (for projects that keep console.log on purpose)"
|
|
22
|
-
)
|
|
23
|
-
.action(async (options) => {
|
|
24
|
-
const exitCode = await runScan(
|
|
25
|
-
Boolean(options.strict),
|
|
26
|
-
Boolean(options.allowConsoleLog)
|
|
27
|
-
);
|
|
28
|
-
process.exit(exitCode);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
program
|
|
32
|
-
.command("wip-fix")
|
|
33
|
-
.description(
|
|
34
|
-
"Apply common repo fixes matching commit-cop checks (.gitignore, debug code, unstaging, etc.)"
|
|
35
|
-
)
|
|
36
|
-
.option(
|
|
37
|
-
"--fix-console-log",
|
|
38
|
-
"Also remove standalone console.log lines from staged files (debugger lines are always removed)"
|
|
39
|
-
)
|
|
40
|
-
.action((options) => {
|
|
41
|
-
runWipFix({ fixConsoleLog: Boolean(options.fixConsoleLog) });
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
program
|
|
45
|
-
.command("install")
|
|
46
|
-
.description("Install a Git pre-commit hook that runs Commit Cop on every commit")
|
|
47
|
-
.option("--strict", "Treat warnings as errors in the hook")
|
|
48
|
-
.action((options) => {
|
|
49
|
-
installHook(Boolean(options.strict));
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
program
|
|
53
|
-
.command("uninstall")
|
|
54
|
-
.description("Remove the Commit Cop pre-commit hook")
|
|
55
|
-
.action(() => {
|
|
56
|
-
uninstallHook();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
program.parse();
|
package/src/reporter.ts
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { PRODUCT_NAME } from "./brand.js";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import type { Finding, Severity } from "./types.js";
|
|
4
|
-
|
|
5
|
-
const WIDTH = 52;
|
|
6
|
-
|
|
7
|
-
function line(char = "─"): string {
|
|
8
|
-
return chalk.dim(char.repeat(WIDTH));
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function printHeader(title: string): void {
|
|
12
|
-
console.log("");
|
|
13
|
-
console.log(line("═"));
|
|
14
|
-
console.log(chalk.bold(` ${title}`));
|
|
15
|
-
console.log(line("═"));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function printSection(title: string, color: (text: string) => string): void {
|
|
19
|
-
console.log("");
|
|
20
|
-
console.log(line());
|
|
21
|
-
console.log(color(chalk.bold(` ${title}`)));
|
|
22
|
-
console.log(line());
|
|
23
|
-
console.log("");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function printSummary(scannedCount: number, errors: number, warnings: number): void {
|
|
27
|
-
console.log("");
|
|
28
|
-
console.log(chalk.dim(` Scanned ${scannedCount} staged file(s)`));
|
|
29
|
-
console.log(
|
|
30
|
-
` ${chalk.red(`Errors: ${errors}`)}${chalk.dim(" │ ")}${chalk.yellow(`Warnings: ${warnings}`)}`
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function printFinding(finding: Finding, index: number): void {
|
|
35
|
-
const location = finding.line
|
|
36
|
-
? `${finding.file}:${finding.line}`
|
|
37
|
-
: finding.file;
|
|
38
|
-
|
|
39
|
-
const checkLabel = chalk.dim(`[${finding.checkName}]`);
|
|
40
|
-
|
|
41
|
-
console.log(chalk.bold(` ${index}.`) + ` ${checkLabel}`);
|
|
42
|
-
console.log(` ${chalk.cyan(location)}`);
|
|
43
|
-
console.log(` ${finding.message}`);
|
|
44
|
-
|
|
45
|
-
if (finding.suggestion) {
|
|
46
|
-
console.log(chalk.gray(` → ${finding.suggestion}`));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
console.log("");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function printFindings(findings: Finding[], severity: Severity): void {
|
|
53
|
-
const filtered = findings.filter((finding) => finding.severity === severity);
|
|
54
|
-
|
|
55
|
-
filtered.forEach((finding, index) => {
|
|
56
|
-
printFinding(finding, index + 1);
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function printReport(findings: Finding[], scannedCount: number) {
|
|
61
|
-
const errors = findings.filter((finding) => finding.severity === "error");
|
|
62
|
-
const warnings = findings.filter((finding) => finding.severity === "warning");
|
|
63
|
-
|
|
64
|
-
printHeader(`${PRODUCT_NAME} Report`);
|
|
65
|
-
printSummary(scannedCount, errors.length, warnings.length);
|
|
66
|
-
|
|
67
|
-
if (findings.length === 0) {
|
|
68
|
-
console.log("");
|
|
69
|
-
console.log(chalk.green(" ✅ All clear — no issues found in staged files."));
|
|
70
|
-
console.log("");
|
|
71
|
-
console.log(line("═"));
|
|
72
|
-
console.log("");
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (errors.length > 0) {
|
|
77
|
-
printSection(`ERRORS (${errors.length})`, chalk.red);
|
|
78
|
-
printFindings(findings, "error");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (warnings.length > 0) {
|
|
82
|
-
printSection(`WARNINGS (${warnings.length})`, chalk.yellow);
|
|
83
|
-
printFindings(findings, "warning");
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
console.log(line("═"));
|
|
87
|
-
console.log("");
|
|
88
|
-
}
|
package/src/runScan.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { PRODUCT_NAME } from "./brand.js";
|
|
2
|
-
import { getStagedFiles } from "./git.js";
|
|
3
|
-
import { runChecks } from "./scanner.js";
|
|
4
|
-
import { printReport } from "./reporter.js";
|
|
5
|
-
|
|
6
|
-
export async function runScan(
|
|
7
|
-
strict = false,
|
|
8
|
-
allowConsoleLog = false
|
|
9
|
-
): Promise<number> {
|
|
10
|
-
const stagedFiles = getStagedFiles();
|
|
11
|
-
|
|
12
|
-
if (stagedFiles.length === 0) {
|
|
13
|
-
console.log(`${PRODUCT_NAME}: No staged files found.`);
|
|
14
|
-
return 0;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const findings = await runChecks({
|
|
18
|
-
stagedFiles,
|
|
19
|
-
strict,
|
|
20
|
-
allowConsoleLog,
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
printReport(findings, stagedFiles.length);
|
|
24
|
-
|
|
25
|
-
const hasErrors = findings.some((finding) => finding.severity === "error");
|
|
26
|
-
const hasWarnings = findings.some(
|
|
27
|
-
(finding) => finding.severity === "warning"
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
if (hasErrors || (strict && hasWarnings)) {
|
|
31
|
-
return 1;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return 0;
|
|
35
|
-
}
|
package/src/scanner.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { CheckContext, Finding } from "./types.js";
|
|
2
|
-
import { envFileCheck } from "./checks/envFileCheck.js";
|
|
3
|
-
import { generatedFolderCheck } from "./checks/generatedFolderCheck.ts";
|
|
4
|
-
import { mergeConflictCheck } from "./checks/mergeConflictCheck.ts";
|
|
5
|
-
import { sensitiveFilenameCheck } from "./checks/sensitiveFilenameCheck.ts";
|
|
6
|
-
import { secretCheck } from "./checks/secretCheck.ts";
|
|
7
|
-
import { focusedTestCheck } from "./checks/focusedTestCheck.ts";
|
|
8
|
-
import { consoleLogCheck } from "./checks/consoleLogCheck.ts";
|
|
9
|
-
import { debuggerCheck } from "./checks/debuggerCheck.ts";
|
|
10
|
-
import { localHostCheck } from "./checks/localHostCheck.ts";
|
|
11
|
-
import { junkFileCheck } from "./checks/junkFileCheck.ts";
|
|
12
|
-
import { lockfileDriftCheck } from "./checks/lockfileDriftCheck.ts";
|
|
13
|
-
import { largeFileCheck } from "./checks/largeFileCheck.ts";
|
|
14
|
-
import { binaryFileCheck } from "./checks/binaryFileCheck.ts";
|
|
15
|
-
|
|
16
|
-
const checks = [
|
|
17
|
-
mergeConflictCheck,
|
|
18
|
-
envFileCheck,
|
|
19
|
-
sensitiveFilenameCheck,
|
|
20
|
-
generatedFolderCheck,
|
|
21
|
-
secretCheck,
|
|
22
|
-
focusedTestCheck,
|
|
23
|
-
consoleLogCheck,
|
|
24
|
-
debuggerCheck,
|
|
25
|
-
localHostCheck,
|
|
26
|
-
junkFileCheck,
|
|
27
|
-
lockfileDriftCheck,
|
|
28
|
-
largeFileCheck,
|
|
29
|
-
binaryFileCheck,
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
export async function runChecks(context: CheckContext): Promise<Finding[]> {
|
|
33
|
-
const allFindings: Finding[] = [];
|
|
34
|
-
const activeChecks = context.allowConsoleLog
|
|
35
|
-
? checks.filter((check) => check !== consoleLogCheck)
|
|
36
|
-
: checks;
|
|
37
|
-
|
|
38
|
-
for (const check of activeChecks) {
|
|
39
|
-
const findings = await check.run(context);
|
|
40
|
-
allFindings.push(...findings);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return allFindings;
|
|
44
|
-
}
|