UNPKG

vibe-codex

Version:

CLI tool to install development rules and git hooks with interactive configuration

457 lines (407 loc) 14.8 kB
/** * Init command - Initialize vibe-codex in a project */ const chalk = require("chalk"); const ora = require("ora"); const inquirer = require("../utils/inquirer-fix"); const fs = require("fs-extra"); const path = require("path"); const semver = require("semver"); const { detectProjectType, detectTestFramework } = require("../utils/detector"); const { preflightChecks } = require("../utils/preflight"); const { installGitHooks } = require("../installer/git-hooks"); const { installGitHubActions } = require("../installer/github-actions"); const { installLocalRules } = require("../installer/local-rules"); const { createConfiguration } = require("../utils/config-creator"); const moduleLoader = require("../modules/loader-wrapper"); const { configExamples } = require("../modules/config-schema-commonjs"); const { validateSetup, getInstallInstructions, } = require("../utils/package-manager"); const logger = require("../utils/logger"); const { createRollbackPoint, rollback } = require("../utils/rollback"); module.exports = async function init(options) { console.log(chalk.blue("\n🎯 Welcome to vibe-codex!\n")); const spinner = ora("Running pre-flight checks...").start(); let rollbackPath = null; try { // Validate package manager setup const setupValidation = await validateSetup(); if (setupValidation.errors.length > 0) { spinner.fail("Setup validation failed"); logger.output(chalk.red("\n❌ Setup errors:")); setupValidation.errors.forEach((err) => logger.output(chalk.red(` - ${err}`)), ); process.exit(1); } if (setupValidation.warnings.length > 0) { spinner.warn("Setup validation completed with warnings"); logger.output(chalk.yellow("\n⚠️ Warnings:")); setupValidation.warnings.forEach((warn) => logger.output(chalk.yellow(` - ${warn}`)), ); if (!setupValidation.npxAvailable) { const instructions = getInstallInstructions( setupValidation.packageManager, ); console.log( chalk.blue( "\n💡 To ensure best compatibility, consider upgrading npm:", ), ); console.log(chalk.cyan(" npm install -g npm@latest")); console.log(chalk.blue("\nOr install vibe-codex locally:")); console.log(chalk.cyan(` ${instructions.local}`)); } } else { spinner.succeed("Setup validation passed"); } // Run pre-flight checks const { packageManager } = await preflightChecks(options); spinner.succeed("Pre-flight checks passed"); // Create rollback point after pre-flight checks if (!options.skipRollback) { spinner.start("Creating rollback point..."); rollbackPath = await createRollbackPoint(); spinner.succeed("Rollback point created"); } // Detect or ask for project type let projectType = options.type; if (projectType === "auto") { projectType = await detectProjectType(); if (!projectType) { const answer = await inquirer.prompt([ { type: "list", name: "projectType", message: "What type of project is this?", choices: [ { name: "Web Application (Frontend)", value: "web" }, { name: "API/Backend Service", value: "api" }, { name: "Full-Stack Application", value: "fullstack" }, { name: "npm Library/Package", value: "library" }, { name: "Custom Configuration", value: "custom" }, ], }, ]); projectType = answer.projectType; } else { logger.output(chalk.green(`✓ Detected project type: ${projectType}`)); } } // Ask about module selection for modular rules let selectedModules = {}; if (options.minimal) { // Use minimal configuration selectedModules = { core: { enabled: true }, ...configExamples.minimal.modules, }; } else if (options.modules !== "all") { const { useModular } = await inquirer.prompt([ { type: "confirm", name: "useModular", message: "Would you like to customize which rule modules to install?", default: true, }, ]); if (useModular) { const { modules } = await inquirer.prompt([ { type: "checkbox", name: "modules", message: "Select rule modules to install:", choices: [ { name: "Core (Essential security & workflow rules)", value: "core", checked: true, disabled: true, }, { name: "GitHub Workflow (PR templates, issue tracking)", value: "github-workflow", checked: true, }, { name: "Testing (Test coverage, framework rules)", value: "testing", checked: projectType !== "library", }, { name: "Deployment (Platform-specific checks)", value: "deployment", checked: projectType === "fullstack", }, { name: "Documentation (README, architecture docs)", value: "documentation", checked: true, }, { name: "Development Patterns (Code organization)", value: "patterns", checked: false, }, ], }, ]); // Always include core selectedModules["core"] = { enabled: true }; modules.forEach((mod) => { selectedModules[mod] = { enabled: true }; }); } else { // Use preset based on project type - always include core selectedModules = { core: { enabled: true }, }; if (projectType === "fullstack") { selectedModules = { ...selectedModules, ...configExamples.fullStack.modules, }; } else if (projectType === "web" || projectType === "api") { selectedModules = { ...selectedModules, ...configExamples.frontend.modules, }; } else { selectedModules = { ...selectedModules, ...configExamples.minimal.modules, }; } } } else { // Install all modules selectedModules = { core: { enabled: true }, "github-workflow": { enabled: true }, testing: { enabled: true }, deployment: { enabled: true }, documentation: { enabled: true }, patterns: { enabled: true }, }; } // Ask about advanced hooks let advancedHooksConfig = null; // Check if advanced hooks specified via CLI if (options.withAdvancedHooks) { const categories = options.withAdvancedHooks .split(",") .map((c) => c.trim()) .filter((c) => c); if (categories.length > 0) { advancedHooksConfig = { enabled: true, categories: categories, }; logger.output( chalk.green( `✓ Will install advanced hooks: ${categories.join(", ")}`, ), ); } } // Skip advanced hooks in minimal mode or when running tests else if (!options.minimal && process.env.NODE_ENV !== "test") { const response = await inquirer.prompt([ { type: "confirm", name: "installAdvancedHooks", message: "Install advanced development hooks? (PR health checks, issue tracking, etc.)", default: false, }, ]); if (response && response.installAdvancedHooks) { const { advancedHooks } = require("../config/advanced-hooks"); const hookChoices = Object.entries(advancedHooks).map( ([key, value]) => ({ name: `${value.name} - ${value.description}`, value: key, checked: key === "issue-tracking", // Default check issue tracking }), ); const { selectedCategories } = await inquirer.prompt([ { type: "checkbox", name: "selectedCategories", message: "Select advanced hook categories to install:", choices: hookChoices, }, ]); advancedHooksConfig = { enabled: true, categories: selectedCategories, }; } } // Create vibe-codex configuration spinner.start("Creating configuration..."); const vibeCodexConfig = { version: "2.0.0", modules: selectedModules, projectType, advancedHooks: advancedHooksConfig, }; // Save configuration await fs.writeFile( path.join(process.cwd(), ".vibe-codex.json"), JSON.stringify(vibeCodexConfig, null, 2), ); // Create legacy configuration object (without writing file) const config = { version: "2.0.0", projectType, modules: vibeCodexConfig.modules, createdAt: new Date().toISOString(), lastModified: new Date().toISOString(), }; // Apply project defaults applyProjectDefaults(config, projectType); // Create ignore and context files await createIgnoreFile(); await createProjectContext(); spinner.succeed("Configuration created"); // Install local rules and related files spinner.start("Installing vibe-codex rules..."); await installLocalRules(vibeCodexConfig, options); spinner.succeed("vibe-codex rules installed"); // Install git hooks if (options.gitHooks !== false) { spinner.start("Installing git hooks..."); await installGitHooks(config, advancedHooksConfig); spinner.succeed("Git hooks installed"); } // Install GitHub Actions if ( selectedModules["github-workflow"] && selectedModules["github-workflow"].enabled ) { spinner.start("Setting up GitHub Actions..."); await installGitHubActions(config); spinner.succeed("GitHub Actions configured"); } // Update package.json spinner.start("Updating package.json..."); await updatePackageJson(config, packageManager); spinner.succeed("package.json updated"); // Initialize module loader to validate configuration spinner.start("Validating module configuration..."); await moduleLoader.initialize(process.cwd()); spinner.succeed("Module configuration validated"); // Run initial validation spinner.start("Running initial validation..."); try { const validate = require("./validate"); const originalExitCode = process.exitCode; await validate({ json: true, silent: true }); if (process.exitCode && process.exitCode !== 0) { spinner.warn("Initial validation completed with warnings"); process.exitCode = originalExitCode; // Reset exit code for init } else { spinner.succeed("Initial validation passed"); } } catch (validationError) { spinner.warn(`Initial validation failed: ${validationError.message}`); } // Show success message and next steps showSuccessMessage( config, selectedModules, setupValidation.packageManager, advancedHooksConfig, ); } catch (error) { spinner.fail(`Installation failed: ${error.message}`); // Attempt rollback on failure if (rollbackPath && !options.skipRollback) { console.log(chalk.yellow("\n⚠️ Attempting to rollback changes...")); try { await rollback(rollbackPath); console.log(chalk.green("✓ Successfully rolled back changes")); } catch (rollbackError) { console.error(chalk.red("❌ Rollback failed:"), rollbackError.message); console.log(chalk.yellow("Manual cleanup may be required")); } } throw error; } }; async function updatePackageJson(config, packageManager) { const pkgPath = "package.json"; if (!(await fs.pathExists(pkgPath))) { logger.warn("No package.json found, skipping script updates"); return; } const pkg = await fs.readJSON(pkgPath); // Add vibe-codex scripts pkg.scripts = pkg.scripts || {}; pkg.scripts["vibe:validate"] = "vibe-codex validate"; pkg.scripts["vibe:update"] = "vibe-codex update"; pkg.scripts["vibe:config"] = "vibe-codex config"; await fs.writeJSON(pkgPath, pkg, { spaces: 2 }); } function showSuccessMessage( config, selectedModules, packageManager = "npm", advancedHooksConfig = null, ) { console.log(chalk.green("\n✨ vibe-codex installed successfully!\n")); console.log(chalk.bold("Installed modules:")); Object.entries(selectedModules).forEach(([name, config]) => { if (config.enabled) { console.log(chalk.green(` ✓ ${name}`)); } }); if (advancedHooksConfig && advancedHooksConfig.enabled) { console.log(chalk.bold("\nInstalled advanced hooks:")); const { advancedHooks } = require("../config/advanced-hooks"); advancedHooksConfig.categories.forEach((category) => { if (advancedHooks[category]) { console.log(chalk.green(` ✓ ${advancedHooks[category].name}`)); } }); } const { getRunCommand } = require("../utils/package-manager"); const runCmd = getRunCommand(packageManager, false); console.log(chalk.bold("\nNext steps:")); console.log("1. Review your configuration:"); console.log(chalk.cyan(" cat .vibe-codex.json")); console.log("\n2. Run validation:"); if (packageManager === "npm" || runCmd.includes("npx")) { console.log(chalk.cyan(" npx vibe-codex validate")); } else { console.log(chalk.cyan(` ${runCmd} vibe-codex validate`)); } console.log("\n3. Configure additional modules:"); if (packageManager === "npm" || runCmd.includes("npx")) { console.log(chalk.cyan(" npx vibe-codex config")); } else { console.log(chalk.cyan(` ${runCmd} vibe-codex config`)); } console.log("\n4. Commit your changes:"); console.log(chalk.cyan(" git add .")); console.log( chalk.cyan(' git commit -m "feat: add vibe-codex configuration"'), ); console.log(chalk.bold("\n💡 Tips:")); console.log(` - Git hooks are installed and will run automatically`); console.log( ` - To skip hooks temporarily: SKIP_VIBE_CODEX=1 git commit ...`, ); console.log(` - Package manager detected: ${packageManager}`); console.log("\nFor more help:"); console.log( chalk.gray( ` ${packageManager === "npm" ? "npx" : runCmd} vibe-codex --help`, ), ); console.log(chalk.gray(" https://github.com/tyabonil/vibe-codex")); }