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