<# .SYNOPSIS Install a built CMBot release into the local launcher layout. .DESCRIPTION Creates or updates this structure: %LOCALAPPDATA%\CMBot\ update.ps1 app\ app.old\ data\ config\ logs\ output\ staging\ The script copies a built release directory (release\CMBot-x.y.z by default) into app\, copies scripts\update.ps1 to the install root, and migrates config files into data\config without overwriting existing user config. .PARAMETER ReleaseDir Built release directory to install. Defaults to release\CMBot-APP_VERSION. .PARAMETER InstallRoot Local install root. Defaults to %LOCALAPPDATA%\CMBot. .PARAMETER LegacyDataDir Optional old flat install/data root. When provided, config files are copied from LegacyDataDir\config before falling back to app.old\config or release defaults. .PARAMETER Force Allows replacing an existing app.old directory. #> [CmdletBinding()] param( [string]$ReleaseDir = "", [string]$InstallRoot = (Join-Path $env:LOCALAPPDATA "CMBot"), [string]$LegacyDataDir = "", [switch]$Force ) $ErrorActionPreference = "Stop" $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $RootDir = Split-Path -Parent $ScriptDir $VersionFile = Join-Path $RootDir "src\version.py" $ReleaseRoot = Join-Path $RootDir "release" $LauncherScript = Join-Path $ScriptDir "update.ps1" $AppExeName = "CMBot" $AppExe = "$AppExeName.exe" function Read-VersionValue { param( [string]$Path, [string]$Name ) $pattern = "^\s*$Name\s*=\s*`"([^`"]+)`"" foreach ($line in Get-Content -LiteralPath $Path -Encoding UTF8) { if ($line -match $pattern) { return $Matches[1] } } throw "Cannot find $Name in $Path" } function Resolve-ExistingPath { param( [string]$Path, [string]$Name ) if (-not (Test-Path -LiteralPath $Path)) { throw "$Name not found: $Path" } return (Resolve-Path -LiteralPath $Path).Path } function Assert-Release { param([string]$Path) $exe = Join-Path $Path $AppExe $version = Join-Path $Path "version.txt" if (-not (Test-Path -LiteralPath $exe)) { throw "Release missing executable: $exe" } if (-not (Test-Path -LiteralPath $version)) { throw "Release missing version.txt: $version" } } function Copy-ConfigIfMissing { param( [string]$SourceConfigDir, [string]$TargetConfigDir, [string]$FileName ) if (-not $SourceConfigDir) { return } $source = Join-Path $SourceConfigDir $FileName $target = Join-Path $TargetConfigDir $FileName if ((Test-Path -LiteralPath $source) -and -not (Test-Path -LiteralPath $target)) { Copy-Item -LiteralPath $source -Destination $target Write-Host "Migrated config: $FileName" } } if (-not $ReleaseDir) { $appVersion = Read-VersionValue -Path $VersionFile -Name "APP_VERSION" $ReleaseDir = Join-Path $ReleaseRoot "$AppExeName-$appVersion" } $ReleaseDir = Resolve-ExistingPath -Path $ReleaseDir -Name "Release directory" $LauncherScript = Resolve-ExistingPath -Path $LauncherScript -Name "Launcher script" Assert-Release -Path $ReleaseDir $InstallRoot = [System.IO.Path]::GetFullPath($InstallRoot) $AppDir = Join-Path $InstallRoot "app" $OldAppDir = Join-Path $InstallRoot "app.old" $DataDir = Join-Path $InstallRoot "data" $ConfigDir = Join-Path $DataDir "config" $LogsDir = Join-Path $DataDir "logs" $OutputDir = Join-Path $DataDir "output" $StagingDir = Join-Path $InstallRoot "staging" New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null New-Item -ItemType Directory -Force -Path $ConfigDir | Out-Null New-Item -ItemType Directory -Force -Path $LogsDir | Out-Null New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null New-Item -ItemType Directory -Force -Path $StagingDir | Out-Null if (Test-Path -LiteralPath $AppDir) { if (Test-Path -LiteralPath $OldAppDir) { if (-not $Force) { throw "app.old already exists: $OldAppDir. Re-run with -Force to replace it." } Remove-Item -LiteralPath $OldAppDir -Recurse -Force } Move-Item -LiteralPath $AppDir -Destination $OldAppDir } Copy-Item -LiteralPath $ReleaseDir -Destination $AppDir -Recurse Copy-Item -LiteralPath $LauncherScript -Destination (Join-Path $InstallRoot "update.ps1") -Force $configSources = @() if ($LegacyDataDir) { $configSources += Join-Path ([System.IO.Path]::GetFullPath($LegacyDataDir)) "config" } if (Test-Path -LiteralPath $OldAppDir) { $configSources += Join-Path $OldAppDir "config" } $configSources += Join-Path $AppDir "config" foreach ($sourceConfig in $configSources) { Copy-ConfigIfMissing -SourceConfigDir $sourceConfig -TargetConfigDir $ConfigDir -FileName "app_config.json" Copy-ConfigIfMissing -SourceConfigDir $sourceConfig -TargetConfigDir $ConfigDir -FileName "templates.json" } Write-Host "Installed CMBot launcher layout:" Write-Host " Root: $InstallRoot" Write-Host " App: $AppDir" Write-Host " Data: $DataDir" Write-Host "" Write-Host "Start with:" Write-Host " powershell -ExecutionPolicy Bypass -File `"$InstallRoot\update.ps1`""