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
JavaScript
;
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