Believe me I can fly

This is a python script that start a frontend server and auto open browser (requires Python3.8+):

#!/usr/bin/env python3
"""前端服务启动脚本(会自动打开浏览器)

Usage::
    $ python <me>.py  # 如果上一次打开浏览器距离现在超过一天,就自动打开浏览器

Or:
    $ python <me>.py --local  # 指定采用本地的后端服务

Or:
    $ python <me>.py --open  # 不管上一次打开浏览器是什么时候,都再次打开浏览器
"""

from __future__ import annotations

import contextlib
import multiprocessing
import os
import platform
import shlex
import subprocess
import sys
import time
from datetime import datetime, timedelta
from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from multiprocessing.managers import DictProxy

try:
    from loguru import logger
except ImportError:
    import logging

    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger(__name__)  # type:ignore[assignment]

CMD = "npm run dev:api"
__version__ = "0.2.0"
__updated_at__ = "2025.08.04"


def open_chrome(comm_dict: DictProxy[str, bool] | None = None) -> None:
    open_browser = "explorer" if platform.system() == "Windows" else "open"
    frontend_url = "http://localhost:3000"
    seconds = 5  # 延迟5秒等待npm那边启动
    for _ in range(seconds * 10):
        time.sleep(0.1)
        if comm_dict is not None and comm_dict.get("stop"):
            # 如果npm run xxx命令启动失败了,直接退出
            logger.debug("Skip browser open as explicit asked to.")
            return
    logger.info(f"Going to open {frontend_url=}")
    subprocess.run([open_browser, frontend_url])


def run_and_echo(
    cmd: str, env: dict[str, str] | None = None, verbose: bool = True
) -> int:
    if verbose:
        print("-->", cmd)
    if env is not None:
        env = dict(os.environ, **env)
    r = subprocess.run(shlex.split(cmd), env=env)
    return r.returncode


def get_acceptable_delta_time(hided_file: Path, fmt: str) -> timedelta | None:
    if "--open" in sys.argv or not hided_file.exists():
        return None
    delta_time = datetime.now() - datetime.strptime(hided_file.read_text(), fmt)
    # 距离上一次启动,已经超过一天的话,就自动打开浏览器
    if delta_time.days >= 1:
        return None
    return delta_time


def run_in_manager(comm_dict: DictProxy[str, bool]) -> None:
    env = None
    if "--local" in sys.argv:
        env = {"VITE_BACKEND_BASE_URL": "http://localhost:9999"}
    hided_file = Path(__file__).parent / ".last_open.txt.bak"
    fmt = "%Y-%m-%d %H:%M:%S"
    process = None
    with contextlib.suppress(ValueError):
        if delta_time := get_acceptable_delta_time(hided_file, fmt):
            logger.info(f"No need to open browser as {delta_time = }")
        else:
            # 启动子进程去打开浏览器,以免阻塞主进程
            process = multiprocessing.Process(target=open_chrome, args=(comm_dict,))
            process.start()
    try:
        rc = run_and_echo(CMD, env=env)
    except KeyboardInterrupt:
        hided_file.write_text(f"{datetime.now():{fmt}}")
        raise
    if rc != 0:
        if process is not None and process.is_alive():
            comm_dict["stop"] = True
            process.terminate()
            process.join()
        sys.exit(1)


def main() -> None:
    if sys.argv[1:] and sys.argv[1] in ("-h", "--help"):
        print(__doc__.replace("<me>", Path(__file__).stem))
        return
    me = Path(__file__).resolve().as_posix()
    if me.startswith("/Users/mac10.12/coding/"):
        dst = me.replace("/coding/", "/frontend/")
        os.chdir(Path(dst).parent)
        logger.info(f"Using {dst} instead~")
        subprocess.run(["python", dst])
        return
    run_and_echo("git pull")
    with multiprocessing.Manager() as pm:
        comm_dict = pm.dict()
        run_in_manager(comm_dict)


if __name__ == "__main__":
    main()
posted @ 2025-08-04 22:24  waketzheng  阅读(5)  评论(0)    收藏  举报