@commitguard/cli 0.0.3 → 0.0.14
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 +167 -0
- package/dist/index.d.mts +0 -0
- package/dist/index.mjs +383 -342
- package/package.json +13 -11
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# CommitGuard
|
|
2
|
+
|
|
3
|
+
Protect your codebase with every commit. CommitGuard automatically analyzes your code for security vulnerabilities, performance issues, and code quality problems before they enter your repository. Grab your free API key at https://commitguard.ai.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Get CommitGuard running in under 60 seconds. It works seamlessly with any git repository and integrates automatically with VSCode and other git clients.
|
|
8
|
+
|
|
9
|
+
### Quick Start
|
|
10
|
+
|
|
11
|
+
**1. Install CommitGuard globally**
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @commitguard/cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**2. Navigate to your project**
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
cd your-project
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**3. Initialize CommitGuard**
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
commitguard init
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**4. Commit as usual**
|
|
30
|
+
|
|
31
|
+
CommitGuard now runs automatically on every commit. It works with all git integrations including VSCode, terminal, and other git clients.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
git commit -m "Add new feature"
|
|
35
|
+
[CommitGuard] Analyzing commit...
|
|
36
|
+
✓ Commit passed all checks
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
### Bypassing Checks
|
|
42
|
+
|
|
43
|
+
To bypass CommitGuard checks for a specific commit, add `--skip` to your commit message:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
git commit -m "Add new feature --skip"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The `--skip` flag is automatically removed from the final commit message. When using git in your terminal, you'll be prompted to confirm if you want to bypass checks.
|
|
50
|
+
|
|
51
|
+
### Configuring Checks
|
|
52
|
+
|
|
53
|
+
View or update CommitGuard preferences for your repository:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
commitguard config
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
On the Pro plan, you can add custom rules through the config comand.
|
|
60
|
+
|
|
61
|
+
## CLI Commands
|
|
62
|
+
|
|
63
|
+
### `commitguard init`
|
|
64
|
+
|
|
65
|
+
Install CommitGuard in your current repository.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
$ commitguard init
|
|
69
|
+
✓ CommitGuard installed successfully
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### `commitguard remove`
|
|
73
|
+
|
|
74
|
+
Remove CommitGuard from the current repository.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
$ commitguard remove
|
|
78
|
+
✓ CommitGuard removed
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### `commitguard config`
|
|
82
|
+
|
|
83
|
+
View or update CommitGuard preferences for the current repository. Pro plan users can add custom rules.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
$ commitguard config
|
|
87
|
+
Select enabled checks for this project:
|
|
88
|
+
Security, Performance, Code Quality, Architecture
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `commitguard keys`
|
|
92
|
+
|
|
93
|
+
Manage your global API key for CommitGuard.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
$ commitguard keys
|
|
97
|
+
Current API key: sk-ant-***************
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## What Happens After Installation?
|
|
101
|
+
|
|
102
|
+
- Every commit is analyzed before it's created.
|
|
103
|
+
- No config files are added to your project
|
|
104
|
+
- Bypass checks anytime with `--skip` in your commit message
|
|
105
|
+
- Works seamlessly with all git clients and IDEs
|
|
106
|
+
|
|
107
|
+
## Troubleshooting
|
|
108
|
+
|
|
109
|
+
<details>
|
|
110
|
+
<summary><strong>CommitGuard isn't running on commits</strong></summary>
|
|
111
|
+
|
|
112
|
+
Try reinstalling the hooks:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
commitguard remove
|
|
116
|
+
commitguard init
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Verify that the hooks are installed:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
ls -la .git/hooks/
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
You should see a `pre-commit` hook file.
|
|
126
|
+
</details>
|
|
127
|
+
|
|
128
|
+
<details>
|
|
129
|
+
<summary><strong>How do I skip a single commit?</strong></summary>
|
|
130
|
+
|
|
131
|
+
Add `--skip` to your commit message:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
git commit -m "Emergency fix --skip"
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The `--skip` flag is automatically removed from the final message.
|
|
138
|
+
</details>
|
|
139
|
+
|
|
140
|
+
<details>
|
|
141
|
+
<summary><strong>How do I completely remove CommitGuard?</strong></summary>
|
|
142
|
+
|
|
143
|
+
Remove the hooks and uninstall the package:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
commitguard remove
|
|
147
|
+
npm uninstall -g @commitguard/cli
|
|
148
|
+
```
|
|
149
|
+
</details>
|
|
150
|
+
|
|
151
|
+
## Features
|
|
152
|
+
|
|
153
|
+
- **Security Analysis** - Detect vulnerabilities before they reach your repo
|
|
154
|
+
- **Performance Checks** - Identify performance bottlenecks early
|
|
155
|
+
- **Code Quality** - Maintain consistent code standards
|
|
156
|
+
- **Architecture Review** - Ensure architectural patterns are followed
|
|
157
|
+
- **Zero Configuration** - Works out of the box
|
|
158
|
+
- **Universal Compatibility** - Works with any git workflow
|
|
159
|
+
-- **Custom Rules** - Add your own custom checks to the code reviews
|
|
160
|
+
|
|
161
|
+
## Support
|
|
162
|
+
|
|
163
|
+
For issues, feature requests, or questions related to this package please open an issue or email us at hello@commitguard.ai
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT
|
package/dist/index.d.mts
CHANGED
|
File without changes
|
package/dist/index.mjs
CHANGED
|
@@ -1,28 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from "node:module";
|
|
3
2
|
import process from "node:process";
|
|
4
3
|
import { consola } from "consola";
|
|
5
4
|
import updateNotifier from "update-notifier";
|
|
6
5
|
import { execFileSync, execSync } from "node:child_process";
|
|
7
|
-
import { createHash } from "node:crypto";
|
|
8
|
-
import { cancel, confirm, intro, isCancel, log, multiselect, note, outro, text } from "@clack/prompts";
|
|
9
|
-
import { Entry } from "@napi-rs/keyring";
|
|
10
6
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
11
7
|
import { homedir } from "node:os";
|
|
12
8
|
import { dirname, join } from "node:path";
|
|
9
|
+
import { cancel, confirm, intro, isCancel, log, multiselect, note, outro, select, text } from "@clack/prompts";
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
import { Entry } from "@napi-rs/keyring";
|
|
12
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
13
13
|
import { readFile } from "node:fs/promises";
|
|
14
|
-
import { pathToFileURL } from "node:url";
|
|
15
14
|
import { findUp } from "find-up";
|
|
16
15
|
import { FlatCache } from "flat-cache";
|
|
17
16
|
import stringWidth from "string-width";
|
|
18
17
|
import "dotenv/config";
|
|
19
18
|
|
|
20
|
-
//#region rolldown:runtime
|
|
21
|
-
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
22
|
-
|
|
23
|
-
//#endregion
|
|
24
19
|
//#region package.json
|
|
25
|
-
var version = "0.0.
|
|
20
|
+
var version = "0.0.14";
|
|
26
21
|
var package_default = {
|
|
27
22
|
name: "@commitguard/cli",
|
|
28
23
|
type: "module",
|
|
@@ -31,7 +26,7 @@ var package_default = {
|
|
|
31
26
|
license: "MIT",
|
|
32
27
|
repository: {
|
|
33
28
|
"type": "git",
|
|
34
|
-
"url": "git+https://github.com/
|
|
29
|
+
"url": "git+https://github.com/commitguard/cli.git"
|
|
35
30
|
},
|
|
36
31
|
exports: {
|
|
37
32
|
".": "./dist/index.mjs",
|
|
@@ -49,7 +44,8 @@ var package_default = {
|
|
|
49
44
|
"typecheck": "tsc --noEmit",
|
|
50
45
|
"prepublishOnly": "pnpm run build",
|
|
51
46
|
"lint": "eslint .",
|
|
52
|
-
"lint:fix": "eslint . --fix"
|
|
47
|
+
"lint:fix": "eslint . --fix",
|
|
48
|
+
"release": "pnpm typecheck && bumpp --tag --push --publish && npm publish"
|
|
53
49
|
},
|
|
54
50
|
dependencies: {
|
|
55
51
|
"@clack/prompts": "^0.11.0",
|
|
@@ -75,6 +71,277 @@ var package_default = {
|
|
|
75
71
|
}
|
|
76
72
|
};
|
|
77
73
|
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/utils/global.ts
|
|
76
|
+
function createDiffHash(diff) {
|
|
77
|
+
return createHash("md5").update(diff).digest("base64url");
|
|
78
|
+
}
|
|
79
|
+
function addGitLineNumbers(diff) {
|
|
80
|
+
if (!diff.trim()) return diff;
|
|
81
|
+
const lines = diff.split("\n");
|
|
82
|
+
const result = [];
|
|
83
|
+
let oldLine = 0;
|
|
84
|
+
let newLine = 0;
|
|
85
|
+
for (const line of lines) if (line.startsWith("@@")) {
|
|
86
|
+
const match = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
87
|
+
if (match) {
|
|
88
|
+
oldLine = Number.parseInt(match[1], 10);
|
|
89
|
+
newLine = Number.parseInt(match[2], 10);
|
|
90
|
+
}
|
|
91
|
+
result.push(line);
|
|
92
|
+
} else if (line.startsWith("---") || line.startsWith("+++") || line.startsWith("diff ") || line.startsWith("index ")) result.push(line);
|
|
93
|
+
else if (line.startsWith("-")) {
|
|
94
|
+
result.push(`${oldLine}:${line}`);
|
|
95
|
+
oldLine++;
|
|
96
|
+
} else if (line.startsWith("+")) {
|
|
97
|
+
result.push(`${newLine}:${line}`);
|
|
98
|
+
newLine++;
|
|
99
|
+
} else {
|
|
100
|
+
result.push(`${newLine}:${line}`);
|
|
101
|
+
oldLine++;
|
|
102
|
+
newLine++;
|
|
103
|
+
}
|
|
104
|
+
return result.join("\n");
|
|
105
|
+
}
|
|
106
|
+
const MESSAGES = { noGit: "No .git folder found. Run this inside a git repository." };
|
|
107
|
+
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/utils/config.ts
|
|
110
|
+
const MAX_CUSTOM_PROMPT_LENGTH = 500;
|
|
111
|
+
const CONFIG_DIR = join(homedir(), ".commitguard");
|
|
112
|
+
const PROJECTS_CONFIG_PATH = join(CONFIG_DIR, "projects.json");
|
|
113
|
+
let projectsConfigCache = null;
|
|
114
|
+
const GIT_DIR$1 = ".git";
|
|
115
|
+
function ensureConfigDir() {
|
|
116
|
+
if (!existsSync(CONFIG_DIR)) try {
|
|
117
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
118
|
+
} catch (e) {
|
|
119
|
+
consola.error(`Failed to create config directory at ${CONFIG_DIR}: ${e.message}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function getDefaultConfig() {
|
|
123
|
+
return {
|
|
124
|
+
context: "normal",
|
|
125
|
+
checks: {
|
|
126
|
+
security: true,
|
|
127
|
+
performance: true,
|
|
128
|
+
codeQuality: true,
|
|
129
|
+
architecture: true
|
|
130
|
+
},
|
|
131
|
+
severityLevels: {
|
|
132
|
+
critical: true,
|
|
133
|
+
warning: true,
|
|
134
|
+
suggestion: false
|
|
135
|
+
},
|
|
136
|
+
customRule: ""
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
let projectIdCache = null;
|
|
140
|
+
function getProjectId() {
|
|
141
|
+
if (projectIdCache) return projectIdCache;
|
|
142
|
+
try {
|
|
143
|
+
projectIdCache = execFileSync("git", [
|
|
144
|
+
"rev-list",
|
|
145
|
+
"--max-parents=0",
|
|
146
|
+
"HEAD"
|
|
147
|
+
], {
|
|
148
|
+
encoding: "utf8",
|
|
149
|
+
stdio: [
|
|
150
|
+
"pipe",
|
|
151
|
+
"pipe",
|
|
152
|
+
"ignore"
|
|
153
|
+
]
|
|
154
|
+
}).trim().split("\n")[0];
|
|
155
|
+
return projectIdCache;
|
|
156
|
+
} catch {
|
|
157
|
+
consola.error("Warning: Unable to determine project ID. Using current working directory as fallback project ID.");
|
|
158
|
+
projectIdCache = process.cwd();
|
|
159
|
+
return projectIdCache;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function loadProjectsConfig() {
|
|
163
|
+
if (projectsConfigCache) return projectsConfigCache;
|
|
164
|
+
if (existsSync(PROJECTS_CONFIG_PATH)) try {
|
|
165
|
+
const content = readFileSync(PROJECTS_CONFIG_PATH, "utf8");
|
|
166
|
+
projectsConfigCache = JSON.parse(content);
|
|
167
|
+
return projectsConfigCache;
|
|
168
|
+
} catch {
|
|
169
|
+
consola.warn("Failed to parse projects config");
|
|
170
|
+
}
|
|
171
|
+
projectsConfigCache = {};
|
|
172
|
+
return projectsConfigCache;
|
|
173
|
+
}
|
|
174
|
+
function saveProjectsConfig(projects) {
|
|
175
|
+
try {
|
|
176
|
+
ensureConfigDir();
|
|
177
|
+
writeFileSync(PROJECTS_CONFIG_PATH, JSON.stringify(projects, null, 2));
|
|
178
|
+
projectsConfigCache = projects;
|
|
179
|
+
} catch (e) {
|
|
180
|
+
consola.error(`Failed to save projects config: ${e.message}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function loadConfig() {
|
|
184
|
+
const projectId = getProjectId();
|
|
185
|
+
return loadProjectsConfig()[projectId] || getDefaultConfig();
|
|
186
|
+
}
|
|
187
|
+
async function manageConfig() {
|
|
188
|
+
if (!existsSync(GIT_DIR$1)) {
|
|
189
|
+
cancel(MESSAGES.noGit);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const projectId = getProjectId();
|
|
193
|
+
const currentConfig = loadConfig();
|
|
194
|
+
intro(`CommitGuard Configuration`);
|
|
195
|
+
const enabledChecks = await multiselect({
|
|
196
|
+
message: "Select enabled checks for this project:",
|
|
197
|
+
options: [
|
|
198
|
+
{
|
|
199
|
+
value: "security",
|
|
200
|
+
label: "Security"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
value: "performance",
|
|
204
|
+
label: "Performance"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
value: "codeQuality",
|
|
208
|
+
label: "Code Quality"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
value: "architecture",
|
|
212
|
+
label: "Architecture"
|
|
213
|
+
}
|
|
214
|
+
],
|
|
215
|
+
initialValues: Object.entries(currentConfig.checks).filter(([_, enabled]) => enabled).map(([key]) => key)
|
|
216
|
+
});
|
|
217
|
+
if (isCancel(enabledChecks)) {
|
|
218
|
+
cancel("Configuration cancelled");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const enabledSeverity = await multiselect({
|
|
222
|
+
message: "Select severity levels for enabled checks:",
|
|
223
|
+
options: [
|
|
224
|
+
{
|
|
225
|
+
value: "suggestion",
|
|
226
|
+
label: "Suggestion"
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
value: "warning",
|
|
230
|
+
label: "Warning"
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
value: "critical",
|
|
234
|
+
label: "Critical"
|
|
235
|
+
}
|
|
236
|
+
],
|
|
237
|
+
initialValues: Object.entries(currentConfig.severityLevels).filter(([_, enabled]) => enabled).map(([key]) => key)
|
|
238
|
+
});
|
|
239
|
+
if (isCancel(enabledSeverity)) {
|
|
240
|
+
cancel("Configuration cancelled");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const contextLevel = await select({
|
|
244
|
+
message: "Select context level for analysis:",
|
|
245
|
+
options: [{
|
|
246
|
+
value: "minimal",
|
|
247
|
+
label: "Minimal (Just Actual Changes)"
|
|
248
|
+
}, {
|
|
249
|
+
value: "normal",
|
|
250
|
+
label: "Normal (Actual Changes + Context Lines)"
|
|
251
|
+
}],
|
|
252
|
+
initialValue: currentConfig.context
|
|
253
|
+
});
|
|
254
|
+
if (isCancel(contextLevel)) {
|
|
255
|
+
cancel("Configuration cancelled");
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
let customRule = currentConfig.customRule;
|
|
259
|
+
if (currentConfig.customRule) {
|
|
260
|
+
log.info(`Current custom rule: ${currentConfig.customRule}`);
|
|
261
|
+
const editCustomRule = await confirm({
|
|
262
|
+
message: "Would you like to edit the custom rule? (Currently only available to pro users)",
|
|
263
|
+
initialValue: false
|
|
264
|
+
});
|
|
265
|
+
if (isCancel(editCustomRule)) {
|
|
266
|
+
cancel("Configuration cancelled");
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (editCustomRule) {
|
|
270
|
+
const newCustomRule = await text({
|
|
271
|
+
message: "Enter new custom rule (leave empty to remove):",
|
|
272
|
+
initialValue: currentConfig.customRule,
|
|
273
|
+
validate: (value) => {
|
|
274
|
+
const val = String(value).trim();
|
|
275
|
+
if (!val) return void 0;
|
|
276
|
+
if (val.length > MAX_CUSTOM_PROMPT_LENGTH) return `Custom rule must be ${MAX_CUSTOM_PROMPT_LENGTH} characters or less (current: ${val.length})`;
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
if (isCancel(newCustomRule)) {
|
|
280
|
+
cancel("Configuration cancelled");
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
customRule = String(newCustomRule).trim();
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
const addCustomRule = await confirm({
|
|
287
|
+
message: "Would you like to add a custom rule for this project? (Currently only available to pro users)",
|
|
288
|
+
initialValue: false
|
|
289
|
+
});
|
|
290
|
+
if (isCancel(addCustomRule)) {
|
|
291
|
+
cancel("Configuration cancelled");
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (addCustomRule) {
|
|
295
|
+
const newCustomRule = await text({
|
|
296
|
+
message: "Enter custom rule (leave empty to skip):",
|
|
297
|
+
placeholder: "e.g., Check for proper error handling in async functions",
|
|
298
|
+
validate: (value) => {
|
|
299
|
+
const val = String(value).trim();
|
|
300
|
+
if (!val) return void 0;
|
|
301
|
+
if (val.length > MAX_CUSTOM_PROMPT_LENGTH) return `Custom rule must be ${MAX_CUSTOM_PROMPT_LENGTH} characters or less (current: ${val.length})`;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
if (isCancel(newCustomRule)) {
|
|
305
|
+
cancel("Configuration cancelled");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
customRule = String(newCustomRule).trim();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const newConfig = {
|
|
312
|
+
context: contextLevel,
|
|
313
|
+
checks: {
|
|
314
|
+
security: enabledChecks.includes("security"),
|
|
315
|
+
performance: enabledChecks.includes("performance"),
|
|
316
|
+
codeQuality: enabledChecks.includes("codeQuality"),
|
|
317
|
+
architecture: enabledChecks.includes("architecture")
|
|
318
|
+
},
|
|
319
|
+
severityLevels: {
|
|
320
|
+
suggestion: enabledSeverity.includes("suggestion"),
|
|
321
|
+
warning: enabledSeverity.includes("warning"),
|
|
322
|
+
critical: enabledSeverity.includes("critical")
|
|
323
|
+
},
|
|
324
|
+
customRule
|
|
325
|
+
};
|
|
326
|
+
if (JSON.stringify(newConfig) === JSON.stringify(currentConfig)) {
|
|
327
|
+
outro("No changes made to the configuration.");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const confirmUpdate = await confirm({ message: "Save this configuration?" });
|
|
331
|
+
if (isCancel(confirmUpdate)) {
|
|
332
|
+
cancel("Configuration cancelled");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (!confirmUpdate) {
|
|
336
|
+
outro("Configuration not saved.");
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const projects = loadProjectsConfig();
|
|
340
|
+
projects[projectId] = newConfig;
|
|
341
|
+
saveProjectsConfig(projects);
|
|
342
|
+
outro("✓ Configuration updated for this project!");
|
|
343
|
+
}
|
|
344
|
+
|
|
78
345
|
//#endregion
|
|
79
346
|
//#region src/data/ignore.json
|
|
80
347
|
var ignore = [
|
|
@@ -170,77 +437,44 @@ var ignore = [
|
|
|
170
437
|
"*.ico",
|
|
171
438
|
"*.mp4",
|
|
172
439
|
"*.webm",
|
|
173
|
-
"*.mov",
|
|
174
|
-
"*.mp3",
|
|
175
|
-
"*.wav",
|
|
176
|
-
"*.ogg",
|
|
177
|
-
"*.flac",
|
|
178
|
-
"*.snap",
|
|
179
|
-
"__snapshots__/**",
|
|
180
|
-
"playwright-report/**",
|
|
181
|
-
"test-results/**",
|
|
182
|
-
".nyc_output/**",
|
|
183
|
-
"docs/.vitepress/dist/**",
|
|
184
|
-
"storybook-static/**",
|
|
185
|
-
".DS_Store",
|
|
186
|
-
"Thumbs.db",
|
|
187
|
-
".idea/**",
|
|
188
|
-
".vscode/**",
|
|
189
|
-
"*.swp",
|
|
190
|
-
"*.dump",
|
|
191
|
-
"*.dmp",
|
|
192
|
-
"coverage/**",
|
|
193
|
-
"tmp/**",
|
|
194
|
-
"temp/**",
|
|
195
|
-
"*.lock",
|
|
196
|
-
"*.sqlite",
|
|
197
|
-
"*.db",
|
|
198
|
-
"*.rdb"
|
|
199
|
-
];
|
|
200
|
-
|
|
201
|
-
//#endregion
|
|
202
|
-
//#region src/utils/global.ts
|
|
203
|
-
function createDiffHash(diff) {
|
|
204
|
-
return createHash("md5").update(diff).digest("base64url");
|
|
205
|
-
}
|
|
206
|
-
function addGitLineNumbers(diff) {
|
|
207
|
-
if (!diff.trim()) return diff;
|
|
208
|
-
const lines = diff.split("\n");
|
|
209
|
-
const result = [];
|
|
210
|
-
let oldLine = 0;
|
|
211
|
-
let newLine = 0;
|
|
212
|
-
for (const line of lines) if (line.startsWith("@@")) {
|
|
213
|
-
const match = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
214
|
-
if (match) {
|
|
215
|
-
oldLine = Number.parseInt(match[1], 10);
|
|
216
|
-
newLine = Number.parseInt(match[2], 10);
|
|
217
|
-
}
|
|
218
|
-
result.push(line);
|
|
219
|
-
} else if (line.startsWith("---") || line.startsWith("+++") || line.startsWith("diff ") || line.startsWith("index ")) result.push(line);
|
|
220
|
-
else if (line.startsWith("-")) {
|
|
221
|
-
result.push(`${oldLine}:${line}`);
|
|
222
|
-
oldLine++;
|
|
223
|
-
} else if (line.startsWith("+")) {
|
|
224
|
-
result.push(`${newLine}:${line}`);
|
|
225
|
-
newLine++;
|
|
226
|
-
} else {
|
|
227
|
-
result.push(`${newLine}:${line}`);
|
|
228
|
-
oldLine++;
|
|
229
|
-
newLine++;
|
|
230
|
-
}
|
|
231
|
-
return result.join("\n");
|
|
232
|
-
}
|
|
233
|
-
const MESSAGES = { noGit: "No .git folder found. Run this inside a git repository." };
|
|
440
|
+
"*.mov",
|
|
441
|
+
"*.mp3",
|
|
442
|
+
"*.wav",
|
|
443
|
+
"*.ogg",
|
|
444
|
+
"*.flac",
|
|
445
|
+
"*.snap",
|
|
446
|
+
"__snapshots__/**",
|
|
447
|
+
"playwright-report/**",
|
|
448
|
+
"test-results/**",
|
|
449
|
+
".nyc_output/**",
|
|
450
|
+
"docs/.vitepress/dist/**",
|
|
451
|
+
"storybook-static/**",
|
|
452
|
+
".DS_Store",
|
|
453
|
+
"Thumbs.db",
|
|
454
|
+
".idea/**",
|
|
455
|
+
".vscode/**",
|
|
456
|
+
"*.swp",
|
|
457
|
+
"*.dump",
|
|
458
|
+
"*.dmp",
|
|
459
|
+
"coverage/**",
|
|
460
|
+
"tmp/**",
|
|
461
|
+
"temp/**",
|
|
462
|
+
"*.lock",
|
|
463
|
+
"*.sqlite",
|
|
464
|
+
"*.db",
|
|
465
|
+
"*.rdb"
|
|
466
|
+
];
|
|
234
467
|
|
|
235
468
|
//#endregion
|
|
236
469
|
//#region src/utils/git.ts
|
|
237
|
-
function getStagedDiff() {
|
|
470
|
+
function getStagedDiff(context) {
|
|
471
|
+
const gitContextCommand = context === "minimal" ? [] : ["--function-context"];
|
|
238
472
|
try {
|
|
239
473
|
return addGitLineNumbers(execFileSync("git", [
|
|
240
474
|
"diff",
|
|
241
475
|
"--cached",
|
|
242
476
|
"--no-color",
|
|
243
|
-
|
|
477
|
+
...gitContextCommand,
|
|
244
478
|
"--diff-algorithm=histogram",
|
|
245
479
|
"--diff-filter=AMC",
|
|
246
480
|
"--",
|
|
@@ -259,14 +493,15 @@ function getStagedDiff() {
|
|
|
259
493
|
return "";
|
|
260
494
|
}
|
|
261
495
|
}
|
|
262
|
-
function getLastDiff() {
|
|
496
|
+
function getLastDiff(context) {
|
|
497
|
+
const gitContextCommand = context === "minimal" ? [] : ["--function-context"];
|
|
263
498
|
try {
|
|
264
499
|
return execFileSync("git", [
|
|
265
500
|
"diff",
|
|
266
501
|
"HEAD~1",
|
|
267
502
|
"HEAD",
|
|
268
503
|
"--no-color",
|
|
269
|
-
|
|
504
|
+
...gitContextCommand,
|
|
270
505
|
"--diff-algorithm=histogram",
|
|
271
506
|
"--diff-filter=AMC",
|
|
272
507
|
"--",
|
|
@@ -351,274 +586,75 @@ async function manageGlobalKey() {
|
|
|
351
586
|
|
|
352
587
|
//#endregion
|
|
353
588
|
//#region src/utils/api.ts
|
|
354
|
-
|
|
589
|
+
const DEFAULT_TIMEOUT = 2e4;
|
|
590
|
+
async function sendToCommitGuard(diff, eslint, config$1) {
|
|
355
591
|
const apiKey = process.env.COMMITGUARD_API_KEY || getGlobalKey() || null;
|
|
356
592
|
if (!apiKey) throw new Error("No API key found. Set one globally with \"commitguard keys\" or add COMMITGUARD_API_KEY to your .env file. Get your free API key at https://commitguard.ai");
|
|
357
593
|
const apiUrl = process.env.COMMITGUARD_API_URL || "https://api.commitguard.ai/v1/analyze";
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
"
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
594
|
+
const controller = new AbortController();
|
|
595
|
+
const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);
|
|
596
|
+
try {
|
|
597
|
+
const response = await fetch(apiUrl, {
|
|
598
|
+
method: "POST",
|
|
599
|
+
headers: {
|
|
600
|
+
"Content-Type": "application/json",
|
|
601
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
602
|
+
"User-Agent": "commitguard-cli"
|
|
603
|
+
},
|
|
604
|
+
body: JSON.stringify({
|
|
605
|
+
diff,
|
|
606
|
+
eslint,
|
|
607
|
+
config: config$1
|
|
608
|
+
}),
|
|
609
|
+
signal: controller.signal
|
|
610
|
+
});
|
|
611
|
+
clearTimeout(timeoutId);
|
|
612
|
+
if (!response.ok) {
|
|
613
|
+
const errorText = await response.text();
|
|
614
|
+
let errorMessage = "Failed to analyze commit";
|
|
615
|
+
if (response.status === 401) errorMessage = "Invalid API key. Check your key with \"commitguard keys\" or get a new one at https://commitguard.ai";
|
|
616
|
+
else if (response.status === 429) errorMessage = "Rate limit exceeded. Please try again later";
|
|
617
|
+
else if (response.status === 500) errorMessage = "CommitGuard service error. Please try again later";
|
|
618
|
+
else if (response.status >= 400 && response.status < 500) errorMessage = `Request error: ${errorText || "Invalid request"}`;
|
|
619
|
+
else errorMessage = `Service unavailable (${response.status}). Please try again later`;
|
|
620
|
+
throw new Error(errorMessage);
|
|
621
|
+
}
|
|
622
|
+
return await response.json();
|
|
623
|
+
} catch (error) {
|
|
624
|
+
clearTimeout(timeoutId);
|
|
625
|
+
if (error instanceof Error && error.name === "AbortError") throw new Error(`Request timed out after ${DEFAULT_TIMEOUT}ms`);
|
|
626
|
+
throw error;
|
|
380
627
|
}
|
|
381
|
-
return await response.json();
|
|
382
628
|
}
|
|
383
629
|
async function bypassCommitGuard() {
|
|
384
630
|
const apiKey = process.env.COMMITGUARD_API_KEY || getGlobalKey() || null;
|
|
385
631
|
if (!apiKey) throw new Error("No API key found. Set one globally with \"commitguard keys\" or add COMMITGUARD_API_KEY to your .env file. Get your free API key at https://commitguard.ai");
|
|
386
632
|
const apiUrl = process.env.COMMITGUARD_API_BYPASS_URL || "https://api.commitguard.ai/v1/bypass";
|
|
387
|
-
const diff = getLastDiff();
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
headers: {
|
|
391
|
-
"Content-Type": "application/json",
|
|
392
|
-
"Authorization": `Bearer ${apiKey}`,
|
|
393
|
-
"User-Agent": "commitguard-cli"
|
|
394
|
-
},
|
|
395
|
-
body: JSON.stringify({ diff })
|
|
396
|
-
});
|
|
397
|
-
if (!response.ok) {
|
|
398
|
-
const errorText = await response.text();
|
|
399
|
-
throw new Error(`API request failed (${response.status}): ${errorText}`);
|
|
400
|
-
}
|
|
401
|
-
return await response.json();
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
//#endregion
|
|
405
|
-
//#region src/utils/config.ts
|
|
406
|
-
const MAX_CUSTOM_PROMPT_LENGTH = 500;
|
|
407
|
-
const CONFIG_DIR = join(homedir(), ".commitguard");
|
|
408
|
-
const PROJECTS_CONFIG_PATH = join(CONFIG_DIR, "projects.json");
|
|
409
|
-
let projectsConfigCache = null;
|
|
410
|
-
const GIT_DIR$1 = ".git";
|
|
411
|
-
function ensureConfigDir() {
|
|
412
|
-
if (!existsSync(CONFIG_DIR)) try {
|
|
413
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
414
|
-
} catch (e) {
|
|
415
|
-
consola.error(`Failed to create config directory at ${CONFIG_DIR}: ${e.message}`);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
function getDefaultConfig() {
|
|
419
|
-
return {
|
|
420
|
-
checks: {
|
|
421
|
-
security: true,
|
|
422
|
-
performance: true,
|
|
423
|
-
codeQuality: true,
|
|
424
|
-
architecture: true
|
|
425
|
-
},
|
|
426
|
-
severityLevels: {
|
|
427
|
-
critical: true,
|
|
428
|
-
warning: true,
|
|
429
|
-
suggestion: false
|
|
430
|
-
},
|
|
431
|
-
customRule: ""
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
let projectIdCache = null;
|
|
435
|
-
function getProjectId() {
|
|
436
|
-
if (projectIdCache) return projectIdCache;
|
|
437
|
-
try {
|
|
438
|
-
projectIdCache = execFileSync("git", [
|
|
439
|
-
"rev-list",
|
|
440
|
-
"--max-parents=0",
|
|
441
|
-
"HEAD"
|
|
442
|
-
], {
|
|
443
|
-
encoding: "utf8",
|
|
444
|
-
stdio: [
|
|
445
|
-
"pipe",
|
|
446
|
-
"pipe",
|
|
447
|
-
"ignore"
|
|
448
|
-
]
|
|
449
|
-
}).trim().split("\n")[0];
|
|
450
|
-
return projectIdCache;
|
|
451
|
-
} catch {
|
|
452
|
-
consola.error("Warning: Unable to determine project ID. Using current working directory as fallback project ID.");
|
|
453
|
-
projectIdCache = process.cwd();
|
|
454
|
-
return projectIdCache;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
function loadProjectsConfig() {
|
|
458
|
-
if (projectsConfigCache) return projectsConfigCache;
|
|
459
|
-
if (existsSync(PROJECTS_CONFIG_PATH)) try {
|
|
460
|
-
const content = readFileSync(PROJECTS_CONFIG_PATH, "utf8");
|
|
461
|
-
projectsConfigCache = JSON.parse(content);
|
|
462
|
-
return projectsConfigCache;
|
|
463
|
-
} catch {
|
|
464
|
-
consola.warn("Failed to parse projects config");
|
|
465
|
-
}
|
|
466
|
-
projectsConfigCache = {};
|
|
467
|
-
return projectsConfigCache;
|
|
468
|
-
}
|
|
469
|
-
function saveProjectsConfig(projects) {
|
|
633
|
+
const diff = getLastDiff(loadConfig().context);
|
|
634
|
+
const controller = new AbortController();
|
|
635
|
+
const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);
|
|
470
636
|
try {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
function loadConfig() {
|
|
479
|
-
const projectId = getProjectId();
|
|
480
|
-
return loadProjectsConfig()[projectId] || getDefaultConfig();
|
|
481
|
-
}
|
|
482
|
-
async function manageConfig() {
|
|
483
|
-
if (!existsSync(GIT_DIR$1)) {
|
|
484
|
-
cancel(MESSAGES.noGit);
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
const projectId = getProjectId();
|
|
488
|
-
const currentConfig = loadConfig();
|
|
489
|
-
intro(`CommitGuard Configuration`);
|
|
490
|
-
const enabledChecks = await multiselect({
|
|
491
|
-
message: "Select enabled checks for this project:",
|
|
492
|
-
options: [
|
|
493
|
-
{
|
|
494
|
-
value: "security",
|
|
495
|
-
label: "Security"
|
|
496
|
-
},
|
|
497
|
-
{
|
|
498
|
-
value: "performance",
|
|
499
|
-
label: "Performance"
|
|
500
|
-
},
|
|
501
|
-
{
|
|
502
|
-
value: "codeQuality",
|
|
503
|
-
label: "Code Quality"
|
|
504
|
-
},
|
|
505
|
-
{
|
|
506
|
-
value: "architecture",
|
|
507
|
-
label: "Architecture"
|
|
508
|
-
}
|
|
509
|
-
],
|
|
510
|
-
initialValues: Object.entries(currentConfig.checks).filter(([_, enabled]) => enabled).map(([key]) => key)
|
|
511
|
-
});
|
|
512
|
-
if (isCancel(enabledChecks)) {
|
|
513
|
-
cancel("Configuration cancelled");
|
|
514
|
-
return;
|
|
515
|
-
}
|
|
516
|
-
const enabledSeverity = await multiselect({
|
|
517
|
-
message: "Select severity levels for enabled checks:",
|
|
518
|
-
options: [
|
|
519
|
-
{
|
|
520
|
-
value: "suggestion",
|
|
521
|
-
label: "Suggestion"
|
|
522
|
-
},
|
|
523
|
-
{
|
|
524
|
-
value: "warning",
|
|
525
|
-
label: "Warning"
|
|
637
|
+
const response = await fetch(apiUrl, {
|
|
638
|
+
method: "POST",
|
|
639
|
+
headers: {
|
|
640
|
+
"Content-Type": "application/json",
|
|
641
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
642
|
+
"User-Agent": "commitguard-cli"
|
|
526
643
|
},
|
|
527
|
-
{
|
|
528
|
-
|
|
529
|
-
label: "Critical"
|
|
530
|
-
}
|
|
531
|
-
],
|
|
532
|
-
initialValues: Object.entries(currentConfig.severityLevels).filter(([_, enabled]) => enabled).map(([key]) => key)
|
|
533
|
-
});
|
|
534
|
-
if (isCancel(enabledSeverity)) {
|
|
535
|
-
cancel("Configuration cancelled");
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
|
-
let customRule = currentConfig.customRule;
|
|
539
|
-
if (currentConfig.customRule) {
|
|
540
|
-
consola.info(`Current custom rule: ${currentConfig.customRule}`);
|
|
541
|
-
const editCustomRule = await confirm({
|
|
542
|
-
message: "Would you like to edit the custom rule? (Currently only available to pro users)",
|
|
543
|
-
initialValue: false
|
|
544
|
-
});
|
|
545
|
-
if (isCancel(editCustomRule)) {
|
|
546
|
-
cancel("Configuration cancelled");
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
if (editCustomRule) {
|
|
550
|
-
const newCustomRule = await text({
|
|
551
|
-
message: "Enter new custom rule (leave empty to remove):",
|
|
552
|
-
initialValue: currentConfig.customRule,
|
|
553
|
-
validate: (value) => {
|
|
554
|
-
const val = String(value).trim();
|
|
555
|
-
if (!val) return void 0;
|
|
556
|
-
if (val.length > MAX_CUSTOM_PROMPT_LENGTH) return `Custom rule must be ${MAX_CUSTOM_PROMPT_LENGTH} characters or less (current: ${val.length})`;
|
|
557
|
-
}
|
|
558
|
-
});
|
|
559
|
-
if (isCancel(newCustomRule)) {
|
|
560
|
-
cancel("Configuration cancelled");
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
customRule = String(newCustomRule).trim();
|
|
564
|
-
}
|
|
565
|
-
} else {
|
|
566
|
-
const addCustomRule = await confirm({
|
|
567
|
-
message: "Would you like to add a custom rule for this project? (Currently only available to pro users)",
|
|
568
|
-
initialValue: false
|
|
644
|
+
body: JSON.stringify({ diff }),
|
|
645
|
+
signal: controller.signal
|
|
569
646
|
});
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
if (addCustomRule) {
|
|
575
|
-
const newCustomRule = await text({
|
|
576
|
-
message: "Enter custom rule (leave empty to skip):",
|
|
577
|
-
placeholder: "e.g., Check for proper error handling in async functions",
|
|
578
|
-
validate: (value) => {
|
|
579
|
-
const val = String(value).trim();
|
|
580
|
-
if (!val) return void 0;
|
|
581
|
-
if (val.length > MAX_CUSTOM_PROMPT_LENGTH) return `Custom rule must be ${MAX_CUSTOM_PROMPT_LENGTH} characters or less (current: ${val.length})`;
|
|
582
|
-
}
|
|
583
|
-
});
|
|
584
|
-
if (isCancel(newCustomRule)) {
|
|
585
|
-
cancel("Configuration cancelled");
|
|
586
|
-
return;
|
|
587
|
-
}
|
|
588
|
-
customRule = String(newCustomRule).trim();
|
|
647
|
+
clearTimeout(timeoutId);
|
|
648
|
+
if (!response.ok) {
|
|
649
|
+
const errorText = await response.text();
|
|
650
|
+
throw new Error(`API request failed (${response.status}): ${errorText}`);
|
|
589
651
|
}
|
|
652
|
+
return await response.json();
|
|
653
|
+
} catch (error) {
|
|
654
|
+
clearTimeout(timeoutId);
|
|
655
|
+
if (error instanceof Error && error.name === "AbortError") throw new Error(`Request timed out after ${DEFAULT_TIMEOUT}ms`);
|
|
656
|
+
throw error;
|
|
590
657
|
}
|
|
591
|
-
const newConfig = {
|
|
592
|
-
checks: {
|
|
593
|
-
security: enabledChecks.includes("security"),
|
|
594
|
-
performance: enabledChecks.includes("performance"),
|
|
595
|
-
codeQuality: enabledChecks.includes("codeQuality"),
|
|
596
|
-
architecture: enabledChecks.includes("architecture")
|
|
597
|
-
},
|
|
598
|
-
severityLevels: {
|
|
599
|
-
suggestion: enabledSeverity.includes("suggestion"),
|
|
600
|
-
warning: enabledSeverity.includes("warning"),
|
|
601
|
-
critical: enabledSeverity.includes("critical")
|
|
602
|
-
},
|
|
603
|
-
customRule
|
|
604
|
-
};
|
|
605
|
-
if (JSON.stringify(newConfig) === JSON.stringify(currentConfig)) {
|
|
606
|
-
outro("No changes made to the configuration.");
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
const confirmUpdate = await confirm({ message: "Save this configuration?" });
|
|
610
|
-
if (isCancel(confirmUpdate)) {
|
|
611
|
-
cancel("Configuration cancelled");
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
if (!confirmUpdate) {
|
|
615
|
-
outro("Configuration not saved.");
|
|
616
|
-
return;
|
|
617
|
-
}
|
|
618
|
-
const projects = loadProjectsConfig();
|
|
619
|
-
projects[projectId] = newConfig;
|
|
620
|
-
saveProjectsConfig(projects);
|
|
621
|
-
outro("✓ Configuration updated for this project!");
|
|
622
658
|
}
|
|
623
659
|
|
|
624
660
|
//#endregion
|
|
@@ -680,13 +716,13 @@ async function getEslintRules({ startDir = process.cwd(), overrideCache = false
|
|
|
680
716
|
} catch {}
|
|
681
717
|
return null;
|
|
682
718
|
});
|
|
683
|
-
const config = (await Promise.all(loaders)).find((r) => r !== null) ?? {
|
|
719
|
+
const config$1 = (await Promise.all(loaders)).find((r) => r !== null) ?? {
|
|
684
720
|
rules: {},
|
|
685
721
|
source: null
|
|
686
722
|
};
|
|
687
|
-
cache.setKey(cacheKey, config);
|
|
723
|
+
cache.setKey(cacheKey, config$1);
|
|
688
724
|
cache.save(true);
|
|
689
|
-
return config;
|
|
725
|
+
return config$1;
|
|
690
726
|
}
|
|
691
727
|
|
|
692
728
|
//#endregion
|
|
@@ -741,7 +777,7 @@ async function installHooks() {
|
|
|
741
777
|
}
|
|
742
778
|
log.info("Installing CommitGuard...");
|
|
743
779
|
const node = process.execPath.replace(/\\/g, "/");
|
|
744
|
-
const cliPath =
|
|
780
|
+
const cliPath = fileURLToPath(import.meta.url).replace(/\\/g, "/");
|
|
745
781
|
writeFileSync(COMMIT_MSG_HOOK_PATH, `#!/bin/sh
|
|
746
782
|
${COMMITGUARD_MARKER}
|
|
747
783
|
# Auto-generated - do not edit manually
|
|
@@ -790,9 +826,14 @@ fi
|
|
|
790
826
|
|
|
791
827
|
exit 0
|
|
792
828
|
`, { mode: 493 });
|
|
793
|
-
log.info("
|
|
794
|
-
|
|
795
|
-
|
|
829
|
+
log.info("Checking ESLint configuration...");
|
|
830
|
+
try {
|
|
831
|
+
const eslintRules = await getEslintRules({ overrideCache: true });
|
|
832
|
+
if (Object.keys(eslintRules.rules).length > 0) log.success("ESLint configuration loaded.");
|
|
833
|
+
else log.info("No ESLint rules detected.");
|
|
834
|
+
} catch {
|
|
835
|
+
log.warn("Failed to load ESLint configuration.");
|
|
836
|
+
}
|
|
796
837
|
if (!getGlobalKey() && process.env.COMMITGUARD_API_KEY === void 0) {
|
|
797
838
|
if (await confirm({
|
|
798
839
|
message: "No global API key found. Do you want to set it now?",
|
|
@@ -844,6 +885,7 @@ async function removeHooks() {
|
|
|
844
885
|
//#endregion
|
|
845
886
|
//#region src/utils/staged.ts
|
|
846
887
|
const CACHE_PATH = join(".git", "commitguard-cache.json");
|
|
888
|
+
const config = loadConfig();
|
|
847
889
|
const CATEGORY_LABELS = {
|
|
848
890
|
security: "🚨 [SECURITY]",
|
|
849
891
|
performance: "🚀 [PERFORMANCE]",
|
|
@@ -902,7 +944,7 @@ function groupIssuesByFile(issues = []) {
|
|
|
902
944
|
};
|
|
903
945
|
}
|
|
904
946
|
async function onStaged() {
|
|
905
|
-
const diff = getStagedDiff();
|
|
947
|
+
const diff = getStagedDiff(config.context);
|
|
906
948
|
if (!diff.trim()) {
|
|
907
949
|
clearCache();
|
|
908
950
|
return;
|
|
@@ -911,7 +953,6 @@ async function onStaged() {
|
|
|
911
953
|
const existingCache = readCache();
|
|
912
954
|
if (existingCache && existingCache.hash === diffHash) return;
|
|
913
955
|
try {
|
|
914
|
-
const config = loadConfig();
|
|
915
956
|
const response = await sendToCommitGuard(diff, (await getEslintRules()).rules, config);
|
|
916
957
|
writeCache({
|
|
917
958
|
hash: diffHash,
|
|
@@ -924,7 +965,7 @@ async function onStaged() {
|
|
|
924
965
|
}
|
|
925
966
|
}
|
|
926
967
|
function getCachedAnalysis(diff, diffHash) {
|
|
927
|
-
const effectiveDiff = diff ?? getStagedDiff();
|
|
968
|
+
const effectiveDiff = diff ?? getStagedDiff(config.context);
|
|
928
969
|
if (!effectiveDiff.trim()) return {
|
|
929
970
|
analysis: {
|
|
930
971
|
status: "pass",
|
|
@@ -943,7 +984,7 @@ function getCachedAnalysis(diff, diffHash) {
|
|
|
943
984
|
};
|
|
944
985
|
}
|
|
945
986
|
async function validateCommit() {
|
|
946
|
-
const diff = getStagedDiff();
|
|
987
|
+
const diff = getStagedDiff(config.context);
|
|
947
988
|
const diffHash = diff.trim() ? createDiffHash(diff) : "";
|
|
948
989
|
const cached = getCachedAnalysis(diff, diffHash);
|
|
949
990
|
if (!cached) {
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commitguard/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.14",
|
|
5
5
|
"description": "AI-powered git commit checker that blocks bad code before it ships",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/
|
|
9
|
+
"url": "git+https://github.com/commitguard/cli.git"
|
|
10
10
|
},
|
|
11
11
|
"exports": {
|
|
12
12
|
".": "./dist/index.mjs",
|
|
@@ -21,6 +21,16 @@
|
|
|
21
21
|
"files": [
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsdown",
|
|
26
|
+
"dev": "tsdown --watch",
|
|
27
|
+
"test": "vitest",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"prepublishOnly": "pnpm run build",
|
|
30
|
+
"lint": "eslint .",
|
|
31
|
+
"lint:fix": "eslint . --fix",
|
|
32
|
+
"release": "pnpm typecheck && bumpp --tag --push --publish && npm publish"
|
|
33
|
+
},
|
|
24
34
|
"dependencies": {
|
|
25
35
|
"@clack/prompts": "^0.11.0",
|
|
26
36
|
"@napi-rs/keyring": "^1.2.0",
|
|
@@ -42,13 +52,5 @@
|
|
|
42
52
|
"tsdown": "^0.18.3",
|
|
43
53
|
"typescript": "^5.9.3",
|
|
44
54
|
"vitest": "^4.0.16"
|
|
45
|
-
},
|
|
46
|
-
"scripts": {
|
|
47
|
-
"build": "tsdown",
|
|
48
|
-
"dev": "tsdown --watch",
|
|
49
|
-
"test": "vitest",
|
|
50
|
-
"typecheck": "tsc --noEmit",
|
|
51
|
-
"lint": "eslint .",
|
|
52
|
-
"lint:fix": "eslint . --fix"
|
|
53
55
|
}
|
|
54
|
-
}
|
|
56
|
+
}
|