Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c300a1a291 | ||
|
|
bdfaa22b0c | ||
|
|
47e5ec279c | ||
|
|
093b83737e |
@@ -580,7 +580,7 @@ def patch_vite_config(
|
|||||||
|
|
||||||
# Check if content actually changed
|
# Check if content actually changed
|
||||||
if content == original_content:
|
if content == original_content:
|
||||||
print(f"⚠️ Skipping {path} (no changes needed)")
|
print(f"ℹ️ Skipping {path} (no changes needed)")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if dry_run:
|
if dry_run:
|
||||||
@@ -618,7 +618,7 @@ def patch_frontend_health_check(frontend_dir: Path, dry_run: bool = False) -> bo
|
|||||||
target_file = app_vue
|
target_file = app_vue
|
||||||
|
|
||||||
if target_file is None:
|
if target_file is None:
|
||||||
print("⚠️ No Vue file found to patch, skipping frontend health check")
|
print("ℹ️ No Vue file found to patch, skipping frontend health check")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
original_content = target_file.read_text("UTF-8")
|
original_content = target_file.read_text("UTF-8")
|
||||||
@@ -663,7 +663,7 @@ def patch_frontend_health_check(frontend_dir: Path, dry_run: bool = False) -> bo
|
|||||||
else:
|
else:
|
||||||
# Minimal App.vue - only patch if it contains the default welcome message
|
# Minimal App.vue - only patch if it contains the default welcome message
|
||||||
if "<h1>You did it!</h1>" not in content:
|
if "<h1>You did it!</h1>" not in content:
|
||||||
print(f"⚠️ Skipping {target_file} (not a default Vue template)")
|
print(f"ℹ️ Skipping {target_file} (not a default Vue template)")
|
||||||
return False
|
return False
|
||||||
# Insert before the </p> tag
|
# Insert before the </p> tag
|
||||||
template_end = content.find("</template>")
|
template_end = content.find("</template>")
|
||||||
@@ -680,7 +680,7 @@ def patch_frontend_health_check(frontend_dir: Path, dry_run: bool = False) -> bo
|
|||||||
|
|
||||||
# Check if content actually changed
|
# Check if content actually changed
|
||||||
if content == original_content:
|
if content == original_content:
|
||||||
print(f"⚠️ Skipping {target_file} (no changes needed)")
|
print(f"ℹ️ Skipping {target_file} (no changes needed)")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if dry_run:
|
if dry_run:
|
||||||
@@ -714,7 +714,7 @@ def write_file(
|
|||||||
"""
|
"""
|
||||||
exists = path.exists()
|
exists = path.exists()
|
||||||
if exists and not overwrite:
|
if exists and not overwrite:
|
||||||
print(f"ℹ️ Skipping {path} (exists)")
|
print(f"⚠️ Skipping {path} (exists)")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if content is the same
|
# Check if content is the same
|
||||||
@@ -1163,7 +1163,9 @@ def cmd_setup(args: argparse.Namespace) -> int:
|
|||||||
overwrite=False,
|
overwrite=False,
|
||||||
dry_run=dry_run,
|
dry_run=dry_run,
|
||||||
)
|
)
|
||||||
# else: no file but has existing entrypoint - don't create (user has custom CLI setup)
|
else:
|
||||||
|
# No file but has existing entrypoint - don't create (user has custom CLI setup)
|
||||||
|
print(f"ℹ️ Skipping __main__.py (package already has CLI: {existing_cli})")
|
||||||
|
|
||||||
# === Update vite.config.js/ts ===
|
# === Update vite.config.js/ts ===
|
||||||
frontend_dir = project_dir / "frontend"
|
frontend_dir = project_dir / "frontend"
|
||||||
|
|||||||
@@ -4,13 +4,11 @@
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import asyncio
|
import asyncio
|
||||||
import contextlib
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from contextlib import suppress
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi_vue import server
|
|
||||||
|
|
||||||
# Import util.py from scripts/fastapi-vue (not a package, so we adjust sys.path)
|
# Import util.py from scripts/fastapi-vue (not a package, so we adjust sys.path)
|
||||||
sys.path.insert(0, str(Path(__file__).with_name("fastapi-vue")))
|
sys.path.insert(0, str(Path(__file__).with_name("fastapi-vue")))
|
||||||
from devutil import ( # type: ignore
|
from devutil import ( # type: ignore
|
||||||
@@ -34,7 +32,7 @@ async def run_devserver(frontend: str, backend: str) -> None:
|
|||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
viteurl, npm_install, vite = setup_vite(frontend, DEFAULT_VITE_PORT)
|
viteurl, npm_install, vite = setup_vite(frontend, DEFAULT_VITE_PORT)
|
||||||
backurl, module, backend_config = setup_fastapi(
|
backurl, uvicorn = setup_fastapi(
|
||||||
backend, "MODULE_NAME.APP_MODULE:APP_VAR", DEFAULT_DEV_PORT
|
backend, "MODULE_NAME.APP_MODULE:APP_VAR", DEFAULT_DEV_PORT
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -45,15 +43,8 @@ async def run_devserver(frontend: str, backend: str) -> None:
|
|||||||
async with ProcessGroup() as pg:
|
async with ProcessGroup() as pg:
|
||||||
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(*uvicorn)
|
||||||
# Run backend in a thread (server.run is blocking)
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.run_in_executor(None, lambda: server.run(module, **backend_config))
|
|
||||||
|
|
||||||
# Wait for both install and backend to be ready
|
|
||||||
await pg.wait(npm_i, ready(backurl, path="/api/health?from=devserver.py"))
|
await pg.wait(npm_i, ready(backurl, path="/api/health?from=devserver.py"))
|
||||||
|
|
||||||
# Start Vite dev server (ProcessGroup waits for any exit, then terminates others)
|
|
||||||
await pg.spawn(*vite, cwd=front)
|
await pg.spawn(*vite, cwd=front)
|
||||||
|
|
||||||
|
|
||||||
@@ -75,7 +66,7 @@ def main():
|
|||||||
help=f"FastAPI backend endpoint (default: localhost:{DEFAULT_DEV_PORT})",
|
help=f"FastAPI backend endpoint (default: localhost:{DEFAULT_DEV_PORT})",
|
||||||
)
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
with contextlib.suppress(KeyboardInterrupt):
|
with suppress(KeyboardInterrupt):
|
||||||
asyncio.run(run_devserver(args.frontend, args.backend))
|
asyncio.run(run_devserver(args.frontend, args.backend))
|
||||||
|
|
||||||
|
|
||||||
@@ -84,7 +75,8 @@ HELP_EPILOG = """
|
|||||||
scripts/devserver.py 3000 # Vite on localhost:3000
|
scripts/devserver.py 3000 # Vite on localhost:3000
|
||||||
scripts/devserver.py :3000 --backend 8000 # *:3000, localhost:8000
|
scripts/devserver.py :3000 --backend 8000 # *:3000, localhost:8000
|
||||||
|
|
||||||
JS_RUNTIME environment variable can be used to select the JS runtime
|
JS_RUNTIME environment variable can be used to select the JS runtime:
|
||||||
|
npm, deno, bun, or full path to the runtime executable (node maps to npm).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ def build(folder: str = "frontend") -> None:
|
|||||||
raise SystemExit(1)
|
raise SystemExit(1)
|
||||||
|
|
||||||
def run(cmd):
|
def run(cmd):
|
||||||
display_cmd = [Path(cmd[0]).name, *cmd[1:]]
|
display_cmd = [Path(cmd[0]).stem, *cmd[1:]]
|
||||||
logger.info("### %s", " ".join(display_cmd))
|
logger.info("### %s", " ".join(display_cmd))
|
||||||
subprocess.run(cmd, check=True, cwd=folder)
|
subprocess.run(cmd, check=True, cwd=folder)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
from collections.abc import Coroutine
|
from collections.abc import Coroutine
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -23,7 +24,7 @@ class ProcessGroup:
|
|||||||
self, *cmd: str, cwd: str | None = None
|
self, *cmd: str, cwd: str | None = None
|
||||||
) -> asyncio.subprocess.Process:
|
) -> asyncio.subprocess.Process:
|
||||||
"""Spawn a subprocess and track it."""
|
"""Spawn a subprocess and track it."""
|
||||||
cmd_name = Path(cmd[0]).name
|
cmd_name = Path(cmd[0]).stem
|
||||||
logger.info(">>> %s", " ".join([cmd_name, *cmd[1:]]))
|
logger.info(">>> %s", " ".join([cmd_name, *cmd[1:]]))
|
||||||
proc = await asyncio.create_subprocess_exec(*cmd, cwd=cwd)
|
proc = await asyncio.create_subprocess_exec(*cmd, cwd=cwd)
|
||||||
self._procs.append(proc)
|
self._procs.append(proc)
|
||||||
@@ -159,10 +160,10 @@ def setup_vite(
|
|||||||
|
|
||||||
def setup_fastapi(
|
def setup_fastapi(
|
||||||
endpoint: str, module: str, default_port: int = 8000
|
endpoint: str, module: str, default_port: int = 8000
|
||||||
) -> tuple[str, str, dict]:
|
) -> tuple[str, list[str]]:
|
||||||
"""Parse backend endpoint and build server.run() config.
|
"""Parse backend endpoint and build uvicorn command.
|
||||||
|
|
||||||
Returns (url, module, config_dict).
|
Returns (url, uvicorn_cmd).
|
||||||
Raises SystemExit(1) on invalid config.
|
Raises SystemExit(1) on invalid config.
|
||||||
"""
|
"""
|
||||||
endpoints = parse_endpoint(endpoint, default_port)
|
endpoints = parse_endpoint(endpoint, default_port)
|
||||||
@@ -173,11 +174,17 @@ def setup_fastapi(
|
|||||||
|
|
||||||
host = endpoints[0]["host"]
|
host = endpoints[0]["host"]
|
||||||
port = endpoints[0]["port"]
|
port = endpoints[0]["port"]
|
||||||
|
reload_dir = module.split(".")[0] # Don't reload on frontend changes
|
||||||
|
|
||||||
config = {
|
cmd = [
|
||||||
"listen": f"{host}:{port}",
|
sys.executable,
|
||||||
"reload": True,
|
"-m",
|
||||||
"reload_dirs": [module.split(".")[0]], # Don't reload on frontend changes
|
"uvicorn",
|
||||||
"forwarded_allow_ips": "*",
|
module,
|
||||||
}
|
f"--host={host}",
|
||||||
return f"http://{host}:{port}", module, config
|
f"--port={port}",
|
||||||
|
"--reload",
|
||||||
|
f"--reload-dir={reload_dir}",
|
||||||
|
"--forwarded-allow-ips=*",
|
||||||
|
]
|
||||||
|
return f"http://{host}:{port}", cmd
|
||||||
|
|||||||
Reference in New Issue
Block a user