import { execSync } from 'child_process'; import { existsSync, rmSync } from 'fs'; import { join } from 'path'; /** * Comprehensive Build Script for SerpentRace Backend * Handles TypeScript compilation, migrations, asset copying, and validation */ interface BuildOptions { runMigrations?: boolean; runTests?: boolean; skipLinting?: boolean; production?: boolean; } class BuildManager { private distDir = join(__dirname, '..', 'dist'); constructor(private options: BuildOptions = {}) {} private log(message: string, level: 'info' | 'error' | 'warn' = 'info') { const timestamp = new Date().toISOString(); const prefix = { info: '🔧', error: '❌', warn: '⚠️' }[level]; console.log(`${prefix} [${timestamp}] ${message}`); } private execute(command: string, description: string) { this.log(`${description}...`); try { execSync(command, { stdio: 'inherit', cwd: join(__dirname, '..') }); this.log(`✅ ${description} completed successfully`); } catch (error) { this.log(`❌ ${description} failed`, 'error'); throw error; } } async clean() { this.log('Cleaning previous build...'); if (existsSync(this.distDir)) { rmSync(this.distDir, { recursive: true, force: true }); this.log('✅ Previous build cleaned'); } else { this.log('No previous build found'); } } async typecheck() { this.execute('npx tsc --noEmit', 'Type checking'); } async lint() { if (this.options.skipLinting) { this.log('Skipping linting...', 'warn'); return; } // For now, just check if TypeScript compiles without errors this.log('Linting (basic type checking)...'); await this.typecheck(); } async runTests() { if (!this.options.runTests) { this.log('Skipping tests...', 'warn'); return; } this.execute('npm test', 'Running tests'); } async runMigrations() { if (!this.options.runMigrations) { this.log('Skipping database migrations...', 'warn'); return; } try { this.log('Checking migration status...'); execSync('npm run migration:show', { stdio: 'pipe', cwd: join(__dirname, '..') }); this.execute('npm run migration:run', 'Running database migrations'); } catch (error) { this.log('Migration check/run failed - this might be expected in CI/CD environments', 'warn'); if (this.options.production) { throw error; // In production builds, migrations should work } } } async compile() { this.execute('npx tsc', 'Compiling TypeScript'); } async copyAssets() { this.execute('node scripts/copy-assets.js', 'Copying assets'); } async validateBuild() { this.log('Validating build output...'); const expectedFiles = [ 'dist/Api/index.js', 'dist/Api/index.d.ts' ]; const missingFiles = expectedFiles.filter(file => !existsSync(join(__dirname, '..', file)) ); if (missingFiles.length > 0) { this.log(`Missing expected build files: ${missingFiles.join(', ')}`, 'error'); throw new Error('Build validation failed'); } this.log('✅ Build validation completed'); } async build() { const startTime = Date.now(); try { this.log('🚀 Starting SerpentRace Backend build process...'); // Step 1: Clean previous build await this.clean(); // Step 2: Lint code (if not skipped) await this.lint(); // Step 3: Run tests (if enabled) await this.runTests(); // Step 4: Run migrations (if enabled) await this.runMigrations(); // Step 5: Compile TypeScript await this.compile(); // Step 6: Copy assets await this.copyAssets(); // Step 7: Validate build await this.validateBuild(); const duration = ((Date.now() - startTime) / 1000).toFixed(2); this.log(`🎉 Build completed successfully in ${duration}s`); } catch (error) { const duration = ((Date.now() - startTime) / 1000).toFixed(2); this.log(`💥 Build failed after ${duration}s`, 'error'); if (error instanceof Error) { this.log(`Error: ${error.message}`, 'error'); } process.exit(1); } } } // Parse command line arguments const args = process.argv.slice(2); const options: BuildOptions = { runMigrations: args.includes('--migrations'), runTests: args.includes('--test'), skipLinting: args.includes('--skip-lint'), production: args.includes('--production') }; // Create and run build const buildManager = new BuildManager(options); buildManager.build().catch(error => { console.error('Unhandled build error:', error); process.exit(1); });