From 5469479f3e08615dee2916f9cb10f351229a63c8 Mon Sep 17 00:00:00 2001 From: chengma Date: Wed, 8 Jul 2026 14:21:31 +0800 Subject: [PATCH] build: use branded release package names --- docs/03-tech-stack.md | 2 +- docs/packaging.md | 24 ++++++++++++------------ docs/tasks/T-558.md | 8 +++++++- docs/troubleshooting.md | 2 +- scripts/build_exe.ps1 | 33 ++++++++++++++++++++++++++++----- tests/test_packaging.py | 8 ++++++-- 6 files changed, 55 insertions(+), 22 deletions(-) diff --git a/docs/03-tech-stack.md b/docs/03-tech-stack.md index b2f08c6..9e5883d 100644 --- a/docs/03-tech-stack.md +++ b/docs/03-tech-stack.md @@ -42,7 +42,7 @@ - **T-504 更新执行增强**:③ 支持 dry-run 预览、运行日志和按账号并行;默认 dry-run 关闭、并行关闭,不引入新依赖。 - **不引入数据库(指外部 DB)**:用 stdlib SQLite 足够;不引入 Postgres/MySQL 等。 - **生产在 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-/` 与 `release/cmshopee--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/蝦皮圈優化助手/` 与 `release/蝦皮圈優化助手.zip`。 ## 三、构建与运行命令 diff --git a/docs/packaging.md b/docs/packaging.md index 7f8b586..280f3bd 100644 --- a/docs/packaging.md +++ b/docs/packaging.md @@ -36,7 +36,7 @@ dist\cmshopee\ ``` -正式分发入口是 `release\cmshopee--portable.zip`。`dist\cmshopee\` 只是 PyInstaller 构建中间产物,不直接作为正式发包名称。 +正式分发入口是 `release\蝦皮圈優化助手.zip`。`dist\cmshopee\` 只是 PyInstaller 构建中间产物,不直接作为正式发包名称。 当前 `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 release\ - cmshopee-0.1.0\ + 蝦皮圈優化助手0.1.0\ cmshopee.exe _internal\ version.txt README.txt - cmshopee-0.1.0-portable.zip + 蝦皮圈優化助手0.1.0.zip ``` 说明: - `dist\cmshopee\` 是构建中间产物,不直接作为正式发包名称。 -- `release\cmshopee-\` 是人工验收和分发目录。 -- `release\cmshopee--portable.zip` 是交付给用户的便携压缩包。 +- `release\蝦皮圈優化助手\` 是人工验收和分发目录。 +- `release\蝦皮圈優化助手.zip` 是交付给用户的便携压缩包。 - `version.txt` 使用 ASCII/UTF-8 无 BOM 写入 `APP_VERSION`,用于人工排查和未来更新机制读取。 - `README.txt` 用中文写明启动方式、不要放入 `Program Files`、保留 `data\`、升级时覆盖程序文件但不覆盖 `data\`。 -当前 PyInstaller 6.11.1 的 onedir 为集中依赖结构,所以 `release\cmshopee-\` 必须包含 `cmshopee.exe` 和 `_internal\`,不得只复制单个 exe。 +当前 PyInstaller 6.11.1 的 onedir 为集中依赖结构,所以 `release\蝦皮圈優化助手\` 必须包含 `cmshopee.exe` 和 `_internal\`,不得只复制单个 exe。 ### 3.3 正式出包流程 1. 修改 `app/version.py` 中的 `APP_VERSION`。 2. 运行语法检查和单元测试。 3. 运行 `powershell -ExecutionPolicy Bypass -File scripts\build_exe.ps1`。 -4. 检查 `release\cmshopee-\version.txt`、GUI 标题栏版本、压缩包文件名三者一致。 -5. 在无 Python 环境的 Windows 10/11 机器上解压 `cmshopee--portable.zip` 并启动验证。 +4. 检查 `release\蝦皮圈優化助手\version.txt`、GUI 标题栏版本、压缩包文件名三者一致。 +5. 在无 Python 环境的 Windows 10/11 机器上解压 `蝦皮圈優化助手.zip` 并启动验证。 正式发布构建固定使用 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", "min_supported_version": "0.1.1", "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", "message": "发现必须升级的新版本,请下载后覆盖当前程序文件,保留 data 目录。" } @@ -141,7 +141,7 @@ T-544 第一版目标是**启动时检查是否必须升级**,但仍不做自 "platform": "windows", "release": { "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": "", "release_notes": "发布说明" } @@ -238,7 +238,7 @@ cmshopee\ 1. 让用户先关闭 cmshopee。 2. 建议用户备份当前整个程序文件夹。 -3. 解压新版 `release\cmshopee--portable.zip`。 +3. 解压新版 `release\蝦皮圈優化助手.zip`。 4. 覆盖新版程序文件和依赖文件。当前 PyInstaller 6.11.1 是 `cmshopee.exe` + `_internal\` onedir,不能只覆盖 `cmshopee.exe`;应把新版 `cmshopee.exe`、`_internal\`、`version.txt`、`README.txt` 等程序文件整体覆盖到旧程序目录。 5. 保留旧目录里的 `data\`,不要删除、覆盖或合并它。 @@ -259,7 +259,7 @@ powershell -ExecutionPolicy Bypass -File scripts\build_exe.ps1 - `dist\cmshopee\cmshopee.exe` 存在。 - 当前 PyInstaller 6.11.1 下 `dist\cmshopee\_internal\` 必须存在。 - `dist\cmshopee\` 中没有第四节列出的本地数据,尤其不能含 `data\`。 -- `release\cmshopee-\version.txt`、GUI 标题栏版本、压缩包文件名三者一致。 +- `release\蝦皮圈優化助手\version.txt`、GUI 标题栏版本、压缩包文件名三者一致。 - 在干净目录首次启动时能生成 `data\config.json` 并进入 GUI。 - 在目标 Windows 10/11 机器或虚拟机上启动 release exe 后,主窗口标题栏完整可见,左边缘不出屏,用户能用标题栏拖动窗口;小分辨率环境不得出现窗口卡在左上角且标题栏不可拖动的问题(见 T-541)。 diff --git a/docs/tasks/T-558.md b/docs/tasks/T-558.md index 4f7aa6d..645dd1e 100644 --- a/docs/tasks/T-558.md +++ b/docs/tasks/T-558.md @@ -3,7 +3,7 @@ id: T-558 title: release 发布目录与 zip 使用中文品牌名 phase: 8 deps: [T-540, T-537, T-538] -status: TODO +status: DONE created: 2026-07-08 --- @@ -51,3 +51,9 @@ T-540 已经把版本号收口到 `app/version.py`,并让构建脚本统一生 ## 执行记录 +- 2026-07-08:`scripts\build_exe.ps1` 的正式 release 命名改为 `$appName$appVersion`,生成目录 `release\蝦皮圈優化助手\`,zip 改为 `release\蝦皮圈優化助手.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、图片、日志或登录态目录。 diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 4581559..68e075e 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -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<版本>」标题,导致无法用鼠标拖动窗口到中间。 ### 原因 diff --git a/scripts/build_exe.ps1 b/scripts/build_exe.ps1 index 4d3d5f4..8b217eb 100644 --- a/scripts/build_exe.ps1 +++ b/scripts/build_exe.ps1 @@ -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" if (-not (Test-Path -LiteralPath $versionFile)) { throw "Missing app\version.py" @@ -125,9 +150,9 @@ if (-not (Test-Path -LiteralPath $internalDir)) { Write-Host "Verified onedir layout: _internal present" $releaseRoot = Join-Path $repoRoot "release" -$releaseName = "$appCodeName-$appVersion" +$releaseName = "$appName$appVersion" $releaseDir = Join-Path $releaseRoot $releaseName -$portableZip = Join-Path $releaseRoot "$releaseName-portable.zip" +$portableZip = Join-Path $releaseRoot "$releaseName.zip" foreach ($path in @($releaseDir, $portableZip)) { Assert-InProject -Path $path @@ -156,11 +181,9 @@ $readme = @( Set-Content -LiteralPath (Join-Path $releaseDir "README.txt") -Value $readme -Encoding UTF8 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 "5Y+R5biD55uu5b2VOg==") $releaseDir" Write-Host "$(Decode-Utf8Base64 "5L6/5pC65Y6L57yp5YyFOg==") $portableZip" Write-Host (Decode-Utf8Base64 "5LiN6KaB5oqK5pys5ZywIGRhdGHjgIHphY3nva7jgIHmlbDmja7lupPjgIHlm77niYfjgIHml6Xlv5fmiJbnmbvlvZXmgIHlpI3liLbov5vlj5HluIPljIXjgII=") - - diff --git a/tests/test_packaging.py b/tests/test_packaging.py index 617a6a4..2000c58 100644 --- a/tests/test_packaging.py +++ b/tests/test_packaging.py @@ -75,11 +75,15 @@ class PackagingTests(unittest.TestCase): self.assertNotIn('python -m PyInstaller --noconfirm --clean', script) self.assertNotIn("3.7.9", script) self.assertIn('$releaseRoot = Join-Path $repoRoot "release"', script) - self.assertIn('$releaseName = "$appCodeName-$appVersion"', script) - self.assertIn('$portableZip = Join-Path $releaseRoot "$releaseName-portable.zip"', script) + self.assertIn('$releaseName = "$appName$appVersion"', 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('README.txt', 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)