feat: build.ps1 produces Launcher.exe + portable layout

- build a lean onefile Launcher.exe from src/launcher.py (stdlib-only, no
  PySide6/Pillow) alongside the onedir app build
- release dir is now the portable layout: Launcher.exe + app\ (CMBot.exe +
  config + version.txt)
- emit two zips: CMBot-<ver>.zip (self-update payload = app\ contents, what
  manifest.url points to) and CMBot-<ver>-portable.zip (initial install)
- launcher: harden logging setup for --windowed builds / read-only roots

Actual PyInstaller build must run on Windows; script is syntax-verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 10:56:53 +08:00
co-authored by Claude Opus 4.8
parent 1d8e4fc240
commit ac428fb5ec
3 changed files with 81 additions and 38 deletions
+19 -8
View File
@@ -214,16 +214,27 @@ def main(argv=None):
args = parser.parse_args(argv)
root = Path(args.install_root) if args.install_root else _default_install_root()
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(message)s",
handlers=[
logging.StreamHandler(),
logging.FileHandler(str(root / "launcher.log"), encoding="utf-8"),
],
)
_setup_logging(root)
return run(root, no_launch=args.no_launch)
def _setup_logging(root):
"""Configure logging defensively for both console and --windowed builds.
A PyInstaller --windowed exe has no stdout/stderr (StreamHandler(None) would
fail), and a read-only install root makes the file handler fail. Add each
handler only when it can be created so the launcher never crashes on logging.
"""
handlers = []
if sys.stderr is not None:
handlers.append(logging.StreamHandler())
try:
handlers.append(logging.FileHandler(str(root / "launcher.log"), encoding="utf-8"))
except OSError:
pass
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s",
handlers=handlers)
if __name__ == "__main__":
sys.exit(main())