feat: add versioned release packaging
This commit is contained in:
@@ -47,3 +47,4 @@ prompts/
|
||||
title_prompt.txt
|
||||
build/
|
||||
dist/
|
||||
release/
|
||||
|
||||
+3
-2
@@ -6,6 +6,7 @@ import os
|
||||
import sys
|
||||
|
||||
from .. import appconfig
|
||||
from ..version import APP_NAME, display_name
|
||||
from . import widgets as _widgets
|
||||
from .widgets import *
|
||||
|
||||
@@ -33,7 +34,7 @@ if QT_IMPORT_ERROR is None:
|
||||
else:
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
raise RuntimeError("蝦皮圈優化助手 GUI 无法启动:当前 Python 环境未安装 PySide6。")
|
||||
raise RuntimeError(f"{APP_NAME} GUI 无法启动:当前 Python 环境未安装 PySide6。")
|
||||
|
||||
|
||||
def _ensure_offscreen_for_headless_tests():
|
||||
@@ -43,7 +44,7 @@ def _ensure_offscreen_for_headless_tests():
|
||||
|
||||
def main() -> int:
|
||||
if QT_IMPORT_ERROR is not None:
|
||||
print("蝦皮圈優化助手 GUI 无法启动:当前 Python 环境未安装 PySide6。")
|
||||
print(f"{APP_NAME} GUI 无法启动:当前 Python 环境未安装 PySide6。")
|
||||
return 1
|
||||
_ensure_offscreen_for_headless_tests()
|
||||
app = QApplication.instance() or QApplication(sys.argv)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ..version import display_name
|
||||
from .tabs.accounts import AccountsTab
|
||||
from .tabs.apply import ApplyTab
|
||||
from .tabs.collect import CollectTab
|
||||
@@ -9,6 +10,7 @@ from .tabs.generate import GenerateTab
|
||||
from .tabs.settings import SettingsTab
|
||||
from .widgets import *
|
||||
|
||||
|
||||
PREFERRED_WINDOW_SIZE = (1180, 760)
|
||||
MIN_WINDOW_SIZE = (960, 640)
|
||||
WINDOW_SCREEN_MARGIN = 40
|
||||
@@ -68,6 +70,7 @@ def _fit_and_center_window(
|
||||
max_y = available.y() + max(0, available.height() - height)
|
||||
window.move(min(max(x, available.x()), max_x), min(max(y, available.y()), max_y))
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
"""Main application window with the fixed five-tab workflow."""
|
||||
|
||||
@@ -86,7 +89,7 @@ class MainWindow(QMainWindow):
|
||||
or self.config.get("ai_models_path")
|
||||
or appconfig.ai_models_config_path(self.config)
|
||||
)
|
||||
self.setWindowTitle("蝦皮圈優化助手")
|
||||
self.setWindowTitle(display_name())
|
||||
_fit_and_center_window(self)
|
||||
self.setStyleSheet(BUTTON_BASE_STYLE)
|
||||
self._settings_tab_index = TAB_TITLES.index("⑤ 设置")
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
"""Application identity and release version."""
|
||||
|
||||
APP_NAME = "蝦皮圈優化助手"
|
||||
APP_CODE_NAME = "cmshopee"
|
||||
APP_VERSION = "0.1.0"
|
||||
|
||||
|
||||
def display_name() -> str:
|
||||
return f"{APP_NAME} v{APP_VERSION}"
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
| 维度 | 选型 | 状态 | 理由 / 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| 语言 | Python 3.10+ | 已定 | 已有脚本均为 Python;标准库够用 |
|
||||
| 语言 | Python 3.10(正式打包固定);CI 使用 3.11 | 已定 | 发布构建固定使用 `py -3.10`;源码曾验证可在 3.7.9 运行,但不作为发布打包基线 |
|
||||
| 运行平台 | Windows(生产);WSL 可用于开发 | 已定 | Chrome 与各 user-data-dir 在 Windows;GUI 与 Chrome 同机,CDP 走 `localhost` |
|
||||
| 浏览器自动化 | 自研 CDP 客户端 `app/cdp.py` | 已定 | 基于 websocket-client + requests 手写;不引入 playwright/selenium,规避代理与 Origin 坑 |
|
||||
| WebSocket | `websocket-client`(import `websocket`) | 已定 | 讲 CDP 协议;用 `suppress_origin=True` 绕过 403 |
|
||||
@@ -41,19 +41,19 @@
|
||||
- **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,不做自动更新器;当前打包依赖锁定 PyInstaller 5.13.2,onedir 产物是扁平结构,`cmshopee.exe` 和 DLL/PYD/依赖目录同级,`_internal/` 不作为当前验收标准。T-538 后打包版不再 `chdir` 到 exe 目录,用户本地配置、DB、图片、日志、登录态和提示词统一保留在 exe 同级 `data/` 子目录,更新时覆盖程序文件并保留 `data/`。
|
||||
- **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`。
|
||||
|
||||
## 三、构建与运行命令
|
||||
|
||||
| 用途 | 命令 |
|
||||
| --- | --- |
|
||||
| 安装依赖 | `python -m pip install -r requirements.txt` |
|
||||
| 安装依赖 | `py -3.10 -m pip install -r requirements.txt` |
|
||||
| 检查 PySide6 | `python -c "import PySide6; print(PySide6.__version__)"` |
|
||||
| 语法检查 | `python -m compileall app main.py` |
|
||||
| 启动 GUI | `python main.py` / `python -m app` |
|
||||
| 单元测试(T-006 后) | `python -m unittest discover -s tests` |
|
||||
| 语法检查 | `py -3.10 -m compileall app main.py` |
|
||||
| 启动 GUI | `py -3.10 main.py` / `py -3.10 -m app` |
|
||||
| 单元测试(T-006 后) | `py -3.10 -m unittest discover -s tests` |
|
||||
| CI 自动验证 | GitHub Actions `.github/workflows/tests.yml`(Windows + Python 3.11 + `QT_QPA_PLATFORM=offscreen`) |
|
||||
| 安装打包依赖 | `python -m pip install -r requirements-build.txt` |
|
||||
| 安装打包依赖 | `py -3.10 -m pip install -r requirements-build.txt` |
|
||||
| 打包 exe | `powershell -ExecutionPolicy Bypass -File scripts\\build_exe.ps1` |
|
||||
| 跑单账号演示 | `python prototypes/demo.py`(分步)/ `set AUTO=1 && python prototypes/demo.py`(自动) |
|
||||
| 提交更新(真改线上) | `set UPDATE=1 && python prototypes/demo.py` |
|
||||
|
||||
+4
-3
@@ -23,7 +23,7 @@
|
||||
|
||||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| T-000 | 正式代码包结构:创建 `app/`、迁入 `cdp.py` 为 `app/cdp.py`、新增 `app/__init__.py`、`app/__main__.py`、根入口 `main.py`、最小 `app/gui.py` 占位入口,并修正 prototypes 导入 | - | `python -m compileall app main.py` 通过;`python -m app`/`python main.py` 可进入入口(本机 `python` 不符合版本时用 `py -3 -m app`);GUI 未完成时给明确提示并退出;`prototypes/demo.py` 可从项目根导入 `app.cdp` | DONE |
|
||||
| T-000 | 正式代码包结构:创建 `app/`、迁入 `cdp.py` 为 `app/cdp.py`、新增 `app/__init__.py`、`app/__main__.py`、根入口 `main.py`、最小 `app/gui.py` 占位入口,并修正 prototypes 导入 | - | `python -m compileall app main.py` 通过;`python -m app`/`python main.py` 可进入入口(本机 `python` 若为 isolated 且 `-m app` 不搜索当前目录,可用 `python main.py` 或 `py -3 -m app`);GUI 未完成时给明确提示并退出;`prototypes/demo.py` 可从项目根导入 `app.cdp` | DONE |
|
||||
| T-001 | `app/editor.py`:改标题/换封面/点更新/登录检测/**采集(读旧标题+旧封面下载)**/apply_task,复用 `app/cdp.py` | T-000 | 函数可调用,在测试商品跑通;与 `prototypes/demo.py` 行为一致 | DONE |
|
||||
| T-002 | `app/appconfig.py` + `config.json`(含 image_dir、ai 选择/参数段、端口等默认值;不含 AI Key) | T-000 | 读写正常;不存在则写默认;AI Key 留给 `config/ai_models.json`/T-501 | DONE |
|
||||
| T-003 | `app/db.py` + SQLite 建表(batches/accounts/tasks,含 Excel 行定位、状态、时间戳、重试字段) | T-000 | `init_db` 幂等;`connect` 设置 WAL/busy_timeout/foreign_keys;账号/批次/任务/各 set_* 可用;schema 同架构 5.2 | DONE |
|
||||
@@ -141,11 +141,12 @@
|
||||
|
||||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| T-541 | 打包版主窗口初始位置与小屏适配 | T-523, T-537, T-540 | Bug:在 Windows 10 虚拟机/小分辨率环境运行打包后的 `dist/cmshopee/cmshopee.exe` 或 release 包时,主窗口可能贴到屏幕左上角且左边/标题栏显示不全,看不到「蝦皮圈優化助手 v<APP_VERSION>」标题,用户无法拖动窗口到中间。根因:`MainWindow.__init__()` 只调用 `self.resize(1180, 760)`,没有根据 `QApplication.primaryScreen().availableGeometry()` 限制初始尺寸、没有居中、没有保证窗口 frame/title bar 在可用屏幕范围内;PyInstaller 本身不是根因,小屏、高 DPI、任务栏占用、多显示器或 VM 分辨率更容易触发。方案:① 抽一个主窗口初始布局 helper(如 `_fit_and_center_window(window, preferred_size=(1180,760), margin=40)`),读取当前屏幕 `availableGeometry()`;② 初始宽高不超过可用区域减安全边距,保留合理最小值(如 960x640,若屏幕更小则继续适配);③ `resize()` 后按可用区域中心 `move()`,确保窗口左上角不小于可用区域左上角,标题栏完整可见;④ offscreen/headless 测试环境不依赖真实屏幕时可跳过或用 mock geometry;⑤ 不保存/恢复历史窗口位置,避免把坏坐标持久化。同步 `docs/packaging.md` 打包验收:在目标 VM/小分辨率环境启动 release exe,窗口标题栏必须完整可见且可拖动。GUI 单测覆盖小屏 mock 下尺寸被限制、窗口被居中/夹进可用区域;不改业务流程、DB、AI、Excel、CDP/Shopee、打包产物结构 | DONE |
|
||||
| T-541 | 打包版主窗口初始位置与小屏适配 | T-523, T-537, T-540 | Bug:在 Windows 10 虚拟机/小分辨率环境运行打包后的 `dist/cmshopee/cmshopee.exe` 或 release 包时,主窗口可能贴到屏幕左上角且左边/标题栏显示不全,看不到「蝦皮圈優化助手 v<APP_VERSION>」标题,用户无法拖动窗口到中间。根因:`MainWindow.__init__()` 只调用 `self.resize(1180, 760)`,没有根据 `QApplication.primaryScreen().availableGeometry()` 限制初始尺寸、没有居中、没有保证窗口 frame/title bar 在可用屏幕范围内;PyInstaller 本身不是根因,小屏、高 DPI、任务栏占用、多显示器或 VM 分辨率更容易触发。方案:① 抽一个主窗口初始布局 helper(如 `_fit_and_center_window(window, preferred_size=(1180,760), margin=40)`),读取当前屏幕 `availableGeometry()`;② 初始宽高不超过可用区域减安全边距,保留合理最小值(如 960x640,若屏幕更小则继续适配);③ `resize()` 后按可用区域中心 `move()`,确保 `frameGeometry()` 左上角不小于可用区域左上角,标题栏完整可见;④ offscreen/headless 测试环境不依赖真实屏幕时可跳过或用 mock geometry;⑤ 不保存/恢复历史窗口位置,避免把坏坐标持久化。同步 `docs/packaging.md` 打包验收:在目标 VM/小分辨率环境启动 release exe,窗口标题栏必须完整可见且可拖动。GUI 单测覆盖小屏 mock 下尺寸被限制、窗口被居中/夹进可用区域;不改业务流程、DB、AI、Excel、CDP/Shopee、打包产物结构 | DONE |
|
||||
| T-525 | 引入 ruff(lint + format)+ 可选 pre-commit | T-006 | 依据 `docs/engineering-review.md` P1。加 `ruff` 配置(lint + format),先以现状为基线不做大规模风格重排,只开启安全规则(未用 import/变量、明显错误);可选 `.pre-commit-config.yaml`;不改业务逻辑;CI(T-522)可串入 ruff 检查。数据模型渐进上 mypy 作为后续可选。当前因业务优先级调整顺延到 T-526~T-528 之后 | TODO |
|
||||
| T-537 | 品牌名「蝦皮圈優化助手」(显示层中文品牌,cmshopee 保留为代号/标识符) | T-523 | 需求:项目/软件正式定名「蝦皮圈優化助手」。**原则**:产品**显示/品牌名** = 「蝦皮圈優化助手」;`cmshopee` 保留为**代码库/仓库/技术代号**,一切**标识符不改**——遵项目「文档与 UI 文案用中文、标识符用英文」规则,且避免非 ASCII 文件名破坏现有用户数据。**改(显示/品牌层)**:① `app/gui/main_window.py` 窗口标题 `setWindowTitle("cmshopee")` → `"蝦皮圈優化助手"`(任务栏/标题栏显示中文品牌);② 文档产品名散文 `docs/00-ai-start-here.md`、`docs/README.md`、`AGENTS.md`(及 vision/brief 若有)把「cmshopee 是一个…」改为「蝦皮圈優化助手(代号 cmshopee)是一个…」,明确品牌名并保留代号;③ 空的 `README.md` 补品牌标题行;④ 可选:`app/gui/__init__.py` PySide6 缺失启动失败提示带上品牌名。**不改(技术标识符,保留 cmshopee)**:`app` 包名、`cmshopee.exe` 打包产物名、`cmshopee.db`(+`-wal/-shm`)/`db_path` 默认值、`cmshopee.spec`、`cmshopee.log`、`config.json`/`config/*.json` 文件名、slug/`chrome_user_data_dir`、仓库目录;`app/editor.py` 注入页面的 JS 全局 `window.__cmshopee_*`(JS 标识符须 ASCII)、`app/ai.py` multipart boundary `----cmshopee-*`、`app/workers.py` `thread._cmshopee_worker`、`app/chrome.py` 快捷方式内部前缀 `cmshopee-`。**决策点**:`app/excel.py` 回写文件名后缀 `_cmshopee回写` 为用户可见产物名——本任务**默认保留**(改动会影响已生成副本命名习惯),如需改「_優化助手回写」另行确认。**影响**:不改 db/exe/spec/包名 → 打包、更新流程、现有用户数据零影响。验收:窗口标题栏/任务栏/README/相关文档显示「蝦皮圈優化助手」;`cmshopee.db`/exe/包名不变;GUI 单测若断言窗口标题须同步更新;`python -m compileall app main.py` + `python -m unittest discover -s tests` 全绿。边界:纯显示/文档层,不改任何文件名、schema、流程、CDP/Shopee 逻辑 | DONE |
|
||||
| T-538 | 打包产物用户数据收进 `data/` 子文件夹(便携方案 1) | T-524 | 问题:当前 onedir 产物把用户数据(`config.json`、`config/ai_models.json`、`config/cmhub.json`、`cmshopee.db(+-wal/-shm)`、`chrome_user_data_dir/`、`images/`、`logs/`、`prompts/`、`title_prompt.txt`)生成在 exe 顶层,和程序文件/依赖文件混放:更新易误删数据、放进 Program Files 只读目录会写失败/被 VirtualStore 重定向、程序与数据关注点混杂。方案(保持便携):① **统一数据根**——所有用户数据落到 exe 同级的单个 `data/` 子文件夹;`app/appconfig.py` 用显式 `data_dir`(默认 `<exe目录>/data`,源码运行时可为项目根/`data`)解析并返回绝对路径,**替代现在 `main.py` 的 chdir**(chdir 影响全进程相对路径/文件对话框默认目录,脆弱);文件名保持不变(仍是 `cmshopee.db` 等),只是移入 `data/`。② **旧布局迁移**——新版首次运行若在 exe 顶层发现旧的 `config.json`/`cmshopee.db` 等,自动迁移进 `data/`(或明确提示手动迁移),不得对老用户生成空 `data/` 丢失原数据。③ **可写性检测**——启动时若 `data_dir` 不可写(如放在 Program Files),弹清晰错误「请把程序放到可写目录,勿放 Program Files」并优雅退出,不静默失败。④ **发布只含程序**——`cmshopee.spec`/`scripts/build_exe.ps1` 构建检查确保不打包 `data/` 或任何用户数据;当前锁定 PyInstaller 5.13.2,产物是扁平 onedir,`cmshopee.exe` 和 DLL/PYD/依赖目录同级,`_internal/` 不作为当前验收标准;发布 zip 不含 `data/`,从而「整包解压覆盖」也安全(覆盖程序文件、不碰 `data/`)。⑤ **更新流程前提**——原地更新=换程序留 `data/`,依赖:DB 迁移幂等且只向前(现 `init_db`+`ALTER TABLE ADD COLUMN`)、`config` 向前兼容(`load_config` 补默认值)。同步更新 `docs/packaging.md`(目录结构、首次运行数据位置、§5 更新步骤简化为「覆盖新版程序文件和依赖文件,`data/` 不动」)。测试:`test_appconfig` 断言各路径解析进 `data_dir`、可写性检测行为、旧布局迁移;`test_packaging` 断言产物不含 `data/`/用户数据。边界:只改路径解析层(`appconfig` 路径 + `main.py` 数据根 + 迁移/可写检测)、spec/构建脚本、打包文档;不改文件名、DB schema、业务逻辑、AI/Excel、CDP/Shopee 流程 | DONE |
|
||||
| T-538 | 打包产物用户数据收进 `data/` 子文件夹(便携方案 1) | T-524 | 问题:当前 onedir 产物把用户数据(`config.json`、`config/ai_models.json`、`config/cmhub.json`、`cmshopee.db(+-wal/-shm)`、`chrome_user_data_dir/`、`images/`、`logs/`、`prompts/`、`title_prompt.txt`)生成在 exe 顶层,和程序文件/依赖文件混放:更新易误删数据、放进 Program Files 只读目录会写失败/被 VirtualStore 重定向、程序与数据关注点混杂。方案(保持便携):① **统一数据根**——所有用户数据落到 exe 同级的单个 `data/` 子文件夹;`app/appconfig.py` 用显式 `data_dir`(默认 `<exe目录>/data`,源码运行时可为项目根/`data`)解析并返回绝对路径,**替代现在 `main.py` 的 chdir**(chdir 影响全进程相对路径/文件对话框默认目录,脆弱);文件名保持不变(仍是 `cmshopee.db` 等),只是移入 `data/`。② **旧布局迁移**——新版首次运行若在 exe 顶层发现旧的 `config.json`/`cmshopee.db` 等,自动迁移进 `data/`(或明确提示手动迁移),不得对老用户生成空 `data/` 丢失原数据。③ **可写性检测**——启动时若 `data_dir` 不可写(如放在 Program Files),弹清晰错误「请把程序放到可写目录,勿放 Program Files」并优雅退出,不静默失败。④ **发布只含程序**——`cmshopee.spec`/`scripts/build_exe.ps1` 构建检查确保不打包 `data/` 或任何用户数据;发布 zip 不含 `data/`,从而「整包解压覆盖」也安全(覆盖程序文件、不碰 `data/`);T-540 后当前打包脚本固定 PyInstaller 6.11.1,产物为 `cmshopee.exe` + `_internal/` onedir,具体版本和 release 命名由 T-540 维护。⑤ **更新流程前提**——原地更新=换程序留 `data/`,依赖:DB 迁移幂等且只向前(现 `init_db`+`ALTER TABLE ADD COLUMN`)、`config` 向前兼容(`load_config` 补默认值)。同步更新 `docs/packaging.md`(目录结构、首次运行数据位置、§5 更新步骤简化为「覆盖新版程序文件和依赖文件,`data/` 不动」)。测试:`test_appconfig` 断言各路径解析进 `data_dir`、可写性检测行为、旧布局迁移;`test_packaging` 断言产物不含 `data/`/用户数据。边界:只改路径解析层(`appconfig` 路径 + `main.py` 数据根 + 迁移/可写检测)、spec/构建脚本、打包文档;不改文件名、DB schema、业务逻辑、AI/Excel、CDP/Shopee 流程 | DONE |
|
||||
| T-539 | ⑤ 隐藏数据路径设置(设置简化,非防逆向) | T-538, T-517 | 背景:T-538 后用户数据固定收进 exe 同级 `data/`,⑤「路径与端口」里的 DB 路径(`dbPathEdit`)、图片目录(`imageDirEdit`)、账号数据根目录(`userDataRootEdit`)已不该由用户改动——改了会指向 `data/` 之外导致数据分裂/丢失。**定性说明**:本任务是**设置简化**,与 T-517/T-529 隐藏 `test_item_id`/`dry_run` 同一思路;**不是防逆向手段**——`cmshopee.db` 在磁盘上任何 SQLite 浏览器可直接打开,隐藏 UI 字段对逆向无意义,防逆向由另行任务(发布包加固/价值上移 cmhub)承担。方案:① ⑤普通设置页**隐藏** `db_path`/`image_dir`/账号数据根三个输入框(不再占表单位置),`config.json` 对应字段保留为内部兼容——手工编辑配置仍生效(内部回滚路径),加载/保存不丢字段;② **保留可见**:Chrome 路径(每台机器安装位置不同,真实配置需求)与默认端口/端口区间/CDP 就绪超时(端口冲突需可调);③ 首次运行/迁移后各路径默认解析到 `data/` 下(沿用 T-538 的 `data_dir` 规则),无需用户感知。同步 `docs/routes.md` ⑤ 说明与 GUI 设置单测(隐藏字段不再可见、config 字段仍读写、手工配置值仍被尊重)。边界:只改⑤设置页展示层与文档;不改 `config.json` schema、路径解析逻辑(T-538 范围)、DB、业务流程、CDP/Shopee | TODO |
|
||||
| T-540 | 打包版本号与 release 产物命名统一 | T-537, T-538, T-524 | 参考 `D:\chengma\cmbot` 的轻量版本控制方式,但本阶段不引入 Launcher/manifest/自动更新。方案:① 新增 `app/version.py`,统一维护 `APP_NAME="蝦皮圈優化助手"`、`APP_CODE_NAME="cmshopee"`、`APP_VERSION`,作为 GUI 标题、打包脚本、release 目录、zip 文件名和 `version.txt` 的唯一版本源;② 主窗口标题改为 `蝦皮圈優化助手 v<APP_VERSION>`,PySide6 缺失提示继续读中文品牌名;③ `scripts/build_exe.ps1` 从 `app/version.py` 读取版本,并固定通过 Windows Python Launcher 调用 `py -3.10`,返回版本必须是 `3.10.x`;④ PyInstaller 仍输出 `dist\cmshopee\` 作为中间产物,再组装 `release\cmshopee-<APP_VERSION>\` 和 `release\cmshopee-<APP_VERSION>-portable.zip`,发布目录内写 `version.txt`(无 BOM)和中文 `README.txt`;⑤ 发布目录/zip 仍不得包含 `data/`、DB、配置、图片、日志、Chrome 登录态或运营 Excel;当前 PyInstaller 6.11.1 onedir 结构必须包含 `cmshopee.exe` + `_internal\`,脚本会校验 `_internal\` 存在;⑥ 测试覆盖版本文件解析、窗口标题版本、构建脚本 release 命名/排除数据/固定 `py -3.10` 打包。边界:只做手动发版版本号一致性和 release 组装;不做在线更新、启动器、manifest、sha256 下载校验、强制更新或自动替换程序目录 | DONE |
|
||||
|
||||
## 里程碑
|
||||
|
||||
|
||||
+35
-31
File diff suppressed because one or more lines are too long
+102
-19
@@ -1,19 +1,19 @@
|
||||
# 打包与分发
|
||||
|
||||
> T-524 目标:先把当前 Windows 桌面工具打成免安装 onedir `.exe`,方便第一版交付使用。T-538 后用户数据统一收进程序同级 `data/`,暂不做自动更新器、安装器、增量补丁或在线升级。
|
||||
> T-524 目标:先把当前 Windows 桌面工具打成免安装 onedir `.exe`,方便第一版交付使用。暂不做自动更新器、安装器、增量补丁或在线升级。
|
||||
|
||||
## 一、打包前提
|
||||
|
||||
- 在 Windows 环境执行。
|
||||
- Python 环境已能运行源码版 `python main.py`。
|
||||
- 打包脚本使用当前 PATH 中的 `python`;正式出包前先确认 `python --version` 符合项目目标 Python 3.10+。
|
||||
- 打包脚本固定使用 Windows Python Launcher 的 `py -3.10`,不使用当前 PATH 中的 `python`;正式出包前先确认 `py -3.10 --version` 可用。
|
||||
- 安装运行依赖和打包依赖:
|
||||
|
||||
```powershell
|
||||
python -m pip install -r requirements-build.txt
|
||||
py -3.10 -m pip install -r requirements-build.txt
|
||||
```
|
||||
|
||||
`requirements-build.txt` 会先安装 `requirements.txt` 的运行依赖,再安装 PyInstaller。
|
||||
`requirements-build.txt` 会先安装 `requirements.txt` 的运行依赖,再安装 PyInstaller。依赖必须安装到 Python 3.10 环境里,避免 PATH 上的 Python 3.7/3.12/3.14 打出不可比对的包。
|
||||
|
||||
## 二、打包命令
|
||||
|
||||
@@ -24,24 +24,105 @@ powershell -ExecutionPolicy Bypass -File scripts\build_exe.ps1
|
||||
脚本会执行:
|
||||
|
||||
```powershell
|
||||
python -m PyInstaller --noconfirm --clean cmshopee.spec
|
||||
py -3.10 -m PyInstaller --noconfirm --clean cmshopee.spec
|
||||
```
|
||||
|
||||
成功后输出:
|
||||
|
||||
```text
|
||||
dist\cmshopee\cmshopee.exe
|
||||
dist\cmshopee\
|
||||
cmshopee.exe
|
||||
_internal\
|
||||
<PyInstaller 依赖文件和依赖目录>
|
||||
```
|
||||
|
||||
发布给用户时,以整个 `dist\cmshopee\` 文件夹为单位压缩分发,用户双击 `cmshopee.exe` 启动。当前 PyInstaller 5.13.2 的 onedir 是扁平结构,`cmshopee.exe` 与 DLL/PYD/依赖目录同级,`_internal\` 不作为当前验收标准。
|
||||
正式分发入口是 `release\cmshopee-<APP_VERSION>-portable.zip`。`dist\cmshopee\` 只是 PyInstaller 构建中间产物,不直接作为正式发包名称。
|
||||
|
||||
## 三、绝不打包的本地数据
|
||||
当前 `requirements-build.txt` 锁定 PyInstaller 6.11.1,onedir 产物必须是 `cmshopee.exe` + `_internal\` 集中依赖布局。`scripts\build_exe.ps1` 会校验 `_internal\` 存在;缺失时直接失败,避免把不一致的打包环境误发给用户。
|
||||
|
||||
发布包只包含程序文件和依赖文件:
|
||||
|
||||
```text
|
||||
cmshopee\
|
||||
cmshopee.exe
|
||||
_internal\
|
||||
<PyInstaller 依赖文件和依赖目录>
|
||||
```
|
||||
|
||||
首次运行后才会在同级生成 `data\`,用于保存用户本地数据。
|
||||
|
||||
## 三、版本号与发布目录
|
||||
|
||||
参考 `D:\chengma\cmbot` 的打包方式,版本控制采用“单一版本源 + 发布目录带版本号”的轻量方案,暂不引入启动器、自动更新或 manifest。
|
||||
|
||||
### 3.1 单一版本源
|
||||
|
||||
新增 `app/version.py`,作为软件名称和版本号的唯一来源:
|
||||
|
||||
```python
|
||||
APP_NAME = "蝦皮圈優化助手"
|
||||
APP_CODE_NAME = "cmshopee"
|
||||
APP_VERSION = "0.1.0"
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 修改版本号只改 `app/version.py` 的 `APP_VERSION`。
|
||||
- GUI 窗口标题读取同一版本源,显示为 `蝦皮圈優化助手 v0.1.0`。
|
||||
- 打包脚本读取同一版本源生成发布目录、压缩包和 `version.txt`。
|
||||
- 禁止在 GUI、构建脚本或文档示例之外重复硬编码不同版本号。
|
||||
|
||||
### 3.2 发布目录结构
|
||||
|
||||
`scripts\build_exe.ps1` 仍先产出 PyInstaller 原始目录 `dist\cmshopee\`,再组装面向用户分发的 `release\` 目录:
|
||||
|
||||
```text
|
||||
release\
|
||||
cmshopee-0.1.0\
|
||||
cmshopee.exe
|
||||
_internal\
|
||||
<PyInstaller 依赖文件和依赖目录>
|
||||
version.txt
|
||||
README.txt
|
||||
cmshopee-0.1.0-portable.zip
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `dist\cmshopee\` 是构建中间产物,不直接作为正式发包名称。
|
||||
- `release\cmshopee-<APP_VERSION>\` 是人工验收和分发目录。
|
||||
- `release\cmshopee-<APP_VERSION>-portable.zip` 是交付给用户的便携压缩包。
|
||||
- `version.txt` 使用 ASCII/UTF-8 无 BOM 写入 `APP_VERSION`,用于人工排查和未来更新机制读取。
|
||||
- `README.txt` 用中文写明启动方式、不要放入 `Program Files`、保留 `data\`、升级时覆盖程序文件但不覆盖 `data\`。
|
||||
|
||||
当前 PyInstaller 6.11.1 的 onedir 为集中依赖结构,所以 `release\cmshopee-<APP_VERSION>\` 必须包含 `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-<APP_VERSION>\version.txt`、GUI 标题栏版本、压缩包文件名三者一致。
|
||||
5. 在无 Python 环境的 Windows 10/11 机器上解压 `cmshopee-<APP_VERSION>-portable.zip` 并启动验证。
|
||||
|
||||
正式发布构建固定使用 Python 3.10。源码在 Python 3.7.9 上曾验证可运行,但发布包不再以 3.7.9 作为打包基线;脚本会直接调用 `py -3.10` 并校验返回版本必须是 `3.10.x`,避免同一个 `APP_VERSION` 在不同 Python 版本下打出不可比对的包。CI 仍可继续使用 Python 3.11 做自动测试。
|
||||
|
||||
### 3.4 暂不引入自动更新
|
||||
|
||||
本阶段不迁移 `cmbot` 的 `Launcher.exe`、`manifest.json`、SHA-256 下载校验、强制更新或在线更新机制。原因:
|
||||
|
||||
- 当前用户已验证“拷贝发布目录到无 Python 环境运行”的第一版交付路径。
|
||||
- `cmshopee` 刚完成 `data\` 数据隔离,先把手动发版和版本号一致性做稳。
|
||||
- 自动更新需要安装根可写、下载暂存、目录切换、回滚和安全校验,属于独立后续任务,不能混入当前打包版本号控制。
|
||||
|
||||
后续如果要做在线更新,再另起任务,参考 `cmbot` 的 `Launcher.exe + app\ + manifest.json + sha256 + version.txt` 设计。
|
||||
|
||||
## 四、绝不打包的本地数据
|
||||
|
||||
发布包里不能包含以下本地数据、密钥、业务数据或登录态:
|
||||
|
||||
- `config.json`
|
||||
- `config/ai_models.json`
|
||||
- `config/cmhub.json`
|
||||
- `cmshopee.db`、`cmshopee.db-wal`、`cmshopee.db-shm`
|
||||
- `db.sqlite`
|
||||
- `chrome_user_data_dir/`
|
||||
@@ -54,7 +135,7 @@ dist\cmshopee\cmshopee.exe
|
||||
|
||||
`cmshopee.spec` 不声明任何 `datas`;`scripts/build_exe.ps1` 会在打包后检查 `dist\cmshopee\`,如果发现上述路径会直接失败。
|
||||
|
||||
## 四、首次运行与本地数据位置
|
||||
## 五、首次运行与本地数据位置
|
||||
|
||||
T-538 后,打包版不再把全进程工作目录切到 `cmshopee.exe` 所在目录;路径由 `app/appconfig.py` 按显式数据根解析。
|
||||
|
||||
@@ -63,7 +144,8 @@ T-538 后,打包版不再把全进程工作目录切到 `cmshopee.exe` 所在
|
||||
```text
|
||||
cmshopee\
|
||||
cmshopee.exe
|
||||
<PyInstaller 依赖文件和依赖目录>
|
||||
_internal\
|
||||
<PyInstaller 依赖文件和依赖目录>
|
||||
data\
|
||||
config.json
|
||||
config\
|
||||
@@ -89,33 +171,34 @@ cmshopee\
|
||||
|
||||
启动时会检测 `data\` 是否可写;如果程序放在 `Program Files` 等只读目录导致写入失败,才会以“数据目录不可写”提示“请把程序放到可写目录,勿放 Program Files”并退出。
|
||||
|
||||
## 五、用户后续更新方式
|
||||
## 六、用户后续更新方式
|
||||
|
||||
第一版不做自动更新。给用户发新版本时:
|
||||
|
||||
1. 让用户先关闭 cmshopee。
|
||||
2. 建议用户备份当前整个程序文件夹。
|
||||
3. 解压新版 `dist\cmshopee\`。
|
||||
4. 覆盖新版程序文件和依赖文件。当前 PyInstaller 5.13.2 是扁平 onedir,不能只覆盖 `cmshopee.exe`,也没有 `_internal/` 可单独替换。
|
||||
3. 解压新版 `release\cmshopee-<APP_VERSION>-portable.zip`。
|
||||
4. 覆盖新版程序文件和依赖文件。当前 PyInstaller 6.11.1 是 `cmshopee.exe` + `_internal\` onedir,不能只覆盖 `cmshopee.exe`;应把新版 `cmshopee.exe`、`_internal\`、`version.txt`、`README.txt` 等程序文件整体覆盖到旧程序目录。
|
||||
5. 保留旧目录里的 `data\`,不要删除、覆盖或合并它。
|
||||
|
||||
如果用户把整个旧目录删除再放新版,`data\` 里的账号、任务记录、图片和登录态也会一起丢失,只能从备份恢复。
|
||||
|
||||
## 六、验证清单
|
||||
## 七、验证清单
|
||||
|
||||
打包前后至少执行:
|
||||
|
||||
```powershell
|
||||
python -m compileall app main.py
|
||||
python -m unittest discover -s tests
|
||||
py -3.10 -m compileall app main.py
|
||||
py -3.10 -m unittest discover -s tests
|
||||
powershell -ExecutionPolicy Bypass -File scripts\build_exe.ps1
|
||||
```
|
||||
|
||||
打包成功后确认:
|
||||
|
||||
- `dist\cmshopee\cmshopee.exe` 存在。
|
||||
- 当前 PyInstaller 5.13.2 下 `dist\cmshopee\` 是扁平 onedir,`_internal\` 不存在也属于正常。
|
||||
- `dist\cmshopee\` 中没有第三节列出的本地数据,尤其不能含 `data\`。
|
||||
- 当前 PyInstaller 6.11.1 下 `dist\cmshopee\_internal\` 必须存在。
|
||||
- `dist\cmshopee\` 中没有第四节列出的本地数据,尤其不能含 `data\`。
|
||||
- `release\cmshopee-<APP_VERSION>\version.txt`、GUI 标题栏版本、压缩包文件名三者一致。
|
||||
- 在干净目录首次启动时能生成 `data\config.json` 并进入 GUI。
|
||||
- 在目标 Windows 10/11 机器或虚拟机上启动 release exe 后,主窗口标题栏完整可见,左边缘不出屏,用户能用标题栏拖动窗口;小分辨率环境不得出现窗口卡在左上角且标题栏不可拖动的问题(见 T-541)。
|
||||
|
||||
|
||||
+20
-14
@@ -1,6 +1,6 @@
|
||||
# 常见问题排查
|
||||
|
||||
> 本文只记录可复用的本地排障步骤。T-538 后用户数据默认位于 `data/`,涉及 `data/config.json`、`data/data/config/ai_models.json`、`data/config/cmhub.json`、`data/cmshopee.db`、`data/chrome_user_data_dir/`、`data/images/` 时,默认它们是本机敏感或业务数据,必须保持 gitignore,不把真实密码、API Key、Cookie、token 写入文档、日志或提交。
|
||||
> 本文只记录可复用的本地排障步骤。T-538 后用户数据默认位于 `data/`,涉及 `data/config.json`、`data/config/ai_models.json`、`data/config/cmhub.json`、`data/cmshopee.db`、`data/chrome_user_data_dir/`、`data/images/` 时,默认它们是本机敏感或业务数据,必须保持 gitignore,不把真实密码、API Key、Cookie、token 写入文档、日志或提交。
|
||||
|
||||
## 启动时报 “AI 模型 category 必须是 text 或 image”
|
||||
|
||||
@@ -14,17 +14,17 @@ AI 模型 category 必须是 text 或 image
|
||||
|
||||
### 原因
|
||||
|
||||
`data/data/config/ai_models.json` 是本地 AI 模型清单,里面每个模型都必须有合法的 `category`:
|
||||
`data/config/ai_models.json` 是本地 AI 模型清单,里面每个模型都必须有合法的 `category`:
|
||||
|
||||
- `text`:标题生成模型,会出现在“标题大模型”下拉。
|
||||
- `image`:封面生成模型,会出现在“图片大模型”下拉。
|
||||
|
||||
早期或手工维护过的 `data/data/config/ai_models.json` 可能缺少 `category`,或者填了中文、空值、旧字段,导致 `appconfig.load_ai_models_config()` 严格校验失败,GUI 启动被阻断。
|
||||
早期或手工维护过的 `data/config/ai_models.json` 可能缺少 `category`,或者填了中文、空值、旧字段,导致 `appconfig.load_ai_models_config()` 严格校验失败,GUI 启动被阻断。
|
||||
|
||||
### 不要这样做
|
||||
|
||||
- 不要删除 `data/data/config/ai_models.json` 来“重置”,否则会丢失本地明文 API Key 和模型配置。
|
||||
- 不要把 `data/data/config/ai_models.json` 提交到 git。
|
||||
- 不要删除 `data/config/ai_models.json` 来“重置”,否则会丢失本地明文 API Key 和模型配置。
|
||||
- 不要把 `data/config/ai_models.json` 提交到 git。
|
||||
- 不要把完整文件内容贴到聊天、文档或日志里;该文件含本地明文 API Key。
|
||||
|
||||
### 推荐修复
|
||||
@@ -35,7 +35,7 @@ AI 模型 category 必须是 text 或 image
|
||||
python -c "import json; p='data/config/ai_models.json'; data=json.load(open(p,encoding='utf-8')); [print({'index': i+1, 'name': m.get('name'), 'category': m.get('category'), 'api_type': m.get('api_type'), 'enabled': m.get('enabled'), 'has_api_key': bool(m.get('api_key'))}) for i,m in enumerate(data.get('models', []))]"
|
||||
```
|
||||
|
||||
然后手工编辑 `data/data/config/ai_models.json`,只补这些字段:
|
||||
然后手工编辑 `data/config/ai_models.json`,只补这些字段:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -75,7 +75,7 @@ print('updated' if changed else 'unchanged')"
|
||||
python -c "import os,sys; sys.path.insert(0, os.getcwd()); from app import appconfig; print([(m['name'], m['category'], m['enabled'], m['api_key_set']) for m in appconfig.list_ai_models()])"
|
||||
```
|
||||
|
||||
确认 `data/data/config/ai_models.json` 仍被忽略:
|
||||
确认 `data/config/ai_models.json` 仍被忽略:
|
||||
|
||||
```powershell
|
||||
git status --short --ignored data/config/ai_models.json
|
||||
@@ -125,7 +125,7 @@ https://api.vectorengine.ai/v1
|
||||
https://api.vectorengine.ai/v1/chat/completions
|
||||
```
|
||||
|
||||
如果仍然 404,优先检查服务商文档要求的 endpoint 路径和 `api_type`,不要把 `data/data/config/ai_models.json` 或 API Key 贴到日志、文档或聊天里。
|
||||
如果仍然 404,优先检查服务商文档要求的 endpoint 路径和 `api_type`,不要把 `data/config/ai_models.json` 或 API Key 贴到日志、文档或聊天里。
|
||||
|
||||
### 非敏感检查
|
||||
|
||||
@@ -207,7 +207,7 @@ T-530 已实现:保存和请求前都会把 Base URL 规整为网关根,去
|
||||
2. 如果②状态列显示“生成失败”:留在②,确认 cmhub Base URL、API Key、别名和点数正常后,直接点击「开始生成」重试当前筛选结果。
|
||||
3. 如果②状态列显示“更新失败”:到③更新shopee处理;需要重复更新时使用③的重置更新状态入口,不要在②重新生成。
|
||||
|
||||
排查时不要把 `data/cmshopee.db`、`data/config/cmhub.json`、`data/data/config/ai_models.json` 或任何 API Key、密码、Cookie 发到聊天、文档或提交里。
|
||||
排查时不要把 `data/cmshopee.db`、`data/config/cmhub.json`、`data/config/ai_models.json` 或任何 API Key、密码、Cookie 发到聊天、文档或提交里。
|
||||
|
||||
## AI生成:标题成功但图片生成失败,且看不到原因
|
||||
|
||||
@@ -216,7 +216,7 @@ T-530 已实现:保存和请求前都会把 Base URL 规整为网关根,去
|
||||
- 页面右下「AI生成运行日志」显示最近一次 `run_type=generate` 的逐条事件,例如 `phase=cover step=cover_request result=failed detail=...`。
|
||||
- 本地 `data/logs/cmshopee.log` 保存脱敏后的 traceback、任务 id、alias、item_id、phase 和 step,用于判断卡在模型配置、封面请求、图片解析、保存文件还是写库。
|
||||
|
||||
排查顺序:先看 ② 页面运行日志里的 `phase` / `step` / `detail`;如果只看到简短错误,再查看本地 `data/logs/cmshopee.log`。不要把 `data/data/config/ai_models.json` 或 API Key 发到聊天、文档或提交里。
|
||||
排查顺序:先看 ② 页面运行日志里的 `phase` / `step` / `detail`;如果只看到简短错误,再查看本地 `data/logs/cmshopee.log`。不要把 `data/config/ai_models.json` 或 API Key 发到聊天、文档或提交里。
|
||||
|
||||
## ④启动登录重复打开 Chrome
|
||||
|
||||
@@ -232,17 +232,23 @@ T-530 已实现:保存和请求前都会把 Base URL 规整为网关根,去
|
||||
|
||||
### 现象
|
||||
|
||||
在 Windows 10 虚拟机或小分辨率环境运行打包后的 `dist/cmshopee/cmshopee.exe` / release 包后,主窗口出现在屏幕左上角,左边和标题栏显示不全,看不到「蝦皮圈優化助手」标题,导致无法用鼠标拖动窗口到中间。
|
||||
在 Windows 10 虚拟机或小分辨率环境运行打包后的 `dist/cmshopee/cmshopee.exe` / `release/cmshopee-<版本>-portable.zip` 后,主窗口出现在屏幕左上角,左边和标题栏显示不全,看不到「蝦皮圈優化助手 v<版本>」标题,导致无法用鼠标拖动窗口到中间。
|
||||
|
||||
### 原因
|
||||
|
||||
旧逻辑只设置固定窗口大小:
|
||||
修复前旧逻辑只设置了固定窗口大小:
|
||||
|
||||
```python
|
||||
self.resize(1180, 760)
|
||||
```
|
||||
|
||||
没有按当前屏幕可用区域限制初始尺寸、居中显示,也没有保证窗口标题栏完整落在屏幕内。虚拟机、小分辨率、高 DPI、任务栏占用较多或多显示器切换环境下更容易暴露该问题;PyInstaller 打包不是根因。
|
||||
没有按当前屏幕可用区域做以下处理:
|
||||
|
||||
- 限制初始尺寸不超过屏幕可用区域。
|
||||
- 居中显示。
|
||||
- 保证窗口 frame/title bar 完整落在屏幕内。
|
||||
|
||||
因此在虚拟机、小分辨率、高 DPI、任务栏占用较多或多显示器切换环境下,Qt/Windows 默认放置窗口时可能让标题栏或左边缘落到屏幕外。PyInstaller 打包不是根因,只是打包版更常在目标虚拟机里暴露该问题。
|
||||
|
||||
### 临时处理
|
||||
|
||||
@@ -261,7 +267,7 @@ T-541 已在主窗口启动时读取 `QApplication.primaryScreen().availableGeom
|
||||
- 大屏保持接近 `1180x760`。
|
||||
- 小屏自动缩小到可用区域内。
|
||||
- `resize()` 后按可用区域居中 `move()`。
|
||||
- 确保窗口左上角不小于可用区域左上角,标题栏完整可见、可拖动。
|
||||
- 确保窗口 frame 左上角不小于可用区域左上角,标题栏完整可见、可拖动。
|
||||
- 不保存坏的历史窗口坐标,避免下次继续打开到屏幕外。
|
||||
|
||||
修复后需要在普通桌面和 Windows 10 虚拟机/小分辨率环境各启动一次打包版,确认标题栏完整可见。
|
||||
|
||||
+56
-17
@@ -1272,22 +1272,6 @@
|
||||
- 边界:只改显示层和文档层;`cmshopee` 继续作为仓库、包名、exe、数据库、日志、配置、回写后缀和兼容标识符保留;未改任何文件名、schema、AI、Excel、DB、CDP 或 Shopee 更新流程。
|
||||
- 验证:`python -m py_compile app\gui\main_window.py app\gui\__init__.py tests\test_gui.py` 通过;`python -m unittest discover -s tests -p test_gui.py` 通过(95 tests);`python -m compileall app main.py` 通过;`python -m unittest discover -s tests` 通过;`git diff --check` 通过。
|
||||
|
||||
## 【2026-07-07】T-541 文档 · 打包版窗口左上角显示不全
|
||||
|
||||
- 现象:运行打包后的 `dist/cmshopee/cmshopee.exe` / release 包时,Windows 10 虚拟机或小分辨率环境可能出现主窗口贴到屏幕左上角、左边和标题栏显示不全,用户看不到品牌标题且无法拖动窗口到中间。
|
||||
- 原因:`MainWindow.__init__()` 当前只 `resize(1180, 760)`,没有读取 `QApplication.primaryScreen().availableGeometry()` 限制初始尺寸、居中显示,也没有保证窗口标题栏完整落在可用屏幕内。
|
||||
- 文档:`docs/06-tasks.md` 新增 T-541;`docs/troubleshooting.md` 增加临时处理(`Alt+Space` → `M` → 方向键移动)和正式修复方案;`docs/packaging.md` 打包验收清单新增标题栏完整可见/可拖动检查;`docs/current-state.md` 记录 T-541 修复口径。
|
||||
- 方案:后续代码在主窗口启动时根据屏幕可用区域限制尺寸并居中,保证标题栏可见;小屏继续适配,不保存坏的历史窗口坐标。
|
||||
- 边界:本轮只更新文档,未改 GUI 代码、打包脚本、业务逻辑、DB、AI、Excel、CDP 或 Shopee 流程。
|
||||
|
||||
## 【2026-07-07】T-541 完成 · 打包版主窗口初始位置与小屏适配
|
||||
|
||||
- 状态:DONE
|
||||
- 代码:`app/gui/main_window.py` 新增 `_fit_and_center_window()`,启动时读取 `QApplication.primaryScreen().availableGeometry()`,将主窗口尺寸限制在可用屏幕范围内并居中;首次 `showEvent` 后再应用一次,避免打包版在 Windows 10 VM/小分辨率环境标题栏出屏。
|
||||
- 文档:`docs/06-tasks.md` 将 T-541 标记为 DONE;`docs/troubleshooting.md`、`docs/packaging.md`、`docs/current-state.md` 更新为当前修复与打包验收口径。
|
||||
- 测试:`tests/test_gui.py` 新增小屏和大屏 mock geometry 单测,覆盖尺寸限制与居中位置;`scripts/build_exe.ps1` 已重新构建 release,确认 T-541 合入后仍可打包。
|
||||
- 边界:不保存/恢复窗口历史坐标;未改业务流程、DB、AI、Excel、CDP/Shopee、打包产物结构或用户数据路径。
|
||||
|
||||
## 【2026-07-07】T-538 完成 · 打包产物用户数据收进 data/
|
||||
|
||||
- 状态:DONE
|
||||
@@ -1296,7 +1280,7 @@
|
||||
- 打包:`.gitignore` 新增 `data/`;`scripts/build_exe.ps1` 发布目录校验禁止带出 `data/`;更新方式改为覆盖发布包里的程序文件和依赖文件并保留 `data/`。当前 PyInstaller 5.13.2 为扁平 onedir,不要求存在 `_internal/`。
|
||||
- 文档:`docs/06-tasks.md` 将 T-538 标记为 DONE;同步 `docs/packaging.md`、架构/技术栈/API/排障/路由/current-state 和 UI 线框图里的数据路径说明。
|
||||
- 边界:未改 DB schema、AI HTTP 协议、Excel、账号、CDP 或 Shopee 页面操作流程;旧 DB 已存图片路径继续按原路径读取。
|
||||
- 验证:`python -m py_compile app\appconfig.py app\diagnostics.py app\prompts.py app\editor.py app\gui\__init__.py app\gui\main_window.py app\gui\tabs\generate.py app\gui\tabs\settings.py app\gui\widgets.py main.py tests\test_appconfig.py tests\test_packaging.py tests\test_gui.py` 通过;`python -m unittest discover -s tests -p test_appconfig.py` 通过;`python -m unittest discover -s tests -p test_packaging.py` 通过;`python -m unittest discover -s tests -p test_gui.py` 通过;`python -m compileall app main.py` 通过;`python -m unittest discover -s tests` 通过;`git diff --check` 通过。
|
||||
- 验证:`python -m py_compile app\appconfig.py app\diagnostics.py app\prompts.py app\editor.py app\gui\__init__.py app\gui\main_window.py app\gui\tabs\generate.py app\gui\tabs\settings.py app\gui\widgets.py main.py tests\test_appconfig.py tests\test_packaging.py tests\test_gui.py` 通过;`python -m unittest discover -s tests -p test_appconfig.py` 通过(14 tests);`python -m unittest discover -s tests -p test_packaging.py` 通过(3 tests);`python -m unittest discover -s tests -p test_gui.py` 通过(95 tests);`python -m compileall app main.py` 通过;`python -m unittest discover -s tests` 通过(217 tests);`git diff --check` 通过。
|
||||
|
||||
## 【2026-07-07】T-538 修正 · 区分数据迁移冲突和目录不可写
|
||||
|
||||
@@ -1312,3 +1296,58 @@
|
||||
- 文档:`docs/packaging.md`、`docs/03-tech-stack.md`、`docs/06-tasks.md`、`docs/current-state.md` 已改为发布整个 `dist/cmshopee` 文件夹,验证重点是不得带出 `data/` 和用户本地数据。
|
||||
- 代码:`scripts/build_exe.ps1` 打包成功后会提示当前依赖布局;无 `_internal/` 时明确说明扁平结构正常,并提醒发布整个 `dist/cmshopee` 目录。
|
||||
- 边界:未升级 PyInstaller,未改变 spec 产物布局,未改运行时路径、DB、AI、Excel、CDP 或 Shopee 流程。
|
||||
|
||||
## 【2026-07-07】T-540 文档 · 打包版本号与 release 产物命名统一
|
||||
|
||||
- 需求:参考 `D:\chengma\cmbot` 的打包版本控制方式,解决当前 `cmshopee` 没有统一版本源、发布目录不带版本号、GUI 标题和构建产物无法一致追踪的问题。
|
||||
- 文档:`docs/packaging.md` 新增“版本号与发布目录(T-540 方案)”,定义 `app/version.py` 为唯一版本源、`release\cmshopee-<APP_VERSION>\` 发布目录、`cmshopee-<APP_VERSION>-portable.zip` 便携包、`version.txt` 和中文 README;`docs/06-tasks.md` 新增 T-540;`docs/current-state.md` 记录后续任务口径。
|
||||
- 决策:本阶段只做手动发版版本号一致性,不搬 `cmbot` 的 `Launcher.exe`、`manifest.json`、SHA-256 下载校验、强制更新或在线更新机制。
|
||||
- 边界:本轮只更新文档,未改代码、构建脚本、测试、运行时路径、DB、AI、Excel、CDP 或 Shopee 流程。
|
||||
|
||||
## 【2026-07-07】T-540 完成 · 打包版本号与 release 产物命名统一
|
||||
|
||||
- 状态:DONE
|
||||
- 代码:新增 `app/version.py`,统一维护 `APP_NAME`、`APP_CODE_NAME`、`APP_VERSION` 和 `display_name()`;GUI 主窗口标题改为 `蝦皮圈優化助手 v<APP_VERSION>`,PySide6 缺失提示继续读取统一品牌名。
|
||||
- 打包:`scripts/build_exe.ps1` 从 `app/version.py` 读取版本,最低支持 Python 3.7.9,低于 3.7.9 直接失败;PyInstaller 仍输出 `dist\cmshopee`,随后组装 `release\cmshopee-<APP_VERSION>\`,写入 `version.txt`、中文 `README.txt`,并生成 `release\cmshopee-<APP_VERSION>-portable.zip`;`release/` 已加入 `.gitignore`。
|
||||
- 兼容:脚本源码保持 ASCII,中文构建提示和 README 文案用 UTF-8 base64 在运行时解码,避免 Windows PowerShell 5.1 误读 UTF-8 无 BOM 导致解析失败。
|
||||
- 测试:`tests/test_packaging.py` 覆盖版本源、release 命名、zip、`version.txt`、Python 3.7.9 最低版本校验和数据排除脚本逻辑;`tests/test_gui.py` 改为断言窗口标题读取统一版本源。
|
||||
- 边界:不做 Launcher、manifest、sha256 下载校验、在线更新、强制更新或自动替换程序目录;未改 DB schema、AI、Excel、CDP 或 Shopee 流程。
|
||||
|
||||
## 【2026-07-07】T-540 修正 · 打包兼容 Python 3.7.9
|
||||
|
||||
- 需求:用户确认当前项目在 Python 3.7.9 环境可运行,打包文档与脚本不应继续要求 Python 3.10+。
|
||||
- 代码:`scripts/build_exe.ps1` 的最低版本校验改为 Python 3.7.9;低于 3.7.9 才失败。
|
||||
- 文档:`docs/packaging.md`、`docs/03-tech-stack.md`、`docs/06-tasks.md`、`docs/current-state.md` 改为当前发布构建兼容 Python 3.7.9+,CI 继续使用 Python 3.11。
|
||||
- 测试:`tests/test_packaging.py` 改为断言构建脚本包含 Python 3.7.9 最低版本校验,并防止重新写回 Python 3.10+ 阻断。
|
||||
- 边界:只改打包版本要求、文档和测试;未改运行依赖版本、业务逻辑、DB、AI、Excel、CDP 或 Shopee 流程。
|
||||
|
||||
## 【2026-07-07】T-540 修正 · 打包固定使用 Python 3.10
|
||||
|
||||
- 需求:用户已安装 Python 3.10,正式打包需要固定使用 Python 3.10,避免 PATH 上的 Python 3.7.9 或其他版本参与发布构建。
|
||||
- 代码:`scripts/build_exe.ps1` 改为固定调用 Windows Python Launcher 的 `py -3.10`,读取到的版本必须是 `3.10.x`;`python -m PyInstaller` 改为 `py -3.10 -m PyInstaller`,并在 PyInstaller 退出码非 0 时直接失败。
|
||||
- 文档:`docs/packaging.md`、`docs/03-tech-stack.md`、`docs/06-tasks.md`、`docs/current-state.md` 改为正式打包固定 Python 3.10;安装打包依赖命令改为 `py -3.10 -m pip install -r requirements-build.txt`。
|
||||
- 测试:`tests/test_packaging.py` 改为断言构建脚本固定 `py -3.10`,且不再使用裸 `python -m PyInstaller` 或 3.7.9 最低版本口径。
|
||||
- 验证:`py -3.10 -m pip install -r requirements-build.txt` 成功;`py -3.10 -m compileall app main.py` 通过;`py -3.10 -m unittest discover -s tests` 通过(219 tests);`powershell -ExecutionPolicy Bypass -File scripts\build_exe.ps1` 成功,输出显示 `使用 Python 3.10: 3.10.11` 并生成 `release\cmshopee-0.1.0-portable.zip`。
|
||||
- 边界:只改正式打包解释器选择、文档和测试;未改运行依赖版本、业务逻辑、DB、AI、Excel、CDP 或 Shopee 流程。
|
||||
|
||||
## 【2026-07-07】T-541 文档 · 打包版窗口左上角显示不全
|
||||
|
||||
- 现象:运行打包后的 `dist/cmshopee/cmshopee.exe` / release 包时,Windows 10 虚拟机或小分辨率环境可能出现主窗口贴到屏幕左上角、左边和标题栏显示不全,用户看不到品牌标题且无法拖动窗口到中间。
|
||||
- 原因:`MainWindow.__init__()` 当前只 `resize(1180, 760)`,没有读取 `QApplication.primaryScreen().availableGeometry()` 限制初始尺寸、居中显示,也没有保证窗口 frame/title bar 完整落在可用屏幕内。
|
||||
- 文档:`docs/06-tasks.md` 新增 T-541;`docs/troubleshooting.md` 增加临时处理(`Alt+Space` → `M` → 方向键移动)和正式修复方案;`docs/packaging.md` 打包验收清单新增标题栏完整可见/可拖动检查;`docs/current-state.md` 记录 T-541 为下一步优先修复。
|
||||
- 方案:后续代码在主窗口启动时根据屏幕可用区域限制尺寸并居中,保证标题栏可见;小屏继续适配,不保存坏的历史窗口坐标。
|
||||
- 边界:本轮只更新文档,未改 GUI 代码、打包脚本、业务逻辑、DB、AI、Excel、CDP 或 Shopee 流程。
|
||||
|
||||
## 【2026-07-07】T-541 完成 · 打包版主窗口初始位置与小屏适配
|
||||
|
||||
- 状态:DONE
|
||||
- 代码:`app/gui/main_window.py` 新增 `_fit_and_center_window()`,启动时读取 `QApplication.primaryScreen().availableGeometry()`,将主窗口尺寸限制在可用屏幕范围内并居中;首次 `showEvent` 后再应用一次,避免打包版在 Windows 10 VM/小分辨率环境标题栏出屏。
|
||||
- 文档:`docs/06-tasks.md` 将 T-541 标记为 DONE;`docs/troubleshooting.md`、`docs/packaging.md`、`docs/current-state.md` 更新为当前修复与打包验收口径。
|
||||
- 测试:`tests/test_gui.py` 新增小屏和大屏 mock geometry 单测,覆盖尺寸限制与居中位置;`scripts/build_exe.ps1` 已重新构建 release,确认 T-541 合入后仍可打包。
|
||||
- 边界:不保存/恢复窗口历史坐标;未改业务流程、DB、AI、Excel、CDP/Shopee、打包产物结构或用户数据路径。
|
||||
|
||||
## 【2026-07-07】T-540 修正 · 当前打包布局统一为 PyInstaller 6
|
||||
|
||||
- 现象:T-540 代码和 `requirements-build.txt` 已固定 PyInstaller 6.11.1,构建脚本也会校验 `_internal/`,但 `docs/packaging.md`、`docs/03-tech-stack.md`、`docs/06-tasks.md`、`docs/current-state.md` 仍有当前 PyInstaller 5.13.2 扁平 onedir 的表述。
|
||||
- 文档:已把当前打包口径统一为 PyInstaller 6.11.1,产物必须包含 `cmshopee.exe` + `_internal/`;用户升级说明改为整体覆盖新版程序文件、`_internal/`、`version.txt`、`README.txt`,并保留旧目录 `data/`。
|
||||
- 边界:只修正文档一致性;历史 T-538 流水仍保留当时 PyInstaller 5 的记录;未改业务流程、DB、AI、Excel、CDP/Shopee 或用户数据路径。
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
-r requirements.txt
|
||||
pyinstaller==5.13.2
|
||||
pyinstaller==6.11.1
|
||||
|
||||
+127
-16
@@ -1,20 +1,99 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
$repoRoot = Resolve-Path (Join-Path $scriptDir "..")
|
||||
$repoRoot = (Resolve-Path (Join-Path $scriptDir "..")).Path
|
||||
Set-Location $repoRoot
|
||||
|
||||
$pythonVersion = python -c "import sys; print('%d.%d.%d' % sys.version_info[:3])"
|
||||
Write-Host "Using Python: $pythonVersion"
|
||||
if ([version]$pythonVersion -lt [version]"3.10.0") {
|
||||
Write-Warning "Project target is Python 3.10+. Switch to a 3.10+ environment before official release builds."
|
||||
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 ', ')"
|
||||
}
|
||||
}
|
||||
|
||||
$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"
|
||||
}
|
||||
|
||||
python -m PyInstaller --noconfirm --clean $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"
|
||||
}
|
||||
|
||||
$distDir = Join-Path $repoRoot "dist\cmshopee"
|
||||
$exePath = Join-Path $distDir "cmshopee.exe"
|
||||
@@ -37,19 +116,51 @@ $excluded = @(
|
||||
"data"
|
||||
)
|
||||
|
||||
$found = @()
|
||||
foreach ($relative in $excluded) {
|
||||
$candidate = Join-Path $distDir $relative
|
||||
if (Test-Path -LiteralPath $candidate) {
|
||||
$found += $relative
|
||||
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 = "$appCodeName-$appVersion"
|
||||
$releaseDir = Join-Path $releaseRoot $releaseName
|
||||
$portableZip = Join-Path $releaseRoot "$releaseName-portable.zip"
|
||||
|
||||
foreach ($path in @($releaseDir, $portableZip)) {
|
||||
Assert-InProject -Path $path
|
||||
if (Test-Path -LiteralPath $path) {
|
||||
Remove-Item -LiteralPath $path -Recurse -Force
|
||||
}
|
||||
}
|
||||
|
||||
if ($found.Count -gt 0) {
|
||||
throw "Build output contains local user data: $($found -join ', ')"
|
||||
}
|
||||
New-Item -ItemType Directory -Force -Path $releaseDir | Out-Null
|
||||
Get-ChildItem -LiteralPath $distDir -Force | Copy-Item -Destination $releaseDir -Recurse -Force
|
||||
Set-Content -LiteralPath (Join-Path $releaseDir "version.txt") -Value $appVersion -Encoding ascii -NoNewline
|
||||
|
||||
Write-Host "Build complete: $exePath"
|
||||
Write-Host "Do not copy local data into the release package."
|
||||
$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
|
||||
Compress-Archive -Path (Join-Path $releaseDir "*") -DestinationPath $portableZip -Force
|
||||
|
||||
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=")
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -157,7 +157,7 @@ class GuiTests(TempDirMixin, unittest.TestCase):
|
||||
window = MainWindow(config=self.make_config(temp_dir))
|
||||
self.addCleanup(window.close)
|
||||
|
||||
self.assertEqual("蝦皮圈優化助手", window.windowTitle())
|
||||
self.assertEqual(gui.display_name(), window.windowTitle())
|
||||
self.assertEqual(5, window.tabs.count())
|
||||
self.assertEqual(
|
||||
TAB_TITLES,
|
||||
|
||||
+37
-1
@@ -1,6 +1,7 @@
|
||||
import unittest
|
||||
|
||||
from _helpers import REPO_ROOT
|
||||
from app import version
|
||||
|
||||
|
||||
class PackagingTests(unittest.TestCase):
|
||||
@@ -43,8 +44,43 @@ class PackagingTests(unittest.TestCase):
|
||||
):
|
||||
self.assertIn(token, script)
|
||||
|
||||
self.assertIn("python -m PyInstaller --noconfirm --clean", script)
|
||||
self.assertIn('$pythonExe = "py"', script)
|
||||
self.assertIn('$pythonArgs = @("-3.10")', script)
|
||||
self.assertIn("& $pythonExe @pythonArgs -m PyInstaller --noconfirm --clean", script)
|
||||
self.assertIn("cmshopee.spec", script)
|
||||
self.assertIn("Decode-Utf8Base64", script)
|
||||
|
||||
def test_release_version_source_is_single_place(self):
|
||||
version_py = self.read_text("app/version.py")
|
||||
|
||||
self.assertIn('APP_NAME = "蝦皮圈優化助手"', version_py)
|
||||
self.assertIn('APP_CODE_NAME = "cmshopee"', version_py)
|
||||
self.assertRegex(version.APP_VERSION, r"^\d+\.\d+\.\d+$")
|
||||
self.assertEqual(
|
||||
f"{version.APP_NAME} v{version.APP_VERSION}",
|
||||
version.display_name(),
|
||||
)
|
||||
|
||||
def test_build_script_assembles_versioned_release_package(self):
|
||||
script = self.read_text("scripts/build_exe.ps1")
|
||||
|
||||
self.assertIn('Read-VersionValue -Path $versionFile -Name "APP_VERSION"', script)
|
||||
self.assertIn('$pythonExe = "py"', script)
|
||||
self.assertIn('$pythonArgs = @("-3.10")', script)
|
||||
self.assertIn(
|
||||
'Decode-Utf8Base64 "5pyq5om+5YiwIFB5dGhvbiAzLjEw44CC6K+35YWI5a6J6KOFIFB5dGhvbiAzLjEw77yM5bm256Gu6K6kIFdpbmRvd3MgUHl0aG9uIExhdW5jaGVyIOWPr+mAmui/hyBweSAtMy4xMCDosIPnlKjjgII="',
|
||||
script,
|
||||
)
|
||||
self.assertIn('$pythonVersion.StartsWith("3.10.")', script)
|
||||
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('version.txt', script)
|
||||
self.assertIn('README.txt', script)
|
||||
self.assertIn('Compress-Archive', script)
|
||||
self.assertIn('Assert-NoExcludedData -Root $releaseDir', script)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user