dna-template-cli
Version:
DNA Template CLI v0.3.4 - Enhanced Commands Added (enhanced-create, enhanced-list, enhanced-validate)
310 lines • 11.8 kB
JavaScript
;
/**
* @fileoverview Git Validation and Rollback System
* Provides validation hooks and rollback capabilities for Git automation
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.gitValidation = exports.GitValidation = void 0;
const tslib_1 = require("tslib");
const child_process_1 = require("child_process");
const enhanced_logger_1 = require("../utils/enhanced-logger");
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const path_1 = tslib_1.__importDefault(require("path"));
const DEFAULT_VALIDATION_CONFIG = {
preCommitValidation: true,
requireTests: false,
requireCoverage: false,
minCoverageThreshold: 80,
requireLinting: true,
requireTypeCheck: true,
maxCommitSize: 50, // 50MB
blockedFiles: [
'.env',
'.env.local',
'.env.production',
'*.key',
'*.pem',
'node_modules/**',
'.git/**'
],
allowedCommitTypes: [
'feat', 'fix', 'docs', 'style', 'refactor',
'test', 'chore', 'perf', 'ci', 'build', 'revert'
]
};
class GitValidation {
constructor(config) {
this.rollbackPoints = [];
this.rollbackFile = path_1.default.join(process.cwd(), '.dna-git-rollback.json');
this.config = { ...DEFAULT_VALIDATION_CONFIG, ...config };
this.loadRollbackPoints();
}
/**
* Validate before commit
*/
async validatePreCommit() {
const errors = [];
const warnings = [];
try {
// Check if there are staged changes
const stagedFiles = this.getStagedFiles();
if (stagedFiles.length === 0) {
errors.push('No staged files found');
return { passed: false, errors, warnings, canProceed: false };
}
// Validate commit size
const commitSize = await this.getCommitSize(stagedFiles);
if (commitSize > this.config.maxCommitSize * 1024 * 1024) {
warnings.push(`Large commit size: ${(commitSize / 1024 / 1024).toFixed(1)}MB`);
}
// Check for blocked files
const blockedFiles = this.checkBlockedFiles(stagedFiles);
if (blockedFiles.length > 0) {
errors.push(`Blocked files detected: ${blockedFiles.join(', ')}`);
}
// Validate tests if required
if (this.config.requireTests) {
const hasTests = this.hasTestFiles(stagedFiles);
if (!hasTests) {
errors.push('Tests required but no test files found in staged changes');
}
}
// Run linting if required
if (this.config.requireLinting) {
const lintResult = await this.runLinting();
if (!lintResult.passed) {
errors.push('Linting failed');
errors.push(...lintResult.errors);
}
}
// Run type checking if required
if (this.config.requireTypeCheck) {
const typeCheckResult = await this.runTypeCheck();
if (!typeCheckResult.passed) {
errors.push('Type checking failed');
errors.push(...typeCheckResult.errors);
}
}
// Check coverage if required
if (this.config.requireCoverage) {
const coverage = await this.getCoverage();
if (coverage < this.config.minCoverageThreshold) {
errors.push(`Coverage below threshold: ${coverage}% < ${this.config.minCoverageThreshold}%`);
}
}
const passed = errors.length === 0;
const canProceed = passed || warnings.length > 0;
return { passed, errors, warnings, canProceed };
}
catch (error) {
errors.push(`Validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
return { passed: false, errors, warnings, canProceed: false };
}
}
/**
* Create rollback point before making changes
*/
async createRollbackPoint(sessionId) {
try {
const rollbackId = `rollback-${Date.now()}`;
const currentBranch = this.getCurrentBranch();
const currentCommit = this.getCurrentCommit();
const stagedFiles = this.getStagedFiles();
const rollbackPoint = {
id: rollbackId,
timestamp: new Date().toISOString(),
branch: currentBranch,
commit: currentCommit,
message: `Rollback point before automation at ${new Date().toLocaleString()}`,
sessionId,
files: stagedFiles
};
this.rollbackPoints.push(rollbackPoint);
await this.saveRollbackPoints();
enhanced_logger_1.enhancedLogger.debug(`Created rollback point: ${rollbackId}`);
return rollbackId;
}
catch (error) {
enhanced_logger_1.enhancedLogger.error(`Failed to create rollback point: ${error instanceof Error ? error.message : 'Unknown error'}`);
throw error;
}
}
/**
* Rollback to a specific point
*/
async rollback(rollbackId) {
try {
const rollbackPoint = this.rollbackPoints.find(rp => rp.id === rollbackId);
if (!rollbackPoint) {
enhanced_logger_1.enhancedLogger.error(`Rollback point not found: ${rollbackId}`);
return false;
}
enhanced_logger_1.enhancedLogger.info(`${enhanced_logger_1.ICONS.warning} Rolling back to: ${rollbackPoint.message}`);
// Reset to the commit
(0, child_process_1.execSync)(`git reset --hard ${rollbackPoint.commit}`, { stdio: 'inherit' });
// Checkout the original branch if different
const currentBranch = this.getCurrentBranch();
if (currentBranch !== rollbackPoint.branch) {
(0, child_process_1.execSync)(`git checkout ${rollbackPoint.branch}`, { stdio: 'inherit' });
}
enhanced_logger_1.enhancedLogger.success(`${enhanced_logger_1.ICONS.check} Rollback completed successfully`);
return true;
}
catch (error) {
enhanced_logger_1.enhancedLogger.error(`Rollback failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
return false;
}
}
/**
* Validate commit message format
*/
validateCommitMessage(message) {
const errors = [];
const warnings = [];
// Check conventional commit format if enabled
const conventionalPattern = /^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .+/;
if (!conventionalPattern.test(message)) {
const firstLine = message.split('\n')[0];
const type = firstLine.split(':')[0].split('(')[0];
if (!this.config.allowedCommitTypes.includes(type)) {
errors.push(`Invalid commit type: ${type}. Allowed: ${this.config.allowedCommitTypes.join(', ')}`);
}
}
// Check message length
const firstLine = message.split('\n')[0];
if (firstLine.length > 72) {
warnings.push('Commit message first line is longer than 72 characters');
}
if (firstLine.length < 10) {
warnings.push('Commit message is very short');
}
const passed = errors.length === 0;
return { passed, errors, warnings, canProceed: passed };
}
/**
* Get list of rollback points
*/
getRollbackPoints() {
return [...this.rollbackPoints];
}
/**
* Clean up old rollback points
*/
async cleanupRollbackPoints(olderThanDays = 30) {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - olderThanDays);
this.rollbackPoints = this.rollbackPoints.filter(rp => {
return new Date(rp.timestamp) > cutoffDate;
});
await this.saveRollbackPoints();
enhanced_logger_1.enhancedLogger.debug(`Cleaned up rollback points older than ${olderThanDays} days`);
}
getStagedFiles() {
try {
const output = (0, child_process_1.execSync)('git diff --cached --name-only', { encoding: 'utf8' });
return output.split('\n').filter(Boolean);
}
catch {
return [];
}
}
async getCommitSize(files) {
let totalSize = 0;
for (const file of files) {
try {
if (await fs_extra_1.default.pathExists(file)) {
const stats = await fs_extra_1.default.stat(file);
totalSize += stats.size;
}
}
catch {
// File might be deleted, skip
}
}
return totalSize;
}
checkBlockedFiles(files) {
return files.filter(file => {
return this.config.blockedFiles.some(pattern => {
if (pattern.includes('*')) {
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
return regex.test(file);
}
return file === pattern;
});
});
}
hasTestFiles(files) {
return files.some(file => file.includes('.test.') ||
file.includes('.spec.') ||
file.includes('/test/') ||
file.includes('/tests/'));
}
async runLinting() {
try {
(0, child_process_1.execSync)('npm run lint', { stdio: 'pipe' });
return { passed: true, errors: [] };
}
catch (error) {
const output = error instanceof Error ? error.message : 'Linting failed';
return { passed: false, errors: [output] };
}
}
async runTypeCheck() {
try {
(0, child_process_1.execSync)('npm run typecheck', { stdio: 'pipe' });
return { passed: true, errors: [] };
}
catch (error) {
const output = error instanceof Error ? error.message : 'Type checking failed';
return { passed: false, errors: [output] };
}
}
async getCoverage() {
try {
// This would typically parse coverage reports
// For now, return a mock value
return 85;
}
catch {
return 0;
}
}
getCurrentBranch() {
try {
return (0, child_process_1.execSync)('git branch --show-current', { encoding: 'utf8' }).trim();
}
catch {
return 'main';
}
}
getCurrentCommit() {
try {
return (0, child_process_1.execSync)('git rev-parse HEAD', { encoding: 'utf8' }).trim();
}
catch {
return '';
}
}
async loadRollbackPoints() {
try {
if (await fs_extra_1.default.pathExists(this.rollbackFile)) {
this.rollbackPoints = await fs_extra_1.default.readJson(this.rollbackFile);
}
}
catch {
this.rollbackPoints = [];
}
}
async saveRollbackPoints() {
try {
await fs_extra_1.default.writeJson(this.rollbackFile, this.rollbackPoints, { spaces: 2 });
}
catch (error) {
enhanced_logger_1.enhancedLogger.error(`Failed to save rollback points: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
}
exports.GitValidation = GitValidation;
exports.gitValidation = new GitValidation();
//# sourceMappingURL=git-validation.js.map