build: use branded release package names

This commit is contained in:
chengma
2026-07-08 14:21:31 +08:00
parent ebb1f8a354
commit 5469479f3e
6 changed files with 55 additions and 22 deletions
+1 -1
View File
@@ -42,7 +42,7 @@
- **T-504 更新执行增强**:③ 支持 dry-run 预览、运行日志和按账号并行;默认 dry-run 关闭、并行关闭,不引入新依赖。 - **T-504 更新执行增强**:③ 支持 dry-run 预览、运行日志和按账号并行;默认 dry-run 关闭、并行关闭,不引入新依赖。
- **不引入数据库(指外部 DB)**:用 stdlib SQLite 足够;不引入 Postgres/MySQL 等。 - **不引入数据库(指外部 DB)**:用 stdlib SQLite 足够;不引入 Postgres/MySQL 等。
- **生产在 Windows 直跑**:开发期我们用过 WSL→Windows 的 `netsh portproxy`(9333→9222)连 CDP;但 GUI 与 Chrome 都在 Windows 时,直接连 `127.0.0.1:9222`,无需 portproxy。 - **生产在 Windows 直跑**:开发期我们用过 WSL→Windows 的 `netsh portproxy`(9333→9222)连 CDP;但 GUI 与 Chrome 都在 Windows 时,直接连 `127.0.0.1:9222`,无需 portproxy。
- **PyInstaller 使用 onedir 免安装包**:T-524 先做 `dist/cmshopee/` onedir,不做自动更新器;T-540 后当前打包依赖锁定 PyInstaller 6.11.1,产物必须是 `cmshopee.exe` + `_internal/` 集中依赖布局。T-538 后打包版不再 `chdir` 到 exe 目录,用户本地配置、DB、图片、日志、登录态和提示词统一保留在 exe 同级 `data/` 子目录,更新时覆盖程序文件并保留 `data/`。`app/version.py` 是唯一版本源,打包脚本固定使用 `py -3.10`,并把 `dist/cmshopee/` 组装为 `release/cmshopee-<APP_VERSION>/` 与 `release/cmshopee-<APP_VERSION>-portable.zip`。 - **PyInstaller 使用 onedir 免安装包**:T-524 先做 `dist/cmshopee/` onedir,不做自动更新器;T-540 后当前打包依赖锁定 PyInstaller 6.11.1,产物必须是 `cmshopee.exe` + `_internal/` 集中依赖布局。T-538 后打包版不再 `chdir` 到 exe 目录,用户本地配置、DB、图片、日志、登录态和提示词统一保留在 exe 同级 `data/` 子目录,更新时覆盖程序文件并保留 `data/`。`app/version.py` 是唯一版本源,打包脚本固定使用 `py -3.10`,并把 `dist/cmshopee/` 组装为 `release/蝦皮圈優化助手<APP_VERSION>/` 与 `release/蝦皮圈優化助手<APP_VERSION>.zip`。
## 三、构建与运行命令 ## 三、构建与运行命令
+12 -12
View File
@@ -36,7 +36,7 @@ dist\cmshopee\
<PyInstaller 依赖文件和依赖目录> <PyInstaller 依赖文件和依赖目录>
``` ```
正式分发入口是 `release\cmshopee-<APP_VERSION>-portable.zip`。`dist\cmshopee\` 只是 PyInstaller 构建中间产物,不直接作为正式发包名称。 正式分发入口是 `release\蝦皮圈優化助手<APP_VERSION>.zip`。`dist\cmshopee\` 只是 PyInstaller 构建中间产物,不直接作为正式发包名称。
当前 `requirements-build.txt` 锁定 PyInstaller 6.11.1,onedir 产物必须是 `cmshopee.exe` + `_internal\` 集中依赖布局。`scripts\build_exe.ps1` 会校验 `_internal\` 存在;缺失时直接失败,避免把不一致的打包环境误发给用户。 当前 `requirements-build.txt` 锁定 PyInstaller 6.11.1,onedir 产物必须是 `cmshopee.exe` + `_internal\` 集中依赖布局。`scripts\build_exe.ps1` 会校验 `_internal\` 存在;缺失时直接失败,避免把不一致的打包环境误发给用户。
@@ -80,32 +80,32 @@ APP_UPDATE_CHECK_URL = "https://cm.833729.com/api/v1/client/releases/latest?plat
```text ```text
release\ release\
cmshopee-0.1.0\ 蝦皮圈優化助手0.1.0\
cmshopee.exe cmshopee.exe
_internal\ _internal\
<PyInstaller 依赖文件和依赖目录> <PyInstaller 依赖文件和依赖目录>
version.txt version.txt
README.txt README.txt
cmshopee-0.1.0-portable.zip 蝦皮圈優化助手0.1.0.zip
``` ```
说明: 说明:
- `dist\cmshopee\` 是构建中间产物,不直接作为正式发包名称。 - `dist\cmshopee\` 是构建中间产物,不直接作为正式发包名称。
- `release\cmshopee-<APP_VERSION>\` 是人工验收和分发目录。 - `release\蝦皮圈優化助手<APP_VERSION>\` 是人工验收和分发目录。
- `release\cmshopee-<APP_VERSION>-portable.zip` 是交付给用户的便携压缩包。 - `release\蝦皮圈優化助手<APP_VERSION>.zip` 是交付给用户的便携压缩包。
- `version.txt` 使用 ASCII/UTF-8 无 BOM 写入 `APP_VERSION`,用于人工排查和未来更新机制读取。 - `version.txt` 使用 ASCII/UTF-8 无 BOM 写入 `APP_VERSION`,用于人工排查和未来更新机制读取。
- `README.txt` 用中文写明启动方式、不要放入 `Program Files`、保留 `data\`、升级时覆盖程序文件但不覆盖 `data\`。 - `README.txt` 用中文写明启动方式、不要放入 `Program Files`、保留 `data\`、升级时覆盖程序文件但不覆盖 `data\`。
当前 PyInstaller 6.11.1 的 onedir 为集中依赖结构,所以 `release\cmshopee-<APP_VERSION>\` 必须包含 `cmshopee.exe` 和 `_internal\`,不得只复制单个 exe。 当前 PyInstaller 6.11.1 的 onedir 为集中依赖结构,所以 `release\蝦皮圈優化助手<APP_VERSION>\` 必须包含 `cmshopee.exe` 和 `_internal\`,不得只复制单个 exe。
### 3.3 正式出包流程 ### 3.3 正式出包流程
1. 修改 `app/version.py` 中的 `APP_VERSION`。 1. 修改 `app/version.py` 中的 `APP_VERSION`。
2. 运行语法检查和单元测试。 2. 运行语法检查和单元测试。
3. 运行 `powershell -ExecutionPolicy Bypass -File scripts\build_exe.ps1`。 3. 运行 `powershell -ExecutionPolicy Bypass -File scripts\build_exe.ps1`。
4. 检查 `release\cmshopee-<APP_VERSION>\version.txt`、GUI 标题栏版本、压缩包文件名三者一致。 4. 检查 `release\蝦皮圈優化助手<APP_VERSION>\version.txt`、GUI 标题栏版本、压缩包文件名三者一致。
5. 在无 Python 环境的 Windows 10/11 机器上解压 `cmshopee-<APP_VERSION>-portable.zip` 并启动验证。 5. 在无 Python 环境的 Windows 10/11 机器上解压 `蝦皮圈優化助手<APP_VERSION>.zip` 并启动验证。
正式发布构建固定使用 Python 3.10。源码在 Python 3.7.9 上曾验证可运行,但发布包不再以 3.7.9 作为打包基线;脚本会直接调用 `py -3.10` 并校验返回版本必须是 `3.10.x`,避免同一个 `APP_VERSION` 在不同 Python 版本下打出不可比对的包。CI 仍可继续使用 Python 3.11 做自动测试。 正式发布构建固定使用 Python 3.10。源码在 Python 3.7.9 上曾验证可运行,但发布包不再以 3.7.9 作为打包基线;脚本会直接调用 `py -3.10` 并校验返回版本必须是 `3.10.x`,避免同一个 `APP_VERSION` 在不同 Python 版本下打出不可比对的包。CI 仍可继续使用 Python 3.11 做自动测试。
@@ -128,7 +128,7 @@ T-544 第一版目标是**启动时检查是否必须升级**,但仍不做自
"latest_version": "0.1.1", "latest_version": "0.1.1",
"min_supported_version": "0.1.1", "min_supported_version": "0.1.1",
"force_update": true, "force_update": true,
"download_url": "https://example.com/cmshopee-0.1.1-portable.zip", "download_url": "https://example.com/蝦皮圈優化助手0.1.1.zip",
"sha256": "optional-release-zip-sha256", "sha256": "optional-release-zip-sha256",
"message": "发现必须升级的新版本,请下载后覆盖当前程序文件,保留 data 目录。" "message": "发现必须升级的新版本,请下载后覆盖当前程序文件,保留 data 目录。"
} }
@@ -141,7 +141,7 @@ T-544 第一版目标是**启动时检查是否必须升级**,但仍不做自
"platform": "windows", "platform": "windows",
"release": { "release": {
"version": "0.1.0", "version": "0.1.0",
"download_url": "https://example.com/cmshopee-0.1.0-portable.zip", "download_url": "https://example.com/蝦皮圈優化助手0.1.0.zip",
"sha256": "", "sha256": "",
"release_notes": "发布说明" "release_notes": "发布说明"
} }
@@ -238,7 +238,7 @@ cmshopee\
1. 让用户先关闭 cmshopee。 1. 让用户先关闭 cmshopee。
2. 建议用户备份当前整个程序文件夹。 2. 建议用户备份当前整个程序文件夹。
3. 解压新版 `release\cmshopee-<APP_VERSION>-portable.zip`。 3. 解压新版 `release\蝦皮圈優化助手<APP_VERSION>.zip`。
4. 覆盖新版程序文件和依赖文件。当前 PyInstaller 6.11.1 是 `cmshopee.exe` + `_internal\` onedir,不能只覆盖 `cmshopee.exe`;应把新版 `cmshopee.exe`、`_internal\`、`version.txt`、`README.txt` 等程序文件整体覆盖到旧程序目录。 4. 覆盖新版程序文件和依赖文件。当前 PyInstaller 6.11.1 是 `cmshopee.exe` + `_internal\` onedir,不能只覆盖 `cmshopee.exe`;应把新版 `cmshopee.exe`、`_internal\`、`version.txt`、`README.txt` 等程序文件整体覆盖到旧程序目录。
5. 保留旧目录里的 `data\`,不要删除、覆盖或合并它。 5. 保留旧目录里的 `data\`,不要删除、覆盖或合并它。
@@ -259,7 +259,7 @@ powershell -ExecutionPolicy Bypass -File scripts\build_exe.ps1
- `dist\cmshopee\cmshopee.exe` 存在。 - `dist\cmshopee\cmshopee.exe` 存在。
- 当前 PyInstaller 6.11.1 下 `dist\cmshopee\_internal\` 必须存在。 - 当前 PyInstaller 6.11.1 下 `dist\cmshopee\_internal\` 必须存在。
- `dist\cmshopee\` 中没有第四节列出的本地数据,尤其不能含 `data\`。 - `dist\cmshopee\` 中没有第四节列出的本地数据,尤其不能含 `data\`。
- `release\cmshopee-<APP_VERSION>\version.txt`、GUI 标题栏版本、压缩包文件名三者一致。 - `release\蝦皮圈優化助手<APP_VERSION>\version.txt`、GUI 标题栏版本、压缩包文件名三者一致。
- 在干净目录首次启动时能生成 `data\config.json` 并进入 GUI。 - 在干净目录首次启动时能生成 `data\config.json` 并进入 GUI。
- 在目标 Windows 10/11 机器或虚拟机上启动 release exe 后,主窗口标题栏完整可见,左边缘不出屏,用户能用标题栏拖动窗口;小分辨率环境不得出现窗口卡在左上角且标题栏不可拖动的问题(见 T-541)。 - 在目标 Windows 10/11 机器或虚拟机上启动 release exe 后,主窗口标题栏完整可见,左边缘不出屏,用户能用标题栏拖动窗口;小分辨率环境不得出现窗口卡在左上角且标题栏不可拖动的问题(见 T-541)。
+7 -1
View File
@@ -3,7 +3,7 @@ id: T-558
title: release 发布目录与 zip 使用中文品牌名 title: release 发布目录与 zip 使用中文品牌名
phase: 8 phase: 8
deps: [T-540, T-537, T-538] deps: [T-540, T-537, T-538]
status: TODO status: DONE
created: 2026-07-08 created: 2026-07-08
--- ---
@@ -51,3 +51,9 @@ T-540 已经把版本号收口到 `app/version.py`,并让构建脚本统一生
## 执行记录 ## 执行记录
- 2026-07-08:`scripts\build_exe.ps1` 的正式 release 命名改为 `$appName$appVersion`,生成目录 `release\蝦皮圈優化助手<APP_VERSION>\`,zip 改为 `release\蝦皮圈優化助手<APP_VERSION>.zip`;`dist\cmshopee\cmshopee.exe` 与 `_internal\` onedir 中间产物保持不变。
- 2026-07-08:同步更新 `docs/packaging.md`、`docs/03-tech-stack.md`、`docs/troubleshooting.md`,明确正式发包名使用中文品牌名,用户更新仍覆盖程序文件和 `_internal\` 并保留 `data\`。
- 2026-07-08:更新 `tests/test_packaging.py`,断言构建脚本使用 `$appName$appVersion` 和 `$releaseName.zip`,并防止旧的 `$appCodeName-$appVersion` / `-portable.zip` 命名回归。
- 2026-07-08:实跑打包时发现 Windows `Compress-Archive` 可能在 PyInstaller 刚复制 `_internal\base_library.zip` 后遇到短暂文件占用;已为压缩发布包步骤增加 5 次短重试,避免最后一步偶发失败。
- 验证通过:`python -m ruff check app tests main.py`、`py -3.10 -m compileall app main.py`、`py -3.10 -m unittest discover -s tests`、`powershell -ExecutionPolicy Bypass -File scripts\build_exe.ps1`、`git diff --check`。
- 打包实测输出:`release\蝦皮圈優化助手0.1.1\` 和 `release\蝦皮圈優化助手0.1.1.zip`;发布目录包含 `_internal`、`cmshopee.exe`、`README.txt`、`version.txt`,未发现 `data`、配置、DB、图片、日志或登录态目录。
+1 -1
View File
@@ -232,7 +232,7 @@ T-530 已实现:保存和请求前都会把 Base URL 规整为网关根,去
### 现象 ### 现象
在 Windows 10 虚拟机或小分辨率环境运行打包后的 `dist/cmshopee/cmshopee.exe` / `release/cmshopee-<版本>-portable.zip` 后,主窗口出现在屏幕左上角,左边和标题栏显示不全,看不到「蝦皮圈優化助手 v<版本>」标题,导致无法用鼠标拖动窗口到中间。 在 Windows 10 虚拟机或小分辨率环境运行打包后的 `dist/cmshopee/cmshopee.exe` / `release/蝦皮圈優化助手<版本>.zip` 后,主窗口出现在屏幕左上角,左边和标题栏显示不全,看不到「蝦皮圈優化助手 v<版本>」标题,导致无法用鼠标拖动窗口到中间。
### 原因 ### 原因
+28 -5
View File
@@ -61,6 +61,31 @@ function Assert-NoExcludedData {
} }
} }
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" $versionFile = Join-Path $repoRoot "app\version.py"
if (-not (Test-Path -LiteralPath $versionFile)) { if (-not (Test-Path -LiteralPath $versionFile)) {
throw "Missing app\version.py" throw "Missing app\version.py"
@@ -125,9 +150,9 @@ if (-not (Test-Path -LiteralPath $internalDir)) {
Write-Host "Verified onedir layout: _internal present" Write-Host "Verified onedir layout: _internal present"
$releaseRoot = Join-Path $repoRoot "release" $releaseRoot = Join-Path $repoRoot "release"
$releaseName = "$appCodeName-$appVersion" $releaseName = "$appName$appVersion"
$releaseDir = Join-Path $releaseRoot $releaseName $releaseDir = Join-Path $releaseRoot $releaseName
$portableZip = Join-Path $releaseRoot "$releaseName-portable.zip" $portableZip = Join-Path $releaseRoot "$releaseName.zip"
foreach ($path in @($releaseDir, $portableZip)) { foreach ($path in @($releaseDir, $portableZip)) {
Assert-InProject -Path $path Assert-InProject -Path $path
@@ -156,11 +181,9 @@ $readme = @(
Set-Content -LiteralPath (Join-Path $releaseDir "README.txt") -Value $readme -Encoding UTF8 Set-Content -LiteralPath (Join-Path $releaseDir "README.txt") -Value $readme -Encoding UTF8
Assert-NoExcludedData -Root $releaseDir -Excluded $excluded Assert-NoExcludedData -Root $releaseDir -Excluded $excluded
Compress-Archive -Path (Join-Path $releaseDir "*") -DestinationPath $portableZip -Force Compress-ArchiveWithRetry -SourcePath (Join-Path $releaseDir "*") -DestinationPath $portableZip
Write-Host "$(Decode-Utf8Base64 "5p6E5bu65a6M5oiQOg==") $exePath" Write-Host "$(Decode-Utf8Base64 "5p6E5bu65a6M5oiQOg==") $exePath"
Write-Host "$(Decode-Utf8Base64 "5Y+R5biD55uu5b2VOg==") $releaseDir" Write-Host "$(Decode-Utf8Base64 "5Y+R5biD55uu5b2VOg==") $releaseDir"
Write-Host "$(Decode-Utf8Base64 "5L6/5pC65Y6L57yp5YyFOg==") $portableZip" Write-Host "$(Decode-Utf8Base64 "5L6/5pC65Y6L57yp5YyFOg==") $portableZip"
Write-Host (Decode-Utf8Base64 "5LiN6KaB5oqK5pys5ZywIGRhdGHjgIHphY3nva7jgIHmlbDmja7lupPjgIHlm77niYfjgIHml6Xlv5fmiJbnmbvlvZXmgIHlpI3liLbov5vlj5HluIPljIXjgII=") Write-Host (Decode-Utf8Base64 "5LiN6KaB5oqK5pys5ZywIGRhdGHjgIHphY3nva7jgIHmlbDmja7lupPjgIHlm77niYfjgIHml6Xlv5fmiJbnmbvlvZXmgIHlpI3liLbov5vlj5HluIPljIXjgII=")
+6 -2
View File
@@ -75,11 +75,15 @@ class PackagingTests(unittest.TestCase):
self.assertNotIn('python -m PyInstaller --noconfirm --clean', script) self.assertNotIn('python -m PyInstaller --noconfirm --clean', script)
self.assertNotIn("3.7.9", script) self.assertNotIn("3.7.9", script)
self.assertIn('$releaseRoot = Join-Path $repoRoot "release"', script) self.assertIn('$releaseRoot = Join-Path $repoRoot "release"', script)
self.assertIn('$releaseName = "$appCodeName-$appVersion"', script) self.assertIn('$releaseName = "$appName$appVersion"', script)
self.assertIn('$portableZip = Join-Path $releaseRoot "$releaseName-portable.zip"', script) self.assertIn('$portableZip = Join-Path $releaseRoot "$releaseName.zip"', script)
self.assertNotIn('$releaseName = "$appCodeName-$appVersion"', script)
self.assertNotIn('$portableZip = Join-Path $releaseRoot "$releaseName-portable.zip"', script)
self.assertIn('version.txt', script) self.assertIn('version.txt', script)
self.assertIn('README.txt', script) self.assertIn('README.txt', script)
self.assertIn('Compress-Archive', script) self.assertIn('Compress-Archive', script)
self.assertIn('Compress-ArchiveWithRetry', script)
self.assertIn('Start-Sleep -Seconds $DelaySeconds', script)
self.assertIn('Assert-NoExcludedData -Root $releaseDir', script) self.assertIn('Assert-NoExcludedData -Root $releaseDir', script)