@commitguard/cli 0.0.14 → 0.0.15
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/dist/index.mjs +268 -287
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -3,12 +3,12 @@ import process from "node:process";
|
|
|
3
3
|
import { consola } from "consola";
|
|
4
4
|
import updateNotifier from "update-notifier";
|
|
5
5
|
import { execFileSync, execSync } from "node:child_process";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { cancel, confirm, intro, isCancel, log, multiselect, note, outro, text } from "@clack/prompts";
|
|
8
|
+
import { Entry } from "@napi-rs/keyring";
|
|
6
9
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
10
|
import { homedir } from "node:os";
|
|
8
11
|
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
12
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
13
13
|
import { readFile } from "node:fs/promises";
|
|
14
14
|
import { findUp } from "find-up";
|
|
@@ -17,7 +17,7 @@ import stringWidth from "string-width";
|
|
|
17
17
|
import "dotenv/config";
|
|
18
18
|
|
|
19
19
|
//#region package.json
|
|
20
|
-
var version = "0.0.
|
|
20
|
+
var version = "0.0.15";
|
|
21
21
|
var package_default = {
|
|
22
22
|
name: "@commitguard/cli",
|
|
23
23
|
type: "module",
|
|
@@ -68,279 +68,8 @@ var package_default = {
|
|
|
68
68
|
"tsdown": "^0.18.3",
|
|
69
69
|
"typescript": "^5.9.3",
|
|
70
70
|
"vitest": "^4.0.16"
|
|
71
|
-
}
|
|
72
|
-
};
|
|
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
|
-
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
344
73
|
|
|
345
74
|
//#endregion
|
|
346
75
|
//#region src/data/ignore.json
|
|
@@ -465,16 +194,49 @@ var ignore = [
|
|
|
465
194
|
"*.rdb"
|
|
466
195
|
];
|
|
467
196
|
|
|
197
|
+
//#endregion
|
|
198
|
+
//#region src/utils/global.ts
|
|
199
|
+
function createDiffHash(diff) {
|
|
200
|
+
return createHash("md5").update(diff).digest("base64url");
|
|
201
|
+
}
|
|
202
|
+
function addGitLineNumbers(diff) {
|
|
203
|
+
if (!diff.trim()) return diff;
|
|
204
|
+
const lines = diff.split("\n");
|
|
205
|
+
const result = [];
|
|
206
|
+
let oldLine = 0;
|
|
207
|
+
let newLine = 0;
|
|
208
|
+
for (const line of lines) if (line.startsWith("@@")) {
|
|
209
|
+
const match = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
|
210
|
+
if (match) {
|
|
211
|
+
oldLine = Number.parseInt(match[1], 10);
|
|
212
|
+
newLine = Number.parseInt(match[2], 10);
|
|
213
|
+
}
|
|
214
|
+
result.push(line);
|
|
215
|
+
} else if (line.startsWith("---") || line.startsWith("+++") || line.startsWith("diff ") || line.startsWith("index ")) result.push(line);
|
|
216
|
+
else if (line.startsWith("-")) {
|
|
217
|
+
result.push(`${oldLine}:${line}`);
|
|
218
|
+
oldLine++;
|
|
219
|
+
} else if (line.startsWith("+")) {
|
|
220
|
+
result.push(`${newLine}:${line}`);
|
|
221
|
+
newLine++;
|
|
222
|
+
} else {
|
|
223
|
+
result.push(`${newLine}:${line}`);
|
|
224
|
+
oldLine++;
|
|
225
|
+
newLine++;
|
|
226
|
+
}
|
|
227
|
+
return result.join("\n");
|
|
228
|
+
}
|
|
229
|
+
const MESSAGES = { noGit: "No .git folder found. Run this inside a git repository." };
|
|
230
|
+
|
|
468
231
|
//#endregion
|
|
469
232
|
//#region src/utils/git.ts
|
|
470
|
-
function getStagedDiff(
|
|
471
|
-
const gitContextCommand = context === "minimal" ? [] : ["--function-context"];
|
|
233
|
+
function getStagedDiff() {
|
|
472
234
|
try {
|
|
473
235
|
return addGitLineNumbers(execFileSync("git", [
|
|
474
236
|
"diff",
|
|
475
237
|
"--cached",
|
|
476
238
|
"--no-color",
|
|
477
|
-
|
|
239
|
+
"--function-context",
|
|
478
240
|
"--diff-algorithm=histogram",
|
|
479
241
|
"--diff-filter=AMC",
|
|
480
242
|
"--",
|
|
@@ -493,15 +255,14 @@ function getStagedDiff(context) {
|
|
|
493
255
|
return "";
|
|
494
256
|
}
|
|
495
257
|
}
|
|
496
|
-
function getLastDiff(
|
|
497
|
-
const gitContextCommand = context === "minimal" ? [] : ["--function-context"];
|
|
258
|
+
function getLastDiff() {
|
|
498
259
|
try {
|
|
499
260
|
return execFileSync("git", [
|
|
500
261
|
"diff",
|
|
501
262
|
"HEAD~1",
|
|
502
263
|
"HEAD",
|
|
503
264
|
"--no-color",
|
|
504
|
-
|
|
265
|
+
"--function-context",
|
|
505
266
|
"--diff-algorithm=histogram",
|
|
506
267
|
"--diff-filter=AMC",
|
|
507
268
|
"--",
|
|
@@ -630,7 +391,7 @@ async function bypassCommitGuard() {
|
|
|
630
391
|
const apiKey = process.env.COMMITGUARD_API_KEY || getGlobalKey() || null;
|
|
631
392
|
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");
|
|
632
393
|
const apiUrl = process.env.COMMITGUARD_API_BYPASS_URL || "https://api.commitguard.ai/v1/bypass";
|
|
633
|
-
const diff = getLastDiff(
|
|
394
|
+
const diff = getLastDiff();
|
|
634
395
|
const controller = new AbortController();
|
|
635
396
|
const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);
|
|
636
397
|
try {
|
|
@@ -657,6 +418,226 @@ async function bypassCommitGuard() {
|
|
|
657
418
|
}
|
|
658
419
|
}
|
|
659
420
|
|
|
421
|
+
//#endregion
|
|
422
|
+
//#region src/utils/config.ts
|
|
423
|
+
const MAX_CUSTOM_PROMPT_LENGTH = 500;
|
|
424
|
+
const CONFIG_DIR = join(homedir(), ".commitguard");
|
|
425
|
+
const PROJECTS_CONFIG_PATH = join(CONFIG_DIR, "projects.json");
|
|
426
|
+
let projectsConfigCache = null;
|
|
427
|
+
const GIT_DIR$1 = ".git";
|
|
428
|
+
function ensureConfigDir() {
|
|
429
|
+
if (!existsSync(CONFIG_DIR)) try {
|
|
430
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
431
|
+
} catch (e) {
|
|
432
|
+
consola.error(`Failed to create config directory at ${CONFIG_DIR}: ${e.message}`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
function getDefaultConfig() {
|
|
436
|
+
return {
|
|
437
|
+
checks: {
|
|
438
|
+
security: true,
|
|
439
|
+
performance: true,
|
|
440
|
+
codeQuality: true,
|
|
441
|
+
architecture: true
|
|
442
|
+
},
|
|
443
|
+
severityLevels: {
|
|
444
|
+
critical: true,
|
|
445
|
+
warning: true,
|
|
446
|
+
suggestion: false
|
|
447
|
+
},
|
|
448
|
+
customRule: ""
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
let projectIdCache = null;
|
|
452
|
+
function getProjectId() {
|
|
453
|
+
if (projectIdCache) return projectIdCache;
|
|
454
|
+
try {
|
|
455
|
+
projectIdCache = execFileSync("git", [
|
|
456
|
+
"rev-list",
|
|
457
|
+
"--max-parents=0",
|
|
458
|
+
"HEAD"
|
|
459
|
+
], {
|
|
460
|
+
encoding: "utf8",
|
|
461
|
+
stdio: [
|
|
462
|
+
"pipe",
|
|
463
|
+
"pipe",
|
|
464
|
+
"ignore"
|
|
465
|
+
]
|
|
466
|
+
}).trim().split("\n")[0];
|
|
467
|
+
return projectIdCache;
|
|
468
|
+
} catch {
|
|
469
|
+
consola.error("Warning: Unable to determine project ID. Using current working directory as fallback project ID.");
|
|
470
|
+
projectIdCache = process.cwd();
|
|
471
|
+
return projectIdCache;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
function loadProjectsConfig() {
|
|
475
|
+
if (projectsConfigCache) return projectsConfigCache;
|
|
476
|
+
if (existsSync(PROJECTS_CONFIG_PATH)) try {
|
|
477
|
+
const content = readFileSync(PROJECTS_CONFIG_PATH, "utf8");
|
|
478
|
+
projectsConfigCache = JSON.parse(content);
|
|
479
|
+
return projectsConfigCache;
|
|
480
|
+
} catch {
|
|
481
|
+
consola.warn("Failed to parse projects config");
|
|
482
|
+
}
|
|
483
|
+
projectsConfigCache = {};
|
|
484
|
+
return projectsConfigCache;
|
|
485
|
+
}
|
|
486
|
+
function saveProjectsConfig(projects) {
|
|
487
|
+
try {
|
|
488
|
+
ensureConfigDir();
|
|
489
|
+
writeFileSync(PROJECTS_CONFIG_PATH, JSON.stringify(projects, null, 2));
|
|
490
|
+
projectsConfigCache = projects;
|
|
491
|
+
} catch (e) {
|
|
492
|
+
consola.error(`Failed to save projects config: ${e.message}`);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
function loadConfig() {
|
|
496
|
+
const projectId = getProjectId();
|
|
497
|
+
return loadProjectsConfig()[projectId] || getDefaultConfig();
|
|
498
|
+
}
|
|
499
|
+
async function manageConfig() {
|
|
500
|
+
if (!existsSync(GIT_DIR$1)) {
|
|
501
|
+
cancel(MESSAGES.noGit);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const projectId = getProjectId();
|
|
505
|
+
const currentConfig = loadConfig();
|
|
506
|
+
intro(`CommitGuard Configuration`);
|
|
507
|
+
const enabledChecks = await multiselect({
|
|
508
|
+
message: "Select enabled checks for this project:",
|
|
509
|
+
options: [
|
|
510
|
+
{
|
|
511
|
+
value: "security",
|
|
512
|
+
label: "Security"
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
value: "performance",
|
|
516
|
+
label: "Performance"
|
|
517
|
+
},
|
|
518
|
+
{
|
|
519
|
+
value: "codeQuality",
|
|
520
|
+
label: "Code Quality"
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
value: "architecture",
|
|
524
|
+
label: "Architecture"
|
|
525
|
+
}
|
|
526
|
+
],
|
|
527
|
+
initialValues: Object.entries(currentConfig.checks).filter(([_, enabled]) => enabled).map(([key]) => key)
|
|
528
|
+
});
|
|
529
|
+
if (isCancel(enabledChecks)) {
|
|
530
|
+
cancel("Configuration cancelled");
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
const enabledSeverity = await multiselect({
|
|
534
|
+
message: "Select severity levels for enabled checks:",
|
|
535
|
+
options: [
|
|
536
|
+
{
|
|
537
|
+
value: "suggestion",
|
|
538
|
+
label: "Suggestion"
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
value: "warning",
|
|
542
|
+
label: "Warning"
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
value: "critical",
|
|
546
|
+
label: "Critical"
|
|
547
|
+
}
|
|
548
|
+
],
|
|
549
|
+
initialValues: Object.entries(currentConfig.severityLevels).filter(([_, enabled]) => enabled).map(([key]) => key)
|
|
550
|
+
});
|
|
551
|
+
if (isCancel(enabledSeverity)) {
|
|
552
|
+
cancel("Configuration cancelled");
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
let customRule = currentConfig.customRule;
|
|
556
|
+
if (currentConfig.customRule) {
|
|
557
|
+
log.info(`Current custom rule: ${currentConfig.customRule}`);
|
|
558
|
+
const editCustomRule = await confirm({
|
|
559
|
+
message: "Would you like to edit the custom rule? (Currently only available to pro users)",
|
|
560
|
+
initialValue: false
|
|
561
|
+
});
|
|
562
|
+
if (isCancel(editCustomRule)) {
|
|
563
|
+
cancel("Configuration cancelled");
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (editCustomRule) {
|
|
567
|
+
const newCustomRule = await text({
|
|
568
|
+
message: "Enter new custom rule (leave empty to remove):",
|
|
569
|
+
initialValue: currentConfig.customRule,
|
|
570
|
+
validate: (value) => {
|
|
571
|
+
const val = String(value).trim();
|
|
572
|
+
if (!val) return void 0;
|
|
573
|
+
if (val.length > MAX_CUSTOM_PROMPT_LENGTH) return `Custom rule must be ${MAX_CUSTOM_PROMPT_LENGTH} characters or less (current: ${val.length})`;
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
if (isCancel(newCustomRule)) {
|
|
577
|
+
cancel("Configuration cancelled");
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
customRule = String(newCustomRule).trim();
|
|
581
|
+
}
|
|
582
|
+
} else {
|
|
583
|
+
const addCustomRule = await confirm({
|
|
584
|
+
message: "Would you like to add a custom rule for this project? (Currently only available to pro users)",
|
|
585
|
+
initialValue: false
|
|
586
|
+
});
|
|
587
|
+
if (isCancel(addCustomRule)) {
|
|
588
|
+
cancel("Configuration cancelled");
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (addCustomRule) {
|
|
592
|
+
const newCustomRule = await text({
|
|
593
|
+
message: "Enter custom rule (leave empty to skip):",
|
|
594
|
+
placeholder: "e.g., Check for proper error handling in async functions",
|
|
595
|
+
validate: (value) => {
|
|
596
|
+
const val = String(value).trim();
|
|
597
|
+
if (!val) return void 0;
|
|
598
|
+
if (val.length > MAX_CUSTOM_PROMPT_LENGTH) return `Custom rule must be ${MAX_CUSTOM_PROMPT_LENGTH} characters or less (current: ${val.length})`;
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
if (isCancel(newCustomRule)) {
|
|
602
|
+
cancel("Configuration cancelled");
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
customRule = String(newCustomRule).trim();
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
const newConfig = {
|
|
609
|
+
checks: {
|
|
610
|
+
security: enabledChecks.includes("security"),
|
|
611
|
+
performance: enabledChecks.includes("performance"),
|
|
612
|
+
codeQuality: enabledChecks.includes("codeQuality"),
|
|
613
|
+
architecture: enabledChecks.includes("architecture")
|
|
614
|
+
},
|
|
615
|
+
severityLevels: {
|
|
616
|
+
suggestion: enabledSeverity.includes("suggestion"),
|
|
617
|
+
warning: enabledSeverity.includes("warning"),
|
|
618
|
+
critical: enabledSeverity.includes("critical")
|
|
619
|
+
},
|
|
620
|
+
customRule
|
|
621
|
+
};
|
|
622
|
+
if (JSON.stringify(newConfig) === JSON.stringify(currentConfig)) {
|
|
623
|
+
outro("No changes made to the configuration.");
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
const confirmUpdate = await confirm({ message: "Save this configuration?" });
|
|
627
|
+
if (isCancel(confirmUpdate)) {
|
|
628
|
+
cancel("Configuration cancelled");
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
if (!confirmUpdate) {
|
|
632
|
+
outro("Configuration not saved.");
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
const projects = loadProjectsConfig();
|
|
636
|
+
projects[projectId] = newConfig;
|
|
637
|
+
saveProjectsConfig(projects);
|
|
638
|
+
outro("✓ Configuration updated for this project!");
|
|
639
|
+
}
|
|
640
|
+
|
|
660
641
|
//#endregion
|
|
661
642
|
//#region src/utils/eslint.ts
|
|
662
643
|
const cacheDir = join(homedir(), ".cache", "commitguard");
|
|
@@ -944,7 +925,7 @@ function groupIssuesByFile(issues = []) {
|
|
|
944
925
|
};
|
|
945
926
|
}
|
|
946
927
|
async function onStaged() {
|
|
947
|
-
const diff = getStagedDiff(
|
|
928
|
+
const diff = getStagedDiff();
|
|
948
929
|
if (!diff.trim()) {
|
|
949
930
|
clearCache();
|
|
950
931
|
return;
|
|
@@ -965,7 +946,7 @@ async function onStaged() {
|
|
|
965
946
|
}
|
|
966
947
|
}
|
|
967
948
|
function getCachedAnalysis(diff, diffHash) {
|
|
968
|
-
const effectiveDiff = diff ?? getStagedDiff(
|
|
949
|
+
const effectiveDiff = diff ?? getStagedDiff();
|
|
969
950
|
if (!effectiveDiff.trim()) return {
|
|
970
951
|
analysis: {
|
|
971
952
|
status: "pass",
|
|
@@ -984,7 +965,7 @@ function getCachedAnalysis(diff, diffHash) {
|
|
|
984
965
|
};
|
|
985
966
|
}
|
|
986
967
|
async function validateCommit() {
|
|
987
|
-
const diff = getStagedDiff(
|
|
968
|
+
const diff = getStagedDiff();
|
|
988
969
|
const diffHash = diff.trim() ? createDiffHash(diff) : "";
|
|
989
970
|
const cached = getCachedAnalysis(diff, diffHash);
|
|
990
971
|
if (!cached) {
|