UNPKG

commit-guard-cli

Version:

Commit validation, security audits, and dependency checks for Node.js projects. Enforces conventional commits with beautiful terminal output.

267 lines • 14.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.InstallCommand = void 0; const child_process_1 = require("child_process"); const chalk_1 = __importDefault(require("chalk")); const ora_1 = __importDefault(require("ora")); const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); const inquirer_1 = __importDefault(require("inquirer")); class InstallCommand { async execute() { console.log(chalk_1.default.blue('šŸ“¦ Installing dependencies and setting up hooks...\n')); const startTime = Date.now(); try { // Check npm version for context (async, don't wait) this.checkNpmVersion(); // Check if this is a vanilla setup const configPath = path_1.default.join(process.cwd(), '.commit-guard.json'); let setupType = 'interactive'; if (await fs_extra_1.default.pathExists(configPath)) { const config = await fs_extra_1.default.readJson(configPath); setupType = config.setupType || 'interactive'; } if (setupType === 'vanilla') { console.log(chalk_1.default.blue('\nšŸŽ‰ Vanilla setup ready:')); console.log(chalk_1.default.gray(' • Zero dependencies - completely standalone')); console.log(chalk_1.default.gray(' • Native git hooks for commit validation')); console.log(chalk_1.default.gray(' • Shell-based security and syntax checks')); console.log(chalk_1.default.gray(' • Git hooks are already active')); return; // Skip all installation for vanilla } else { await this.installDependencies(); await this.setupHusky(); } console.log(chalk_1.default.green('\nāœ… Installation complete!')); const duration = Date.now() - startTime; console.log(chalk_1.default.gray(` Completed in ${(duration / 1000).toFixed(1)}s`)); if (setupType !== 'vanilla') { console.log(chalk_1.default.blue('\nšŸŽ‰ You can now use:')); console.log(chalk_1.default.gray(' • npm run commit - for guided commits')); console.log(chalk_1.default.gray(' • Regular git commits will be validated automatically')); } } catch (error) { console.error(chalk_1.default.red('āŒ Installation failed:'), error); process.exit(1); } } async installMinimalDependencies() { const spinner = (0, ora_1.default)('Installing minimal dependencies (husky only)...').start(); try { await this.tryInstallWithFallbacks('npm install --save-dev husky@^8.0.3'); spinner.succeed('Minimal dependencies installed'); } catch (error) { spinner.fail('Failed to install minimal dependencies'); throw error; } } async installDependencies() { const spinner = (0, ora_1.default)('Installing npm dependencies...').start(); try { await this.tryInstallWithFallbacks('npm install'); spinner.succeed('Dependencies installed'); } catch (error) { spinner.fail('Failed to install dependencies'); throw error; } } async tryInstallWithFallbacks(baseCommand) { // First try standard installation with optimized flags const optimizedCommand = `${baseCommand} --no-audit --no-fund`; try { console.log(chalk_1.default.gray(` Trying optimized installation...`)); (0, child_process_1.execSync)(optimizedCommand, { stdio: 'pipe', timeout: 30000 }); return; // Success! } catch (error) { // Fall back to original command try { console.log(chalk_1.default.gray(` Trying standard installation...`)); (0, child_process_1.execSync)(baseCommand, { stdio: 'pipe', timeout: 30000 }); return; // Success! } catch (originalError) { const errorOutput = originalError instanceof Error ? originalError.message : String(originalError); // Check if it's a peer dependency issue if (this.isPeerDependencyError(errorOutput)) { console.log(chalk_1.default.yellow('\nāš ļø Peer dependency conflict detected!')); console.log(chalk_1.default.gray('This is common with certain npm packages that have conflicting peer dependencies.')); // Ask user for their preference const answer = await inquirer_1.default.prompt([ { type: 'list', name: 'installStrategy', message: 'How would you like to handle this conflict?', choices: [ { name: 'šŸ”§ Use --legacy-peer-deps (recommended - uses npm 6 behavior)', value: 'legacy' }, { name: '⚔ Use --force (override conflict resolution)', value: 'force' }, { name: 'šŸ”„ Try both --legacy-peer-deps and --force', value: 'both' }, { name: 'āŒ Cancel installation (continue without dependencies)', value: 'cancel' } ], default: 'legacy' } ]); if (answer.installStrategy === 'cancel') { console.log(chalk_1.default.yellow('\nā­ļø Skipping dependency installation.')); this.showManualInstallationGuidance(baseCommand); return; } // Try the selected strategy await this.executeSelectedStrategy(baseCommand, answer.installStrategy); } else { // For non-peer dependency errors, show error and ask if they want to try alternatives console.log(chalk_1.default.red('\nāŒ Installation failed with error:')); console.log(chalk_1.default.gray(errorOutput.substring(0, 300) + (errorOutput.length > 300 ? '...' : ''))); const answer = await inquirer_1.default.prompt([ { type: 'confirm', name: 'tryAlternatives', message: 'Would you like to try alternative installation methods?', default: true } ]); if (!answer.tryAlternatives) { throw originalError; } // Try alternative strategies await this.executeSelectedStrategy(baseCommand, 'both'); } } } } async executeSelectedStrategy(baseCommand, strategy) { const strategies = []; switch (strategy) { case 'legacy': strategies.push({ command: `${baseCommand} --legacy-peer-deps --no-audit --no-fund`, description: 'with --legacy-peer-deps (optimized)' }); break; case 'force': strategies.push({ command: `${baseCommand} --force --no-audit --no-fund`, description: 'with --force flag (optimized)' }); break; case 'both': strategies.push({ command: `${baseCommand} --legacy-peer-deps --no-audit --no-fund`, description: 'with --legacy-peer-deps (optimized)' }, { command: `${baseCommand} --force --no-audit --no-fund`, description: 'with --force flag (optimized)' }, { command: `${baseCommand} --legacy-peer-deps --force --no-audit --no-fund`, description: 'with both flags (optimized)' }); break; } let lastError; for (const strategyItem of strategies) { try { console.log(chalk_1.default.gray(` Trying ${strategyItem.description}...`)); (0, child_process_1.execSync)(strategyItem.command, { stdio: 'pipe', timeout: 30000 }); console.log(chalk_1.default.green(` āœ… Installation succeeded using ${strategyItem.description}`)); return; // Success! } catch (error) { lastError = error; console.log(chalk_1.default.gray(` ${strategyItem.description} failed, trying next option...`)); } } // If we get here, all selected strategies failed console.log(chalk_1.default.red('\nāŒ All attempted installation methods failed.')); const errorOutput = lastError instanceof Error ? lastError.message : String(lastError); const advice = this.getInstallationAdvice(errorOutput); if (advice.length > 0) { console.log(chalk_1.default.yellow('\nšŸ’” Troubleshooting suggestions:')); advice.forEach(line => console.log(chalk_1.default.gray(` ${line}`))); } console.log(chalk_1.default.yellow('\nļæ½ You can try manual installation:')); console.log(chalk_1.default.gray(` ${baseCommand} --legacy-peer-deps`)); console.log(chalk_1.default.gray(` ${baseCommand} --force`)); throw lastError; } isPeerDependencyError(errorOutput) { const peerDepIndicators = [ 'peer dep', 'peer dependency', 'ERESOLVE unable to resolve dependency tree', 'conflicting peer dependency', 'Fix the upstream dependency conflict', 'unable to resolve dependency tree', 'ERESOLVE could not resolve', 'Found: undefined', 'Could not resolve dependency' ]; const errorLower = errorOutput.toLowerCase(); return peerDepIndicators.some(indicator => errorLower.includes(indicator.toLowerCase())); } getInstallationAdvice(errorOutput) { const advice = []; if (this.isPeerDependencyError(errorOutput)) { advice.push('This appears to be a peer dependency conflict.'); advice.push('Common solutions:'); advice.push(' • npm install --legacy-peer-deps (use npm 6 behavior)'); advice.push(' • npm install --force (override conflict resolution)'); advice.push(' • Update conflicting packages to compatible versions'); } if (errorOutput.includes('commitizen') || errorOutput.includes('cz-conventional-changelog')) { advice.push('Commitizen-related packages sometimes have peer dependency issues.'); advice.push('Try: npm install commitizen cz-conventional-changelog --legacy-peer-deps'); } if (errorOutput.includes('@commitlint')) { advice.push('Commitlint packages may require specific peer dependencies.'); advice.push('Check if you have compatible TypeScript/Node versions.'); } return advice; } async setupHusky() { const spinner = (0, ora_1.default)('Setting up Husky hooks...').start(); try { (0, child_process_1.execSync)('npx husky install', { stdio: 'pipe' }); spinner.succeed('Husky hooks configured'); } catch (error) { spinner.fail('Failed to setup Husky'); throw error; } } async checkNpmVersion() { try { const npmVersion = (0, child_process_1.execSync)('npm --version', { encoding: 'utf8', timeout: 3000 }).trim(); const majorVersion = parseInt(npmVersion.split('.')[0]); if (majorVersion >= 7) { console.log(chalk_1.default.gray(` Using npm v${npmVersion} (peer dependency resolution enabled)`)); } else { console.log(chalk_1.default.yellow(` āš ļø Using npm v${npmVersion} - consider upgrading to npm 7+ for better dependency resolution`)); } } catch (error) { console.log(chalk_1.default.gray(' Could not determine npm version')); } } showManualInstallationGuidance(baseCommand) { console.log(chalk_1.default.blue('\nšŸ“‹ Manual Installation Guide:')); console.log(chalk_1.default.gray('────────────────────────────')); console.log(chalk_1.default.yellow('\n1. For peer dependency conflicts:')); console.log(chalk_1.default.gray(` ${baseCommand} --legacy-peer-deps`)); console.log(chalk_1.default.gray(' This uses npm 6 behavior and usually resolves conflicts')); console.log(chalk_1.default.yellow('\n2. For other conflicts:')); console.log(chalk_1.default.gray(` ${baseCommand} --force`)); console.log(chalk_1.default.gray(' This forces installation by ignoring conflict checks')); console.log(chalk_1.default.yellow('\n3. After successful installation:')); console.log(chalk_1.default.gray(' npx husky install')); console.log(chalk_1.default.gray(' npm run commit # for guided commits')); console.log(chalk_1.default.blue('\nšŸ’” The project structure is ready - only dependencies need installation.')); } } exports.InstallCommand = InstallCommand; //# sourceMappingURL=install.js.map