<# .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")] [string]$Component = "", [string]$OutputPath = "./deploy-package", [string]$ServerHost = "", [string]$ServerPath = "", [bool]$Clean = $true, [switch]$CleanCache ) $ErrorActionPreference = "Stop" # ============================================================================= # CONFIGURATION # ============================================================================= $config = @{ # Ultrathin Monolith sources (relative to deployment/windows/ directory) BackendSource = "../../backend" FrontendSource = "../../src" 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 Write-Info { param([string]$Message) Write-Host " [INFO] $Message" -ForegroundColor Gray } 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 " Ultrathin Monolith Architecture" -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 " (Unified Backend + Frontend)" -ForegroundColor Gray Write-Host " Includes: Reports, Data Entry, and Telegram modules" -ForegroundColor DarkGray Write-Host "" Write-Host " [2] Frontend + Backend" -ForegroundColor White Write-Host " (Vue.js SPA build + Unified FastAPI backend)" -ForegroundColor Gray Write-Host "" Write-Host " [3] Backend Only" -ForegroundColor White Write-Host " (Unified FastAPI backend + shared modules)" -ForegroundColor Gray Write-Host " All modules included - control via MODULE_*_ENABLED flags" -ForegroundColor DarkGray 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" } "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-3, 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" } # For ultrathin monolith, frontend sources are in src/ directory # but package.json and vite.config.js are in project root $appFolder = "roa2web-ultrathin-monolith" # Create temporary build directory - this is the project root # Structure: # .temp-frontend-build/ # |- package.json (from project root) # |- vite.config.js (from project root) # |- src/ (frontend source) # \- shared/ $tempRootDir = Join-Path $OutputPath ".temp-frontend-build" if (Test-Path $tempRootDir) { Write-Step "Cleaning existing temp build directory..." Remove-Item -Path $tempRootDir -Recurse -Force } # Create temp root (this will be where npm install runs) New-Item -ItemType Directory -Path $tempRootDir -Force | Out-Null # Create src subdirectory $tempSrcDir = Join-Path $tempRootDir "src" New-Item -ItemType Directory -Path $tempSrcDir -Force | Out-Null Write-Success "Created temp build directory (isolated from WSL)" # The build directory is the root, not src $tempBuildDir = $tempRootDir # Create cache directory for node_modules (OUTSIDE deploy-package) $scriptDir = Split-Path -Parent $PSScriptRoot $cacheDir = Join-Path $scriptDir ".build-cache-$appFolder" if (-not (Test-Path $cacheDir)) { New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null } # Copy src/ contents to temp src/ subdirectory 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 } # Copy to src/ subdirectory $destPath = Join-Path $tempSrcDir $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" # Copy package.json, vite.config.js, package-lock.json, index.html from project root Write-Step "Copying build configuration files..." $projectRoot = Split-Path $SourcePath -Parent $configFiles = @("package.json", "vite.config.js", "package-lock.json", "index.html") foreach ($file in $configFiles) { $srcFile = Join-Path $projectRoot $file if (Test-Path $srcFile) { $destFile = Join-Path $tempBuildDir $file Copy-Item -Path $srcFile -Destination $destFile -Force Write-Success "Copied: $file" } else { Write-Warning "Not found: $file" } } # Copy public/ folder (contains web.config and other static assets) $publicSourcePath = Join-Path $projectRoot "public" if (Test-Path $publicSourcePath) { $publicDestPath = Join-Path $tempBuildDir "public" Write-Step "Copying public/ folder..." Write-Info "Source: $publicSourcePath" Write-Info "Dest: $publicDestPath" Copy-Item -Path $publicSourcePath -Destination $publicDestPath -Recurse -Force Write-Success "public/ folder copied (includes web.config)" # Verify web.config was copied $copiedWebConfig = Join-Path $publicDestPath "web.config" if (Test-Path $copiedWebConfig) { Write-Success "web.config found in public/" } else { Write-Warning "web.config NOT found in public/ - check source" } } else { Write-Warning "public/ folder not found at: $publicSourcePath" } # Copy shared folder to maintain relative imports (../shared/) # For ultrathin monolith: src/ and shared/ are siblings at project root $projectRoot = Split-Path $SourcePath -Parent $sharedSourcePath = Join-Path $projectRoot "shared" if (Test-Path $sharedSourcePath) { $sharedDestPath = Join-Path $tempRootDir "shared" Write-Step "Copying shared components for build..." Write-Info "Source: $sharedSourcePath" Write-Info "Dest: $sharedDestPath" # Remove existing shared folder in dest if exists if (Test-Path $sharedDestPath) { Remove-Item -Path $sharedDestPath -Recurse -Force } Copy-Item -Path $sharedSourcePath -Destination $sharedDestPath -Recurse -Force -Exclude @("__pycache__", "*.pyc", "tests") Write-Success "Shared components copied for relative imports" } else { Write-Warning "Shared folder not found at: $sharedSourcePath" } # Build in temp directory (now at tempRootDir/src) 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" } # node_modules is already in tempBuildDir (which is tempRootDir) # No need for junction since we build from project root # 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)" # Verify web.config was built $webConfigPath = Join-Path $distPath "web.config" if (Test-Path $webConfigPath) { Write-Success "web.config found in build output" } else { Write-Warning "web.config NOT found in build output: $webConfigPath" Write-Warning "Check if public/web.config exists in source" } 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" } # Copy-TelegramBotFiles function removed - Telegram is now part of unified backend (backend/modules/telegram/) # Copy-DataEntryBackendFiles function removed - Data Entry is now part of unified backend (backend/modules/data_entry/) 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 # Debug: Show paths Write-Host " [DEBUG] ScriptsSourcePath: $ScriptsSourcePath" -ForegroundColor Magenta Write-Host " [DEBUG] DestPath: $DestPath" -ForegroundColor Magenta Write-Host " [DEBUG] ScriptsDir: $scriptsDir" -ForegroundColor Magenta # 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) { $destScript = Join-Path $scriptsDir $script # Debug: Show file info $sourceSize = (Get-Item $scriptPath).Length Write-Host " [DEBUG] Copying $script (size: $sourceSize bytes)" -ForegroundColor Magenta # Force delete destination first to avoid caching issues if (Test-Path $destScript) { $oldSize = (Get-Item $destScript).Length Write-Host " [DEBUG] Deleting old $script (was: $oldSize bytes)" -ForegroundColor Magenta Remove-Item -Path $destScript -Force } Copy-Item -Path $scriptPath -Destination $scriptsDir -Force # Debug: Verify copy $newSize = (Get-Item $destScript).Length Write-Host " [DEBUG] Copied $script (new size: $newSize bytes)" -ForegroundColor Magenta $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 (Ultrathin Monolith - All Modules)" } "Frontend" { "UNIFIED FRONTEND + BACKEND DEPLOYMENT PACKAGE" } "Backend" { "UNIFIED BACKEND DEPLOYMENT PACKAGE (All Modules)" } } $readme = @" ================================================================================ ROA2WEB DEPLOYMENT PACKAGE $componentDesc Generated: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss") ================================================================================ CONTENTS: --------- backend/ Unified FastAPI backend (port 8000) |- modules/ | |- reports/ Reports module (Oracle) | |- data_entry/ Data Entry module (SQLite) | \- telegram/ Telegram bot module (background task) \- main.py Single entry point (uvicorn) frontend/ Unified Vue.js SPA (production build) Single-page application with integrated modules shared/ Shared Python modules |- auth/ JWT authentication & middleware |- database/ Oracle connection pool \- routes/ Shared API routes (companies, calendar) config/ Configuration templates \- web.config IIS configuration for unified backend scripts/ PowerShell deployment scripts MODULE CONTROL: --------------- Enable/disable modules via environment variables in backend/.env: MODULE_REPORTS_ENABLED=true # Reports module MODULE_DATA_ENTRY_ENABLED=true # Data Entry module MODULE_TELEGRAM_ENABLED=true # Telegram bot module DEPLOYMENT SCRIPTS: ------------------- ROA2WEB-Console.ps1 Unified deployment & management console - Deploy components (Backend/Frontend) - Manage single unified service - Check system status and health Install-ROA2WEB.ps1 First-time installation (creates Windows service) Enable-HTTPS.ps1 Configure HTTPS/SSL certificates "@ $readme += @" ================================================================================ DEPLOYMENT WORKFLOW ================================================================================ >> FIRST TIME INSTALLATION: --------------------------- 1. Install Application (creates Windows service): cd scripts .\Install-ROA2WEB.ps1 2. Configure environment (.env file): notepad C:\inetpub\wwwroot\roa2web\backend\.env IMPORTANT - Configure module flags: MODULE_REPORTS_ENABLED=true # Enable/disable Reports module MODULE_DATA_ENTRY_ENABLED=true # Enable/disable Data Entry module MODULE_TELEGRAM_ENABLED=true # Enable/disable Telegram bot module 3. Start the unified backend service: Start-Service ROA2WEB-Backend OR using console: .\ROA2WEB-Console.ps1 (Select: Manage Services > Start Service) 4. Verify installation: - Backend API: http://localhost:8000/docs - Health check: http://localhost:8000/health - Frontend: http://localhost/ (via IIS) >> UPDATES (Deploy New Version): ---------------------------------- .\ROA2WEB-Console.ps1 -NonInteractive -Action DeployAll (Stops service, updates files, restarts service) >> SERVICE MANAGEMENT: ----------------------- # Start service Start-Service ROA2WEB-Backend # Stop service Stop-Service ROA2WEB-Backend # Restart service Restart-Service ROA2WEB-Backend # Check status Get-Service ROA2WEB-Backend .\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) - Single Windows service: ROA2WEB-Backend (manages all modules) ARCHITECTURE: ------------- ULTRATHIN MONOLITH: One backend process with multiple modules - Modules controlled via .env flags (MODULE_*_ENABLED) - All modules share: Oracle pool, auth, cache - Telegram bot runs as background task (not separate service) TROUBLESHOOTING: ---------------- Backend logs: C:\inetpub\wwwroot\roa2web\logs\backend-stdout.log C:\inetpub\wwwroot\roa2web\logs\backend-stderr.log IIS logs: C:\inetpub\logs\LogFiles\ Service status: Get-Service ROA2WEB-Backend 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" { # Unified Frontend (Vue.js SPA) $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 Unified Frontend files (SPA)..." Write-Info "Source: $frontendDistPath" Write-Info "Destination: $frontendDest" Copy-Item -Path "$frontendDistPath\*" -Destination $frontendDest -Recurse -Force # Verify web.config was copied $copiedWebConfig = Join-Path $frontendDest "web.config" if (Test-Path $copiedWebConfig) { Write-Success "web.config copied to package" } else { Write-Warning "web.config NOT copied to package" } Write-Success "Unified Frontend files copied" # Unified Backend (includes Reports, Data Entry, Telegram modules) $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 } "Frontend" { # Unified Frontend build (Vue.js SPA) $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 Unified Frontend files (SPA)..." Write-Info "Source: $frontendDistPath" Write-Info "Destination: $frontendDest" Copy-Item -Path "$frontendDistPath\*" -Destination $frontendDest -Recurse -Force # Verify web.config was copied $copiedWebConfig = Join-Path $frontendDest "web.config" if (Test-Path $copiedWebConfig) { Write-Success "web.config copied to package" } else { Write-Warning "web.config NOT copied to package" } Write-Success "Unified Frontend files copied" # Unified Backend (includes all modules) $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" { # Unified Backend only (includes Reports, Data Entry, Telegram modules) $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 } } # 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 = @{ # Ultrathin Monolith sources BackendSource = Resolve-FullPath -Path $config.BackendSource FrontendSource = Resolve-FullPath -Path $config.FrontendSource 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" -or $Component -eq "DataEntryApp") { 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