chore: release v1.0.3
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,10 +29,17 @@ flutter build web --release \
|
||||
"--dart-define=APP_VERSION=${TAG}"
|
||||
cd ..
|
||||
|
||||
# Build marketing site
|
||||
cd web
|
||||
npm ci --prefer-offline
|
||||
npm run build
|
||||
cd ..
|
||||
|
||||
# Package artifacts
|
||||
mkdir -p dist
|
||||
mv backend/jiu-server dist/jiu-server
|
||||
tar -czf dist/web.tar.gz -C client/build web
|
||||
tar -czf dist/marketing.tar.gz -C web/dist .
|
||||
|
||||
echo "==> compile: done — dist/ contents:"
|
||||
ls -lh dist/
|
||||
|
||||
@@ -10,7 +10,7 @@ TAG="$1"
|
||||
echo "==> deploy: tag=${TAG}"
|
||||
|
||||
# Download from Forgejo if dist/ was not built in this job
|
||||
if [ ! -f dist/jiu-server ] || [ ! -f dist/web.tar.gz ] || [ ! -f dist/configs.tar.gz ]; then
|
||||
if [ ! -f dist/jiu-server ] || [ ! -f dist/web.tar.gz ] || [ ! -f dist/configs.tar.gz ] || [ ! -f dist/marketing.tar.gz ]; then
|
||||
echo "==> deploy: dist/ incomplete — downloading assets for ${TAG} from Forgejo"
|
||||
mkdir -p dist
|
||||
|
||||
@@ -57,6 +57,11 @@ rm -rf /tmp/jiu-web-new
|
||||
mkdir -p /tmp/jiu-web-new
|
||||
tar -xzf dist/web.tar.gz -C /tmp/jiu-web-new --strip-components=1
|
||||
|
||||
# Extract marketing site
|
||||
rm -rf /tmp/jiu-marketing-new
|
||||
mkdir -p /tmp/jiu-marketing-new
|
||||
tar -xzf dist/marketing.tar.gz -C /tmp/jiu-marketing-new
|
||||
|
||||
# Setup SSH
|
||||
mkdir -p ~/.ssh
|
||||
printf '%s' "${EC2_SSH_KEY}" > ~/.ssh/ec2_deploy.pem
|
||||
@@ -81,9 +86,9 @@ ${SCP} /tmp/jiu-configs/deploy/nginx-jiu.conf "${EC2_USER}@${EC2_HOST}:/tmp/ngin
|
||||
rsync -avz --delete -e "ssh -i ~/.ssh/ec2_deploy.pem -o StrictHostKeyChecking=no" \
|
||||
/tmp/jiu-web-new/ "${EC2_USER}@${EC2_HOST}:/tmp/jiu-web-new/"
|
||||
|
||||
# Upload marketing site (from repo checkout)
|
||||
# Upload marketing site (built by compile.sh)
|
||||
rsync -avz --delete -e "ssh -i ~/.ssh/ec2_deploy.pem -o StrictHostKeyChecking=no" \
|
||||
web/ "${EC2_USER}@${EC2_HOST}:/tmp/jiu-marketing-new/"
|
||||
/tmp/jiu-marketing-new/ "${EC2_USER}@${EC2_HOST}:/tmp/jiu-marketing-new/"
|
||||
|
||||
echo "==> deploy: running remote deploy"
|
||||
|
||||
|
||||
@@ -121,6 +121,7 @@ upload_asset() {
|
||||
|
||||
upload_asset dist/jiu-server
|
||||
upload_asset dist/web.tar.gz
|
||||
upload_asset dist/marketing.tar.gz
|
||||
upload_asset dist/configs.tar.gz
|
||||
upload_asset dist/jiu-windows-x64.zip
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env node
|
||||
// dev-proxy.js — 本地开发反向代理
|
||||
// localhost:3000 → 营销站 :3001 / Flutter Web :8888
|
||||
// /app/* 路由到 Flutter,其余路由到 Eleventy
|
||||
// 自动把 Flutter index.html 里的 base href="/" 改写为 /app/,使静态资源请求正确走代理
|
||||
|
||||
const http = require('http');
|
||||
const net = require('net');
|
||||
|
||||
const PROXY_PORT = parseInt(process.env.PROXY_PORT || '3000', 10);
|
||||
const ELEVENTY_PORT = parseInt(process.env.ELEVENTY_PORT || '3001', 10);
|
||||
const FLUTTER_PORT = parseInt(process.env.FLUTTER_PORT || '9090', 10);
|
||||
const BACKEND_PORT = parseInt(process.env.BACKEND_PORT || '8080', 10);
|
||||
|
||||
function isFlutter(url) {
|
||||
return url === '/app' || url.startsWith('/app/');
|
||||
}
|
||||
|
||||
function isBackend(url) {
|
||||
return url.startsWith('/api/') || url === '/health' || url.startsWith('/images/');
|
||||
}
|
||||
|
||||
function stripApp(url) {
|
||||
return url.slice(4) || '/';
|
||||
}
|
||||
|
||||
function proxyHttp(req, res, port, path) {
|
||||
const opts = {
|
||||
hostname: 'localhost',
|
||||
port,
|
||||
path,
|
||||
method: req.method,
|
||||
headers: { ...req.headers, host: `localhost:${port}` },
|
||||
};
|
||||
|
||||
const pr = http.request(opts, (upstream) => {
|
||||
const ct = upstream.headers['content-type'] || '';
|
||||
|
||||
if (port === FLUTTER_PORT && ct.includes('text/html')) {
|
||||
let body = '';
|
||||
upstream.setEncoding('utf8');
|
||||
upstream.on('data', (c) => { body += c; });
|
||||
upstream.on('end', () => {
|
||||
// rewrite base href so Flutter asset requests stay under /app/
|
||||
body = body.replace(/(<base\s+href=")[^"]*(")/i, '$1/app/$2');
|
||||
const headers = { ...upstream.headers };
|
||||
headers['content-length'] = Buffer.byteLength(body);
|
||||
delete headers['transfer-encoding'];
|
||||
res.writeHead(upstream.statusCode, headers);
|
||||
res.end(body);
|
||||
});
|
||||
} else {
|
||||
res.writeHead(upstream.statusCode, upstream.headers);
|
||||
upstream.pipe(res, { end: true });
|
||||
}
|
||||
});
|
||||
|
||||
pr.on('error', () => res.writeHead(502).end('upstream error'));
|
||||
req.pipe(pr, { end: true });
|
||||
}
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
if (isFlutter(req.url)) {
|
||||
proxyHttp(req, res, FLUTTER_PORT, stripApp(req.url));
|
||||
} else if (isBackend(req.url)) {
|
||||
proxyHttp(req, res, BACKEND_PORT, req.url);
|
||||
} else {
|
||||
proxyHttp(req, res, ELEVENTY_PORT, req.url);
|
||||
}
|
||||
});
|
||||
|
||||
// WebSocket 透传(Eleventy 热重载 / Flutter DevTools)
|
||||
server.on('upgrade', (req, socket, head) => {
|
||||
const port = isFlutter(req.url) ? FLUTTER_PORT
|
||||
: isBackend(req.url) ? BACKEND_PORT
|
||||
: ELEVENTY_PORT;
|
||||
const path = isFlutter(req.url) ? stripApp(req.url) : req.url;
|
||||
|
||||
const conn = net.createConnection(port, 'localhost', () => {
|
||||
conn.write(`${req.method} ${path} HTTP/1.1\r\n`);
|
||||
for (const [k, v] of Object.entries(req.headers)) {
|
||||
conn.write(`${k}: ${v}\r\n`);
|
||||
}
|
||||
conn.write('\r\n');
|
||||
if (head && head.length) conn.write(head);
|
||||
socket.pipe(conn);
|
||||
conn.pipe(socket);
|
||||
});
|
||||
|
||||
conn.on('error', () => socket.destroy());
|
||||
socket.on('error', () => conn.destroy());
|
||||
});
|
||||
|
||||
server.listen(PROXY_PORT, () => {
|
||||
console.log(`[proxy] http://localhost:${PROXY_PORT}`);
|
||||
console.log(` / → Eleventy :${ELEVENTY_PORT}`);
|
||||
console.log(` /app/ → Flutter Web :${FLUTTER_PORT}`);
|
||||
});
|
||||
+93
-7
@@ -32,6 +32,9 @@ BACKEND_PID_FILE="$LOG_DIR/backend.pid"
|
||||
BACKEND_HASH_FILE="$LOG_DIR/backend.hash"
|
||||
WEB_LOG="$LOG_DIR/web.log"
|
||||
WEB_PID_FILE="$LOG_DIR/web.pid"
|
||||
ELEVENTY_LOG="$LOG_DIR/eleventy.log"
|
||||
ELEVENTY_PID_FILE="$LOG_DIR/eleventy.pid"
|
||||
PROXY_PID_FILE="$LOG_DIR/proxy.pid"
|
||||
|
||||
mkdir -p "$LOG_DIR"
|
||||
|
||||
@@ -115,6 +118,79 @@ RUN_BACKEND=true
|
||||
RUN_FRONTEND=true # macOS
|
||||
RUN_WEB=false
|
||||
|
||||
cmd_marketing() {
|
||||
local WEB_DIR="$ROOT/web"
|
||||
local FLUTTER_WEB_PORT=9090
|
||||
local ELEVENTY_PORT=3001
|
||||
local PROXY_PORT=3000
|
||||
|
||||
# 检查依赖
|
||||
if ! command -v node &>/dev/null; then
|
||||
error "未找到 node,请安装 Node.js"
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v flutter &>/dev/null; then
|
||||
error "未找到 flutter"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 清理旧进程
|
||||
for pid_file in "$ELEVENTY_PID_FILE" "$PROXY_PID_FILE" "$WEB_PID_FILE"; do
|
||||
if [ -f "$pid_file" ]; then
|
||||
pid=$(cat "$pid_file")
|
||||
kill "$pid" 2>/dev/null || true
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
done
|
||||
for port in $ELEVENTY_PORT $FLUTTER_WEB_PORT $PROXY_PORT; do
|
||||
pids=$(lsof -ti:"$port" 2>/dev/null || true)
|
||||
[ -n "$pids" ] && echo "$pids" | xargs kill -9 2>/dev/null || true
|
||||
done
|
||||
sleep 0.5
|
||||
|
||||
# 启动后端(如已运行则跳过)
|
||||
if ! curl -s -o /dev/null http://localhost:8080/health 2>/dev/null; then
|
||||
info "启动后端..."
|
||||
cd "$BACKEND_DIR"
|
||||
go run main.go >>"$BACKEND_LOG" 2>&1 &
|
||||
local bp=$!
|
||||
echo "$bp" > "$BACKEND_PID_FILE"
|
||||
wait_backend
|
||||
cd "$ROOT"
|
||||
else
|
||||
success "后端已在运行 → http://localhost:8080"
|
||||
fi
|
||||
|
||||
# 启动 Eleventy(宣传站)在后台,3001 口
|
||||
info "启动 Eleventy(宣传站后台)→ :${ELEVENTY_PORT}..."
|
||||
cd "$WEB_DIR"
|
||||
npx eleventy --serve --port=$ELEVENTY_PORT >>"$ELEVENTY_LOG" 2>&1 &
|
||||
echo "$!" > "$ELEVENTY_PID_FILE"
|
||||
cd "$ROOT"
|
||||
|
||||
# 启动代理(后台)
|
||||
info "启动代理(后台)→ http://localhost:${PROXY_PORT}..."
|
||||
PROXY_PORT=$PROXY_PORT ELEVENTY_PORT=$ELEVENTY_PORT FLUTTER_PORT=$FLUTTER_WEB_PORT \
|
||||
node "$ROOT/scripts/dev-proxy.js" >>"$LOG_DIR/proxy.log" 2>&1 &
|
||||
echo "$!" > "$PROXY_PID_FILE"
|
||||
sleep 1
|
||||
|
||||
echo ""
|
||||
success "代理已就绪:http://localhost:${PROXY_PORT}(宣传站)/ http://localhost:${PROXY_PORT}/app/(Flutter)"
|
||||
echo -e " ${CYAN}Eleventy 日志:tail -f $ELEVENTY_LOG${NC}"
|
||||
echo ""
|
||||
info "在前台启动 Flutter Web(支持热重载,按 r 重载,按 q 退出)..."
|
||||
echo ""
|
||||
|
||||
# Flutter 在前台运行,支持交互热重载
|
||||
cd "$CLIENT_DIR"
|
||||
flutter pub get
|
||||
flutter run -d chrome --web-port "$FLUTTER_WEB_PORT" \
|
||||
"--dart-define=BASE_URL=http://localhost:8080" \
|
||||
"--dart-define=PUBLIC_URL=http://localhost:${PROXY_PORT}" \
|
||||
"--dart-define=APP_VERSION=dev"
|
||||
}
|
||||
|
||||
show_help() {
|
||||
echo "用法: sh scripts/dev.sh <命令> [选项]"
|
||||
echo ""
|
||||
@@ -126,6 +202,7 @@ show_help() {
|
||||
echo " --backend-only 仅启动后端"
|
||||
echo " --frontend-only 仅启动前端(macOS)"
|
||||
echo " --web-only 仅启动前端(Web)"
|
||||
echo " marketing 宣传站 + Flutter Web + 代理(localhost:3000,/app/ 路由到 Flutter)"
|
||||
echo ""
|
||||
echo "数据库命令:"
|
||||
echo " seed <shop_code> 写入指定门店测试数据(如 S001)"
|
||||
@@ -139,6 +216,8 @@ show_help() {
|
||||
}
|
||||
|
||||
case "$COMMAND" in
|
||||
marketing)
|
||||
;; # 在底部所有函数定义完后执行
|
||||
run)
|
||||
case "${2:-}" in
|
||||
--force) FORCE=true ;;
|
||||
@@ -223,12 +302,13 @@ esac
|
||||
cleanup() {
|
||||
echo ""
|
||||
info "正在关闭服务..."
|
||||
if [ -f "$WEB_PID_FILE" ]; then
|
||||
pid=$(cat "$WEB_PID_FILE")
|
||||
kill "$pid" 2>/dev/null || true
|
||||
rm -f "$WEB_PID_FILE"
|
||||
info "Web 服务已停止"
|
||||
fi
|
||||
for pid_file in "$WEB_PID_FILE" "$ELEVENTY_PID_FILE" "$PROXY_PID_FILE"; do
|
||||
if [ -f "$pid_file" ]; then
|
||||
pid=$(cat "$pid_file")
|
||||
kill "$pid" 2>/dev/null || true
|
||||
rm -f "$pid_file"
|
||||
fi
|
||||
done
|
||||
info "后端继续在后台运行。如需停止:sh scripts/dev.sh stop"
|
||||
exit 0
|
||||
}
|
||||
@@ -284,7 +364,7 @@ wait_backend() {
|
||||
info "等待后端启动..."
|
||||
local i=0
|
||||
while [ $i -lt 30 ]; do
|
||||
if curl -s -o /dev/null http://localhost:8080/api/v1/auth/login 2>/dev/null; then
|
||||
if curl -s -o /dev/null http://localhost:8080/health 2>/dev/null; then
|
||||
success "后端已就绪 → http://localhost:8080"
|
||||
return 0
|
||||
fi
|
||||
@@ -356,6 +436,12 @@ start_web_bg() {
|
||||
}
|
||||
|
||||
# ════════════════════════════════════════════════════════
|
||||
|
||||
if [ "$COMMAND" = "marketing" ]; then
|
||||
cmd_marketing
|
||||
exit 0
|
||||
fi
|
||||
|
||||
check_deps
|
||||
|
||||
if [ "$RUN_BACKEND" = true ]; then
|
||||
|
||||
Reference in New Issue
Block a user