feat(scripts): support skipping frontend build on make start (#5053)

* feat(scripts): support skipping frontend build on make start

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix(scripts): validate skip-frontend-build before stop_all and format test

---------

Co-authored-by: PoetryLin <PoetryLin@users.noreply.github.com>
Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
zhang 2026-08-28 10:44:30 +08:00 committed by GitHub
parent 2d0568a14f
commit 23d8e4b3a3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 93 additions and 8 deletions

View File

@ -124,7 +124,7 @@ make extension-enable NAME=... # Enable an installed extension (restart requ
make extension-disable NAME=... # Disable without uninstalling (restart required) make extension-disable NAME=... # Disable without uninstalling (restart required)
make extension-remove NAME=... # Remove package and config entry (restart required) make extension-remove NAME=... # Remove package and config entry (restart required)
make dev # Start all services with hot-reload (Gateway + Frontend + Nginx) make dev # Start all services with hot-reload (Gateway + Frontend + Nginx)
make start # Start all services in production mode (local, optimized) make start # Start all services in production mode (local, optimized); SKIP_FRONTEND_BUILD=1 reuses the last frontend build
make stop # Stop all running services make stop # Stop all running services
make up / down # Build/stop the production Docker stack (browser at localhost:2026) make up / down # Build/stop the production Docker stack (browser at localhost:2026)
make docker-start / docker-stop / docker-logs # Docker development environment make docker-start / docker-stop / docker-logs # Docker development environment

View File

@ -137,10 +137,12 @@ dev:
@$(PYTHON) ./scripts/check.py @$(PYTHON) ./scripts/check.py
@$(RUN_WITH_GIT_BASH) ./scripts/serve.sh --dev @$(RUN_WITH_GIT_BASH) ./scripts/serve.sh --dev
# Start all services in production mode (with optimizations) # Start all services in production mode (with optimizations).
# SKIP_FRONTEND_BUILD=1 reuses the existing frontend build instead of running
# `next build`; see scripts/serve.sh --skip-frontend-build.
start: start:
@$(PYTHON) ./scripts/check.py @$(PYTHON) ./scripts/check.py
@$(RUN_WITH_GIT_BASH) ./scripts/serve.sh --prod @$(RUN_WITH_GIT_BASH) ./scripts/serve.sh --prod $(if $(filter 1,$(SKIP_FRONTEND_BUILD)),--skip-frontend-build)
# Start all services in daemon mode (background) # Start all services in daemon mode (background)
dev-daemon: dev-daemon:
@ -150,7 +152,7 @@ dev-daemon:
# Start prod services in daemon mode (background) # Start prod services in daemon mode (background)
start-daemon: start-daemon:
@$(PYTHON) ./scripts/check.py @$(PYTHON) ./scripts/check.py
@$(RUN_WITH_GIT_BASH) ./scripts/serve.sh --prod --daemon @$(RUN_WITH_GIT_BASH) ./scripts/serve.sh --prod --daemon $(if $(filter 1,$(SKIP_FRONTEND_BUILD)),--skip-frontend-build)
# Start nginx alone in the foreground with the local dev config # Start nginx alone in the foreground with the local dev config
nginx: nginx:

View File

@ -392,6 +392,11 @@ DeerFlow runs the agent runtime inside the Gateway API. Development mode enables
| **Stop** | `./scripts/serve.sh --stop`<br/>`make stop` | `./scripts/docker.sh stop`<br/>`make docker-stop` | `./scripts/deploy.sh down`<br/>`make down` | | **Stop** | `./scripts/serve.sh --stop`<br/>`make stop` | `./scripts/docker.sh stop`<br/>`make docker-stop` | `./scripts/deploy.sh down`<br/>`make down` |
| **Restart** | `./scripts/serve.sh --restart [flags]` | `./scripts/docker.sh restart` | — | | **Restart** | `./scripts/serve.sh --restart [flags]` | `./scripts/docker.sh restart` | — |
`make start` and `make start-daemon` rebuild the frontend with `next build` on
every run. To reuse the last build instead, pass `SKIP_FRONTEND_BUILD=1` (or add
`--skip-frontend-build` when calling `./scripts/serve.sh --prod` directly). This
is opt-in: it fails fast when `frontend/.next` has no completed build.
Gateway owns `/api/langgraph/*` and translates those public LangGraph-compatible paths to its native `/api/*` routers behind nginx. Gateway owns `/api/langgraph/*` and translates those public LangGraph-compatible paths to its native `/api/*` routers behind nginx.
#### LangGraph Studio (Optional) #### LangGraph Studio (Optional)

View File

@ -0,0 +1,55 @@
"""Regression coverage for the opt-in --skip-frontend-build startup flag."""
from __future__ import annotations
import re
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
SERVE_SH = REPO_ROOT / "scripts" / "serve.sh"
ROOT_MAKEFILE = REPO_ROOT / "Makefile"
def _make_recipe(target: str) -> str:
makefile = ROOT_MAKEFILE.read_text(encoding="utf-8")
match = re.search(rf"^{target}:\n((?:\t.*\n?)+)", makefile, re.M)
assert match, f"target {target!r} not found in root Makefile"
return match.group(1)
def test_serve_script_parses_skip_frontend_build_flag() -> None:
serve = SERVE_SH.read_text(encoding="utf-8")
assert "SKIP_FRONTEND_BUILD=false" in serve
assert "--skip-frontend-build) SKIP_FRONTEND_BUILD=true ;;" in serve
def test_prod_default_still_builds_via_preview() -> None:
serve = SERVE_SH.read_text(encoding="utf-8")
# Verify the control-flow: skip flag -> `run start`, otherwise -> `run preview`.
assert re.search(
r"elif\s+\$SKIP_FRONTEND_BUILD;\s+then.*?FRONTEND_CMD=.*?run start.*?\nelse\n\s*FRONTEND_CMD=.*?run preview",
serve,
re.S,
), "expected prod default to use `run preview` and --skip-frontend-build to use `run start`"
def test_skip_build_reuses_existing_build_and_requires_build_id() -> None:
serve = SERVE_SH.read_text(encoding="utf-8")
assert 'if [ ! -f "$REPO_ROOT/frontend/.next/BUILD_ID" ]; then' in serve
assert "Run 'make start' once (full build)" in serve
def test_skip_build_preflight_runs_before_stop_all() -> None:
serve = SERVE_SH.read_text(encoding="utf-8")
assert serve.index("frontend/.next/BUILD_ID") < serve.index('if [ "$ACTION" = "restart" ]; then')
def test_make_start_exposes_flag_as_opt_in() -> None:
for target in ("start", "start-daemon"):
recipe = _make_recipe(target)
assert "--skip-frontend-build" in recipe
assert "$(if $(filter 1,$(SKIP_FRONTEND_BUILD)),--skip-frontend-build)" in recipe

View File

@ -11,9 +11,11 @@
# --daemon Run all services in background (nohup), exit after startup # --daemon Run all services in background (nohup), exit after startup
# #
# Actions: # Actions:
# --skip-install Skip dependency installation (faster restart) # --skip-install Skip dependency installation (faster restart)
# --stop Stop all running services and exit # --skip-frontend-build With --prod, reuse the existing .next build via `next start`
# --restart Stop all services, then start with the given mode flags # instead of `next build` (opt-in; fails if no build exists)
# --stop Stop all running services and exit
# --restart Stop all services, then start with the given mode flags
# #
# Examples: # Examples:
# ./scripts/serve.sh --dev # Gateway dev, hot reload # ./scripts/serve.sh --dev # Gateway dev, hot reload
@ -53,6 +55,7 @@ _pick_python() {
DEV_MODE=true DEV_MODE=true
DAEMON_MODE=false DAEMON_MODE=false
SKIP_INSTALL=false SKIP_INSTALL=false
SKIP_FRONTEND_BUILD=false
ACTION="start" # start | stop | restart ACTION="start" # start | stop | restart
for arg in "$@"; do for arg in "$@"; do
@ -61,11 +64,12 @@ for arg in "$@"; do
--prod) DEV_MODE=false ;; --prod) DEV_MODE=false ;;
--daemon) DAEMON_MODE=true ;; --daemon) DAEMON_MODE=true ;;
--skip-install) SKIP_INSTALL=true ;; --skip-install) SKIP_INSTALL=true ;;
--skip-frontend-build) SKIP_FRONTEND_BUILD=true ;;
--stop) ACTION="stop" ;; --stop) ACTION="stop" ;;
--restart) ACTION="restart" ;; --restart) ACTION="restart" ;;
*) *)
echo "Unknown argument: $arg" echo "Unknown argument: $arg"
echo "Usage: $0 [--dev|--prod] [--daemon] [--skip-install] [--stop|--restart]" echo "Usage: $0 [--dev|--prod] [--daemon] [--skip-install] [--skip-frontend-build] [--stop|--restart]"
exit 1 exit 1
;; ;;
esac esac
@ -268,6 +272,16 @@ stop_all() {
echo "✓ All services stopped" echo "✓ All services stopped"
} }
# Validate the reusable frontend build before any stop_all runs, so start and
# restart never tear down a healthy stack only to fail here. --stop is exempt.
if [ "$ACTION" != "stop" ] && ! $DEV_MODE && $SKIP_FRONTEND_BUILD; then
if [ ! -f "$REPO_ROOT/frontend/.next/BUILD_ID" ]; then
echo "✗ --skip-frontend-build requires an existing frontend build."
echo " Run 'make start' once (full build), or: cd frontend && pnpm run build"
exit 1
fi
fi
# ── Action routing ─────────────────────────────────────────────────────────── # ── Action routing ───────────────────────────────────────────────────────────
if [ "$ACTION" = "stop" ]; then if [ "$ACTION" = "stop" ]; then
@ -305,6 +319,12 @@ export DEERFLOW_PNPM_PYTHON DEERFLOW_PNPM_RUNNER
# Frontend command # Frontend command
if $DEV_MODE; then if $DEV_MODE; then
FRONTEND_CMD='env PORT=3000 "$DEERFLOW_PNPM_PYTHON" "$DEERFLOW_PNPM_RUNNER" run dev' FRONTEND_CMD='env PORT=3000 "$DEERFLOW_PNPM_PYTHON" "$DEERFLOW_PNPM_RUNNER" run dev'
if $SKIP_FRONTEND_BUILD; then
echo " Note: --skip-frontend-build is ignored in dev mode (next dev does not build)."
fi
elif $SKIP_FRONTEND_BUILD; then
# The BUILD_ID preflight above already guarantees a reusable build exists.
FRONTEND_CMD="env PORT=3000 BETTER_AUTH_SECRET=$($DEERFLOW_PNPM_PYTHON -c 'import secrets; print(secrets.token_hex(16))') \"\$DEERFLOW_PNPM_PYTHON\" \"\$DEERFLOW_PNPM_RUNNER\" run start"
else else
FRONTEND_CMD="env PORT=3000 BETTER_AUTH_SECRET=$($DEERFLOW_PNPM_PYTHON -c 'import secrets; print(secrets.token_hex(16))') \"\$DEERFLOW_PNPM_PYTHON\" \"\$DEERFLOW_PNPM_RUNNER\" run preview" FRONTEND_CMD="env PORT=3000 BETTER_AUTH_SECRET=$($DEERFLOW_PNPM_PYTHON -c 'import secrets; print(secrets.token_hex(16))') \"\$DEERFLOW_PNPM_PYTHON\" \"\$DEERFLOW_PNPM_RUNNER\" run preview"
fi fi
@ -405,6 +425,9 @@ echo " Starting DeerFlow"
echo "==========================================" echo "=========================================="
echo "" echo ""
echo " Mode: $MODE_LABEL" echo " Mode: $MODE_LABEL"
if ! $DEV_MODE && $SKIP_FRONTEND_BUILD; then
echo " (frontend: reusing existing build)"
fi
echo "" echo ""
echo " Services:" echo " Services:"
echo " Gateway → localhost:8001 (REST API + agent runtime)" echo " Gateway → localhost:8001 (REST API + agent runtime)"