188 lines
5.4 KiB
TypeScript
188 lines
5.4 KiB
TypeScript
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);
|
|
});
|