Add a script to run devserver. Migrate to build and JS utils provided by fastapi-vue. Frontend directory renamed to frontend-build. Update Sanic, deprecations.
This commit is contained in:
+1
-1
@@ -4,5 +4,5 @@
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
/cista/_version.py
|
/cista/_version.py
|
||||||
/cista/wwwroot/*
|
/cista/frontend-build/
|
||||||
/dist
|
/dist
|
||||||
|
|||||||
+2
-2
@@ -15,12 +15,12 @@ fileserver = FileServer()
|
|||||||
|
|
||||||
|
|
||||||
@bp.before_server_start
|
@bp.before_server_start
|
||||||
async def start_fileserver(app, _):
|
async def start_fileserver(app):
|
||||||
await fileserver.start()
|
await fileserver.start()
|
||||||
|
|
||||||
|
|
||||||
@bp.after_server_stop
|
@bp.after_server_stop
|
||||||
async def stop_fileserver(app, _):
|
async def stop_fileserver(app):
|
||||||
await fileserver.stop()
|
await fileserver.stop()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -36,19 +36,19 @@ setproctitle("cista-main")
|
|||||||
|
|
||||||
|
|
||||||
@app.before_server_start
|
@app.before_server_start
|
||||||
async def main_start(app, loop):
|
async def main_start(app):
|
||||||
config.load_config()
|
config.load_config()
|
||||||
setproctitle(f"cista {config.config.path.name}")
|
setproctitle(f"cista {config.config.path.name}")
|
||||||
workers = max(2, min(8, cpu_count()))
|
workers = max(2, min(8, cpu_count()))
|
||||||
app.ctx.threadexec = ThreadPoolExecutor(
|
app.ctx.threadexec = ThreadPoolExecutor(
|
||||||
max_workers=workers, thread_name_prefix="cista-ioworker"
|
max_workers=workers, thread_name_prefix="cista-ioworker"
|
||||||
)
|
)
|
||||||
watching.start(app, loop)
|
watching.start(app)
|
||||||
|
|
||||||
|
|
||||||
# Sanic sometimes fails to execute after_server_stop, so we do it before instead (potentially interrupting handlers)
|
# Sanic sometimes fails to execute after_server_stop, so we do it before instead (potentially interrupting handlers)
|
||||||
@app.before_server_stop
|
@app.before_server_stop
|
||||||
async def main_stop(app, loop):
|
async def main_stop(app):
|
||||||
quit.set()
|
quit.set()
|
||||||
watching.stop(app)
|
watching.stop(app)
|
||||||
app.ctx.threadexec.shutdown()
|
app.ctx.threadexec.shutdown()
|
||||||
@@ -75,7 +75,7 @@ async def use_session(req):
|
|||||||
|
|
||||||
|
|
||||||
@app.before_server_start
|
@app.before_server_start
|
||||||
def http_fileserver(app, _):
|
def http_fileserver(app):
|
||||||
bp = Blueprint("fileserver")
|
bp = Blueprint("fileserver")
|
||||||
bp.on_request(auth.verify)
|
bp.on_request(auth.verify)
|
||||||
bp.static(
|
bp.static(
|
||||||
@@ -93,9 +93,9 @@ www = {}
|
|||||||
|
|
||||||
def _load_wwwroot(www):
|
def _load_wwwroot(www):
|
||||||
wwwnew = {}
|
wwwnew = {}
|
||||||
base = Path(__file__).with_name("wwwroot")
|
base = Path(__file__).with_name("frontend-build")
|
||||||
paths = [PurePath()]
|
paths = [PurePath()]
|
||||||
zstd = ZstdCompressor(level=10)
|
zstd = ZstdCompressor(level=18)
|
||||||
while paths:
|
while paths:
|
||||||
path = paths.pop(0)
|
path = paths.pop(0)
|
||||||
current = base / path
|
current = base / path
|
||||||
|
|||||||
+2
-2
@@ -440,14 +440,14 @@ def watcher_poll(loop):
|
|||||||
quit.wait(0.1 + 8 * dur)
|
quit.wait(0.1 + 8 * dur)
|
||||||
|
|
||||||
|
|
||||||
def start(app, loop):
|
def start(app):
|
||||||
global rootpath
|
global rootpath
|
||||||
config.load_config()
|
config.load_config()
|
||||||
rootpath = config.config.path
|
rootpath = config.config.path
|
||||||
use_inotify = sys.platform == "linux"
|
use_inotify = sys.platform == "linux"
|
||||||
app.ctx.watcher = threading.Thread(
|
app.ctx.watcher = threading.Thread(
|
||||||
target=watcher_inotify if use_inotify else watcher_poll,
|
target=watcher_inotify if use_inotify else watcher_poll,
|
||||||
args=[loop],
|
args=[app.loop],
|
||||||
# Descriptive name for system monitoring
|
# Descriptive name for system monitoring
|
||||||
name=f"cista-watcher {rootpath}",
|
name=f"cista-watcher {rootpath}",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,11 +7,8 @@ import vue from '@vitejs/plugin-vue'
|
|||||||
import svgLoader from 'vite-svg-loader'
|
import svgLoader from 'vite-svg-loader'
|
||||||
import Components from 'unplugin-vue-components/vite'
|
import Components from 'unplugin-vue-components/vite'
|
||||||
|
|
||||||
// Development mode:
|
|
||||||
// bun run dev # Run frontend that proxies to dev_backend
|
|
||||||
// cista -l :8000 --dev # Run backend
|
|
||||||
const dev_backend = {
|
const dev_backend = {
|
||||||
target: "http://localhost:8000",
|
target: process.env.CISTA_BACKEND_URL || "http://localhost:8989",
|
||||||
changeOrigin: false, // Use frontend "host" to match "origin" from browser
|
changeOrigin: false, // Use frontend "host" to match "origin" from browser
|
||||||
ws: true,
|
ws: true,
|
||||||
}
|
}
|
||||||
@@ -48,7 +45,7 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
outDir: "../cista/wwwroot",
|
outDir: "../cista/frontend-build",
|
||||||
emptyOutDir: true,
|
emptyOutDir: true,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+5
-3
@@ -37,7 +37,7 @@ dependencies = [
|
|||||||
"pillow-heif>=1.1.0",
|
"pillow-heif>=1.1.0",
|
||||||
"pyjwt>=2.10.1",
|
"pyjwt>=2.10.1",
|
||||||
"pymupdf>=1.26.3",
|
"pymupdf>=1.26.3",
|
||||||
"sanic>=25.3.0",
|
"sanic>=25.12.0",
|
||||||
"setproctitle>=1.3.6",
|
"setproctitle>=1.3.6",
|
||||||
"stream-zip>=0.0.83",
|
"stream-zip>=0.0.83",
|
||||||
"tomli_w>=1.2.0",
|
"tomli_w>=1.2.0",
|
||||||
@@ -71,8 +71,8 @@ docs = [
|
|||||||
source = "vcs"
|
source = "vcs"
|
||||||
|
|
||||||
[tool.hatch.build]
|
[tool.hatch.build]
|
||||||
artifacts = ["cista/wwwroot"]
|
artifacts = ["cista/frontend-build"]
|
||||||
targets.sdist.hooks.custom.path = "scripts/build-frontend.py"
|
targets.sdist.hooks.custom.path = "scripts/fastapi-vue/build-frontend.py"
|
||||||
targets.sdist.include = [
|
targets.sdist.include = [
|
||||||
"/cista",
|
"/cista",
|
||||||
]
|
]
|
||||||
@@ -82,6 +82,7 @@ hooks.vcs.template = """
|
|||||||
__version__ = {version!r}
|
__version__ = {version!r}
|
||||||
"""
|
"""
|
||||||
only-packages = true
|
only-packages = true
|
||||||
|
packages = ["cista"]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
addopts = [
|
addopts = [
|
||||||
@@ -119,6 +120,7 @@ dev = [
|
|||||||
"ruff>=0.8.0",
|
"ruff>=0.8.0",
|
||||||
"mypy>=1.13.0",
|
"mypy>=1.13.0",
|
||||||
"pre-commit>=4.0.0",
|
"pre-commit>=4.0.0",
|
||||||
|
"httpx>=0.28.1",
|
||||||
]
|
]
|
||||||
test = [
|
test = [
|
||||||
"pytest>=8.4.1",
|
"pytest>=8.4.1",
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
# noqa: INP001
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
from sys import stderr
|
|
||||||
|
|
||||||
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
||||||
|
|
||||||
|
|
||||||
class CustomBuildHook(BuildHookInterface):
|
|
||||||
def initialize(self, version, build_data):
|
|
||||||
super().initialize(version, build_data)
|
|
||||||
stderr.write(">>> Building Cista frontend\n")
|
|
||||||
npm = None
|
|
||||||
bun = shutil.which("bun")
|
|
||||||
if bun is None:
|
|
||||||
npm = shutil.which("npm")
|
|
||||||
if npm is None:
|
|
||||||
raise RuntimeError(
|
|
||||||
"Bun or NodeJS `npm` is required for building but neither was found\n Visit https://bun.com/"
|
|
||||||
)
|
|
||||||
# npm --prefix doesn't work on Windows, so we chdir instead
|
|
||||||
os.chdir("frontend")
|
|
||||||
try:
|
|
||||||
if npm:
|
|
||||||
stderr.write("### npm install\n")
|
|
||||||
subprocess.run([npm, "install"], check=True) # noqa: S603
|
|
||||||
stderr.write("\n### npm run build\n")
|
|
||||||
subprocess.run([npm, "run", "build"], check=True) # noqa: S603
|
|
||||||
else:
|
|
||||||
assert bun
|
|
||||||
stderr.write("### bun install\n")
|
|
||||||
subprocess.run([bun, "install"], check=True) # noqa: S603
|
|
||||||
stderr.write("\n### bun run build\n")
|
|
||||||
subprocess.run([bun, "run", "build"], check=True) # noqa: S603
|
|
||||||
finally:
|
|
||||||
os.chdir("..")
|
|
||||||
Executable
+210
@@ -0,0 +1,210 @@
|
|||||||
|
#!/usr/bin/env -S uv run
|
||||||
|
"""Run Vite development server for frontend and Cista backend with auto-reload.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
uv run scripts/devserver.py [-l <listen>]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-l LISTEN Listen address for backend (default: from config, or :8000)
|
||||||
|
|
||||||
|
Environment:
|
||||||
|
JS_RUNTIME Path or name of JS runtime to use (deno, npm/node or bun).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import contextlib
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from sys import stderr
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from cista import config
|
||||||
|
from cista.serve import parse_listen
|
||||||
|
|
||||||
|
exec((Path(__file__).parent / "fastapi-vue/util.py").read_text("UTF-8")) # noqa: S102
|
||||||
|
|
||||||
|
DEFAULT_VITE_PORT = 5173
|
||||||
|
FRONTEND_PATH = Path(__file__).parent.parent / "frontend"
|
||||||
|
|
||||||
|
BUN_BUG = """\
|
||||||
|
┃ ⚠️ Bun cannot correctly proxy API requests to the backend.
|
||||||
|
┃ Bug report: https://github.com/oven-sh/bun/issues/9882
|
||||||
|
┃
|
||||||
|
┃ Consider using deno or npm instead for development.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_frontend_tools(vite_port: int) -> tuple[list[str], list[str], str]:
|
||||||
|
"""Resolve frontend install and dev commands.
|
||||||
|
|
||||||
|
Returns (install_cmd, dev_cmd, tool_name).
|
||||||
|
Raises SystemExit if tools are not available.
|
||||||
|
"""
|
||||||
|
if not (FRONTEND_PATH / "package.json").exists():
|
||||||
|
stderr.write(f"┃ ⚠️ Frontend source not found at {FRONTEND_PATH}\n")
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
install_cmd, build_cmd = find_build_tool() # noqa # type: ignore
|
||||||
|
dev_cmd, name = find_dev_tool() # noqa # type: ignore
|
||||||
|
if dev_cmd is None:
|
||||||
|
if not os.environ.get("JS_RUNTIME"):
|
||||||
|
stderr.write("┃ ⚠️ deno, npm or bun needed to run the frontend server.\n")
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
dev_cmd = [*dev_cmd, "--clearScreen=false", f"--port={vite_port}"]
|
||||||
|
|
||||||
|
if name == "bun":
|
||||||
|
stderr.write(BUN_BUG)
|
||||||
|
|
||||||
|
return install_cmd, dev_cmd, name
|
||||||
|
|
||||||
|
|
||||||
|
async def wait_for_backend(host: str, port: int):
|
||||||
|
"""Wait for the backend to be ready by polling the health endpoint."""
|
||||||
|
max_attempts = 50
|
||||||
|
url = f"http://{host}:{port}"
|
||||||
|
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
for attempt in range(max_attempts):
|
||||||
|
try:
|
||||||
|
await client.get(url, timeout=1.0)
|
||||||
|
stderr.write("✓ Backend ready!\n")
|
||||||
|
return True
|
||||||
|
except httpx.RequestError:
|
||||||
|
if attempt == max_attempts - 1:
|
||||||
|
stderr.write("┃ ⚠️ Backend didn't start in time\n")
|
||||||
|
return False
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def _terminate_process(proc: asyncio.subprocess.Process, name: str) -> None:
|
||||||
|
"""Gracefully terminate a subprocess."""
|
||||||
|
if proc.returncode is not None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
proc.terminate()
|
||||||
|
except ProcessLookupError:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(proc.wait(), timeout=2)
|
||||||
|
except TimeoutError:
|
||||||
|
try:
|
||||||
|
proc.kill()
|
||||||
|
except ProcessLookupError:
|
||||||
|
return
|
||||||
|
await proc.wait()
|
||||||
|
|
||||||
|
|
||||||
|
async def run_devserver(backend_port: int, cista_args: list[str]) -> None:
|
||||||
|
"""Run the development server with install, backend, and frontend."""
|
||||||
|
vite_port = DEFAULT_VITE_PORT
|
||||||
|
install_cmd, dev_cmd, tool_name = resolve_frontend_tools(vite_port)
|
||||||
|
|
||||||
|
# Tell the backend where the Vite dev server is (not used yet)
|
||||||
|
os.environ["CISTA_DEV_FRONTEND_URL"] = f"http://localhost:{vite_port}"
|
||||||
|
|
||||||
|
backend_cmd = ["cista", "--dev", *cista_args]
|
||||||
|
|
||||||
|
cwd = str(Path(__file__).parent.parent)
|
||||||
|
frontend_cwd = str(FRONTEND_PATH)
|
||||||
|
|
||||||
|
backend_proc: asyncio.subprocess.Process | None = None
|
||||||
|
install_proc: asyncio.subprocess.Process | None = None
|
||||||
|
frontend_proc: asyncio.subprocess.Process | None = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Start install (concurrent with backend)
|
||||||
|
stderr.write(f">>> {tool_name} {' '.join(install_cmd[1:])}\n")
|
||||||
|
install_proc = await asyncio.create_subprocess_exec(
|
||||||
|
*install_cmd, cwd=frontend_cwd
|
||||||
|
)
|
||||||
|
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
|
# Start backend (concurrent with install)
|
||||||
|
stderr.write(f">>> {' '.join(backend_cmd)}\n")
|
||||||
|
backend_proc = await asyncio.create_subprocess_exec(*backend_cmd, cwd=cwd)
|
||||||
|
|
||||||
|
# Wait for install to complete and backend to be ready
|
||||||
|
install_task = asyncio.create_task(install_proc.wait(), name="install")
|
||||||
|
backend_ready_task = asyncio.create_task(
|
||||||
|
wait_for_backend("localhost", backend_port), name="backend_ready"
|
||||||
|
)
|
||||||
|
|
||||||
|
done, pending = await asyncio.wait(
|
||||||
|
{install_task, backend_ready_task},
|
||||||
|
return_when=asyncio.FIRST_COMPLETED,
|
||||||
|
)
|
||||||
|
|
||||||
|
for task in done:
|
||||||
|
if task.get_name() == "install":
|
||||||
|
if task.result() != 0:
|
||||||
|
stderr.write("┃ ⚠️ Install failed\n")
|
||||||
|
raise SystemExit(1)
|
||||||
|
elif task.get_name() == "backend_ready" and not task.result():
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
if pending:
|
||||||
|
done2, _ = await asyncio.wait(pending)
|
||||||
|
for task in done2:
|
||||||
|
if task.get_name() == "install":
|
||||||
|
if task.result() != 0:
|
||||||
|
stderr.write("┃ ⚠️ Install failed\n")
|
||||||
|
raise SystemExit(1)
|
||||||
|
elif task.get_name() == "backend_ready" and not task.result():
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
install_proc = None
|
||||||
|
|
||||||
|
# Start Vite dev server
|
||||||
|
stderr.write(f">>> {tool_name} {' '.join(dev_cmd[1:])}\n")
|
||||||
|
frontend_proc = await asyncio.create_subprocess_exec(*dev_cmd, cwd=frontend_cwd)
|
||||||
|
|
||||||
|
# Wait for either process to exit
|
||||||
|
done, pending = await asyncio.wait(
|
||||||
|
{
|
||||||
|
asyncio.create_task(backend_proc.wait(), name="backend"),
|
||||||
|
asyncio.create_task(frontend_proc.wait(), name="frontend"),
|
||||||
|
},
|
||||||
|
return_when=asyncio.FIRST_COMPLETED,
|
||||||
|
)
|
||||||
|
for t in done:
|
||||||
|
t.result()
|
||||||
|
for t in pending:
|
||||||
|
t.cancel()
|
||||||
|
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
stderr.write("\n✓ Shutting down...\n")
|
||||||
|
finally:
|
||||||
|
if frontend_proc is not None:
|
||||||
|
await _terminate_process(frontend_proc, "frontend")
|
||||||
|
if install_proc is not None:
|
||||||
|
await _terminate_process(install_proc, "install")
|
||||||
|
if backend_proc is not None:
|
||||||
|
await _terminate_process(backend_proc, "backend")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Pass all arguments to cista, parse -l to determine backend port
|
||||||
|
cista_args = sys.argv[1:]
|
||||||
|
listen_arg = None
|
||||||
|
if "-l" in cista_args:
|
||||||
|
idx = cista_args.index("-l")
|
||||||
|
if idx + 1 < len(cista_args):
|
||||||
|
listen_arg = cista_args[idx + 1]
|
||||||
|
|
||||||
|
# Load config to get the backend port
|
||||||
|
config.load_config()
|
||||||
|
listen = listen_arg or config.config.listen or ":8000"
|
||||||
|
_, opts = parse_listen(listen)
|
||||||
|
backend_port = opts.get("port", 8000)
|
||||||
|
|
||||||
|
with contextlib.suppress(KeyboardInterrupt):
|
||||||
|
asyncio.run(run_devserver(backend_port, cista_args))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
"""Hatch build hook for building Vue frontend during package build."""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
from sys import stderr
|
||||||
|
|
||||||
|
from hatchling.builders.hooks.plugin.interface import BuildHookInterface # type: ignore
|
||||||
|
|
||||||
|
exec(Path(__file__).with_name("util.py").read_text("UTF-8")) # noqa: S102
|
||||||
|
|
||||||
|
|
||||||
|
def run(cmd, **kwargs):
|
||||||
|
"""Run a command and display it."""
|
||||||
|
display_cmd = [Path(cmd[0]).name, *cmd[1:]]
|
||||||
|
stderr.write(f"### {' '.join(display_cmd)}\n")
|
||||||
|
subprocess.run(cmd, check=True, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomBuildHook(BuildHookInterface):
|
||||||
|
"""Build hook that compiles Vue frontend before packaging."""
|
||||||
|
|
||||||
|
def initialize(self, version, build_data):
|
||||||
|
super().initialize(version, build_data)
|
||||||
|
stderr.write(">>> Building the frontend\n")
|
||||||
|
|
||||||
|
install_cmd, build_cmd = find_build_tool() # noqa # type: ignore
|
||||||
|
|
||||||
|
try:
|
||||||
|
run(install_cmd, cwd="frontend")
|
||||||
|
stderr.write("\n")
|
||||||
|
run(build_cmd, cwd="frontend")
|
||||||
|
except Exception as e:
|
||||||
|
stderr.write(f"Error occurred while building frontend: {e}\n")
|
||||||
|
raise
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
"""Shared utilities for build and dev scripts."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
from sys import stderr
|
||||||
|
|
||||||
|
|
||||||
|
def find_js_runtime() -> tuple[str, str] | None:
|
||||||
|
"""Find a JavaScript runtime from JS_RUNTIME env or auto-detect.
|
||||||
|
|
||||||
|
Returns (tool_path, tool_name) where tool_name is "deno", "npm", or "bun".
|
||||||
|
Returns None if no runtime is found.
|
||||||
|
"""
|
||||||
|
options = ["deno", "npm", "bun"]
|
||||||
|
|
||||||
|
# Check for JS_RUNTIME environment variable
|
||||||
|
if js_runtime_env := os.environ.get("JS_RUNTIME"):
|
||||||
|
js_runtime = js_runtime_env
|
||||||
|
js_path = Path(js_runtime)
|
||||||
|
runtime_name = js_path.name
|
||||||
|
# Map node to npm
|
||||||
|
if runtime_name == "node":
|
||||||
|
runtime_name = "npm"
|
||||||
|
js_runtime = str(js_path.parent / "npm") if js_path.parent.name else "npm"
|
||||||
|
for option in options:
|
||||||
|
if option == runtime_name or runtime_name.startswith(option):
|
||||||
|
tool = shutil.which(js_runtime)
|
||||||
|
if tool is None:
|
||||||
|
stderr.write(f"┃ ⚠️ JS_RUNTIME={js_runtime_env} not found\n")
|
||||||
|
return None
|
||||||
|
return tool, option
|
||||||
|
stderr.write(f"┃ ⚠️ JS_RUNTIME={js_runtime_env} not recognized\n")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Auto-detect
|
||||||
|
for option in options:
|
||||||
|
if tool := shutil.which(option):
|
||||||
|
return tool, option
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def find_build_tool():
|
||||||
|
"""Find JavaScript runtime and construct install/build commands.
|
||||||
|
|
||||||
|
Returns (install_cmd, build_cmd) tuples of command lists.
|
||||||
|
Raises RuntimeError if no runtime is found.
|
||||||
|
"""
|
||||||
|
install = {
|
||||||
|
"deno": ("install", "--allow-scripts=npm:vue-demi"),
|
||||||
|
"npm": ("install",),
|
||||||
|
"bun": ("--bun", "install"),
|
||||||
|
}
|
||||||
|
# Run vite directly for deno to avoid npm-run-all2/run-p issues
|
||||||
|
build = {
|
||||||
|
"deno": ("run", "-A", "npm:vite", "build"),
|
||||||
|
"npm": ("run", "build"),
|
||||||
|
"bun": ("--bun", "run", "build"),
|
||||||
|
}
|
||||||
|
|
||||||
|
result = find_js_runtime()
|
||||||
|
if result is None:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Deno, npm or Bun is required for building but none was found"
|
||||||
|
)
|
||||||
|
|
||||||
|
tool, name = result
|
||||||
|
return [tool, *install[name]], [tool, *build[name]]
|
||||||
|
|
||||||
|
|
||||||
|
def find_dev_tool():
|
||||||
|
"""Find JavaScript runtime and construct dev command.
|
||||||
|
|
||||||
|
Returns (dev_cmd, tool_name) or (None, None) if not found.
|
||||||
|
"""
|
||||||
|
dev_args = {
|
||||||
|
"deno": ("run", "dev", "--"),
|
||||||
|
"npm": ("--silent", "run", "dev", "--"),
|
||||||
|
"bun": ("run", "dev", "--"),
|
||||||
|
}
|
||||||
|
|
||||||
|
result = find_js_runtime()
|
||||||
|
if result is None:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
tool, name = result
|
||||||
|
return [tool, *dev_args[name]], name
|
||||||
Reference in New Issue
Block a user