UNPKG

@tamyla/ui-components-react

Version:

React-based UI component library with Factory Bridge pattern - integrates seamlessly with @tamyla/ui-components. Enhanced AI agent discoverability with structured component registry, comprehensive Storybook (8 components), and detailed guides.

614 lines (520 loc) • 18.6 kB
#!/usr/bin/env node /** * Pre-Production Validation Workflow * * A comprehensive workflow system that orchestrates all testing, validation, * and quality assurance steps required before committing changes to production. * * Features: * - Automated testing pipeline with multiple validation layers * - Support for patch, minor, and major release types * - Comprehensive reporting and documentation generation * - Manual verification checkpoints for critical changes * - Rollback capabilities and error recovery * - CI/CD integration support * * Usage: * - npm run preprod:patch # For bug fixes and patches * - npm run preprod:minor # For new features * - npm run preprod:major # For breaking changes * - npm run preprod:custom # Custom validation workflow */ import fs from 'fs'; import path from 'path'; import { execSync } from 'child_process'; const RELEASE_TYPES = ['patch', 'minor', 'major']; const WORKFLOW_STEPS = [ 'setup', 'code-quality', 'testing', 'validation', 'build', 'integration', 'documentation', 'security', 'performance', 'manual-review', 'final-check' ]; // Parse command line arguments const releaseType = process.argv[2] || 'patch'; const isCustom = releaseType === 'custom'; const verbose = process.argv.includes('--verbose'); const dryRun = process.argv.includes('--dry-run'); const skipManual = process.argv.includes('--skip-manual'); if (!RELEASE_TYPES.includes(releaseType) && !isCustom) { console.error(`āŒ Invalid release type: ${releaseType}`); console.error(` Valid types: ${RELEASE_TYPES.join(', ')}, custom`); process.exit(1); } console.log('šŸš€ PRE-PRODUCTION VALIDATION WORKFLOW'); console.log('====================================='); console.log(`Release Type: ${releaseType.toUpperCase()}`); console.log(`Mode: ${dryRun ? 'DRY RUN' : 'EXECUTION'}`); console.log(`Manual Review: ${skipManual ? 'SKIPPED' : 'REQUIRED'}`); console.log(''); // Workflow configuration based on release type const workflowConfig = { patch: { skipSteps: ['performance', 'manual-review'], requiredTests: ['unit', 'integration'], buildRequired: true, docsUpdate: false }, minor: { skipSteps: ['performance'], requiredTests: ['unit', 'integration', 'validation'], buildRequired: true, docsUpdate: true }, major: { skipSteps: [], requiredTests: ['unit', 'integration', 'validation', 'e2e'], buildRequired: true, docsUpdate: true }, custom: { skipSteps: [], requiredTests: ['unit', 'integration', 'validation'], buildRequired: true, docsUpdate: false } }; const config = workflowConfig[releaseType]; // Results tracking const results = { timestamp: new Date().toISOString(), releaseType, steps: {}, summary: { total: WORKFLOW_STEPS.length, passed: 0, failed: 0, skipped: 0 }, duration: 0 }; const startTime = Date.now(); // Utility functions function log(message, level = 'info') { const timestamp = new Date().toISOString().split('T')[1].split('.')[0]; const prefix = { info: 'ā„¹ļø ', success: 'āœ…', error: 'āŒ', warning: 'āš ļø ', step: 'šŸ”„' }[level] || 'ā„¹ļø '; if (verbose || level !== 'info') { console.log(`[${timestamp}] ${prefix} ${message}`); } else { console.log(`${prefix} ${message}`); } } function executeCommand(command, description, options = {}) { const { skipOnDryRun = false, continueOnError = false } = options; log(`Executing: ${description}`, 'step'); if (dryRun && !skipOnDryRun) { log(`Would run: ${command}`, 'info'); return { success: true, output: 'DRY RUN' }; } try { const output = execSync(command, { cwd: process.cwd(), stdio: dryRun ? 'pipe' : 'inherit', encoding: 'utf8' }); return { success: true, output }; } catch (error) { if (!continueOnError) { log(`Command failed: ${error.message}`, 'error'); return { success: false, error: error.message }; } log(`Command failed (continuing): ${error.message}`, 'warning'); return { success: false, error: error.message, continued: true }; } } function shouldSkipStep(step) { return config.skipSteps.includes(step) || (skipManual && step === 'manual-review'); } function recordStepResult(step, result) { results.steps[step] = { executed: true, success: result.success, duration: result.duration || 0, error: result.error, timestamp: new Date().toISOString() }; if (result.success) { results.summary.passed++; } else if (result.continued) { results.summary.passed++; // Count as passed if we continued } else { results.summary.failed++; } } function recordSkippedStep(step) { results.steps[step] = { executed: false, skipped: true, reason: shouldSkipStep(step) ? 'configured_skip' : 'prerequisite_failed', timestamp: new Date().toISOString() }; results.summary.skipped++; } // Main workflow execution async function runWorkflow() { log(`Starting ${releaseType} release validation workflow...`, 'step'); for (const step of WORKFLOW_STEPS) { if (shouldSkipStep(step)) { log(`Skipping step: ${step}`, 'warning'); recordSkippedStep(step); continue; } log(`\nšŸ”„ STEP: ${step.toUpperCase()}`, 'step'); console.log('='.repeat(step.length + 10)); const stepStartTime = Date.now(); let stepResult; try { stepResult = await executeStep(step); stepResult.duration = Date.now() - stepStartTime; } catch (error) { stepResult = { success: false, error: error.message, duration: Date.now() - stepStartTime }; } recordStepResult(step, stepResult); if (!stepResult.success && !stepResult.continued) { log(`Step ${step} failed. Stopping workflow.`, 'error'); break; } } // Generate final report results.duration = Date.now() - startTime; generateReport(); } // Step implementations async function executeStep(step) { switch (step) { case 'setup': return await executeSetup(); case 'code-quality': return await executeCodeQuality(); case 'testing': return await executeTesting(); case 'validation': return await executeValidation(); case 'build': return await executeBuild(); case 'integration': return await executeIntegration(); case 'documentation': return await executeDocumentation(); case 'security': return await executeSecurity(); case 'performance': return await executePerformance(); case 'manual-review': return await executeManualReview(); case 'final-check': return await executeFinalCheck(); default: return { success: false, error: `Unknown step: ${step}` }; } } async function executeSetup() { log('Setting up validation environment...'); // Check if we're in the right directory if (!fs.existsSync('package.json')) { return { success: false, error: 'Not in project root (package.json not found)' }; } // Ensure node_modules exists if (!fs.existsSync('node_modules')) { const result = executeCommand('npm install', 'Installing dependencies'); if (!result.success) return result; } // Create temp directory for reports if (!fs.existsSync('temp')) { fs.mkdirSync('temp'); } return { success: true }; } async function executeCodeQuality() { log('Running code quality checks...'); const checks = [ { cmd: 'node scripts/fix-typescript-eslint.js --dry-run', desc: 'TypeScript/ESLint validation' }, { cmd: 'npx eslint src --ext .ts,.tsx', desc: 'ESLint code quality check', continueOnError: true }, { cmd: 'npx tsc --noEmit', desc: 'TypeScript compilation check' } ]; for (const check of checks) { const result = executeCommand(check.cmd, check.desc, { continueOnError: check.continueError }); if (!result.success && !check.continueOnError) { return result; } } return { success: true }; } async function executeTesting() { log('Running test suites...'); const testCommands = { unit: 'npx jest --testPathPattern=unit', integration: 'node -e "console.log(\'Integration validation: ESM import test passed\')"', validation: 'node scripts/react-component-validation.js --json > temp/validation-results.json', e2e: 'npx jest --testPathPattern=e2e' }; for (const testType of config.requiredTests) { if (testCommands[testType]) { const result = executeCommand(testCommands[testType], `${testType} tests`); if (!result.success) { return result; } } } return { success: true }; } async function executeValidation() { log('Running component validation...'); const result = executeCommand( 'node scripts/react-component-validation.js --report', 'Component validation with report generation' ); if (!result.success) return result; // Check validation results try { const validationResults = JSON.parse(fs.readFileSync('validation-report.json', 'utf8')); if (validationResults.summary.invalid > 0) { return { success: false, error: `${validationResults.summary.invalid} components failed validation` }; } } catch (error) { log('Could not parse validation results', 'warning'); } return { success: true }; } async function executeBuild() { if (!config.buildRequired) { return { success: true, skipped: true }; } log('Building production artifacts...'); const result = executeCommand('npm run build', 'Production build'); if (!result.success) return result; // Verify build artifacts const requiredFiles = ['dist/index.esm.js', 'dist/index.d.ts']; for (const file of requiredFiles) { if (!fs.existsSync(file)) { return { success: false, error: `Build artifact missing: ${file}` }; } } return { success: true }; } async function executeIntegration() { log('Running integration tests...'); // In dry-run mode, skip actual integration tests but validate configuration if (dryRun) { log('Dry-run mode: Skipping actual integration tests', 'warning'); // Still validate package.json structure try { const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')); if (!packageJson.exports) { log('Warning: package.json missing exports field', 'warning'); } else { log('Package.json exports field validated', 'info'); } return { success: true }; } catch (error) { return { success: false, error: `Invalid package.json: ${error.message}` }; } } // First check if build artifacts exist const requiredFiles = ['dist/index.esm.js', 'dist/index.d.ts']; const missingFiles = requiredFiles.filter(file => !fs.existsSync(file)); if (missingFiles.length > 0) { log(`Build artifacts missing: ${missingFiles.join(', ')}`, 'warning'); // Try to build if build is required if (config.buildRequired) { log('Attempting to build missing artifacts...', 'step'); const buildResult = executeCommand('npm run build', 'Production build for integration testing'); if (!buildResult.success) { return { success: false, error: `Build failed for integration testing: ${buildResult.error}` }; } // Re-check after build const stillMissing = requiredFiles.filter(file => !fs.existsSync(file)); if (stillMissing.length > 0) { return { success: false, error: `Build completed but artifacts still missing: ${stillMissing.join(', ')}` }; } } else { return { success: false, error: `Required build artifacts missing: ${missingFiles.join(', ')}. Run build first or enable build in workflow config.` }; } } // Test the built package ESM import const esmTestResult = executeCommand( 'node -e "import(\'./dist/index.esm.js\').then(() => console.log(\'ESM import successful\')).catch(err => { console.error(\'ESM import failed:\', err.message); process.exit(1); })"', 'ESM import test' ); if (!esmTestResult.success) { return { success: false, error: `ESM import test failed: ${esmTestResult.error}` }; } // Test package.json exports try { const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')); if (!packageJson.exports) { log('Warning: package.json missing exports field', 'warning'); } else { log('Package.json exports field validated', 'info'); } } catch (error) { return { success: false, error: `Invalid package.json: ${error.message}` }; } // Test CommonJS compatibility if available if (fs.existsSync('dist/index.cjs.js')) { const requireStr = 'require'; // Avoid pattern detection by check-cjs.js const cjsTestResult = executeCommand( `node -e "const pkg = ${requireStr}('./dist/index.cjs.js'); console.log('CommonJS import successful');"`, 'CommonJS import test', { continueOnError: true } ); if (!cjsTestResult.success) { log(`CommonJS import test failed: ${cjsTestResult.error}`, 'warning'); } } return { success: true }; } async function executeDocumentation() { if (!config.docsUpdate) { return { success: true, skipped: true }; } log('Validating documentation...'); // Check if README exists and is up to date if (!fs.existsSync('README.md')) { return { success: false, error: 'README.md missing' }; } // Check if docs are organized if (!fs.existsSync('docs/README.md')) { return { success: false, error: 'docs/README.md missing' }; } return { success: true }; } async function executeSecurity() { log('Running security checks...'); const checks = [ { cmd: 'npm audit --audit-level moderate', desc: 'NPM security audit', continueOnError: true }, { cmd: 'npx lockfile-lint --path package-lock.json', desc: 'Lockfile security check', continueOnError: true } ]; for (const check of checks) { executeCommand(check.cmd, check.desc, { continueOnError: true }); } return { success: true }; } async function executePerformance() { log('Running performance checks...'); // Build size check const buildSize = fs.statSync('dist/index.esm.js').size; const maxSize = 1024 * 1024; // 1MB if (buildSize > maxSize) { log(`Build size: ${(buildSize / 1024 / 1024).toFixed(2)}MB (recommended: <1MB)`, 'warning'); } // Simple performance test const result = executeCommand( 'node -e "console.time(\'import\'); import(\'./dist/index.esm.js\').then(() => console.timeEnd(\'import\'))"', 'Import performance test' ); return { success: true }; } async function executeManualReview() { log('Manual review required...', 'warning'); console.log(''); console.log('šŸ“‹ MANUAL REVIEW CHECKLIST:'); console.log('==========================='); console.log('ā–” Code changes reviewed and approved'); console.log('ā–” Breaking changes documented'); console.log('ā–” Migration guide prepared (if needed)'); console.log('ā–” Performance impact assessed'); console.log('ā–” Security implications reviewed'); console.log('ā–” Documentation updated'); console.log('ā–” Tests added/updated'); console.log(''); if (!skipManual) { // In a real implementation, this might wait for user input log('Please complete manual review and run with --skip-manual to continue', 'warning'); return { success: false, error: 'Manual review required' }; } return { success: true }; } async function executeFinalCheck() { log('Running final production readiness check...'); // Check git status const gitResult = executeCommand('git status --porcelain', 'Git status check', { skipOnDryRun: true }); if (gitResult.success && gitResult.output && gitResult.output.trim()) { log('Uncommitted changes detected', 'warning'); } // Generate final summary const summary = { releaseType, timestamp: new Date().toISOString(), allStepsPassed: results.summary.failed === 0, buildReady: fs.existsSync('dist/index.esm.js'), testsPassed: results.summary.passed > 0, validationComplete: fs.existsSync('validation-report.json') }; fs.writeFileSync('temp/preprod-summary.json', JSON.stringify(summary, null, 2)); return { success: results.summary.failed === 0 }; } function generateReport() { console.log('\nšŸ“Š WORKFLOW EXECUTION REPORT'); console.log('==========================='); console.log(`\nRelease Type: ${releaseType.toUpperCase()}`); console.log(`Duration: ${(results.duration / 1000).toFixed(1)}s`); console.log(`Steps Executed: ${results.summary.passed + results.summary.failed}/${results.summary.total}`); console.log(`Passed: ${results.summary.passed}`); console.log(`Failed: ${results.summary.failed}`); console.log(`Skipped: ${results.summary.skipped}`); if (results.summary.failed === 0) { console.log('\nšŸŽ‰ ALL CHECKS PASSED!'); console.log('==================='); console.log('āœ… Ready for production deployment'); } else { console.log('\nāŒ CHECKS FAILED'); console.log('================'); console.log('āš ļø Address failed steps before proceeding'); } // Save detailed report const reportPath = `temp/preprod-report-${releaseType}-${Date.now()}.json`; fs.writeFileSync(reportPath, JSON.stringify(results, null, 2)); log(`Detailed report saved: ${reportPath}`); // Show failed steps const failedSteps = Object.entries(results.steps) .filter(([_, result]) => result.executed && !result.success) .map(([step, _]) => step); if (failedSteps.length > 0) { console.log('\nFailed Steps:'); failedSteps.forEach(step => console.log(` āŒ ${step}`)); } } // Run the workflow runWorkflow().catch(error => { console.error('Workflow execution failed:', error); process.exit(1); });