213 lines
8.5 KiB
PowerShell
213 lines
8.5 KiB
PowerShell
$ErrorActionPreference = "Stop"
|
||
|
||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||
$repoRoot = (Resolve-Path (Join-Path $scriptDir "..")).Path
|
||
Set-Location $repoRoot
|
||
|
||
function Read-VersionValue {
|
||
param(
|
||
[string]$Path,
|
||
[string]$Name
|
||
)
|
||
|
||
$pattern = '^\s*{0}\s*=\s*"([^"]+)"' -f [regex]::Escape($Name)
|
||
foreach ($line in Get-Content -LiteralPath $Path -Encoding UTF8) {
|
||
if ($line -match $pattern) {
|
||
return $Matches[1]
|
||
}
|
||
}
|
||
throw "Cannot find $Name in $Path"
|
||
}
|
||
|
||
function Decode-Utf8Base64 {
|
||
param([string]$Text)
|
||
|
||
return [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Text))
|
||
}
|
||
|
||
function Assert-InProject {
|
||
param([string]$Path)
|
||
|
||
$resolvedRoot = (Resolve-Path -LiteralPath $repoRoot).Path
|
||
$rootPrefix = $resolvedRoot.TrimEnd("\") + "\"
|
||
if (Test-Path -LiteralPath $Path) {
|
||
$resolvedPath = (Resolve-Path -LiteralPath $Path).Path
|
||
if ($resolvedPath -ne $resolvedRoot -and -not $resolvedPath.StartsWith($rootPrefix, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||
throw "Refusing to modify path outside project: $resolvedPath"
|
||
}
|
||
} else {
|
||
$fullPath = [System.IO.Path]::GetFullPath($Path)
|
||
if ($fullPath -ne $resolvedRoot -and -not $fullPath.StartsWith($rootPrefix, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||
throw "Refusing to modify path outside project: $fullPath"
|
||
}
|
||
}
|
||
}
|
||
|
||
function Assert-NoExcludedData {
|
||
param(
|
||
[string]$Root,
|
||
[string[]]$Excluded
|
||
)
|
||
|
||
$found = @()
|
||
foreach ($relative in $Excluded) {
|
||
$candidate = Join-Path $Root $relative
|
||
if (Test-Path -LiteralPath $candidate) {
|
||
$found += $relative
|
||
}
|
||
}
|
||
if ($found.Count -gt 0) {
|
||
throw "Build output contains local user data: $($found -join ', ')"
|
||
}
|
||
}
|
||
|
||
function Compress-ArchiveWithRetry {
|
||
param(
|
||
[string]$SourcePath,
|
||
[string]$DestinationPath,
|
||
[int]$Attempts = 5,
|
||
[int]$DelaySeconds = 2
|
||
)
|
||
|
||
for ($attempt = 1; $attempt -le $Attempts; $attempt++) {
|
||
try {
|
||
if (Test-Path -LiteralPath $DestinationPath) {
|
||
Remove-Item -LiteralPath $DestinationPath -Force
|
||
}
|
||
Compress-Archive -Path $SourcePath -DestinationPath $DestinationPath -Force
|
||
return
|
||
} catch {
|
||
if ($attempt -ge $Attempts) {
|
||
throw
|
||
}
|
||
Write-Host "压缩发布包被临时文件占用阻塞,等待 $DelaySeconds 秒后重试 ($attempt/$Attempts):$($_.Exception.Message)"
|
||
Start-Sleep -Seconds $DelaySeconds
|
||
}
|
||
}
|
||
}
|
||
|
||
$versionFile = Join-Path $repoRoot "app\version.py"
|
||
if (-not (Test-Path -LiteralPath $versionFile)) {
|
||
throw "Missing app\version.py"
|
||
}
|
||
$appName = Read-VersionValue -Path $versionFile -Name "APP_NAME"
|
||
$appCodeName = Read-VersionValue -Path $versionFile -Name "APP_CODE_NAME"
|
||
$appVersion = Read-VersionValue -Path $versionFile -Name "APP_VERSION"
|
||
if ($appCodeName -ne "cmshopee") {
|
||
throw "APP_CODE_NAME must be cmshopee, current value: $appCodeName"
|
||
}
|
||
|
||
$pythonExe = "py"
|
||
$pythonArgs = @("-3.10")
|
||
$pythonVersionOutput = & $pythonExe @pythonArgs -c "import sys; print('%d.%d.%d' % sys.version_info[:3])"
|
||
if ($LASTEXITCODE -ne 0 -or -not $pythonVersionOutput) {
|
||
throw (Decode-Utf8Base64 "5pyq5om+5YiwIFB5dGhvbiAzLjEw44CC6K+35YWI5a6J6KOFIFB5dGhvbiAzLjEw77yM5bm256Gu6K6kIFdpbmRvd3MgUHl0aG9uIExhdW5jaGVyIOWPr+mAmui/hyBweSAtMy4xMCDosIPnlKjjgII=")
|
||
}
|
||
$pythonVersion = ($pythonVersionOutput | Select-Object -First 1).Trim()
|
||
Write-Host "$(Decode-Utf8Base64 "5L2/55SoIFB5dGhvbiAzLjEwOg==") $pythonVersion"
|
||
if (-not $pythonVersion.StartsWith("3.10.")) {
|
||
throw "$(Decode-Utf8Base64 "5b2T5YmN5omT5YyF6ISa5pys5Zu65a6a5L2/55SoIFB5dGhvbiAzLjEw77yM5b2T5YmN54mI5pys"): $pythonVersion"
|
||
}
|
||
$spec = Join-Path $repoRoot "cmshopee.spec"
|
||
if (-not (Test-Path -LiteralPath $spec)) {
|
||
throw "Missing cmshopee.spec"
|
||
}
|
||
|
||
Write-Host "$(Decode-Utf8Base64 "5q2j5Zyo5p6E5bu6") $appName $appVersion"
|
||
Write-Host (Decode-Utf8Base64 "5omT5YyF5ZG95Luk5Zu65a6a5L2/55SoIHB5IC0zLjEwIC1tIFB5SW5zdGFsbGVy44CC")
|
||
& $pythonExe @pythonArgs -m PyInstaller --noconfirm --clean $spec
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "$(Decode-Utf8Base64 "UHlJbnN0YWxsZXIg5omT5YyF5aSx6LSl77yM6YCA5Ye656CB"): $LASTEXITCODE"
|
||
}
|
||
|
||
$updaterSpec = Join-Path $repoRoot "cmshopee-updater.spec"
|
||
$updaterDist = Join-Path $repoRoot "dist\updater"
|
||
$updaterWork = Join-Path $repoRoot "build\updater"
|
||
& $pythonExe @pythonArgs -m PyInstaller --noconfirm --clean --distpath $updaterDist --workpath $updaterWork $updaterSpec
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "独立更新器打包失败,退出码: $LASTEXITCODE"
|
||
}
|
||
$updaterExe = Join-Path $updaterDist "cmshopee-updater.exe"
|
||
if (-not (Test-Path -LiteralPath $updaterExe)) {
|
||
throw "独立更新器产物不存在: $updaterExe"
|
||
}
|
||
|
||
$distDir = Join-Path $repoRoot "dist\cmshopee"
|
||
$exePath = Join-Path $distDir "cmshopee.exe"
|
||
if (-not (Test-Path -LiteralPath $exePath)) {
|
||
throw "Build failed: $exePath not found"
|
||
}
|
||
|
||
$excluded = @(
|
||
"config.json",
|
||
"config\ai_models.json",
|
||
"cmshopee.db",
|
||
"cmshopee.db-wal",
|
||
"cmshopee.db-shm",
|
||
"db.sqlite",
|
||
"chrome_user_data_dir",
|
||
"images",
|
||
"logs",
|
||
"prompts",
|
||
"title_prompt.txt",
|
||
"data"
|
||
)
|
||
|
||
Assert-NoExcludedData -Root $distDir -Excluded $excluded
|
||
|
||
$internalDir = Join-Path $distDir "_internal"
|
||
if (-not (Test-Path -LiteralPath $internalDir)) {
|
||
throw "Build output missing _internal directory. Expected PyInstaller 6.x onedir layout (cmshopee.exe + _internal). Ensure PyInstaller >= 6.0 is installed: py -3.10 -m pip install -r requirements-build.txt"
|
||
}
|
||
Write-Host "Verified onedir layout: _internal present"
|
||
|
||
$releaseRoot = Join-Path $repoRoot "release"
|
||
$releaseName = "$appName$appVersion"
|
||
$releaseDir = Join-Path $releaseRoot $releaseName
|
||
$portableZip = Join-Path $releaseRoot "$releaseName.zip"
|
||
$releaseMetadata = Join-Path $releaseRoot "release-metadata.json"
|
||
|
||
foreach ($path in @($releaseDir, $portableZip, $releaseMetadata)) {
|
||
Assert-InProject -Path $path
|
||
if (Test-Path -LiteralPath $path) {
|
||
Remove-Item -LiteralPath $path -Recurse -Force
|
||
}
|
||
}
|
||
|
||
New-Item -ItemType Directory -Force -Path $releaseDir | Out-Null
|
||
Get-ChildItem -LiteralPath $distDir -Force | Copy-Item -Destination $releaseDir -Recurse -Force
|
||
Copy-Item -LiteralPath $updaterExe -Destination (Join-Path $releaseDir "cmshopee-updater.exe") -Force
|
||
Set-Content -LiteralPath (Join-Path $releaseDir "version.txt") -Value $appVersion -Encoding ascii -NoNewline
|
||
|
||
$readme = @(
|
||
"$appName v$appVersion",
|
||
"",
|
||
(Decode-Utf8Base64 "5ZCv5Yqo5pa55byP77ya"),
|
||
(Decode-Utf8Base64 "5Y+M5Ye7IGNtc2hvcGVlLmV4ZSDlkK/liqjjgII="),
|
||
"",
|
||
(Decode-Utf8Base64 "5L2/55So6K+05piO77ya"),
|
||
(Decode-Utf8Base64 "LSDor7fmiormlbTkuKrmlofku7blpLnop6PljovliLDlj6/lhpnnm67lvZXvvIzkvovlpoIgRDpcY21zaG9wZWUg5oiW5qGM6Z2i44CC"),
|
||
(Decode-Utf8Base64 "LSDkuI3opoHmlL7liLAgQzpcUHJvZ3JhbSBGaWxlcyDmiJblhbbku5bpnIDopoHnrqHnkIblkZjmnYPpmZDlhpnlhaXnmoTnm67lvZXjgII="),
|
||
(Decode-Utf8Base64 "LSDpppbmrKHov5DooYzkvJrlnKjnqIvluo/lkIznuqfliJvlu7ogZGF0YVzvvIznlKjkuo7kv53lrZjotKblj7fjgIHku7vliqHjgIHlm77niYfjgIHml6Xlv5fjgIHphY3nva7lkoznmbvlvZXmgIHjgII="),
|
||
(Decode-Utf8Base64 "LSDljYfnuqfml7bopobnm5bmlrDniYjnqIvluo/mlofku7blkozkvp3otZbmlofku7bvvIzkvYblv4Xpobvkv53nlZnml6fnm67lvZXph4znmoQgZGF0YVzjgII="),
|
||
(Decode-Utf8Base64 "LSDkuI3opoHmioogZGF0YVwg5Y+R57uZ5YW25LuW5Lq677yM6YeM6Z2i5YyF5ZCr5pys5Zyw6YWN572u44CB5Lia5Yqh5pWw5o2u44CB5Zu+54mH5ZKM55m75b2V5oCB44CC")
|
||
)
|
||
Set-Content -LiteralPath (Join-Path $releaseDir "README.txt") -Value $readme -Encoding UTF8
|
||
|
||
Assert-NoExcludedData -Root $releaseDir -Excluded $excluded
|
||
& $pythonExe @pythonArgs -m app.release_manifest manifest --release-dir $releaseDir --version $appVersion
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "生成 package-manifest.json 失败,退出码: $LASTEXITCODE"
|
||
}
|
||
Compress-ArchiveWithRetry -SourcePath (Join-Path $releaseDir "*") -DestinationPath $portableZip
|
||
& $pythonExe @pythonArgs -m app.release_manifest metadata --zip $portableZip --version $appVersion --output $releaseMetadata
|
||
if ($LASTEXITCODE -ne 0) {
|
||
throw "生成 release-metadata.json 失败,退出码: $LASTEXITCODE"
|
||
}
|
||
|
||
Write-Host "$(Decode-Utf8Base64 "5p6E5bu65a6M5oiQOg==") $exePath"
|
||
Write-Host "$(Decode-Utf8Base64 "5Y+R5biD55uu5b2VOg==") $releaseDir"
|
||
Write-Host "$(Decode-Utf8Base64 "5L6/5pC65Y6L57yp5YyFOg==") $portableZip"
|
||
Write-Host "发布元数据: $releaseMetadata"
|
||
Write-Host (Decode-Utf8Base64 "5LiN6KaB5oqK5pys5ZywIGRhdGHjgIHphY3nva7jgIHmlbDmja7lupPjgIHlm77niYfjgIHml6Xlv5fmiJbnmbvlvZXmgIHlpI3liLbov5vlj5HluIPljIXjgII=")
|