UNPKG

commit-guard-cli

Version:

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

386 lines • 19.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.InitCommand = void 0; const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); const chalk_1 = __importDefault(require("chalk")); const ora_1 = __importDefault(require("ora")); const inquirer_1 = __importDefault(require("inquirer")); class InitCommand { constructor() { this.projectRoot = process.cwd(); } async execute(options) { const startTime = Date.now(); console.log(chalk_1.default.blue('šŸ”§ Initializing commit-guard-cli...\n')); try { // Check if already initialized if (!options.force && await this.isAlreadyInitialized()) { const { proceed } = await inquirer_1.default.prompt([ { type: 'confirm', name: 'proceed', message: 'Project already has commit-guard configuration. Continue anyway?', default: false } ]); if (!proceed) { console.log(chalk_1.default.yellow('Initialization cancelled.')); return; } } // Ask user for setup preferences (unless already provided) let setupType = options.setupType; let installDeps = options.installDeps; if (!setupType || installDeps === undefined) { const answers = await inquirer_1.default.prompt([ ...(!setupType ? [{ type: 'list', name: 'setupType', message: 'Choose your setup style:', choices: [ { name: 'šŸŽØ Interactive (colorful, detailed output with all features)', value: 'interactive' }, { name: 'šŸ“‹ Minimal (clean output with dependency counts)', value: 'minimal' }, { name: '⚔ Fast (ultra-fast with minimal output)', value: 'fast' }, { name: 'šŸš€ Vanilla (zero dependencies, native git hooks only)', value: 'vanilla' } ], default: 'interactive' }] : []), ...(installDeps === undefined && setupType !== 'vanilla' ? [{ type: 'confirm', name: 'installDeps', message: 'Install required dependencies now? (husky, commitlint, commitizen)', default: true }] : []) ]); setupType = setupType || answers.setupType; installDeps = setupType === 'vanilla' ? false : (installDeps !== undefined ? installDeps : answers.installDeps); } // Ask for additional preferences for non-vanilla setups let preferences = {}; if (setupType !== 'vanilla' && setupType !== 'fast') { const prefAnswers = await inquirer_1.default.prompt([ { type: 'confirm', name: 'runFormatter', message: 'Run code formatter before commits? (requires npm run format script)', default: false }, { type: 'confirm', name: 'runTypecheck', message: 'Run TypeScript type checking before commits?', default: true }, { type: 'confirm', name: 'autoStage', message: 'Automatically offer to stage files during commits?', default: true } ]); preferences = prefAnswers; } else if (setupType === 'fast') { // Fast mode defaults preferences = { runFormatter: false, runTypecheck: true, autoStage: true }; } else { // Vanilla mode defaults preferences = { runFormatter: false, runTypecheck: false, autoStage: true }; } // Run setup operations sequentially but optimized await this.createHuskyStructure(setupType || 'interactive'); await this.updatePackageJson(setupType || 'interactive'); await this.createCommitlintConfig(setupType || 'interactive'); await this.createGitIgnoreEntries(setupType || 'interactive'); // Store setup preferences await this.storeSetupPreferences(setupType || 'interactive', preferences); if (installDeps) { console.log(chalk_1.default.blue('\nšŸ“¦ Installing dependencies...')); try { const installCommand = new (await Promise.resolve().then(() => __importStar(require('./install')))).InstallCommand(); await installCommand.execute(); } catch (installError) { console.log(chalk_1.default.yellow('\nāš ļø Dependency installation was not completed.')); console.log(chalk_1.default.blue('\nšŸ“ The project structure is ready! Complete setup manually:')); console.log(chalk_1.default.gray(' 1. Try: npm install --legacy-peer-deps')); console.log(chalk_1.default.gray(' 2. Or: npm install --force')); console.log(chalk_1.default.gray(' 3. Run: npx husky install')); console.log(chalk_1.default.gray(' 4. Use: npm run commit (for guided commits)')); // Don't fail the entire initialization - the structure is already created console.log(chalk_1.default.green('\nāœ… Project structure created successfully!')); console.log(chalk_1.default.blue(' You can manually install dependencies when ready.')); return; } } console.log(chalk_1.default.green('\nāœ… Initialization complete!')); const duration = Date.now() - startTime; console.log(chalk_1.default.gray(` Completed in ${(duration / 1000).toFixed(1)}s`)); if (!installDeps) { 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 are active and ready')); console.log(chalk_1.default.gray(' • Commit validation will run automatically')); console.log(chalk_1.default.gray(' • No additional setup required!')); } else { console.log(chalk_1.default.blue('\nšŸ“ Next steps:')); console.log(chalk_1.default.gray(' 1. Run: npm install (to install dependencies)')); console.log(chalk_1.default.gray(' 2. Run: npx husky install (to setup git hooks)')); console.log(chalk_1.default.gray(' 3. Use: npm run commit (for guided commits)')); } } else { console.log(chalk_1.default.blue('\nšŸŽ‰ Ready to 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('āŒ Initialization failed:'), error); process.exit(1); } } async storeSetupPreferences(setupType, preferences = {}) { const configPath = path_1.default.join(this.projectRoot, '.commit-guard.json'); const config = { setupType, version: '1.0.0', createdAt: new Date().toISOString(), preferences: { runFormatter: preferences.runFormatter || false, runTypecheck: preferences.runTypecheck !== false, // default true autoStage: preferences.autoStage !== false // default true } }; await fs_extra_1.default.writeJson(configPath, config, { spaces: 2 }); } async isAlreadyInitialized() { const huskyDir = path_1.default.join(this.projectRoot, '.husky'); const packageJsonPath = path_1.default.join(this.projectRoot, 'package.json'); if (await fs_extra_1.default.pathExists(huskyDir)) return true; if (await fs_extra_1.default.pathExists(packageJsonPath)) { const packageJson = await fs_extra_1.default.readJson(packageJsonPath); return !!(packageJson.scripts?.commit || packageJson.devDependencies?.husky); } return false; } async createHuskyStructure(setupType) { if (setupType === 'vanilla') { await this.createVanillaGitHooks(); } else { await this.createHuskyStructure_NonVanilla(setupType); } } async createVanillaGitHooks() { const spinner = (0, ora_1.default)('Creating native git hooks (no dependencies)...').start(); const gitHooksDir = path_1.default.join(this.projectRoot, '.git', 'hooks'); const templatesDir = path_1.default.join(__dirname, '..', '..', 'templates'); // Ensure .git/hooks directory exists if (!(await fs_extra_1.default.pathExists(gitHooksDir))) { spinner.fail('Git repository not found. Please run this in a git repository.'); throw new Error('Not a git repository'); } // Copy vanilla commit-msg hook directly to .git/hooks await fs_extra_1.default.copy(path_1.default.join(templatesDir, 'commit-msg-vanilla'), path_1.default.join(gitHooksDir, 'commit-msg')); // Make hook executable await fs_extra_1.default.chmod(path_1.default.join(gitHooksDir, 'commit-msg'), '755'); spinner.succeed('Native git hooks created (vanilla mode - no dependencies)'); } async createHuskyStructure_NonVanilla(setupType) { const spinner = (0, ora_1.default)('Creating Husky structure...').start(); const huskyDir = path_1.default.join(this.projectRoot, '.husky'); const templatesDir = path_1.default.join(__dirname, '..', '..', 'templates'); await fs_extra_1.default.ensureDir(huskyDir); await fs_extra_1.default.ensureDir(path_1.default.join(huskyDir, '_')); // Copy husky.sh await fs_extra_1.default.copy(path_1.default.join(templatesDir, 'husky.sh'), path_1.default.join(huskyDir, '_', 'husky.sh')); // Copy pre-commit hook for staging and formatting await fs_extra_1.default.copy(path_1.default.join(templatesDir, 'pre-commit'), path_1.default.join(huskyDir, 'pre-commit')); // Copy appropriate commit-msg hook based on setup type let commitMsgTemplate = 'commit-msg'; // default to interactive if (setupType === 'minimal') { commitMsgTemplate = 'commit-msg-minimal'; } else if (setupType === 'fast') { commitMsgTemplate = 'commit-msg-fast'; } await fs_extra_1.default.copy(path_1.default.join(templatesDir, commitMsgTemplate), path_1.default.join(huskyDir, 'commit-msg')); // Make files executable await fs_extra_1.default.chmod(path_1.default.join(huskyDir, '_', 'husky.sh'), '755'); await fs_extra_1.default.chmod(path_1.default.join(huskyDir, 'pre-commit'), '755'); await fs_extra_1.default.chmod(path_1.default.join(huskyDir, 'commit-msg'), '755'); spinner.succeed(`Husky structure created (${setupType || 'interactive'} mode)`); } async updatePackageJson(setupType) { const spinner = (0, ora_1.default)('Updating package.json...').start(); const packageJsonPath = path_1.default.join(this.projectRoot, 'package.json'); if (!(await fs_extra_1.default.pathExists(packageJsonPath))) { throw new Error('package.json not found. Please run this command in a Node.js project root.'); } const packageJson = await fs_extra_1.default.readJson(packageJsonPath); // Use passed setupType or read from config if (!setupType) { const configPath = path_1.default.join(this.projectRoot, '.commit-guard.json'); if (await fs_extra_1.default.pathExists(configPath)) { const config = await fs_extra_1.default.readJson(configPath); setupType = config.setupType || 'interactive'; } else { setupType = 'interactive'; } } // Add scripts packageJson.scripts = packageJson.scripts || {}; packageJson.scripts.prepare = 'husky install'; // Add typecheck script if TypeScript is detected if (await fs_extra_1.default.pathExists(path_1.default.join(this.projectRoot, 'tsconfig.json'))) { packageJson.scripts.typecheck = 'tsc --noEmit'; } if (setupType !== 'vanilla') { // Add commit script and dependencies only for non-vanilla setups packageJson.scripts.commit = 'git-cz'; // Add devDependencies packageJson.devDependencies = packageJson.devDependencies || {}; packageJson.devDependencies.husky = '^8.0.3'; packageJson.devDependencies['@commitlint/cli'] = '^18.4.3'; packageJson.devDependencies['@commitlint/config-conventional'] = '^18.4.3'; packageJson.devDependencies['commitizen'] = '^4.3.0'; packageJson.devDependencies['cz-conventional-changelog'] = '^3.3.0'; // Add commitizen config packageJson.config = packageJson.config || {}; packageJson.config.commitizen = { path: './node_modules/cz-conventional-changelog' }; } else { // Vanilla setup - NO dependencies at all console.log(chalk_1.default.gray(' Vanilla setup: No dependencies will be added to package.json')); } await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 }); spinner.succeed(`package.json updated (${setupType} mode)`); } async createCommitlintConfig(setupType) { // Use passed setupType or read from config if (!setupType) { const configPath = path_1.default.join(this.projectRoot, '.commit-guard.json'); if (await fs_extra_1.default.pathExists(configPath)) { const config = await fs_extra_1.default.readJson(configPath); setupType = config.setupType || 'interactive'; } else { setupType = 'interactive'; } } if (setupType === 'vanilla') { return; // Skip commitlint config for vanilla setup } const spinner = (0, ora_1.default)('Creating commitlint configuration...').start(); const commitlintConfigContent = `module.exports = { extends: ['@commitlint/config-conventional'], rules: { 'type-enum': [ 2, 'always', ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', 'perf', 'ci', 'build', 'revert'] ], 'subject-max-length': [2, 'always', 100], 'body-max-line-length': [2, 'always', 100] } }; `; await fs_extra_1.default.writeFile(path_1.default.join(this.projectRoot, 'commitlint.config.js'), commitlintConfigContent); spinner.succeed('Commitlint configuration created'); } async createGitIgnoreEntries(setupType) { const spinner = (0, ora_1.default)('Updating .gitignore...').start(); // Use passed setupType or read from config if (!setupType) { const configPath = path_1.default.join(this.projectRoot, '.commit-guard.json'); if (await fs_extra_1.default.pathExists(configPath)) { const config = await fs_extra_1.default.readJson(configPath); setupType = config.setupType || 'interactive'; } else { setupType = 'interactive'; } } const gitignorePath = path_1.default.join(this.projectRoot, '.gitignore'); let entries; if (setupType === 'vanilla') { entries = [ '# Commit Guard (Vanilla)', '.commit-guard.json' ]; } else { entries = [ '# Commit Guard', '.husky/_/*', '!.husky/_/husky.sh', '.commit-guard.json' ]; } if (await fs_extra_1.default.pathExists(gitignorePath)) { const content = await fs_extra_1.default.readFile(gitignorePath, 'utf-8'); const firstEntryCheck = setupType === 'vanilla' ? '.commit-guard.json' : '.husky/_/*'; if (!content.includes(firstEntryCheck)) { await fs_extra_1.default.appendFile(gitignorePath, `\n${entries.join('\n')}\n`); } } else { await fs_extra_1.default.writeFile(gitignorePath, entries.join('\n') + '\n'); } spinner.succeed(`.gitignore updated (${setupType} mode)`); } } exports.InitCommand = InitCommand; //# sourceMappingURL=init.js.map