vibe-codex
Version:
CLI tool to install development rules and git hooks with interactive configuration
219 lines (189 loc) • 5.84 kB
JavaScript
/**
* Simple installer for git hooks
*/
const fs = require("fs").promises;
const path = require("path");
const chalk = require("chalk");
const HOOK_NAMES = ["pre-commit", "commit-msg"];
/**
* Install git hooks based on configuration
*/
async function installHooks(config) {
const gitDir = path.join(process.cwd(), ".git");
const hooksDir = path.join(gitDir, "hooks");
// Check if .git directory exists
try {
await fs.access(gitDir);
} catch (error) {
console.log(
chalk.yellow("⚠️ Not a git repository. Skipping hooks installation."),
);
return;
}
// Create hooks directory if it doesn't exist
await fs.mkdir(hooksDir, { recursive: true });
// Install each hook
for (const hookName of HOOK_NAMES) {
const hookContent = generateHookContent(hookName, config);
const hookPath = path.join(hooksDir, hookName);
// Check if hook already exists
const exists = await fs
.access(hookPath)
.then(() => true)
.catch(() => false);
if (exists) {
// Backup existing hook
const backupPath = `${hookPath}.vibe-codex-backup`;
await fs.copyFile(hookPath, backupPath);
console.log(
chalk.gray(
` Backed up existing ${hookName} to ${hookName}.vibe-codex-backup`,
),
);
}
// Write hook content
await fs.writeFile(hookPath, hookContent, { mode: 0o755 });
console.log(chalk.green(` ✓ Installed ${hookName} hook`));
}
}
/**
* Uninstall git hooks
*/
async function uninstallHooks() {
const hooksDir = path.join(process.cwd(), ".git", "hooks");
try {
for (const hookName of HOOK_NAMES) {
const hookPath = path.join(hooksDir, hookName);
const backupPath = `${hookPath}.vibe-codex-backup`;
// Check if our hook exists
const hookExists = await fs
.access(hookPath)
.then(() => true)
.catch(() => false);
if (hookExists) {
const content = await fs.readFile(hookPath, "utf8");
if (content.includes("vibe-codex")) {
// Restore backup if it exists
const backupExists = await fs
.access(backupPath)
.then(() => true)
.catch(() => false);
if (backupExists) {
await fs.copyFile(backupPath, hookPath);
await fs.unlink(backupPath);
console.log(chalk.green(` ✓ Restored original ${hookName} hook`));
} else {
await fs.unlink(hookPath);
console.log(chalk.green(` ✓ Removed ${hookName} hook`));
}
}
}
}
} catch (error) {
// Hooks directory might not exist
}
}
/**
* Generate hook content based on hook type and configuration
*/
function generateHookContent(hookName, config) {
const rules = config.rules || [];
if (hookName === "pre-commit") {
return generatePreCommitHook(rules);
} else if (hookName === "commit-msg") {
return generateCommitMsgHook(rules);
}
return "";
}
/**
* Generate pre-commit hook content
*/
function generatePreCommitHook(rules) {
let checks = [];
if (rules.includes("security")) {
checks.push(`
# Security check - look for potential secrets
echo "🔒 Running security checks..."
if git diff --cached --name-only | xargs grep -E "(api_key|apikey|secret|password|token)\\s*=\\s*['\\"'][^'\\""]+['\\"']" 2>/dev/null; then
echo "❌ Error: Potential secrets detected in staged files!"
echo "Please remove sensitive information before committing."
exit 1
fi`);
}
if (rules.includes("testing")) {
checks.push(`
# Test check - run tests if available
echo "🧪 Running tests..."
if [ -f "package.json" ] && grep -q '"test"' package.json; then
npm test || (echo "❌ Tests failed! Fix tests before committing." && exit 1)
fi`);
}
if (rules.includes("documentation")) {
checks.push(`
# Documentation check
echo "📚 Checking documentation..."
if [ ! -f "README.md" ]; then
echo "⚠️ Warning: No README.md file found"
fi`);
}
if (rules.includes("code-style")) {
checks.push(`
# Code style check
echo "🎨 Checking code style..."
if [ -f "package.json" ] && grep -q '"lint"' package.json; then
npm run lint || (echo "⚠️ Warning: Linting issues found" && exit 0)
fi`);
}
return `#!/bin/sh
# vibe-codex pre-commit hook
# Generated by vibe-codex - do not edit manually
echo "🚀 Running vibe-codex pre-commit checks..."
${checks.join("\n")}
echo "✅ All pre-commit checks passed!"
exit 0`;
}
/**
* Generate commit-msg hook content
*/
function generateCommitMsgHook(rules) {
if (!rules.includes("commit-format")) {
return `#!/bin/sh
# vibe-codex commit-msg hook
# No commit message validation configured
exit 0`;
}
return `#!/bin/sh
# vibe-codex commit-msg hook
# Validates commit message format
commit_regex='^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\\(.+\\))?: .{1,100}$'
commit_msg=$(cat "$1")
echo "📝 Validating commit message format..."
if ! echo "$commit_msg" | grep -qE "$commit_regex"; then
echo "❌ Invalid commit message format!"
echo ""
echo "Expected format: type(scope): description"
echo "Example: feat(auth): add login functionality"
echo ""
echo "Valid types:"
echo " feat - New feature"
echo " fix - Bug fix"
echo " docs - Documentation changes"
echo " style - Code style changes"
echo " refactor - Code refactoring"
echo " test - Test changes"
echo " chore - Build/tool changes"
echo " perf - Performance improvements"
echo " ci - CI/CD changes"
echo " build - Build system changes"
echo " revert - Revert previous commit"
echo ""
echo "Your message: $commit_msg"
exit 1
fi
echo "✅ Commit message format is valid!"
exit 0`;
}
module.exports = {
installHooks,
uninstallHooks,
};