2026-05-09 09:29:34 +08:00
#!/usr/bin/env sh
set -eu
if ( set -o pipefail) >/dev/null 2>& 1; then
set -o pipefail
fi
feat: refactor monolithic App.vue into modular Vue component architecture
- Extract 711-line App.vue into 15+ focused files across 5 directories
- Add data layer (icons, metrics, policies, auditTrail, requests)
- Add composables (useNavigation, useRequests, useChat, useToast)
- Add layout components (SidebarRail, TopBar, FilterBar)
- Add shared components (PanelHead, InfoRow, ToastNotification)
- Add business component (RequestTable) and 5 view components
- Extract global CSS to assets/styles/global.css
- Add start.sh with WSL/Windows cross-platform support
- Add .gitignore for node_modules, dist, and IDE dirs
2026-04-28 17:20:52 +08:00
2026-05-07 11:50:10 +08:00
export MSYS_NO_PATHCONV = 1
2026-05-09 09:29:34 +08:00
SCRIPT_PATH = " $0 "
case " $SCRIPT_PATH " in
/*) ; ;
*) SCRIPT_PATH = " $( pwd ) / $SCRIPT_PATH " ; ;
esac
SCRIPT_DIR = " $( CDPATH = cd -- " $( dirname -- " $SCRIPT_PATH " ) " && pwd ) "
2026-05-06 17:43:47 +08:00
ENV_FILE = " $SCRIPT_DIR /.env "
ENV_EXAMPLE_FILE = " $SCRIPT_DIR /.env.example "
2026-05-08 10:52:54 +08:00
ADMIN_SECRET_FILE = " $SCRIPT_DIR /server/.secrets/admin.json "
2026-05-06 17:43:47 +08:00
MODE = " ${ 1 :- all } "
feat: refactor monolithic App.vue into modular Vue component architecture
- Extract 711-line App.vue into 15+ focused files across 5 directories
- Add data layer (icons, metrics, policies, auditTrail, requests)
- Add composables (useNavigation, useRequests, useChat, useToast)
- Add layout components (SidebarRail, TopBar, FilterBar)
- Add shared components (PanelHead, InfoRow, ToastNotification)
- Add business component (RequestTable) and 5 view components
- Extract global CSS to assets/styles/global.css
- Add start.sh with WSL/Windows cross-platform support
- Add .gitignore for node_modules, dist, and IDE dirs
2026-04-28 17:20:52 +08:00
2026-05-06 17:43:47 +08:00
RED = '\033[0;31m'
GREEN = '\033[0;32m'
YELLOW = '\033[1;33m'
NC = '\033[0m'
2026-05-09 09:29:34 +08:00
info( ) { printf '%b\n' " ${ GREEN } [INFO] ${ NC } $* " ; }
warn( ) { printf '%b\n' " ${ YELLOW } [WARN] ${ NC } $* " ; }
error( ) { printf '%b\n' " ${ RED } [ERROR] ${ NC } $* " ; exit 1; }
2026-05-06 17:43:47 +08:00
if [ ! -f " $ENV_FILE " ] ; then
if [ -f " $ENV_EXAMPLE_FILE " ] ; then
warn ".env not found. Creating it from .env.example"
cp " $ENV_EXAMPLE_FILE " " $ENV_FILE "
else
error ".env and .env.example are both missing."
fi
fi
2026-05-09 09:29:34 +08:00
ENV_OVERRIDE_WEB_HOST_SET = false
ENV_OVERRIDE_SERVER_HOST_SET = false
2026-05-09 05:59:46 +00:00
ENV_OVERRIDE_ONLYOFFICE_ENABLED_SET = false
ENV_OVERRIDE_ONLYOFFICE_PUBLIC_URL_SET = false
ENV_OVERRIDE_ONLYOFFICE_BACKEND_URL_SET = false
ENV_OVERRIDE_ONLYOFFICE_JWT_SECRET_SET = false
2026-05-09 07:29:49 +00:00
PREFER_ENV_FILE_FOR_ONLYOFFICE = false
case " ${ X_FINANCIAL_PREFER_ENV_FILE :- false } " in
1| true| TRUE| yes| YES| on| ON)
PREFER_ENV_FILE_FOR_ONLYOFFICE = true
; ;
esac
2026-05-09 09:29:34 +08:00
if [ " ${ WEB_HOST +x } " = x ] ; then
ENV_OVERRIDE_WEB_HOST_SET = true
ENV_OVERRIDE_WEB_HOST = " $WEB_HOST "
fi
if [ " ${ SERVER_HOST +x } " = x ] ; then
ENV_OVERRIDE_SERVER_HOST_SET = true
ENV_OVERRIDE_SERVER_HOST = " $SERVER_HOST "
fi
2026-05-09 07:29:49 +00:00
if [ " $PREFER_ENV_FILE_FOR_ONLYOFFICE " != true ] && [ " ${ ONLYOFFICE_ENABLED +x } " = x ] ; then
2026-05-09 05:59:46 +00:00
ENV_OVERRIDE_ONLYOFFICE_ENABLED_SET = true
ENV_OVERRIDE_ONLYOFFICE_ENABLED = " $ONLYOFFICE_ENABLED "
fi
2026-05-09 07:29:49 +00:00
if [ " $PREFER_ENV_FILE_FOR_ONLYOFFICE " != true ] && [ " ${ ONLYOFFICE_PUBLIC_URL +x } " = x ] ; then
2026-05-09 05:59:46 +00:00
ENV_OVERRIDE_ONLYOFFICE_PUBLIC_URL_SET = true
ENV_OVERRIDE_ONLYOFFICE_PUBLIC_URL = " $ONLYOFFICE_PUBLIC_URL "
fi
2026-05-09 07:29:49 +00:00
if [ " $PREFER_ENV_FILE_FOR_ONLYOFFICE " != true ] && [ " ${ ONLYOFFICE_BACKEND_URL +x } " = x ] ; then
2026-05-09 05:59:46 +00:00
ENV_OVERRIDE_ONLYOFFICE_BACKEND_URL_SET = true
ENV_OVERRIDE_ONLYOFFICE_BACKEND_URL = " $ONLYOFFICE_BACKEND_URL "
fi
2026-05-09 07:29:49 +00:00
if [ " $PREFER_ENV_FILE_FOR_ONLYOFFICE " != true ] && [ " ${ ONLYOFFICE_JWT_SECRET +x } " = x ] ; then
2026-05-09 05:59:46 +00:00
ENV_OVERRIDE_ONLYOFFICE_JWT_SECRET_SET = true
ENV_OVERRIDE_ONLYOFFICE_JWT_SECRET = " $ONLYOFFICE_JWT_SECRET "
fi
2026-05-06 17:43:47 +08:00
set -a
. " $ENV_FILE "
set +a
2026-05-09 09:29:34 +08:00
if [ " $ENV_OVERRIDE_WEB_HOST_SET " = true ] ; then
WEB_HOST = " $ENV_OVERRIDE_WEB_HOST "
fi
if [ " $ENV_OVERRIDE_SERVER_HOST_SET " = true ] ; then
SERVER_HOST = " $ENV_OVERRIDE_SERVER_HOST "
fi
2026-05-09 05:59:46 +00:00
if [ " $ENV_OVERRIDE_ONLYOFFICE_ENABLED_SET " = true ] ; then
ONLYOFFICE_ENABLED = " $ENV_OVERRIDE_ONLYOFFICE_ENABLED "
fi
if [ " $ENV_OVERRIDE_ONLYOFFICE_PUBLIC_URL_SET " = true ] ; then
ONLYOFFICE_PUBLIC_URL = " $ENV_OVERRIDE_ONLYOFFICE_PUBLIC_URL "
fi
if [ " $ENV_OVERRIDE_ONLYOFFICE_BACKEND_URL_SET " = true ] ; then
ONLYOFFICE_BACKEND_URL = " $ENV_OVERRIDE_ONLYOFFICE_BACKEND_URL "
fi
if [ " $ENV_OVERRIDE_ONLYOFFICE_JWT_SECRET_SET " = true ] ; then
ONLYOFFICE_JWT_SECRET = " $ENV_OVERRIDE_ONLYOFFICE_JWT_SECRET "
fi
2026-05-06 17:43:47 +08:00
SERVER_STARTUP_TIMEOUT = " ${ SERVER_STARTUP_TIMEOUT :- 300 } "
SETUP_COMPLETED = " ${ SETUP_COMPLETED :- false } "
2026-05-08 08:56:52 +08:00
APP_DEBUG = " ${ APP_DEBUG :- true } "
APP_ENV = " ${ APP_ENV :- local } "
SERVER_RELOAD = " ${ SERVER_RELOAD :- } "
2026-05-09 09:29:34 +08:00
DEFAULT_SERVER_RELOAD = "false"
case " $APP_ENV " in
local| dev| development)
DEFAULT_SERVER_RELOAD = "true"
; ;
esac
if [ " $APP_DEBUG " = "true" ] ; then
DEFAULT_SERVER_RELOAD = "true"
fi
EFFECTIVE_SERVER_RELOAD = " ${ SERVER_RELOAD :- $DEFAULT_SERVER_RELOAD } "
2026-05-06 17:43:47 +08:00
2026-05-08 10:52:54 +08:00
setup_ready( ) {
[ " $SETUP_COMPLETED " = "true" ] && [ -f " $ADMIN_SECRET_FILE " ]
}
server_probe_host( ) {
case " ${ SERVER_HOST :- 127 .0.0.1 } " in
0.0.0.0| ::)
echo "127.0.0.1"
; ;
*)
echo " ${ SERVER_HOST :- 127 .0.0.1 } "
; ;
esac
}
2026-05-07 11:50:10 +08:00
server_probe_url( ) {
2026-05-08 10:52:54 +08:00
echo " http:// $( server_probe_host) : ${ SERVER_PORT :- 8000 } ${ API_V1_PREFIX :- /api/v1 } /health "
2026-05-07 11:50:10 +08:00
}
server_smoke_url( ) {
2026-05-08 10:52:54 +08:00
echo " http:// $( server_probe_host) : ${ SERVER_PORT :- 8000 } ${ API_V1_PREFIX :- /api/v1 } /employees/meta "
2026-05-07 11:50:10 +08:00
}
server_probe_python( ) {
if [ -x " $SCRIPT_DIR /server/.venv/Scripts/python.exe " ] ; then
echo " $SCRIPT_DIR /server/.venv/Scripts/python.exe "
return 0
fi
if [ -x " $SCRIPT_DIR /server/.venv/bin/python " ] ; then
echo " $SCRIPT_DIR /server/.venv/bin/python "
return 0
fi
return 1
}
probe_server_health( ) {
2026-05-09 09:29:34 +08:00
_probe_url = " ${ 1 :- $( server_probe_url) } "
_probe_python = ""
2026-05-07 11:50:10 +08:00
2026-05-09 09:29:34 +08:00
if _probe_python = " $( server_probe_python) " ; then
" $_probe_python " -c "import json, sys, urllib.request; data = json.load(urllib.request.urlopen(sys.argv[1], timeout=2)); raise SystemExit(0 if data.get('status') == 'ok' else 1)" " $_probe_url " >/dev/null 2>& 1
2026-05-07 11:50:10 +08:00
return $?
fi
if command -v curl >/dev/null 2>& 1; then
2026-05-09 09:29:34 +08:00
curl --silent --fail --max-time 2 " $_probe_url " | grep -q '"status"[[:space:]]*:[[:space:]]*"ok"'
2026-05-07 11:50:10 +08:00
return $?
fi
return 1
}
probe_server_smoke( ) {
2026-05-09 09:29:34 +08:00
_probe_url = " ${ 1 :- $( server_smoke_url) } "
_probe_python = ""
2026-05-07 11:50:10 +08:00
2026-05-09 09:29:34 +08:00
if _probe_python = " $( server_probe_python) " ; then
" $_probe_python " -c "import sys, urllib.request; response = urllib.request.urlopen(sys.argv[1], timeout=3); raise SystemExit(0 if response.status == 200 else 1)" " $_probe_url " >/dev/null 2>& 1
2026-05-07 11:50:10 +08:00
return $?
fi
if command -v curl >/dev/null 2>& 1; then
2026-05-09 09:29:34 +08:00
curl --silent --fail --max-time 3 " $_probe_url " >/dev/null 2>& 1
2026-05-07 11:50:10 +08:00
return $?
fi
return 1
}
probe_server_ready( ) {
2026-05-09 09:29:34 +08:00
_health_url = " ${ 1 :- $( server_probe_url) } "
_smoke_url = " ${ 2 :- $( server_smoke_url) } "
2026-05-07 11:50:10 +08:00
2026-05-09 09:29:34 +08:00
probe_server_health " $_health_url " && probe_server_smoke " $_smoke_url "
2026-05-07 11:50:10 +08:00
}
2026-05-06 17:43:47 +08:00
prepare_web( ) {
info "Preparing web dependencies..."
(
cd " $SCRIPT_DIR /web "
2026-05-08 10:52:54 +08:00
./web_start.sh deps
2026-05-06 17:43:47 +08:00
)
}
prepare_server( ) {
info "Preparing server dependencies..."
(
cd " $SCRIPT_DIR /server "
2026-05-08 10:52:54 +08:00
./server_start.sh deps
2026-05-06 17:43:47 +08:00
)
}
start_web( ) {
prepare_web
cd " $SCRIPT_DIR /web "
2026-05-08 10:52:54 +08:00
exec ./web_start.sh start
2026-05-06 17:43:47 +08:00
}
start_server( ) {
prepare_server
cd " $SCRIPT_DIR /server "
2026-05-08 10:52:54 +08:00
exec ./server_start.sh start
2026-05-06 17:43:47 +08:00
}
start_setup_web( ) {
warn "Initial setup is not completed. Starting web only."
2026-05-08 10:52:54 +08:00
warn "Setup requires both .env completion and server/.secrets/admin.json."
warn "Finish the setup form first; the web setup bridge will start FastAPI after saving."
2026-05-06 17:43:47 +08:00
prepare_web
cd " $SCRIPT_DIR /web "
2026-05-08 10:52:54 +08:00
export X_FINANCIAL_FORCE_SETUP = true
exec ./web_start.sh start
2026-05-06 17:43:47 +08:00
}
start_all( ) {
2026-05-09 09:29:34 +08:00
server_pid = ""
started_server = false
probe_url = ""
smoke_url = ""
2026-05-06 17:43:47 +08:00
prepare_server
cleanup( ) {
2026-05-07 11:50:10 +08:00
if [ " $started_server " = true ] && [ -n " $server_pid " ] && kill -0 " $server_pid " 2>/dev/null; then
2026-05-06 17:43:47 +08:00
warn "Stopping FastAPI server..."
kill " $server_pid " 2>/dev/null || true
wait " $server_pid " 2>/dev/null || true
fi
}
trap cleanup EXIT INT TERM
2026-05-07 11:50:10 +08:00
probe_url = " $( server_probe_url) "
smoke_url = " $( server_smoke_url) "
if probe_server_ready " $probe_url " " $smoke_url " ; then
warn " FastAPI is already ready at $probe_url . Reusing the existing backend process. "
2026-05-09 09:29:34 +08:00
if [ " $APP_DEBUG " = "true" ] && [ " $EFFECTIVE_SERVER_RELOAD " != "true" ] ; then
2026-05-08 08:56:52 +08:00
warn "This backend may be stale because SERVER_RELOAD is disabled. If new API routes are missing, stop the old backend process and rerun ./start.sh."
fi
2026-05-07 11:50:10 +08:00
elif probe_server_health " $probe_url " ; then
error " An existing backend process is responding at $probe_url , but the smoke check failed at $smoke_url . Stop the old FastAPI process and rerun ./start.sh. "
else
info "Starting FastAPI server..."
(
cd " $SCRIPT_DIR /server "
2026-05-08 10:52:54 +08:00
./server_start.sh start
2026-05-07 11:50:10 +08:00
) &
server_pid = $!
started_server = true
fi
2026-05-06 17:43:47 +08:00
2026-05-07 11:50:10 +08:00
wait_for_server_ready( ) {
2026-05-09 09:29:34 +08:00
attempt = 1
max_attempts = " $SERVER_STARTUP_TIMEOUT "
2026-05-06 17:43:47 +08:00
2026-05-07 11:50:10 +08:00
info "Waiting for FastAPI readiness before starting the web frontend..."
2026-05-06 17:43:47 +08:00
while [ " $attempt " -le " $max_attempts " ] ; do
2026-05-07 11:50:10 +08:00
if probe_server_ready " $probe_url " " $smoke_url " ; then
info "FastAPI is ready. Starting web frontend next."
2026-05-06 17:43:47 +08:00
return 0
fi
2026-05-07 11:50:10 +08:00
if [ " $started_server " = true ] && ! kill -0 " $server_pid " 2>/dev/null; then
if probe_server_ready " $probe_url " " $smoke_url " ; then
warn " FastAPI is already available at $probe_url . Continuing with the existing process. "
started_server = false
server_pid = ""
return 0
fi
wait " $server_pid " 2>/dev/null || true
2026-05-08 10:52:54 +08:00
error "FastAPI process exited before becoming ready. Run ./server/server_start.sh start directly to inspect the backend error."
2026-05-06 17:43:47 +08:00
fi
sleep 1
attempt = $(( attempt + 1 ))
done
2026-05-07 11:50:10 +08:00
if probe_server_health " $probe_url " ; then
error " FastAPI answered health checks at $probe_url , but the smoke check failed at $smoke_url . The running backend is stale or incompatible. "
fi
error " FastAPI did not become ready within ${ SERVER_STARTUP_TIMEOUT } s. Inspect server/logs/app.log. "
2026-05-06 17:43:47 +08:00
}
2026-05-07 11:50:10 +08:00
wait_for_server_ready
2026-05-06 17:43:47 +08:00
2026-05-07 11:50:10 +08:00
prepare_web
2026-05-06 17:43:47 +08:00
info "Starting web frontend..."
cd " $SCRIPT_DIR /web "
2026-05-08 10:52:54 +08:00
./web_start.sh start
2026-05-06 17:43:47 +08:00
}
case " $MODE " in
web)
start_web
; ;
server)
start_server
; ;
all)
2026-05-08 10:52:54 +08:00
if setup_ready; then
2026-05-06 17:43:47 +08:00
start_all
else
start_setup_web
fi
; ;
*)
error " Unknown mode: $MODE . Use one of: web, server, all "
; ;
esac