diff --git a/.gitignore b/.gitignore index ca32c5a..8f27094 100644 --- a/.gitignore +++ b/.gitignore @@ -354,6 +354,11 @@ deployment/windows/deploy-package/ deploy-package/ **/deploy-package/ +# Build cache (npm node_modules cache for faster builds) +deployment/windows/.build-cache/ +.build-cache/ +**/.build-cache/ + # Deployment logs and temporary files deployment/windows/scripts/*.log deployment/windows/temp/ diff --git a/deployment/windows/scripts/Build-ROA2WEB.ps1 b/deployment/windows/scripts/Build-ROA2WEB.ps1 index c59bfcf..2e837a8 100644 --- a/deployment/windows/scripts/Build-ROA2WEB.ps1 +++ b/deployment/windows/scripts/Build-ROA2WEB.ps1 @@ -33,6 +33,9 @@ .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 @@ -53,6 +56,10 @@ .\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) @@ -67,7 +74,8 @@ param( [string]$OutputPath = "./deploy-package", [string]$ServerHost = "", [string]$ServerPath = "", - [bool]$Clean = $true + [bool]$Clean = $true, + [switch]$CleanCache ) $ErrorActionPreference = "Stop" @@ -119,6 +127,46 @@ function Resolve-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 @@ -138,6 +186,9 @@ function Show-BuildMenu { 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 @@ -151,12 +202,18 @@ function Show-BuildMenu { "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 or Q." -ForegroundColor Red + Write-Host "Invalid choice. Please select 1-4, C or Q." -ForegroundColor Red } } } while ($true) @@ -210,6 +267,13 @@ function Build-Frontend { 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") @@ -257,19 +321,66 @@ function Build-Frontend { # Build in temp directory Push-Location $tempBuildDir try { - # 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 + # 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" - $nodeModulesPath = Join-Path $tempBuildDir "node_modules" - if (-not (Test-Path $nodeModulesPath)) { - throw "npm install failed: node_modules not created" + $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" } - Write-Success "Dependencies installed in temp" # 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" @@ -376,6 +487,16 @@ function Copy-BackendFiles { $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)) { @@ -464,28 +585,15 @@ function Copy-TelegramBotFiles { Write-Success "requirements.txt copied" } - # Create .env.example - $envTemplate = @" -# ROA2WEB Telegram Bot - Production Configuration -TELEGRAM_BOT_TOKEN=your_bot_token_from_BotFather -CLAUDE_API_KEY=your_claude_api_key -BACKEND_URL=http://localhost:8000 -BACKEND_TIMEOUT=30 -SQLITE_DB_PATH=C:\inetpub\wwwroot\roa2web\telegram-bot\data\telegram_bot.db -INTERNAL_API_HOST=127.0.0.1 -INTERNAL_API_PORT=8002 -LOG_LEVEL=INFO -LOG_FILE=C:\inetpub\wwwroot\roa2web\telegram-bot\logs\bot.log -ENVIRONMENT=production -AUTH_CODE_EXPIRY_MINUTES=15 -JWT_REFRESH_THRESHOLD_MINUTES=5 -SESSION_TIMEOUT_MINUTES=60 -MAX_CONVERSATION_HISTORY=20 -"@ - - $envPath = Join-Path $DestPath ".env.example" - Set-Content -Path $envPath -Value $envTemplate -Encoding UTF8 - Write-Success ".env.example template created" + # 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 { @@ -552,11 +660,9 @@ function Copy-DeploymentScripts { # Essential scripts for all deployments $scripts = @( + "ROA2WEB-Console.ps1", # Unified deployment & management console "Install-ROA2WEB.ps1", - "Install-TelegramBot.ps1", - "Deploy-ROA2WEB.ps1", - "Deploy-TelegramBot.ps1", - "Manage-ROA2WEB.ps1" + "Install-TelegramBot.ps1" ) # Add utility scripts for complete deployments @@ -637,11 +743,13 @@ CONTENTS: 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 - Deploy-ROA2WEB.ps1 Update backend + frontend - Deploy-TelegramBot.ps1 Update Telegram bot - Manage-ROA2WEB.ps1 Unified service management (start/stop/restart) "@ if ($ComponentType -eq "All" -or $ComponentType -eq "TelegramBot") { @@ -653,6 +761,13 @@ DEPLOYMENT SCRIPTS: "@ } + if ($ComponentType -eq "All" -or $ComponentType -eq "Frontend") { + $readme += @" + + Enable-HTTPS.ps1 Configure HTTPS/SSL certificates +"@ + } + $readme += @" @@ -674,8 +789,9 @@ DEPLOYMENT WORKFLOW 2. Configure environment: notepad C:\inetpub\wwwroot\roa2web\backend\.env -3. Start services: - .\Manage-ROA2WEB.ps1 -Action Start -Component Backend +3. Start services using the unified console: + .\ROA2WEB-Console.ps1 + (Select: Manage Services > Start All) "@ } @@ -690,34 +806,44 @@ DEPLOYMENT WORKFLOW notepad C:\inetpub\wwwroot\roa2web\telegram-bot\.env 6. Start Telegram bot: - .\Manage-ROA2WEB.ps1 -Action Start -Component TelegramBot + .\ROA2WEB-Console.ps1 + (Select: Manage Services > Start Telegram Bot) "@ } $readme += @" ->> UPDATES: ------------ +>> UPDATES (Interactive Console): +---------------------------------- cd scripts -.\Manage-ROA2WEB.ps1 -Action Stop -.\Deploy-ROA2WEB.ps1 # Updates backend + frontend -.\Deploy-TelegramBot.ps1 # Updates Telegram bot -.\Manage-ROA2WEB.ps1 -Action Start +.\ROA2WEB-Console.ps1 +(Select: Deploy Components > choose what to update) ->> SERVICE MANAGEMENT: ----------------------- +>> 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 -.\Manage-ROA2WEB.ps1 -Action Start +.\ROA2WEB-Console.ps1 -NonInteractive -Action StartAll # Stop all services -.\Manage-ROA2WEB.ps1 -Action Stop +.\ROA2WEB-Console.ps1 -NonInteractive -Action StopAll -# Restart backend only -.\Manage-ROA2WEB.ps1 -Action Restart -Component Backend +# Restart all services +.\ROA2WEB-Console.ps1 -NonInteractive -Action RestartAll # Check status -.\Manage-ROA2WEB.ps1 -Action Status +.\ROA2WEB-Console.ps1 -NonInteractive -Action Status ================================================================================ REQUIREMENTS @@ -733,6 +859,7 @@ NOTES: - .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: ---------------- @@ -867,6 +994,12 @@ function New-DeploymentPackage { # ============================================================================= 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 diff --git a/deployment/windows/scripts/ROA2WEB-Console.ps1 b/deployment/windows/scripts/ROA2WEB-Console.ps1 index 0e2c772..707d3df 100644 --- a/deployment/windows/scripts/ROA2WEB-Console.ps1 +++ b/deployment/windows/scripts/ROA2WEB-Console.ps1 @@ -459,7 +459,7 @@ function Show-ServiceStatus { $service = Get-ServiceSafe -ServiceName $ServiceName - Write-Host "`n$ComponentName:" -ForegroundColor Cyan + Write-Host "`n${ComponentName}:" -ForegroundColor Cyan Write-Host " Service Name: $ServiceName" -ForegroundColor Gray if ($service) { @@ -633,6 +633,14 @@ function Update-BackendFiles { Write-Success "Backend files updated" + # Copy .env.example explicitly (excluded from recursive copy) + $sourceEnvExample = Join-Path $sourceBackend ".env.example" + $destEnvExample = Join-Path $Config.BackendPath ".env.example" + if (Test-Path $sourceEnvExample) { + Copy-Item -Path $sourceEnvExample -Destination $destEnvExample -Force + Write-Success ".env.example template updated" + } + # Check if requirements.txt changed $sourceReq = Join-Path $sourceBackend "requirements.txt" $destReq = Join-Path $Config.BackendPath "requirements.txt" @@ -743,12 +751,19 @@ function Update-TelegramBotFiles { } } - # Preserve .env file + # Copy .env.example template (always update to keep in sync) + $sourceEnvExample = Join-Path $Config.SourcePath ".env.example" + $destEnvExample = Join-Path $Config.TelegramBotPath ".env.example" + if (Test-Path $sourceEnvExample) { + Copy-Item -Path $sourceEnvExample -Destination $destEnvExample -Force + Write-Success ".env.example template updated" + } + + # Preserve .env file (or create from .env.example if missing) $envFile = Join-Path $Config.TelegramBotPath ".env" if (-not (Test-Path $envFile)) { - $sourceEnv = Join-Path $Config.SourcePath ".env.example" - if (Test-Path $sourceEnv) { - Copy-Item -Path $sourceEnv -Destination $envFile -Force + if (Test-Path $sourceEnvExample) { + Copy-Item -Path $sourceEnvExample -Destination $envFile -Force Write-Warning "Created .env from .env.example - PLEASE CONFIGURE" } } else {