build: use branded release package names
This commit is contained in:
@@ -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
@@ -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
@@ -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、图片、日志或登录态目录。
|
||||||
|
|||||||
@@ -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
@@ -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=")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user