Setup script cleanup. Use discovered app module and var names when creating __main__.py from template. README updates.

This commit is contained in:
2026-01-17 23:04:07 +00:00
parent 64f43df3e3
commit 2dec331afb
3 changed files with 98 additions and 92 deletions
+11 -2
View File
@@ -63,7 +63,16 @@ Options:
In development, you access the Vite dev server at `http://localhost:5173`. Vite proxies `/api/*` requests to FastAPI at port 5180. Ports and hosts of Vite and FastAPI are configurable by `devserver.py` arguments.
In production, FastAPI serves both the API and static files at `http://localhost:5080`. Configurable by `host:port` argument.
In production, FastAPI serves both the API and static files at `http://localhost:5080`. Configurable by `host:port` argument with defaults set in `__main__.py`
## Main CLI
If your project didn't already have `__main__.py`, we create one that runs the FastAPI app with richer configuration than what the FastAPI CLI offers. Running your module starts it in production mode, and optionally host:port may be given as argument to specify where it listens.
If you are running behind a reverse proxy like [Caddy](https://caddyserver.com/) on localhost, your app will trust the proxy headers it sends. However, if you need to configure another proxy host or IP, set `FORWARDED_ALLOW_IPS` env variable before running the server.
The devserver script depends on this CLI entry for running the backend. You will have to modify the `devserver.py` script if your app has its own incompatible main module. Note that we set FastAPI debug mode and Uvicorn reload when configured via `FASTAPI_VUE_BACKEND_URL` env variable (set by `devserver.py`), while for normal production use these stay disabled. The same variable also controls static files serving (disabled in dev mode).
## Vite Plugin Configuration
@@ -108,7 +117,7 @@ my-app/
└── pyproject.toml
```
The project directory tree looks roughly like this after project creation or patching. The script finds your existing fastpi app module and other files and patches them with minimal changes to enable the Vue-FastAPI interconnection. New Python and Vue projects are created automatically if none exist.
The project directory tree looks roughly like this after project creation or patching. The script finds your existing app module and other files and patches them with minimal changes to enable the Vue-FastAPI interconnection. New Python and Vue projects are created automatically if none exist.
## Development Workflow
+84 -87
View File
@@ -44,6 +44,71 @@ PYPROJECT_ADDITIONS = {
}
# Frontend instantiation block for patching existing apps
FRONTEND_BLOCK = """
# Vue Frontend static files
frontend = Frontend(
Path(__file__).with_name("frontend-build"), spa=True, cached=["/assets/"]
)
"""
# TypeScript health check script for Vue components
TS_HEALTH_CHECK_SCRIPT = """\
import { ref, onMounted } from 'vue'
const backendStatus = ref<'checking' | 'connected' | 'error'>('checking')
onMounted(async () => {
try {
const res = await fetch('/api/health')
backendStatus.value = res.ok ? 'connected' : 'error'
} catch {
backendStatus.value = 'error'
}
})
"""
# JavaScript health check script for Vue components
JS_HEALTH_CHECK_SCRIPT = """\
import { ref, onMounted } from 'vue'
const backendStatus = ref('checking')
onMounted(async () => {
try {
const res = await fetch('/api/health')
backendStatus.value = res.ok ? 'connected' : 'error'
} catch {
backendStatus.value = 'error'
}
})
"""
# Status indicator template for Vue components
STATUS_SPAN_TEMPLATE = """\
<span style="white-space: nowrap">
— FastAPI:
<span v-if="backendStatus === 'checking'">⏳</span>
<span v-else-if="backendStatus === 'connected'">✅</span>
<span v-else>❌ not reachable</span>
</span>
"""
# Setup complete message template
SETUP_COMPLETE_MESSAGE = """
Next steps:
1. Build for production:
CD_CMDuv build
2. Start development server:
CD_CMDuv run scripts/devserver.py
3. Run production server:
CD_CMDuv run SCRIPT_NAME
"""
# =============================================================================
# Utility functions
# =============================================================================
@@ -149,16 +214,6 @@ def patch_app_file(
lines = content.split("\n")
import_line = "from fastapi_vue import Frontend"
# Frontend instantiation block - use module_name for the frontend-build path
frontend_block = """
# Frontend static file server - configure options here
frontend = Frontend(
Path(__file__).parent / "frontend-build",
spa=True,
favicon="/assets/favicon",
cached=["/assets/"],
)
"""
route_line = f'frontend.route({app_var}, "/")'
# Find last import line and check if pathlib is imported
@@ -179,7 +234,7 @@ frontend = Frontend(
lines.insert(last_import_idx + 1, "from pathlib import Path")
last_import_idx += 1
lines.insert(last_import_idx + 1, import_line)
lines.insert(last_import_idx + 2, frontend_block)
lines.insert(last_import_idx + 2, FRONTEND_BLOCK)
# Append route at end
lines.append("")
@@ -343,44 +398,9 @@ def patch_frontend_health_check(frontend_dir: Path, dry_run: bool = False) -> bo
is_typescript = 'lang="ts"' in content
# Build the script content based on JS/TS
if is_typescript:
script_addition = """
import { ref, onMounted } from 'vue'
const backendStatus = ref<'checking' | 'connected' | 'error'>('checking')
onMounted(async () => {
try {
const res = await fetch('/api/health')
backendStatus.value = res.ok ? 'connected' : 'error'
} catch {
backendStatus.value = 'error'
}
})
"""
else:
script_addition = """
import { ref, onMounted } from 'vue'
const backendStatus = ref('checking')
onMounted(async () => {
try {
const res = await fetch('/api/health')
backendStatus.value = res.ok ? 'connected' : 'error'
} catch {
backendStatus.value = 'error'
}
})
"""
# Template addition - inline status indicator using emoji for colors
status_span = """<span style="white-space: nowrap">
— FastAPI:
<span v-if="backendStatus === 'checking'">⏳</span>
<span v-else-if="backendStatus === 'connected'">✅</span>
<span v-else>❌ not reachable</span>
</span>"""
script_addition = (
TS_HEALTH_CHECK_SCRIPT if is_typescript else JS_HEALTH_CHECK_SCRIPT
)
# Insert script addition before </script>
script_end_match = re.search(r"</script>", content)
@@ -396,7 +416,7 @@ onMounted(async () => {
h3_close = content.find("</h3>")
if h3_close != -1:
content = (
f"{content[:h3_close]}\n {status_span}\n {content[h3_close:]}"
f"{content[:h3_close]}\n{STATUS_SPAN_TEMPLATE} {content[h3_close:]}"
)
else:
# Minimal App.vue - insert before the last </p> before </template>
@@ -405,7 +425,9 @@ onMounted(async () => {
# Find last </p> before </template>
last_p = content.rfind("</p>", 0, template_end)
if last_p != -1:
content = f"{content[:last_p]}\n {status_span}\n {content[last_p:]}"
content = (
f"{content[:last_p]}\n{STATUS_SPAN_TEMPLATE} {content[last_p:]}"
)
target_file.write_text(content)
print(f"✅ Patched {target_file}")
@@ -625,12 +647,12 @@ def cmd_setup(args: argparse.Namespace) -> int:
if dry_run:
print("\n🏃 DRY RUN MODE - no changes will be made\n")
# Step 1: Ensure Python project exists
if not ensure_python_project(project_dir, dry_run):
# Step 1: Ensure frontend exists (do this first so cancellation doesn't leave partial setup)
if not ensure_frontend(project_dir, dry_run):
return 1
# Step 2: Ensure frontend exists
if not ensure_frontend(project_dir, dry_run):
# Step 2: Ensure Python project exists
if not ensure_python_project(project_dir, dry_run):
return 1
# Detect module name
@@ -819,18 +841,10 @@ def cmd_setup(args: argparse.Namespace) -> int:
cd_cmd = "" if project_dir == Path.cwd() else f"cd {project_dir}\n "
script_name = module_name.replace("_", "-")
print(f"""
Next steps:
1. Build for production:
{cd_cmd}uv build
2. Start development server:
{cd_cmd}uv run scripts/devserver.py
3. Run production server:
{cd_cmd}uv run {script_name}
""")
message = SETUP_COMPLETE_MESSAGE.replace("CD_CMD", cd_cmd).replace(
"SCRIPT_NAME", script_name
)
print(message)
return 0
@@ -875,7 +889,7 @@ Examples:
"project_dir",
nargs="?",
default=None,
help="Project directory (default: current directory)",
help="Project directory (use . for current directory)",
)
parser.add_argument("--module-name", help="Python module name (auto-detected)")
parser.add_argument(
@@ -884,26 +898,9 @@ Examples:
args = parser.parse_args()
# Handle default project directory with safety check
if args.project_dir is None:
cwd = Path.cwd()
if not is_uninitialized_folder(cwd) and not is_already_patched(cwd):
print(
"⚠️ Current directory contains an existing project that hasn't been patched yet."
)
print()
print(
" If you want to set up FastAPI+Vue integration in the current directory, run:"
)
print(" fastapi-vue-setup .")
print()
print(" Or specify a new project directory:")
print(" fastapi-vue-setup my-new-project")
print()
print(" Python project will be at root, while Vue lives in frontend/.")
print()
return 1
args.project_dir = "."
parser.print_help()
return 0
return cmd_setup(args)
+3 -3
View File
@@ -6,20 +6,20 @@ import uvicorn
from fastapi_vue.hostutil import parse_endpoint
from uvicorn import Config, Server
from .app import app
from .APP_MODULE import APP_VAR
DEFAULT_PORT = 5080
def run_server(endpoints: list[dict], *, proxy="", devmode=False):
conf: dict[str, object] = {"app": "MODULE_NAME.app:app"}
conf: dict[str, object] = {"app": "MODULE_NAME.APP_MODULE:APP_VAR"}
if proxy:
conf["proxy_headers"] = True
conf["forwarded_allow_ips"] = proxy
if devmode:
conf["reload"] = True
conf["reload_dirs"] = ["MODULE_NAME"]
app.debug = True
APP_VAR.debug = True
if len(endpoints) > 1:
# Run separate servers for multiple endpoints