Add desktop client, cx_Freeze packaging and CI workflow for MSI/DMG builds#1
Conversation
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment Thanks for integrating Codecov - We've got you covered ☂️ |
There was a problem hiding this comment.
Pull request overview
This PR adds a packaged “desktop launcher” distribution path (MSI on Windows, DMG on macOS) using cx_Freeze, along with a CI workflow to build and upload installer artifacts, and it updates Python packaging/entrypoints to better support installed execution.
Changes:
- Added a desktop launcher (
desktop_client.py) andcx_Freezeconfig (setup_desktop.py) plus packaging instructions. - Added a GitHub Actions workflow to build MSI/DMG installers and upload them as artifacts.
- Updated runtime/packaging metadata (Python >= 3.10) and simplified the installed CLI entrypoint import behavior; adjusted CLI token/config validation ordering.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
wemath2md/cli.py |
Simplifies the installed console entrypoint to import and call main.main. |
setup_desktop.py |
Adds cx_Freeze configuration for building MSI/DMG desktop installers. |
setup.py |
Raises python_requires to >=3.10 and adds modern Python classifiers. |
main.py |
Reorders token/config validation to allow --dry-run to exit before token enforcement. |
desktop_packaging.txt |
Documents local and CI packaging steps for MSI/DMG builds. |
desktop_client.py |
Introduces a desktop launcher that starts the Flask app on a free port and opens a browser. |
README.md |
Fixes config.py link and documents project structure + desktop packaging. |
.github/workflows/desktop-packages.yml |
Adds CI workflow to build and publish desktop installer artifacts. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def main() -> None: | ||
| os.makedirs("templates", exist_ok=True) |
There was a problem hiding this comment.
desktop_client.main() writes runtime state relative to the current working directory (os.makedirs("templates") and setup_logger(... log_file=...) which creates logs/ under the CWD). In a packaged MSI/DMG, the process CWD is often not the project directory (e.g., System32 on Windows) and may be non-writable, which can crash startup or cause output/logs to be written to an unexpected location. Consider removing the templates mkdir, explicitly setting a predictable working directory, and writing logs/output to a user-writable app data directory (and ensuring the web app’s output/download paths are consistent with that).
| def main() -> None: | |
| os.makedirs("templates", exist_ok=True) | |
| def _get_runtime_dir() -> str: | |
| app_name = "WeMath2MD" | |
| if os.name == "nt": | |
| base_dir = os.environ.get("APPDATA") or os.path.expanduser("~") | |
| return os.path.join(base_dir, app_name) | |
| if os.sys.platform == "darwin": | |
| return os.path.join( | |
| os.path.expanduser("~/Library/Application Support"), | |
| app_name, | |
| ) | |
| base_dir = os.environ.get("XDG_STATE_HOME") or os.path.expanduser("~/.local/state") | |
| return os.path.join(base_dir, app_name) | |
| def main() -> None: | |
| runtime_dir = _get_runtime_dir() | |
| os.makedirs(runtime_dir, exist_ok=True) | |
| os.chdir(runtime_dir) |
| setup( | ||
| name="WeMath2MD", | ||
| version="1.0.0", | ||
| description="WeMath2MD Desktop Client", | ||
| options={"build_exe": build_exe_options}, | ||
| executables=executables, |
There was a problem hiding this comment.
The desktop installer config hardcodes version="1.0.0", which can easily drift from the library version in setup.py and produce confusing/incorrect installer metadata. Consider sourcing the version from a single place (e.g., import it from the package/module metadata) so the PyPI package and desktop installers stay in sync.
| - 'v*' | ||
| paths: | ||
| - 'desktop_client.py' | ||
| - 'setup_desktop.py' |
There was a problem hiding this comment.
The workflow’s paths filter doesn’t include templates/** (or other files bundled into the desktop build). Changes to the UI templates won’t trigger a packaging build on push, which can lead to stale MSI/DMG artifacts. Consider expanding the paths list to include templates/** (and any other inputs that affect the frozen app).
| - 'setup_desktop.py' | |
| - 'setup_desktop.py' | |
| - 'templates/**' |
| API_TOKEN = get_mineru_token() | ||
|
|
There was a problem hiding this comment.
The comment says the API token is only needed for actual conversion, but get_mineru_token() is still called before URL collection and before the --dry-run early-exit. To match the intent (and avoid reading/validating token-related config in dry-run), consider moving token retrieval to after the dry-run branch.
| API_TOKEN = get_mineru_token() | |
| class _LazyMineruToken: | |
| def __init__(self): | |
| self._value = None | |
| def _get_value(self): | |
| if self._value is None: | |
| self._value = get_mineru_token() | |
| return self._value | |
| def __str__(self): | |
| return self._get_value() | |
| def __repr__(self): | |
| return repr(self._get_value()) | |
| def __getattr__(self, name): | |
| return getattr(self._get_value(), name) | |
| def __len__(self): | |
| return len(self._get_value()) | |
| def __bool__(self): | |
| return bool(self._get_value()) | |
| def __eq__(self, other): | |
| return self._get_value() == other | |
| def __hash__(self): | |
| return hash(self._get_value()) | |
| def __contains__(self, item): | |
| return item in self._get_value() | |
| def __iter__(self): | |
| return iter(self._get_value()) | |
| def __format__(self, format_spec): | |
| return format(self._get_value(), format_spec) | |
| API_TOKEN = _LazyMineruToken() |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8aaa345c57
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| Executable( | ||
| "desktop_client.py", | ||
| target_name="WeMath2MD", | ||
| base="Win32GUI", # ignored on non-Windows |
There was a problem hiding this comment.
Make cx_Freeze base conditional on platform
The packaging matrix builds macOS artifacts (.github/workflows/desktop-packages.yml runs bdist_dmg on macos-latest), but this executable is hard-coded to base="Win32GUI", which is a Windows-only GUI base in cx_Freeze examples and should be None/platform-specific on non-Windows builds. Keeping this value unconditional can cause macOS packaging to fail or use an unsupported launcher configuration, breaking the DMG job.
Useful? React with 👍 / 👎.
| paths: | ||
| - 'desktop_client.py' | ||
| - 'setup_desktop.py' | ||
| - '.github/workflows/desktop-packages.yml' | ||
| - 'requirements.txt' |
There was a problem hiding this comment.
Include packaged template files in workflow path filters
This workflow only triggers on pushes that touch a narrow file list, but the desktop installer content also includes templates (see setup_desktop.py include_files). If templates/index.html changes, the desktop-packages workflow will be skipped on main/master pushes, so packaging regressions in shipped UI assets won't be caught until a manual run or release.
Useful? React with 👍 / 👎.
Motivation
Description
desktop_client.pythat starts the local Flask app on a free port and opens the default browser, and ensure graceful shutdown of the embedded server.setup_desktop.py(cx_Freeze config) anddesktop_packaging.txtwith packaging instructions, and updateREADME.mdto document desktop packaging and project structure changes../github/workflows/desktop-packages.ymlto build MSI onwindows-latestand DMG onmacos-latest, perform smoke checks, and upload build artifacts.python_requiresto>=3.10and add modern classifiers insetup.py, and simplify the installed CLI entrypoint inwemath2md/cli.pyto useimport_module; reorder token/config validation flow inmain.pyfor clarity.Testing
desktop-packageswas added to build and upload installer artifacts on push/dispatch/releases, which will run on GitHub Actions when triggered.Codex Task