New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

@commitguard/cli

Package Overview
Dependencies
Maintainers
1
Versions
8
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@commitguard/cli - npm Package Compare versions

Comparing version
0.0.14
to
0.0.15
+266
-285
dist/index.mjs

@@ -6,8 +6,8 @@ #!/usr/bin/env node

import { execFileSync, execSync } from "node:child_process";
import { createHash } from "node:crypto";
import { cancel, confirm, intro, isCancel, log, multiselect, note, outro, text } from "@clack/prompts";
import { Entry } from "@napi-rs/keyring";
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { dirname, join } from "node:path";
import { cancel, confirm, intro, isCancel, log, multiselect, note, outro, select, text } from "@clack/prompts";
import { createHash } from "node:crypto";
import { Entry } from "@napi-rs/keyring";
import { fileURLToPath, pathToFileURL } from "node:url";

@@ -21,3 +21,3 @@ import { readFile } from "node:fs/promises";

//#region package.json
var version = "0.0.14";
var version = "0.0.15";
var package_default = {

@@ -77,273 +77,2 @@ name: "@commitguard/cli",

//#endregion
//#region src/utils/global.ts
function createDiffHash(diff) {
return createHash("md5").update(diff).digest("base64url");
}
function addGitLineNumbers(diff) {
if (!diff.trim()) return diff;
const lines = diff.split("\n");
const result = [];
let oldLine = 0;
let newLine = 0;
for (const line of lines) if (line.startsWith("@@")) {
const match = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
if (match) {
oldLine = Number.parseInt(match[1], 10);
newLine = Number.parseInt(match[2], 10);
}
result.push(line);
} else if (line.startsWith("---") || line.startsWith("+++") || line.startsWith("diff ") || line.startsWith("index ")) result.push(line);
else if (line.startsWith("-")) {
result.push(`${oldLine}:${line}`);
oldLine++;
} else if (line.startsWith("+")) {
result.push(`${newLine}:${line}`);
newLine++;
} else {
result.push(`${newLine}:${line}`);
oldLine++;
newLine++;
}
return result.join("\n");
}
const MESSAGES = { noGit: "No .git folder found. Run this inside a git repository." };
//#endregion
//#region src/utils/config.ts
const MAX_CUSTOM_PROMPT_LENGTH = 500;
const CONFIG_DIR = join(homedir(), ".commitguard");
const PROJECTS_CONFIG_PATH = join(CONFIG_DIR, "projects.json");
let projectsConfigCache = null;
const GIT_DIR$1 = ".git";
function ensureConfigDir() {
if (!existsSync(CONFIG_DIR)) try {
mkdirSync(CONFIG_DIR, { recursive: true });
} catch (e) {
consola.error(`Failed to create config directory at ${CONFIG_DIR}: ${e.message}`);
}
}
function getDefaultConfig() {
return {
context: "normal",
checks: {
security: true,
performance: true,
codeQuality: true,
architecture: true
},
severityLevels: {
critical: true,
warning: true,
suggestion: false
},
customRule: ""
};
}
let projectIdCache = null;
function getProjectId() {
if (projectIdCache) return projectIdCache;
try {
projectIdCache = execFileSync("git", [
"rev-list",
"--max-parents=0",
"HEAD"
], {
encoding: "utf8",
stdio: [
"pipe",
"pipe",
"ignore"
]
}).trim().split("\n")[0];
return projectIdCache;
} catch {
consola.error("Warning: Unable to determine project ID. Using current working directory as fallback project ID.");
projectIdCache = process.cwd();
return projectIdCache;
}
}
function loadProjectsConfig() {
if (projectsConfigCache) return projectsConfigCache;
if (existsSync(PROJECTS_CONFIG_PATH)) try {
const content = readFileSync(PROJECTS_CONFIG_PATH, "utf8");
projectsConfigCache = JSON.parse(content);
return projectsConfigCache;
} catch {
consola.warn("Failed to parse projects config");
}
projectsConfigCache = {};
return projectsConfigCache;
}
function saveProjectsConfig(projects) {
try {
ensureConfigDir();
writeFileSync(PROJECTS_CONFIG_PATH, JSON.stringify(projects, null, 2));
projectsConfigCache = projects;
} catch (e) {
consola.error(`Failed to save projects config: ${e.message}`);
}
}
function loadConfig() {
const projectId = getProjectId();
return loadProjectsConfig()[projectId] || getDefaultConfig();
}
async function manageConfig() {
if (!existsSync(GIT_DIR$1)) {
cancel(MESSAGES.noGit);
return;
}
const projectId = getProjectId();
const currentConfig = loadConfig();
intro(`CommitGuard Configuration`);
const enabledChecks = await multiselect({
message: "Select enabled checks for this project:",
options: [
{
value: "security",
label: "Security"
},
{
value: "performance",
label: "Performance"
},
{
value: "codeQuality",
label: "Code Quality"
},
{
value: "architecture",
label: "Architecture"
}
],
initialValues: Object.entries(currentConfig.checks).filter(([_, enabled]) => enabled).map(([key]) => key)
});
if (isCancel(enabledChecks)) {
cancel("Configuration cancelled");
return;
}
const enabledSeverity = await multiselect({
message: "Select severity levels for enabled checks:",
options: [
{
value: "suggestion",
label: "Suggestion"
},
{
value: "warning",
label: "Warning"
},
{
value: "critical",
label: "Critical"
}
],
initialValues: Object.entries(currentConfig.severityLevels).filter(([_, enabled]) => enabled).map(([key]) => key)
});
if (isCancel(enabledSeverity)) {
cancel("Configuration cancelled");
return;
}
const contextLevel = await select({
message: "Select context level for analysis:",
options: [{
value: "minimal",
label: "Minimal (Just Actual Changes)"
}, {
value: "normal",
label: "Normal (Actual Changes + Context Lines)"
}],
initialValue: currentConfig.context
});
if (isCancel(contextLevel)) {
cancel("Configuration cancelled");
return;
}
let customRule = currentConfig.customRule;
if (currentConfig.customRule) {
log.info(`Current custom rule: ${currentConfig.customRule}`);
const editCustomRule = await confirm({
message: "Would you like to edit the custom rule? (Currently only available to pro users)",
initialValue: false
});
if (isCancel(editCustomRule)) {
cancel("Configuration cancelled");
return;
}
if (editCustomRule) {
const newCustomRule = await text({
message: "Enter new custom rule (leave empty to remove):",
initialValue: currentConfig.customRule,
validate: (value) => {
const val = String(value).trim();
if (!val) return void 0;
if (val.length > MAX_CUSTOM_PROMPT_LENGTH) return `Custom rule must be ${MAX_CUSTOM_PROMPT_LENGTH} characters or less (current: ${val.length})`;
}
});
if (isCancel(newCustomRule)) {
cancel("Configuration cancelled");
return;
}
customRule = String(newCustomRule).trim();
}
} else {
const addCustomRule = await confirm({
message: "Would you like to add a custom rule for this project? (Currently only available to pro users)",
initialValue: false
});
if (isCancel(addCustomRule)) {
cancel("Configuration cancelled");
return;
}
if (addCustomRule) {
const newCustomRule = await text({
message: "Enter custom rule (leave empty to skip):",
placeholder: "e.g., Check for proper error handling in async functions",
validate: (value) => {
const val = String(value).trim();
if (!val) return void 0;
if (val.length > MAX_CUSTOM_PROMPT_LENGTH) return `Custom rule must be ${MAX_CUSTOM_PROMPT_LENGTH} characters or less (current: ${val.length})`;
}
});
if (isCancel(newCustomRule)) {
cancel("Configuration cancelled");
return;
}
customRule = String(newCustomRule).trim();
}
}
const newConfig = {
context: contextLevel,
checks: {
security: enabledChecks.includes("security"),
performance: enabledChecks.includes("performance"),
codeQuality: enabledChecks.includes("codeQuality"),
architecture: enabledChecks.includes("architecture")
},
severityLevels: {
suggestion: enabledSeverity.includes("suggestion"),
warning: enabledSeverity.includes("warning"),
critical: enabledSeverity.includes("critical")
},
customRule
};
if (JSON.stringify(newConfig) === JSON.stringify(currentConfig)) {
outro("No changes made to the configuration.");
return;
}
const confirmUpdate = await confirm({ message: "Save this configuration?" });
if (isCancel(confirmUpdate)) {
cancel("Configuration cancelled");
return;
}
if (!confirmUpdate) {
outro("Configuration not saved.");
return;
}
const projects = loadProjectsConfig();
projects[projectId] = newConfig;
saveProjectsConfig(projects);
outro("✓ Configuration updated for this project!");
}
//#endregion
//#region src/data/ignore.json

@@ -472,5 +201,38 @@ var ignore = [

//#endregion
//#region src/utils/global.ts
function createDiffHash(diff) {
return createHash("md5").update(diff).digest("base64url");
}
function addGitLineNumbers(diff) {
if (!diff.trim()) return diff;
const lines = diff.split("\n");
const result = [];
let oldLine = 0;
let newLine = 0;
for (const line of lines) if (line.startsWith("@@")) {
const match = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
if (match) {
oldLine = Number.parseInt(match[1], 10);
newLine = Number.parseInt(match[2], 10);
}
result.push(line);
} else if (line.startsWith("---") || line.startsWith("+++") || line.startsWith("diff ") || line.startsWith("index ")) result.push(line);
else if (line.startsWith("-")) {
result.push(`${oldLine}:${line}`);
oldLine++;
} else if (line.startsWith("+")) {
result.push(`${newLine}:${line}`);
newLine++;
} else {
result.push(`${newLine}:${line}`);
oldLine++;
newLine++;
}
return result.join("\n");
}
const MESSAGES = { noGit: "No .git folder found. Run this inside a git repository." };
//#endregion
//#region src/utils/git.ts
function getStagedDiff(context) {
const gitContextCommand = context === "minimal" ? [] : ["--function-context"];
function getStagedDiff() {
try {

@@ -481,3 +243,3 @@ return addGitLineNumbers(execFileSync("git", [

"--no-color",
...gitContextCommand,
"--function-context",
"--diff-algorithm=histogram",

@@ -501,4 +263,3 @@ "--diff-filter=AMC",

}
function getLastDiff(context) {
const gitContextCommand = context === "minimal" ? [] : ["--function-context"];
function getLastDiff() {
try {

@@ -510,3 +271,3 @@ return execFileSync("git", [

"--no-color",
...gitContextCommand,
"--function-context",
"--diff-algorithm=histogram",

@@ -640,3 +401,3 @@ "--diff-filter=AMC",

const apiUrl = process.env.COMMITGUARD_API_BYPASS_URL || "https://api.commitguard.ai/v1/bypass";
const diff = getLastDiff(loadConfig().context);
const diff = getLastDiff();
const controller = new AbortController();

@@ -669,2 +430,222 @@ const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);

//#endregion
//#region src/utils/config.ts
const MAX_CUSTOM_PROMPT_LENGTH = 500;
const CONFIG_DIR = join(homedir(), ".commitguard");
const PROJECTS_CONFIG_PATH = join(CONFIG_DIR, "projects.json");
let projectsConfigCache = null;
const GIT_DIR$1 = ".git";
function ensureConfigDir() {
if (!existsSync(CONFIG_DIR)) try {
mkdirSync(CONFIG_DIR, { recursive: true });
} catch (e) {
consola.error(`Failed to create config directory at ${CONFIG_DIR}: ${e.message}`);
}
}
function getDefaultConfig() {
return {
checks: {
security: true,
performance: true,
codeQuality: true,
architecture: true
},
severityLevels: {
critical: true,
warning: true,
suggestion: false
},
customRule: ""
};
}
let projectIdCache = null;
function getProjectId() {
if (projectIdCache) return projectIdCache;
try {
projectIdCache = execFileSync("git", [
"rev-list",
"--max-parents=0",
"HEAD"
], {
encoding: "utf8",
stdio: [
"pipe",
"pipe",
"ignore"
]
}).trim().split("\n")[0];
return projectIdCache;
} catch {
consola.error("Warning: Unable to determine project ID. Using current working directory as fallback project ID.");
projectIdCache = process.cwd();
return projectIdCache;
}
}
function loadProjectsConfig() {
if (projectsConfigCache) return projectsConfigCache;
if (existsSync(PROJECTS_CONFIG_PATH)) try {
const content = readFileSync(PROJECTS_CONFIG_PATH, "utf8");
projectsConfigCache = JSON.parse(content);
return projectsConfigCache;
} catch {
consola.warn("Failed to parse projects config");
}
projectsConfigCache = {};
return projectsConfigCache;
}
function saveProjectsConfig(projects) {
try {
ensureConfigDir();
writeFileSync(PROJECTS_CONFIG_PATH, JSON.stringify(projects, null, 2));
projectsConfigCache = projects;
} catch (e) {
consola.error(`Failed to save projects config: ${e.message}`);
}
}
function loadConfig() {
const projectId = getProjectId();
return loadProjectsConfig()[projectId] || getDefaultConfig();
}
async function manageConfig() {
if (!existsSync(GIT_DIR$1)) {
cancel(MESSAGES.noGit);
return;
}
const projectId = getProjectId();
const currentConfig = loadConfig();
intro(`CommitGuard Configuration`);
const enabledChecks = await multiselect({
message: "Select enabled checks for this project:",
options: [
{
value: "security",
label: "Security"
},
{
value: "performance",
label: "Performance"
},
{
value: "codeQuality",
label: "Code Quality"
},
{
value: "architecture",
label: "Architecture"
}
],
initialValues: Object.entries(currentConfig.checks).filter(([_, enabled]) => enabled).map(([key]) => key)
});
if (isCancel(enabledChecks)) {
cancel("Configuration cancelled");
return;
}
const enabledSeverity = await multiselect({
message: "Select severity levels for enabled checks:",
options: [
{
value: "suggestion",
label: "Suggestion"
},
{
value: "warning",
label: "Warning"
},
{
value: "critical",
label: "Critical"
}
],
initialValues: Object.entries(currentConfig.severityLevels).filter(([_, enabled]) => enabled).map(([key]) => key)
});
if (isCancel(enabledSeverity)) {
cancel("Configuration cancelled");
return;
}
let customRule = currentConfig.customRule;
if (currentConfig.customRule) {
log.info(`Current custom rule: ${currentConfig.customRule}`);
const editCustomRule = await confirm({
message: "Would you like to edit the custom rule? (Currently only available to pro users)",
initialValue: false
});
if (isCancel(editCustomRule)) {
cancel("Configuration cancelled");
return;
}
if (editCustomRule) {
const newCustomRule = await text({
message: "Enter new custom rule (leave empty to remove):",
initialValue: currentConfig.customRule,
validate: (value) => {
const val = String(value).trim();
if (!val) return void 0;
if (val.length > MAX_CUSTOM_PROMPT_LENGTH) return `Custom rule must be ${MAX_CUSTOM_PROMPT_LENGTH} characters or less (current: ${val.length})`;
}
});
if (isCancel(newCustomRule)) {
cancel("Configuration cancelled");
return;
}
customRule = String(newCustomRule).trim();
}
} else {
const addCustomRule = await confirm({
message: "Would you like to add a custom rule for this project? (Currently only available to pro users)",
initialValue: false
});
if (isCancel(addCustomRule)) {
cancel("Configuration cancelled");
return;
}
if (addCustomRule) {
const newCustomRule = await text({
message: "Enter custom rule (leave empty to skip):",
placeholder: "e.g., Check for proper error handling in async functions",
validate: (value) => {
const val = String(value).trim();
if (!val) return void 0;
if (val.length > MAX_CUSTOM_PROMPT_LENGTH) return `Custom rule must be ${MAX_CUSTOM_PROMPT_LENGTH} characters or less (current: ${val.length})`;
}
});
if (isCancel(newCustomRule)) {
cancel("Configuration cancelled");
return;
}
customRule = String(newCustomRule).trim();
}
}
const newConfig = {
checks: {
security: enabledChecks.includes("security"),
performance: enabledChecks.includes("performance"),
codeQuality: enabledChecks.includes("codeQuality"),
architecture: enabledChecks.includes("architecture")
},
severityLevels: {
suggestion: enabledSeverity.includes("suggestion"),
warning: enabledSeverity.includes("warning"),
critical: enabledSeverity.includes("critical")
},
customRule
};
if (JSON.stringify(newConfig) === JSON.stringify(currentConfig)) {
outro("No changes made to the configuration.");
return;
}
const confirmUpdate = await confirm({ message: "Save this configuration?" });
if (isCancel(confirmUpdate)) {
cancel("Configuration cancelled");
return;
}
if (!confirmUpdate) {
outro("Configuration not saved.");
return;
}
const projects = loadProjectsConfig();
projects[projectId] = newConfig;
saveProjectsConfig(projects);
outro("✓ Configuration updated for this project!");
}
//#endregion
//#region src/utils/eslint.ts

@@ -956,3 +937,3 @@ const cacheDir = join(homedir(), ".cache", "commitguard");

async function onStaged() {
const diff = getStagedDiff(config.context);
const diff = getStagedDiff();
if (!diff.trim()) {

@@ -978,3 +959,3 @@ clearCache();

function getCachedAnalysis(diff, diffHash) {
const effectiveDiff = diff ?? getStagedDiff(config.context);
const effectiveDiff = diff ?? getStagedDiff();
if (!effectiveDiff.trim()) return {

@@ -998,3 +979,3 @@ analysis: {

async function validateCommit() {
const diff = getStagedDiff(config.context);
const diff = getStagedDiff();
const diffHash = diff.trim() ? createDiffHash(diff) : "";

@@ -1001,0 +982,0 @@ const cached = getCachedAnalysis(diff, diffHash);

{
"name": "@commitguard/cli",
"type": "module",
"version": "0.0.14",
"version": "0.0.15",
"description": "AI-powered git commit checker that blocks bad code before it ships",

@@ -6,0 +6,0 @@ "license": "MIT",