Files
cmbot/scripts/build.ps1
T

179 lines
5.5 KiB
PowerShell
Raw Normal View History

2026-06-16 11:49:25 +08:00
param(
2026-06-18 09:45:08 +08:00
[switch]$SkipBuild,
[string]$ManifestBaseUrl = "",
[string]$PublishDir = "",
[string]$ReleaseNotes = "",
[string]$MinSupported = "",
[switch]$Mandatory
2026-06-16 11:49:25 +08:00
)
$ErrorActionPreference = "Stop"
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$RootDir = Split-Path -Parent $ScriptDir
$SrcDir = Join-Path $RootDir "src"
$VersionFile = Join-Path $SrcDir "version.py"
$DefaultConfigDir = Join-Path $RootDir "packaging\default_config"
$BuildDir = Join-Path $RootDir "build"
$DistDir = Join-Path $RootDir "dist"
$ReleaseRoot = Join-Path $RootDir "release"
$AppExeName = "CMBot"
2026-06-16 11:49:25 +08:00
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 Assert-InProject {
param([string]$Path)
$resolvedRoot = (Resolve-Path -LiteralPath $RootDir).Path
if (Test-Path -LiteralPath $Path) {
$resolvedPath = (Resolve-Path -LiteralPath $Path).Path
if (-not $resolvedPath.StartsWith($resolvedRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
throw "Refusing to modify path outside project: $resolvedPath"
}
}
}
2026-06-18 09:45:08 +08:00
function Join-Url {
param(
[string]$BaseUrl,
[string]$Name
)
if (-not $BaseUrl) {
return $Name
}
if (-not $BaseUrl.EndsWith("/")) {
$BaseUrl = "$BaseUrl/"
}
return (New-Object -TypeName System.Uri -ArgumentList ([System.Uri]$BaseUrl), $Name).AbsoluteUri
}
2026-06-16 11:49:25 +08:00
$AppName = Read-VersionValue -Path $VersionFile -Name "APP_NAME"
$AppVersion = Read-VersionValue -Path $VersionFile -Name "APP_VERSION"
$ReleaseName = "$AppExeName-$AppVersion"
$ReleaseDir = Join-Path $ReleaseRoot $ReleaseName
2026-06-18 09:45:08 +08:00
$ReleaseZip = Join-Path $ReleaseRoot "$ReleaseName.zip"
$ManifestPath = Join-Path $ReleaseRoot "manifest.json"
2026-06-16 11:49:25 +08:00
$PyInstallerOutputDir = Join-Path $DistDir $AppExeName
$ExePath = Join-Path $ReleaseDir "$AppExeName.exe"
Write-Host "Building $AppName $AppVersion"
$pythonVersionText = & python --version
if ($LASTEXITCODE -ne 0) {
throw "Python is not available."
}
if ($pythonVersionText -notmatch "Python 3\.7\.") {
throw "Python 3.7 is required, current version: $pythonVersionText"
}
& python -c "import PyInstaller"
if ($LASTEXITCODE -ne 0) {
throw "PyInstaller is not installed. Run: pip install -r requirements.txt"
}
if (-not (Test-Path -LiteralPath $DefaultConfigDir)) {
throw "Default config directory not found: $DefaultConfigDir"
}
New-Item -ItemType Directory -Force -Path $ReleaseRoot | Out-Null
2026-06-18 09:45:08 +08:00
foreach ($path in @($BuildDir, $PyInstallerOutputDir, $ReleaseDir, $ReleaseZip, $ManifestPath)) {
2026-06-16 11:49:25 +08:00
Assert-InProject -Path $path
if (Test-Path -LiteralPath $path) {
Remove-Item -LiteralPath $path -Recurse -Force
}
}
if (-not $SkipBuild) {
& python -m PyInstaller `
--noconfirm `
--clean `
--onedir `
--windowed `
--name $AppExeName `
--paths $SrcDir `
--add-data "$SrcDir\resources;resources" `
"$SrcDir\main.py"
if ($LASTEXITCODE -ne 0) {
throw "PyInstaller build failed."
}
}
if (-not (Test-Path -LiteralPath $PyInstallerOutputDir)) {
throw "PyInstaller output not found: $PyInstallerOutputDir"
}
Copy-Item -LiteralPath $PyInstallerOutputDir -Destination $ReleaseDir -Recurse
Copy-Item -LiteralPath $DefaultConfigDir -Destination (Join-Path $ReleaseDir "config") -Recurse
New-Item -ItemType Directory -Force -Path (Join-Path $ReleaseDir "logs") | Out-Null
New-Item -ItemType Directory -Force -Path (Join-Path $ReleaseDir "output") | Out-Null
2026-06-18 09:45:08 +08:00
Set-Content -LiteralPath (Join-Path $ReleaseDir "version.txt") -Value $AppVersion -Encoding ascii -NoNewline
2026-06-16 11:49:25 +08:00
$ReadmePath = Join-Path $ReleaseDir "README.txt"
$Readme = @(
"$AppName $AppVersion",
"",
"Start:",
"Double-click $AppExeName.exe.",
"",
"Folders:",
"- config: default config and custom templates.",
"- logs: runtime logs.",
"- output: default export folder.",
"",
"Do not delete dependency files in this folder."
)
Set-Content -LiteralPath $ReadmePath -Value $Readme -Encoding UTF8
if (-not (Test-Path -LiteralPath $ExePath)) {
throw "Executable not found: $ExePath"
}
2026-06-18 09:45:08 +08:00
Compress-Archive -Path (Join-Path $ReleaseDir "*") -DestinationPath $ReleaseZip -Force
$ReleaseHash = (Get-FileHash -LiteralPath $ReleaseZip -Algorithm SHA256).Hash
$ReleaseSize = (Get-Item -LiteralPath $ReleaseZip).Length
if (-not $MinSupported) {
$MinSupported = $AppVersion
}
$Manifest = [ordered]@{
version = $AppVersion
url = Join-Url -BaseUrl $ManifestBaseUrl -Name (Split-Path -Leaf $ReleaseZip)
sha256 = $ReleaseHash
size = $ReleaseSize
mandatory = [bool]$Mandatory
min_supported = $MinSupported
notes = $ReleaseNotes
}
# Write UTF-8 WITHOUT BOM: Set-Content -Encoding UTF8 emits a BOM that breaks
# json.loads in the in-app update check (services/update_service.py).
[System.IO.File]::WriteAllText(
$ManifestPath,
($Manifest | ConvertTo-Json),
(New-Object System.Text.UTF8Encoding $false)
)
2026-06-18 09:45:08 +08:00
if ($PublishDir) {
New-Item -ItemType Directory -Force -Path $PublishDir | Out-Null
Copy-Item -LiteralPath $ReleaseZip -Destination $PublishDir -Force
Copy-Item -LiteralPath $ManifestPath -Destination $PublishDir -Force
Write-Host "Published update files to: $PublishDir"
}
2026-06-16 11:49:25 +08:00
Write-Host "Release created: $ReleaseDir"
2026-06-18 09:45:08 +08:00
Write-Host "Release zip: $ReleaseZip"
Write-Host "Manifest: $ManifestPath"