<# .SYNOPSIS Unified Build Script for ROA2WEB Windows Deployment .DESCRIPTION This script builds complete deployment packages for ROA2WEB application. Supports building Frontend, Backend, Telegram Bot, or all components together. Features: - Flexible component selection - Validates dependencies (Node.js, Python) - Creates production-optimized builds - Generates deployment-ready packages - Supports automatic server transfer .PARAMETER Component Component(s) to build (optional - shows interactive menu if not specified): - All: Build complete package (frontend + backend + telegram bot) - Frontend: Build frontend + backend files only - Backend: Copy backend files only (no frontend build) - TelegramBot: Build telegram bot package only - If omitted: Interactive menu will be displayed .PARAMETER OutputPath Output path for deployment package (default: ./deploy-package) .PARAMETER ServerHost Remote server hostname/IP for automatic deployment (optional) .PARAMETER ServerPath Remote server path for automatic deployment (optional) .PARAMETER Clean Clean output directory before building (default: true) .PARAMETER CleanCache Clean build cache (cached node_modules) and exit .EXAMPLE .\Build-ROA2WEB.ps1 Shows interactive menu to select components to build .EXAMPLE .\Build-ROA2WEB.ps1 -Component All Build complete deployment package (all components) without menu .EXAMPLE .\Build-ROA2WEB.ps1 -Component Frontend Build only frontend + backend files .EXAMPLE .\Build-ROA2WEB.ps1 -Component TelegramBot Build only Telegram bot package .EXAMPLE .\Build-ROA2WEB.ps1 -Component All -OutputPath "D:\deployments\roa2web-$(Get-Date -Format 'yyyyMMdd')" Build complete package to custom output path .EXAMPLE .\Build-ROA2WEB.ps1 -CleanCache Clean build cache to free disk space .NOTES Author: ROA2WEB Team Version: 2.0 (Unified Build Script) Requires: Node.js 16+ (for Frontend), Python 3.11+ (for validation) #> [CmdletBinding()] param( [ValidateSet("All", "Frontend", "Backend", "TelegramBot")] [string]$Component = "", [string]$OutputPath = "./deploy-package", [string]$ServerHost = "", [string]$ServerPath = "", [bool]$Clean = $true, [switch]$CleanCache ) $ErrorActionPreference = "Stop" # ============================================================================= # CONFIGURATION # ============================================================================= $config = @{ BackendSource = "../../reports-app/backend" FrontendSource = "../../reports-app/frontend" TelegramBotSource = "../../reports-app/telegram-bot" SharedSource = "../../shared" ConfigSource = "../config" RequiredNodeVersion = 16 } # ============================================================================= # HELPER FUNCTIONS # ============================================================================= function Write-Step { param([string]$Message) Write-Host "`n[*] $Message" -ForegroundColor Cyan } function Write-Success { param([string]$Message) Write-Host " [OK] $Message" -ForegroundColor Green } function Write-Error { param([string]$Message) Write-Host " [ERROR] $Message" -ForegroundColor Red } function Write-Warning { param([string]$Message) Write-Host " [WARN] $Message" -ForegroundColor Yellow } function Resolve-FullPath { param([string]$Path) $scriptDir = Split-Path -Parent $PSScriptRoot $fullPath = Join-Path $scriptDir $Path $fullPath = [System.IO.Path]::GetFullPath($fullPath) return $fullPath } function Clear-BuildCache { Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan Write-Host " CLEAN BUILD CACHE" -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan $scriptDir = Split-Path -Parent $PSScriptRoot $cacheDir = Join-Path $scriptDir ".build-cache" if (-not (Test-Path $cacheDir)) { Write-Warning "No build cache found at: $cacheDir" Write-Host "`nNothing to clean." -ForegroundColor Gray return } try { # Calculate cache size $cacheFiles = Get-ChildItem -Path $cacheDir -Recurse -File -ErrorAction SilentlyContinue $cacheSize = ($cacheFiles | Measure-Object -Property Length -Sum).Sum / 1MB Write-Host "`nCache location: $cacheDir" -ForegroundColor Gray Write-Host "Cache size: $([math]::Round($cacheSize, 2)) MB" -ForegroundColor Gray Write-Host "Files: $(($cacheFiles).Count)" -ForegroundColor Gray Write-Host "" Write-Host "Are you sure you want to delete the build cache? [Y/N]: " -ForegroundColor Yellow -NoNewline $confirmation = Read-Host if ($confirmation.ToUpper() -eq "Y") { Write-Step "Removing build cache..." Remove-Item -Path $cacheDir -Recurse -Force -ErrorAction Stop Write-Success "Build cache cleared successfully" Write-Success "Freed $([math]::Round($cacheSize, 2)) MB of disk space" } else { Write-Host "`nCache cleanup cancelled." -ForegroundColor Yellow } } catch { Write-Host "`n[ERROR] Failed to clear cache: $_" -ForegroundColor Red } } function Show-BuildMenu { Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan Write-Host " ROA2WEB - Build Component Selection Menu" -ForegroundColor Cyan Write-Host ("=" * 70) -ForegroundColor Cyan Write-Host "" Write-Host " Select components to build:" -ForegroundColor Yellow Write-Host "" Write-Host " [1] All Components" -ForegroundColor White Write-Host " (Frontend + Backend + Telegram Bot)" -ForegroundColor Gray Write-Host "" Write-Host " [2] Frontend + Backend" -ForegroundColor White Write-Host " (Vue.js build + FastAPI backend files)" -ForegroundColor Gray Write-Host "" Write-Host " [3] Backend Only" -ForegroundColor White Write-Host " (FastAPI backend files + shared modules)" -ForegroundColor Gray Write-Host "" Write-Host " [4] Telegram Bot Only" -ForegroundColor White Write-Host " (Telegram bot standalone package)" -ForegroundColor Gray Write-Host "" Write-Host " [C] Clean Build Cache" -ForegroundColor Yellow Write-Host " (Remove cached node_modules to free disk space)" -ForegroundColor Gray Write-Host "" Write-Host " [Q] Quit" -ForegroundColor Red Write-Host "" Write-Host ("=" * 70) -ForegroundColor Cyan do { Write-Host "`nYour choice: " -ForegroundColor Yellow -NoNewline $choice = Read-Host switch ($choice.ToUpper()) { "1" { return "All" } "2" { return "Frontend" } "3" { return "Backend" } "4" { return "TelegramBot" } "C" { Clear-BuildCache Write-Host "`nPress any key to return to menu..." -ForegroundColor Gray $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") return Show-BuildMenu } "Q" { Write-Host "`nBuild cancelled by user." -ForegroundColor Yellow exit 0 } default { Write-Host "Invalid choice. Please select 1-4, C or Q." -ForegroundColor Red } } } while ($true) } function Test-NodeJS { Write-Step "Checking Node.js installation..." try { $nodeVersion = node --version 2>&1 $npmVersion = npm --version 2>&1 Write-Success "Node.js: $nodeVersion" Write-Success "npm: $npmVersion" # Check minimum version if ($nodeVersion -match "v(\d+)\.") { $major = [int]$matches[1] if ($major -lt $config.RequiredNodeVersion) { throw "Node.js version $($config.RequiredNodeVersion)+ required (found: $nodeVersion)" } } return $true } catch { Write-Error "Node.js not found or version too old" Write-Host "`n Install Node.js from: https://nodejs.org/" -ForegroundColor Yellow Write-Host " Minimum version: $($config.RequiredNodeVersion).x" -ForegroundColor Yellow throw } } function Build-Frontend { param( [string]$SourcePath, [string]$OutputPath ) Write-Step "Building Vue.js frontend in isolated environment..." if (-not (Test-Path $SourcePath)) { throw "Frontend source path not found: $SourcePath" } # Create temporary build directory $tempBuildDir = Join-Path $OutputPath ".temp-frontend-build" if (Test-Path $tempBuildDir) { Write-Step "Cleaning existing temp build directory..." Remove-Item -Path $tempBuildDir -Recurse -Force } New-Item -ItemType Directory -Path $tempBuildDir -Force | Out-Null Write-Success "Created temp build directory (isolated from WSL)" # Create cache directory for node_modules (OUTSIDE deploy-package) $scriptDir = Split-Path -Parent $PSScriptRoot $cacheDir = Join-Path $scriptDir ".build-cache" if (-not (Test-Path $cacheDir)) { New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null } # Copy frontend sources to temp (exclude node_modules, dist, .git) Write-Step "Copying frontend sources to temp directory..." $excludeDirs = @("node_modules", "dist", ".git", "__pycache__", ".vscode", ".idea") $excludeFiles = @(".env", ".env.local", "*.log") Get-ChildItem -Path $SourcePath -Recurse | ForEach-Object { $relativePath = $_.FullName.Substring($SourcePath.Length).TrimStart('\', '/') # Check if in excluded directory $inExcludedDir = $false foreach ($excludeDir in $excludeDirs) { if ($relativePath -match "^$excludeDir" -or $relativePath -match "[\\/]$excludeDir[\\/]") { $inExcludedDir = $true break } } if ($inExcludedDir) { return } # Check if excluded file $isExcludedFile = $false foreach ($pattern in $excludeFiles) { if ($_.Name -like $pattern) { $isExcludedFile = $true break } } if ($isExcludedFile) { return } $destPath = Join-Path $tempBuildDir $relativePath if ($_.PSIsContainer) { if (-not (Test-Path $destPath)) { New-Item -ItemType Directory -Path $destPath -Force | Out-Null } } else { $destDir = Split-Path $destPath -Parent if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir -Force | Out-Null } Copy-Item -Path $_.FullName -Destination $destPath -Force } } Write-Success "Frontend sources copied to temp" # Build in temp directory Push-Location $tempBuildDir try { # Check if dependencies need to be reinstalled $needsInstall = $true $cachedNodeModules = Join-Path $cacheDir "node_modules" $cachedPackageJson = Join-Path $cacheDir "package.json" $cachedPackageLock = Join-Path $cacheDir "package-lock.json" $currentPackageJson = Join-Path $tempBuildDir "package.json" $currentPackageLock = Join-Path $tempBuildDir "package-lock.json" if ((Test-Path $cachedNodeModules) -and (Test-Path $cachedPackageJson)) { # Compare package.json hashes $currentHash = (Get-FileHash $currentPackageJson -Algorithm SHA256).Hash $cachedHash = (Get-FileHash $cachedPackageJson -Algorithm SHA256).Hash # Also compare package-lock.json if it exists $lockMatches = $true if ((Test-Path $currentPackageLock) -and (Test-Path $cachedPackageLock)) { $currentLockHash = (Get-FileHash $currentPackageLock -Algorithm SHA256).Hash $cachedLockHash = (Get-FileHash $cachedPackageLock -Algorithm SHA256).Hash $lockMatches = ($currentLockHash -eq $cachedLockHash) } if (($currentHash -eq $cachedHash) -and $lockMatches) { Write-Step "Reusing cached node_modules (dependencies unchanged)..." Copy-Item -Path $cachedNodeModules -Destination $tempBuildDir -Recurse -Force $needsInstall = $false Write-Success "Dependencies restored from cache (saved 2-5 minutes!)" } else { Write-Info "Dependencies changed, will reinstall" } } if ($needsInstall) { # Install dependencies (including devDependencies for Vite) Write-Step "Installing npm dependencies (Windows binaries)..." # Clear NODE_ENV to ensure devDependencies (vite, etc.) are installed Remove-Item Env:\NODE_ENV -ErrorAction SilentlyContinue npm install | Out-Default $nodeModulesPath = Join-Path $tempBuildDir "node_modules" if (-not (Test-Path $nodeModulesPath)) { throw "npm install failed: node_modules not created" } Write-Success "Dependencies installed in temp" # Update cache Write-Step "Caching node_modules for future builds..." if (Test-Path $cachedNodeModules) { Remove-Item -Path $cachedNodeModules -Recurse -Force } Copy-Item -Path $nodeModulesPath -Destination $cachedNodeModules -Recurse -Force Copy-Item -Path $currentPackageJson -Destination $cachedPackageJson -Force if (Test-Path $currentPackageLock) { Copy-Item -Path $currentPackageLock -Destination $cachedPackageLock -Force } Write-Success "Cache updated for next build" } # Verify Vite is installed $nodeModulesPath = Join-Path $tempBuildDir "node_modules" $vitePath = Join-Path $nodeModulesPath ".bin\vite.cmd" if (-not (Test-Path $vitePath)) { throw "Vite not found in node_modules - devDependencies not installed" } # Build for production Write-Step "Building for production..." $env:NODE_ENV = "production" npm run build | Out-Default Write-Success "Build completed" # Verify dist folder $distPath = Join-Path $tempBuildDir "dist" if (-not (Test-Path $distPath)) { throw "Build failed: dist folder not found" } $distFiles = Get-ChildItem -Path $distPath -Recurse -File $totalSize = ($distFiles | Measure-Object -Property Length -Sum).Sum / 1MB Write-Success "Generated $(($distFiles).Count) files ($([math]::Round($totalSize, 2)) MB)" return $distPath } finally { Pop-Location } } function Copy-BackendFiles { param( [string]$SourcePath, [string]$DestPath ) Write-Step "Copying backend files..." if (-not (Test-Path $SourcePath)) { throw "Backend source path not found: $SourcePath" } if (-not (Test-Path $DestPath)) { New-Item -ItemType Directory -Path $DestPath -Force | Out-Null } # Exclude patterns $excludeDirs = @("venv", "__pycache__", ".pytest_cache", "logs", "temp", "node_modules") $excludeFiles = @("*.pyc", "*.pyo", "*.log", ".env", ".env.local") $normalizedSourcePath = $SourcePath.TrimEnd('\', '/') + '\' $testExclude = { param([string]$RelativePath, [bool]$IsDirectory, [array]$ExcludeDirs, [array]$ExcludeFiles) if ($IsDirectory) { $dirName = Split-Path $RelativePath -Leaf if ($ExcludeDirs -contains $dirName) { return $true } } $pathParts = $RelativePath -split '[\\/]' foreach ($part in $pathParts) { if ($ExcludeDirs -contains $part) { return $true } } if (-not $IsDirectory) { foreach ($pattern in $ExcludeFiles) { if ($RelativePath -like $pattern) { return $true } } } return $false } Get-ChildItem -Path $SourcePath -Recurse | ForEach-Object { if ($_.FullName.Length -le $normalizedSourcePath.Length) { return } $relativePath = $_.FullName.Substring($normalizedSourcePath.Length) $shouldExclude = & $testExclude -RelativePath $relativePath -IsDirectory $_.PSIsContainer -ExcludeDirs $excludeDirs -ExcludeFiles $excludeFiles if ($shouldExclude) { return } $destFile = Join-Path $DestPath $relativePath if ($_.PSIsContainer) { if (-not (Test-Path $destFile)) { New-Item -ItemType Directory -Path $destFile -Force | Out-Null } } else { $destDir = Split-Path $destFile -Parent if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir -Force | Out-Null } Copy-Item -Path $_.FullName -Destination $destFile -Force } } $backendFiles = Get-ChildItem -Path $DestPath -Recurse -File Write-Success "Copied $(($backendFiles).Count) backend files" # Copy .env.example explicitly (excluded from recursive copy) $sourceEnvExample = Join-Path $SourcePath ".env.example" $destEnvExample = Join-Path $DestPath ".env.example" if (Test-Path $sourceEnvExample) { Copy-Item -Path $sourceEnvExample -Destination $destEnvExample -Force Write-Success ".env.example template copied" } else { Write-Warning ".env.example not found in source - manual configuration required" } # Verify requirements.txt $requirementsTxt = Join-Path $DestPath "requirements.txt" if (-not (Test-Path $requirementsTxt)) { Write-Error "CRITICAL: requirements.txt not found!" throw "Backend package incomplete - missing requirements.txt" } Write-Success "Verified: requirements.txt present" } function Copy-TelegramBotFiles { param( [string]$SourcePath, [string]$DestPath ) Write-Step "Copying Telegram bot files..." if (-not (Test-Path $SourcePath)) { throw "Telegram bot source path not found: $SourcePath" } if (-not (Test-Path $DestPath)) { New-Item -ItemType Directory -Path $DestPath -Force | Out-Null } # Exclude patterns $excludeDirs = @("venv", "data", "logs", "temp", "backups", "__pycache__", ".pytest_cache", "tests", ".git") $excludeFiles = @(".env", "*.pyc", "*.pyo", "*.log", "*.db", ".DS_Store", "Thumbs.db") # Copy app/ directory $sourceApp = Join-Path $SourcePath "app" $destApp = Join-Path $DestPath "app" New-Item -ItemType Directory -Path $destApp -Force | Out-Null $fileCount = 0 Get-ChildItem -Path $sourceApp -Recurse | ForEach-Object { # Check exclusions $inExcludedDir = $false foreach ($excludeDir in $excludeDirs) { if ($_.FullName -like "*\$excludeDir\*" -or $_.FullName -like "*/$excludeDir/*") { $inExcludedDir = $true break } } if ($inExcludedDir) { return } $isExcludedFile = $false foreach ($pattern in $excludeFiles) { if ($_.Name -like $pattern) { $isExcludedFile = $true break } } if ($isExcludedFile) { return } $relativePath = $_.FullName.Substring($sourceApp.Length) $destFile = Join-Path $destApp $relativePath if ($_.PSIsContainer) { if (-not (Test-Path $destFile)) { New-Item -ItemType Directory -Path $destFile -Force | Out-Null } } else { $destFileDir = Split-Path $destFile -Parent if (-not (Test-Path $destFileDir)) { New-Item -ItemType Directory -Path $destFileDir -Force | Out-Null } Copy-Item -Path $_.FullName -Destination $destFile -Force $fileCount++ } } Write-Success "Copied $fileCount app files" # Copy requirements.txt $sourceReq = Join-Path $SourcePath "requirements.txt" $destReq = Join-Path $DestPath "requirements.txt" if (Test-Path $sourceReq) { Copy-Item -Path $sourceReq -Destination $destReq -Force Write-Success "requirements.txt copied" } # Copy .env.example from source (keeps it synchronized with development) $sourceEnvExample = Join-Path $SourcePath ".env.example" $destEnvExample = Join-Path $DestPath ".env.example" if (Test-Path $sourceEnvExample) { Copy-Item -Path $sourceEnvExample -Destination $destEnvExample -Force Write-Success ".env.example template copied" } else { Write-Warning ".env.example not found in source - manual configuration required" } } function Copy-SharedModules { param( [string]$SourcePath, [string]$DestPath ) Write-Step "Copying shared modules..." if (Test-Path $SourcePath) { Copy-Item -Path $SourcePath -Destination $DestPath -Recurse -Force -Exclude @("__pycache__", "*.pyc", "tests") Write-Success "Shared modules copied" } else { Write-Warning "Shared modules not found at: $SourcePath" } } function Copy-ConfigTemplates { param( [string]$SourcePath, [string]$DestPath ) Write-Step "Copying config templates..." if (Test-Path $SourcePath) { Copy-Item -Path $SourcePath -Destination $DestPath -Recurse -Force Write-Success "Config templates copied" } } function Remove-TempDirectories { param([string]$OutputPath) Write-Step "Cleaning up temporary build directories..." $tempBuildDir = Join-Path $OutputPath ".temp-frontend-build" if (Test-Path $tempBuildDir) { try { Remove-Item -Path $tempBuildDir -Recurse -Force -ErrorAction Stop Write-Success "Temporary build directory cleaned" } catch { Write-Warning "Could not remove temp directory: $_" Write-Warning "You may need to manually delete: $tempBuildDir" } } else { Write-Success "No temporary directories to clean" } } function Copy-DeploymentScripts { param( [string]$ScriptsSourcePath, [string]$DestPath, [string]$ComponentType ) Write-Step "Copying deployment scripts..." $scriptsDir = Join-Path $DestPath "scripts" New-Item -ItemType Directory -Path $scriptsDir -Force | Out-Null # Essential scripts for all deployments $scripts = @( "ROA2WEB-Console.ps1", # Unified deployment & management console "Install-ROA2WEB.ps1", "Install-TelegramBot.ps1" ) # Add utility scripts for complete deployments if ($ComponentType -eq "All" -or $ComponentType -eq "TelegramBot") { $scripts += @( "Backup-TelegramDB.ps1", "Setup-DailyBackup.ps1", "Setup-ClaudeAuth.ps1" ) } if ($ComponentType -eq "All" -or $ComponentType -eq "Frontend") { $scripts += @( "Enable-HTTPS.ps1" ) } $copiedCount = 0 foreach ($script in $scripts) { $scriptPath = Join-Path $ScriptsSourcePath $script if (Test-Path $scriptPath) { Copy-Item -Path $scriptPath -Destination $scriptsDir -Force $copiedCount++ } else { Write-Warning "Script not found: $script" } } Write-Success "Copied $copiedCount deployment scripts" } function New-DeploymentReadme { param( [string]$DestPath, [string]$ComponentType ) Write-Step "Creating deployment README..." $componentDesc = switch ($ComponentType) { "All" { "COMPLETE DEPLOYMENT PACKAGE (Frontend + Backend + Telegram Bot)" } "Frontend" { "FRONTEND + BACKEND DEPLOYMENT PACKAGE" } "Backend" { "BACKEND DEPLOYMENT PACKAGE" } "TelegramBot" { "TELEGRAM BOT DEPLOYMENT PACKAGE" } } $readme = @" ================================================================================ ROA2WEB DEPLOYMENT PACKAGE $componentDesc Generated: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss") ================================================================================ CONTENTS: --------- "@ if ($ComponentType -eq "All" -or $ComponentType -eq "Frontend") { $readme += @" backend/ FastAPI backend application (Python) frontend/ Vue.js static files (production build) shared/ Shared Python modules (auth, database, utils) config/ Configuration templates (.env, web.config) "@ } if ($ComponentType -eq "All" -or $ComponentType -eq "TelegramBot") { $readme += @" telegram-bot/ Telegram bot application "@ } $readme += @" scripts/ PowerShell deployment scripts DEPLOYMENT SCRIPTS: ------------------- ROA2WEB-Console.ps1 Unified deployment & management console - Deploy components (Backend/Frontend/TelegramBot) - Manage services (Start/Stop/Restart) - Check system status and health Install-ROA2WEB.ps1 First-time backend + frontend setup Install-TelegramBot.ps1 First-time Telegram bot setup "@ if ($ComponentType -eq "All" -or $ComponentType -eq "TelegramBot") { $readme += @" Backup-TelegramDB.ps1 Backup Telegram bot database Setup-DailyBackup.ps1 Schedule automated backups Setup-ClaudeAuth.ps1 Configure Claude API credentials "@ } if ($ComponentType -eq "All" -or $ComponentType -eq "Frontend") { $readme += @" Enable-HTTPS.ps1 Configure HTTPS/SSL certificates "@ } $readme += @" ================================================================================ DEPLOYMENT WORKFLOW ================================================================================ >> FIRST TIME INSTALLATION: --------------------------- "@ if ($ComponentType -eq "All" -or $ComponentType -eq "Frontend") { $readme += @" 1. Install Main Application (Backend + Frontend): cd scripts .\Install-ROA2WEB.ps1 2. Configure environment: notepad C:\inetpub\wwwroot\roa2web\backend\.env 3. Start services using the unified console: .\ROA2WEB-Console.ps1 (Select: Manage Services > Start All) "@ } if ($ComponentType -eq "All" -or $ComponentType -eq "TelegramBot") { $readme += @" 4. Install Telegram Bot: .\Install-TelegramBot.ps1 5. Configure Telegram bot: notepad C:\inetpub\wwwroot\roa2web\telegram-bot\.env 6. Start Telegram bot: .\ROA2WEB-Console.ps1 (Select: Manage Services > Start Telegram Bot) "@ } $readme += @" >> UPDATES (Interactive Console): ---------------------------------- cd scripts .\ROA2WEB-Console.ps1 (Select: Deploy Components > choose what to update) >> UPDATES (Command Line): --------------------------- .\ROA2WEB-Console.ps1 -NonInteractive -Action DeployBackend # Update backend + frontend .\ROA2WEB-Console.ps1 -NonInteractive -Action DeployTelegramBot # Update Telegram bot .\ROA2WEB-Console.ps1 -NonInteractive -Action DeployAll # Update everything >> SERVICE MANAGEMENT (Interactive): ------------------------------------- .\ROA2WEB-Console.ps1 (Select: Manage Services > choose action) >> SERVICE MANAGEMENT (Command Line): -------------------------------------- # Start all services .\ROA2WEB-Console.ps1 -NonInteractive -Action StartAll # Stop all services .\ROA2WEB-Console.ps1 -NonInteractive -Action StopAll # Restart all services .\ROA2WEB-Console.ps1 -NonInteractive -Action RestartAll # Check status .\ROA2WEB-Console.ps1 -NonInteractive -Action Status ================================================================================ REQUIREMENTS ================================================================================ - Windows Server 2016+ or Windows 10/11 - IIS with URL Rewrite Module - Python 3.11+ - PowerShell 5.1+ (run as Administrator) NOTES: ------ - Virtual environments created automatically during installation - .env files preserved during updates - Automatic backup before each update - Default install location: C:\inetpub\wwwroot\roa2web\ - Build cache NOT included in this package (stays on build machine) TROUBLESHOOTING: ---------------- Backend logs: C:\inetpub\wwwroot\roa2web\logs\ Telegram logs: C:\inetpub\wwwroot\roa2web\telegram-bot\logs\ IIS logs: C:\inetpub\logs\LogFiles\ For detailed documentation, see: deployment/windows/docs/WINDOWS_DEPLOYMENT.md ================================================================================ "@ $readmePath = Join-Path $DestPath "README.txt" Set-Content -Path $readmePath -Value $readme -Force Write-Success "Deployment README created" } function New-DeploymentPackage { param( [string]$OutputPath, [string]$ComponentType, [hashtable]$Paths ) Write-Step "Creating deployment package structure..." # Clean output if requested if ($Clean -and (Test-Path $OutputPath)) { Write-Warning "Cleaning output directory..." Remove-Item -Path $OutputPath -Recurse -Force } if (-not (Test-Path $OutputPath)) { New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null } # Build based on component type switch ($ComponentType) { "All" { # Frontend $frontendDistPath = Build-Frontend -SourcePath $Paths.FrontendSource -OutputPath $OutputPath $frontendDest = Join-Path $OutputPath "frontend" New-Item -ItemType Directory -Path $frontendDest -Force | Out-Null Write-Step "Copying frontend files..." Copy-Item -Path "$frontendDistPath\*" -Destination $frontendDest -Recurse -Force Write-Success "Frontend files copied" # Backend $backendDest = Join-Path $OutputPath "backend" Copy-BackendFiles -SourcePath $Paths.BackendSource -DestPath $backendDest # Shared modules $sharedDest = Join-Path $OutputPath "shared" Copy-SharedModules -SourcePath $Paths.SharedSource -DestPath $sharedDest # Config templates $configDest = Join-Path $OutputPath "config" Copy-ConfigTemplates -SourcePath $Paths.ConfigSource -DestPath $configDest # Telegram Bot $telegramDest = Join-Path $OutputPath "telegram-bot" Copy-TelegramBotFiles -SourcePath $Paths.TelegramBotSource -DestPath $telegramDest } "Frontend" { # Frontend build $frontendDistPath = Build-Frontend -SourcePath $Paths.FrontendSource -OutputPath $OutputPath $frontendDest = Join-Path $OutputPath "frontend" New-Item -ItemType Directory -Path $frontendDest -Force | Out-Null Write-Step "Copying frontend files..." Copy-Item -Path "$frontendDistPath\*" -Destination $frontendDest -Recurse -Force Write-Success "Frontend files copied" # Backend $backendDest = Join-Path $OutputPath "backend" Copy-BackendFiles -SourcePath $Paths.BackendSource -DestPath $backendDest # Shared modules $sharedDest = Join-Path $OutputPath "shared" Copy-SharedModules -SourcePath $Paths.SharedSource -DestPath $sharedDest # Config templates $configDest = Join-Path $OutputPath "config" Copy-ConfigTemplates -SourcePath $Paths.ConfigSource -DestPath $configDest } "Backend" { # Backend only $backendDest = Join-Path $OutputPath "backend" Copy-BackendFiles -SourcePath $Paths.BackendSource -DestPath $backendDest # Shared modules $sharedDest = Join-Path $OutputPath "shared" Copy-SharedModules -SourcePath $Paths.SharedSource -DestPath $sharedDest # Config templates $configDest = Join-Path $OutputPath "config" Copy-ConfigTemplates -SourcePath $Paths.ConfigSource -DestPath $configDest } "TelegramBot" { # Telegram Bot only $telegramDest = Join-Path $OutputPath "telegram-bot" Copy-TelegramBotFiles -SourcePath $Paths.TelegramBotSource -DestPath $telegramDest } } # Cleanup temporary directories if ($ComponentType -eq "All" -or $ComponentType -eq "Frontend") { Remove-TempDirectories -OutputPath $OutputPath } # Copy deployment scripts Copy-DeploymentScripts -ScriptsSourcePath $PSScriptRoot -DestPath $OutputPath -ComponentType $ComponentType # Create README New-DeploymentReadme -DestPath $OutputPath -ComponentType $ComponentType # Calculate package size $packageFiles = Get-ChildItem -Path $OutputPath -Recurse -File $packageSize = ($packageFiles | Measure-Object -Property Length -Sum).Sum / 1MB Write-Success "Deployment package created" Write-Success "Total files: $(($packageFiles).Count)" Write-Success "Total size: $([math]::Round($packageSize, 2)) MB" return $OutputPath } # ============================================================================= # MAIN BUILD FLOW # ============================================================================= function Main { # Handle -CleanCache parameter if ($CleanCache) { Clear-BuildCache exit 0 } # Show interactive menu if no component specified if ([string]::IsNullOrWhiteSpace($Component)) { $script:Component = Show-BuildMenu } $banner = @" ==================================================================== ROA2WEB - Unified Build Script (v2.0) Building: $Component ==================================================================== "@ Write-Host $banner -ForegroundColor Cyan try { # Resolve paths $paths = @{ BackendSource = Resolve-FullPath -Path $config.BackendSource FrontendSource = Resolve-FullPath -Path $config.FrontendSource TelegramBotSource = Resolve-FullPath -Path $config.TelegramBotSource SharedSource = Resolve-FullPath -Path $config.SharedSource ConfigSource = Resolve-FullPath -Path $config.ConfigSource } $outputFullPath = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { Resolve-FullPath -Path $OutputPath } Write-Host "`nConfiguration:" -ForegroundColor Yellow Write-Host " Component: $Component" Write-Host " Output Path: $outputFullPath" # Validate Node.js for frontend builds if ($Component -eq "All" -or $Component -eq "Frontend") { Test-NodeJS } # Build package $packagePath = New-DeploymentPackage ` -OutputPath $outputFullPath ` -ComponentType $Component ` -Paths $paths # Show success Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan Write-Host " BUILD COMPLETED SUCCESSFULLY" -ForegroundColor Green Write-Host ("=" * 70) -ForegroundColor Cyan Write-Host "`nDeployment Package: $packagePath" -ForegroundColor Yellow Write-Host "`nNext Steps:" -ForegroundColor Yellow Write-Host " 1. Transfer package to Windows Server" -ForegroundColor Gray Write-Host " 2. On server, run deployment scripts from scripts/ directory" -ForegroundColor Gray Write-Host " 3. Use Manage-ROA2WEB.ps1 for service management" -ForegroundColor Gray Write-Host "`n" + ("=" * 70) -ForegroundColor Cyan } catch { Write-Host "`n[BUILD FAILED] $_" -ForegroundColor Red Write-Host $_.ScriptStackTrace -ForegroundColor Red exit 1 } } # Run main build Main