Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1aaf040db7 | ||
|
|
aac27da67c |
@@ -41,7 +41,7 @@ def run(
|
|||||||
conf["forwarded_allow_ips"] = proxy
|
conf["forwarded_allow_ips"] = proxy
|
||||||
conf.update(uvicorn_config)
|
conf.update(uvicorn_config)
|
||||||
|
|
||||||
with suppress(KeyboardInterrupt):
|
with suppress(KeyboardInterrupt, asyncio.CancelledError):
|
||||||
if reload or workers:
|
if reload or workers:
|
||||||
serve_multiprocess(endpoints, **conf)
|
serve_multiprocess(endpoints, **conf)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -110,6 +110,9 @@ def uv_add_packages(
|
|||||||
# If vite == dev, dev is incremented by 100
|
# If vite == dev, dev is incremented by 100
|
||||||
DEFAULT_PORTS = (3100, 3100, 3200)
|
DEFAULT_PORTS = (3100, 3100, 3200)
|
||||||
|
|
||||||
|
# Default health check path for devserver backend readiness check
|
||||||
|
DEFAULT_HEALTH = "/api/health?from=devserver.py"
|
||||||
|
|
||||||
# Marker comment indicating file can be auto-upgraded
|
# Marker comment indicating file can be auto-upgraded
|
||||||
# Users should remove this line to prevent automatic updates
|
# Users should remove this line to prevent automatic updates
|
||||||
UPGRADE_MARKER = "auto-upgrade@fastapi-vue-setup"
|
UPGRADE_MARKER = "auto-upgrade@fastapi-vue-setup"
|
||||||
@@ -300,6 +303,29 @@ def extract_existing_ports(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Sentinel for "not found" in extract_existing_health
|
||||||
|
_HEALTH_NOT_FOUND = object()
|
||||||
|
|
||||||
|
|
||||||
|
def extract_existing_health(project_dir: Path) -> str | object:
|
||||||
|
"""Extract existing health path configuration from devserver.py.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
- The path string (may be empty to disable)
|
||||||
|
- _HEALTH_NOT_FOUND sentinel if not found or file doesn't exist
|
||||||
|
"""
|
||||||
|
devserver_file = project_dir / "scripts" / "devserver.py"
|
||||||
|
if not devserver_file.exists():
|
||||||
|
return _HEALTH_NOT_FOUND
|
||||||
|
|
||||||
|
content = devserver_file.read_text("UTF-8")
|
||||||
|
# Match HEALTH = "/path" or HEALTH = ""
|
||||||
|
match = re.search(r'^HEALTH\s*=\s*"([^"]*)"', content, re.MULTILINE)
|
||||||
|
if match:
|
||||||
|
return match.group(1)
|
||||||
|
return _HEALTH_NOT_FOUND
|
||||||
|
|
||||||
|
|
||||||
def load_template(path: str) -> str:
|
def load_template(path: str) -> str:
|
||||||
"""Load a template file from the template directory."""
|
"""Load a template file from the template directory."""
|
||||||
return (TEMPLATE_DIR / path).read_text("UTF-8")
|
return (TEMPLATE_DIR / path).read_text("UTF-8")
|
||||||
@@ -1323,6 +1349,27 @@ def cmd_setup(args: argparse.Namespace) -> int:
|
|||||||
f"📡 Ports: default={default_port}, vite={vite_port}, dev={dev_port} {ports_note}"
|
f"📡 Ports: default={default_port}, vite={vite_port}, dev={dev_port} {ports_note}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Determine health path configuration
|
||||||
|
# Priority: --health argument > existing project value > default
|
||||||
|
if args.health is not None:
|
||||||
|
health = args.health
|
||||||
|
health_note = "(--health)" if health else "(disabled via --health)"
|
||||||
|
else:
|
||||||
|
existing_health = extract_existing_health(project_dir)
|
||||||
|
if existing_health is not _HEALTH_NOT_FOUND:
|
||||||
|
# Explicitly configured (path string, possibly empty to disable)
|
||||||
|
health = existing_health
|
||||||
|
health_note = "(kept for upgrade)"
|
||||||
|
else:
|
||||||
|
# Not found - use default
|
||||||
|
health = DEFAULT_HEALTH
|
||||||
|
health_note = "(--health to override)"
|
||||||
|
|
||||||
|
if health:
|
||||||
|
print(f"🏥 Health check: {health} {health_note}")
|
||||||
|
else:
|
||||||
|
print(f"🏥 Health check: disabled {health_note}")
|
||||||
|
|
||||||
# Title for templates
|
# Title for templates
|
||||||
project_title = module_name.replace("_", " ").title()
|
project_title = module_name.replace("_", " ").title()
|
||||||
|
|
||||||
@@ -1338,6 +1385,7 @@ def cmd_setup(args: argparse.Namespace) -> int:
|
|||||||
"TEMPLATE_DEFAULT_PORT": str(default_port),
|
"TEMPLATE_DEFAULT_PORT": str(default_port),
|
||||||
"TEMPLATE_VITE_PORT": str(vite_port),
|
"TEMPLATE_VITE_PORT": str(vite_port),
|
||||||
"TEMPLATE_DEV_PORT": str(dev_port),
|
"TEMPLATE_DEV_PORT": str(dev_port),
|
||||||
|
"TEMPLATE_HEALTH": f'"{health}"',
|
||||||
"ENVPREFIX": module_name.upper(),
|
"ENVPREFIX": module_name.upper(),
|
||||||
"PROJECT_CLI": module_name,
|
"PROJECT_CLI": module_name,
|
||||||
"MAIN_MODULE": main_module_path,
|
"MAIN_MODULE": main_module_path,
|
||||||
@@ -1635,6 +1683,11 @@ Examples:
|
|||||||
metavar="BACKEND,VITE,DEV",
|
metavar="BACKEND,VITE,DEV",
|
||||||
help="Port configuration as comma-separated values (default: 3100,3100,3200)",
|
help="Port configuration as comma-separated values (default: 3100,3100,3200)",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--health",
|
||||||
|
metavar="PATH",
|
||||||
|
help="Health check path for devserver (default: /api/health?from=devserver.py, '' to disable)",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--dry", "--dry-run", action="store_true", help="Show what would be done"
|
"--dry", "--dry-run", action="store_true", help="Show what would be done"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ from devutil import ( # type: ignore
|
|||||||
|
|
||||||
DEFAULT_VITE_PORT = TEMPLATE_VITE_PORT
|
DEFAULT_VITE_PORT = TEMPLATE_VITE_PORT
|
||||||
DEFAULT_DEV_PORT = TEMPLATE_DEV_PORT
|
DEFAULT_DEV_PORT = TEMPLATE_DEV_PORT
|
||||||
|
HEALTH = TEMPLATE_HEALTH
|
||||||
|
|
||||||
|
|
||||||
async def run_devserver(
|
async def run_devserver(
|
||||||
@@ -45,7 +46,7 @@ async def run_devserver(
|
|||||||
npm_i = await pg.spawn(*npm_install, cwd=front)
|
npm_i = await pg.spawn(*npm_install, cwd=front)
|
||||||
await check_ports_free(viteurl, backurl)
|
await check_ports_free(viteurl, backurl)
|
||||||
await pg.spawn(*MODULE_NAME, *(extra_args or []))
|
await pg.spawn(*MODULE_NAME, *(extra_args or []))
|
||||||
await pg.wait(npm_i, ready(backurl, path="/api/health?from=devserver.py"))
|
await pg.wait(npm_i, ready(backurl, path=HEALTH))
|
||||||
await pg.spawn(*vite, cwd=front)
|
await pg.spawn(*vite, cwd=front)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -111,18 +111,19 @@ async def check_ports_free(*urls: str) -> None:
|
|||||||
await asyncio.gather(*[check(client, url) for url in urls])
|
await asyncio.gather(*[check(client, url) for url in urls])
|
||||||
|
|
||||||
|
|
||||||
async def ready(url: str, path: str = "") -> None:
|
async def ready(url: str, path: str = "", max_attempts=50) -> None:
|
||||||
"""Wait for the server to be ready by polling an endpoint.
|
"""Wait for the server to be ready by polling an endpoint.
|
||||||
|
|
||||||
|
Use empty path to disable the check and make this return immediately.
|
||||||
Raises SystemExit(1) if server doesn't start in time.
|
Raises SystemExit(1) if server doesn't start in time.
|
||||||
"""
|
"""
|
||||||
max_attempts = 50
|
if not path:
|
||||||
full_url = f"{url}{path}"
|
return
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
for attempt in range(max_attempts):
|
for attempt in range(max_attempts):
|
||||||
try:
|
try:
|
||||||
await client.get(full_url, timeout=1.0)
|
await client.get(f"{url}{path}", timeout=1.0)
|
||||||
logger.info("✓ Backend ready!")
|
logger.info("✓ Backend ready!")
|
||||||
return
|
return
|
||||||
except httpx.RequestError:
|
except httpx.RequestError:
|
||||||
|
|||||||
Reference in New Issue
Block a user