- 通过 127.0.0.1 打开 Tasks,避免受到 localhost 旧 Service Worker 和缓存影响 - 更新 Windows 开发服务器文档,使其与实际生成的访问地址一致
187 lines
5.5 KiB
Python
187 lines
5.5 KiB
Python
"""Tasks 项目的 Windows 本地开发服务器控制脚本。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import subprocess
|
||
import sys
|
||
import time
|
||
import webbrowser
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parent
|
||
SOURCE_DIR = ROOT / "src"
|
||
DEFAULT_PORT = 8000
|
||
HOST = "127.0.0.1"
|
||
REQUIRED_PATHS = (
|
||
SOURCE_DIR / "index.html",
|
||
SOURCE_DIR / "css",
|
||
SOURCE_DIR / "js",
|
||
)
|
||
|
||
|
||
def listener_pid(port: int) -> int | None:
|
||
command = (
|
||
f"Get-NetTCPConnection -LocalPort {port} -State Listen "
|
||
"-ErrorAction SilentlyContinue | "
|
||
"Select-Object -First 1 -ExpandProperty OwningProcess"
|
||
)
|
||
result = subprocess.run(
|
||
["powershell", "-NoProfile", "-Command", command],
|
||
capture_output=True,
|
||
text=True,
|
||
check=False,
|
||
)
|
||
try:
|
||
return int(result.stdout.strip())
|
||
except ValueError:
|
||
return None
|
||
|
||
|
||
def command_line(pid: int) -> str:
|
||
command = f"(Get-CimInstance Win32_Process -Filter 'ProcessId = {pid}').CommandLine"
|
||
result = subprocess.run(
|
||
["powershell", "-NoProfile", "-Command", command],
|
||
capture_output=True,
|
||
text=True,
|
||
check=False,
|
||
)
|
||
return result.stdout.strip()
|
||
|
||
|
||
def is_tasks_server(pid: int, port: int) -> bool:
|
||
command = command_line(pid).lower()
|
||
return (
|
||
"-m http.server" in command
|
||
and str(SOURCE_DIR).lower() in command
|
||
and str(port) in command
|
||
)
|
||
|
||
|
||
def terminate_process(pid: int) -> bool:
|
||
result = subprocess.run(
|
||
["taskkill", "/PID", str(pid), "/T", "/F"],
|
||
stdout=subprocess.DEVNULL,
|
||
stderr=subprocess.DEVNULL,
|
||
check=False,
|
||
)
|
||
return result.returncode == 0
|
||
|
||
|
||
def validate_project() -> bool:
|
||
missing = [path.relative_to(ROOT) for path in REQUIRED_PATHS if not path.exists()]
|
||
if not missing:
|
||
return True
|
||
|
||
print("Tasks 项目文件不完整,缺少:")
|
||
for path in missing:
|
||
print(f" - {path}")
|
||
return False
|
||
|
||
|
||
def server_url(port: int) -> str:
|
||
return f"http://{HOST}:{port}/"
|
||
|
||
|
||
def open_browser(port: int) -> None:
|
||
url = server_url(port)
|
||
if not webbrowser.open(url):
|
||
print(f"无法自动打开浏览器,请手动访问:{url}")
|
||
|
||
|
||
def start(port: int) -> None:
|
||
if not validate_project():
|
||
return
|
||
|
||
listener = listener_pid(port)
|
||
if listener:
|
||
if is_tasks_server(listener, port):
|
||
print(f"Tasks 开发服务器已运行:{server_url(port)}(PID {listener})")
|
||
open_browser(port)
|
||
return
|
||
|
||
answer = input(
|
||
f"端口 {port} 已被其他程序使用(PID {listener})。"
|
||
"是否关闭该进程并强制启动?[y/N]: "
|
||
).strip().lower()
|
||
if answer not in ("y", "yes"):
|
||
print("已取消启动。")
|
||
return
|
||
if not terminate_process(listener):
|
||
print(f"无法关闭 PID {listener},请以管理员身份运行终端后重试。")
|
||
return
|
||
time.sleep(0.3)
|
||
if listener_pid(port):
|
||
print(f"端口 {port} 仍被占用,无法启动服务。")
|
||
return
|
||
print(f"已关闭 PID {listener},正在启动 Tasks 开发服务器。")
|
||
|
||
process = subprocess.Popen(
|
||
[
|
||
sys.executable,
|
||
"-m",
|
||
"http.server",
|
||
str(port),
|
||
"--bind",
|
||
HOST,
|
||
"--directory",
|
||
str(SOURCE_DIR),
|
||
],
|
||
cwd=ROOT,
|
||
creationflags=subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.DETACHED_PROCESS,
|
||
stdout=subprocess.DEVNULL,
|
||
stderr=subprocess.DEVNULL,
|
||
)
|
||
time.sleep(0.5)
|
||
if listener_pid(port) == process.pid:
|
||
print(f"Tasks 开发服务器已启动:{server_url(port)}(PID {process.pid})")
|
||
open_browser(port)
|
||
else:
|
||
print("服务启动失败,请确认 Python 可用且端口未被占用。")
|
||
|
||
|
||
def stop(port: int) -> None:
|
||
listener = listener_pid(port)
|
||
if not listener or not is_tasks_server(listener, port):
|
||
print(f"端口 {port} 上没有运行 Tasks 开发服务器。")
|
||
return
|
||
|
||
if terminate_process(listener):
|
||
print("Tasks 开发服务器已停止。")
|
||
else:
|
||
print(f"无法停止 PID {listener},请以管理员身份运行终端后重试。")
|
||
|
||
|
||
def status(port: int) -> None:
|
||
listener = listener_pid(port)
|
||
if listener and is_tasks_server(listener, port):
|
||
print(f"Tasks 开发服务器运行中:{server_url(port)}(PID {listener})")
|
||
elif listener:
|
||
print(f"端口 {port} 已被其他程序使用(PID {listener})。")
|
||
else:
|
||
print(f"端口 {port} 上没有运行 Tasks 开发服务器。")
|
||
|
||
|
||
def main() -> None:
|
||
parser = argparse.ArgumentParser(description="控制 Tasks 项目的本地开发服务器。")
|
||
parser.add_argument(
|
||
"command",
|
||
choices=("start", "stop", "status"),
|
||
help="启动、停止或查看服务器状态",
|
||
)
|
||
parser.add_argument(
|
||
"--port",
|
||
type=int,
|
||
default=DEFAULT_PORT,
|
||
help=f"HTTP 端口(默认:{DEFAULT_PORT})",
|
||
)
|
||
args = parser.parse_args()
|
||
if not 1 <= args.port <= 65535:
|
||
parser.error("端口必须在 1 到 65535 之间")
|
||
|
||
{"start": start, "stop": stop, "status": status}[args.command](args.port)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|