Files
roa2web-service-auto/deployment/windows/scripts/Build-ROA2WEB.ps1
Marius Mutu 1832684aca Refactor Windows deployment scripts: unify build and management tools
Major improvements to deployment workflow with unified scripts and interactive menus.

New unified scripts:
- Build-ROA2WEB.ps1: Interactive menu for building all components
  * Isolated temp directory for frontend builds (prevents WSL node_modules corruption)
  * Automatic devDependencies installation (fixes Vite not found issue)
  * Auto-cleanup after build
  * Supports both interactive menu and non-interactive CLI

- ROA2WEB-Console.ps1: All-in-one deployment and management console
  * Interactive menus for deploy, manage services, and status checks
  * Automatic backups before deployment
  * Smart dependency updates (only if requirements.txt changed)
  * Health checks after service operations
  * Color-coded status output
  * Both interactive and non-interactive modes

Removed deprecated scripts (replaced by unified tools):
- Build-Frontend.ps1 → Use Build-ROA2WEB.ps1 -Component Frontend
- Build-TelegramBot.ps1 → Use Build-ROA2WEB.ps1 -Component TelegramBot
- Deploy-ROA2WEB.ps1 → Use ROA2WEB-Console.ps1 [Deploy menu]
- Deploy-TelegramBot.ps1 → Use ROA2WEB-Console.ps1 [Deploy menu]
- Manage-ROA2WEB.ps1 → Use ROA2WEB-Console.ps1 [Manage menu]

Updated documentation:
- Complete rewrite of scripts/README.md
- Clear workflow guides for first-time deployment and updates
- Comparison table v1.0 vs v2.0
- Updated best practices and troubleshooting

Benefits:
 Reduced from 13 to 8 scripts (better maintainability)
 Interactive menus for better UX
 Fixed WSL node_modules corruption issue
 Smart dependency management (faster deployments)
 Unified interface reduces learning curve
 Better error handling and health checks

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-12 01:35:14 +02:00

934 lines
29 KiB
PowerShell

<#
.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)
.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
.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
)
$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 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 " [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" }
"Q" {
Write-Host "`nBuild cancelled by user." -ForegroundColor Yellow
exit 0
}
default {
Write-Host "Invalid choice. Please select 1-4 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)"
# 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 {
# 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"
# Verify Vite is installed
$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"
# 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"
}
# 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"
}
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 = @(
"Install-ROA2WEB.ps1",
"Install-TelegramBot.ps1",
"Deploy-ROA2WEB.ps1",
"Deploy-TelegramBot.ps1",
"Manage-ROA2WEB.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:
-------------------
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") {
$readme += @"
Backup-TelegramDB.ps1 Backup Telegram bot database
Setup-DailyBackup.ps1 Schedule automated backups
Setup-ClaudeAuth.ps1 Configure Claude API credentials
"@
}
$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:
.\Manage-ROA2WEB.ps1 -Action Start -Component Backend
"@
}
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:
.\Manage-ROA2WEB.ps1 -Action Start -Component TelegramBot
"@
}
$readme += @"
>> UPDATES:
-----------
cd scripts
.\Manage-ROA2WEB.ps1 -Action Stop
.\Deploy-ROA2WEB.ps1 # Updates backend + frontend
.\Deploy-TelegramBot.ps1 # Updates Telegram bot
.\Manage-ROA2WEB.ps1 -Action Start
>> SERVICE MANAGEMENT:
----------------------
# Start all services
.\Manage-ROA2WEB.ps1 -Action Start
# Stop all services
.\Manage-ROA2WEB.ps1 -Action Stop
# Restart backend only
.\Manage-ROA2WEB.ps1 -Action Restart -Component Backend
# Check status
.\Manage-ROA2WEB.ps1 -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\
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 {
# 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