fix: prevent white screens during production rebuilds
This commit is contained in:
parent
abc6a7266a
commit
3c1b84ccd0
@ -29,6 +29,7 @@ function resolvePascalEditorFrameSources(isDev: boolean) {
|
||||
}
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
distDir: process.env.NEXT_DIST_DIR?.trim() || ".next",
|
||||
poweredByHeader: false,
|
||||
allowedDevOrigins: ["127.0.0.1"],
|
||||
// 禁用 cacheComponents 以支持 dynamic = "force-dynamic"
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
"prebuild": "node scripts/security/auto-enable-perf-guard.mjs",
|
||||
"build": "next build",
|
||||
"build:with-migrate": "pnpm db:migrate && next build",
|
||||
"postbuild": "find .next/static -type f -name '*.map' -delete 2>/dev/null || true",
|
||||
"postbuild": "find \"${NEXT_DIST_DIR:-.next}/static\" -type f -name '*.map' -delete 2>/dev/null || true",
|
||||
"start": "next start",
|
||||
"lint": "pnpm exec ultracite check",
|
||||
"check:logs": "node scripts/check-log-hygiene.mjs && tsx scripts/check-log-redaction.ts",
|
||||
|
||||
@ -10,6 +10,7 @@ MAIN_PM2_NAME="${MAIN_PM2_NAME:-ai-chatbot}"
|
||||
MAIN_INTERNAL_URL="${MAIN_INTERNAL_URL:-http://127.0.0.1:3125}"
|
||||
MAIN_NGINX_UPSTREAM="${MAIN_NGINX_UPSTREAM:-127.0.0.1:3125}"
|
||||
BUILD_MAIN_APP="${BUILD_MAIN_APP:-true}"
|
||||
MAIN_STATIC_RETENTION_DAYS="${MAIN_STATIC_RETENTION_DAYS:-7}"
|
||||
|
||||
PASCAL_INSTALL_DIR="${PASCAL_INSTALL_DIR:-/opt/pascal-editor}"
|
||||
PASCAL_SOURCE_DIR="${PASCAL_SOURCE_DIR:-$MAIN_APP_DIR/services/pascal-editor}"
|
||||
@ -137,9 +138,20 @@ install_rsync() {
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq rsync
|
||||
}
|
||||
|
||||
restart_main_app() {
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$MAIN_ENV_FILE"
|
||||
set +a
|
||||
pm2 restart "$MAIN_PM2_NAME" --update-env
|
||||
}
|
||||
|
||||
configure_main_app() {
|
||||
local secret="$1"
|
||||
local next_env_backup=""
|
||||
local current_build="$MAIN_APP_DIR/.next"
|
||||
local staged_build="$MAIN_APP_DIR/.next-build"
|
||||
local previous_build="$MAIN_APP_DIR/.next-previous"
|
||||
mkdir -p "$PASCAL_BACKUP_DIR"
|
||||
if [ -f "$MAIN_ENV_FILE" ]; then
|
||||
cp "$MAIN_ENV_FILE" "$PASCAL_BACKUP_DIR/main-env-$(date +%Y%m%d%H%M%S).bak"
|
||||
@ -155,7 +167,11 @@ configure_main_app() {
|
||||
|
||||
require_command pnpm
|
||||
require_command pm2
|
||||
[[ "$MAIN_STATIC_RETENTION_DAYS" =~ ^[0-9]+$ ]] ||
|
||||
die "MAIN_STATIC_RETENTION_DAYS must be a non-negative integer"
|
||||
log "Building main application in $MAIN_APP_DIR"
|
||||
[ ! -e "$staged_build" ] || rm -r "$staged_build"
|
||||
[ ! -e "$previous_build" ] || rm -r "$previous_build"
|
||||
if [ -f "$MAIN_APP_DIR/next-env.d.ts" ]; then
|
||||
next_env_backup="$(mktemp)"
|
||||
cp "$MAIN_APP_DIR/next-env.d.ts" "$next_env_backup"
|
||||
@ -169,21 +185,56 @@ configure_main_app() {
|
||||
source "$MAIN_ENV_FILE"
|
||||
set +a
|
||||
DISABLE_PERF_GUARD_AUTO_INSTALL=1 pnpm install --frozen-lockfile --prod=false
|
||||
DISABLE_PERF_GUARD_AUTO_INSTALL=1 pnpm build
|
||||
NEXT_DIST_DIR=.next-build DISABLE_PERF_GUARD_AUTO_INSTALL=1 pnpm build
|
||||
) || build_status=$?
|
||||
set -e
|
||||
if [ -n "$next_env_backup" ]; then
|
||||
cp "$next_env_backup" "$MAIN_APP_DIR/next-env.d.ts"
|
||||
rm -f "$next_env_backup"
|
||||
fi
|
||||
[ "$build_status" -eq 0 ] || die "Main application build failed"
|
||||
(
|
||||
set -a
|
||||
# shellcheck disable=SC1090
|
||||
source "$MAIN_ENV_FILE"
|
||||
set +a
|
||||
pm2 restart "$MAIN_PM2_NAME" --update-env
|
||||
)
|
||||
[ "$build_status" -eq 0 ] || {
|
||||
rm -rf "$staged_build"
|
||||
die "Main application build failed"
|
||||
}
|
||||
[ -f "$staged_build/BUILD_ID" ] || die "Main staged build did not produce a BUILD_ID"
|
||||
|
||||
if [ -d "$current_build/static" ]; then
|
||||
log "Retaining static chunks from the active main build"
|
||||
mkdir -p "$staged_build/static"
|
||||
rsync -a --ignore-existing "$current_build/static/" "$staged_build/static/"
|
||||
find "$staged_build/static" -type f -mtime +"$MAIN_STATIC_RETENTION_DAYS" -delete
|
||||
find "$staged_build/static" -depth -type d -empty -delete
|
||||
fi
|
||||
|
||||
if [ -e "$current_build" ]; then
|
||||
mv "$current_build" "$previous_build"
|
||||
fi
|
||||
if ! mv "$staged_build" "$current_build"; then
|
||||
[ ! -e "$previous_build" ] || mv "$previous_build" "$current_build"
|
||||
die "Could not activate the staged main build"
|
||||
fi
|
||||
|
||||
if ! restart_main_app; then
|
||||
log "Main restart failed; rolling back the previous build"
|
||||
rm -rf "$current_build"
|
||||
[ ! -e "$previous_build" ] || mv "$previous_build" "$current_build"
|
||||
restart_main_app || true
|
||||
die "Main application failed to restart"
|
||||
fi
|
||||
|
||||
local health_attempt
|
||||
for health_attempt in $(seq 1 30); do
|
||||
if curl -fsS "$MAIN_INTERNAL_URL/ping" >/dev/null 2>&1; then
|
||||
return
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
log "Main health check failed; rolling back the previous build"
|
||||
rm -rf "$current_build"
|
||||
[ ! -e "$previous_build" ] || mv "$previous_build" "$current_build"
|
||||
restart_main_app || true
|
||||
die "Main application did not become healthy after deployment"
|
||||
}
|
||||
|
||||
sync_pascal_source() {
|
||||
@ -444,6 +495,7 @@ main() {
|
||||
fi
|
||||
|
||||
[ "$(id -u)" -eq 0 ] || die "Production deployment must run as root"
|
||||
install_rsync
|
||||
configure_main_app "$secret"
|
||||
install_bun
|
||||
sync_pascal_source
|
||||
|
||||
@ -21,3 +21,23 @@ test("requires an explicit environment switch for local fallbacks", () => {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("production deploy stages the main build and retains active static chunks", () => {
|
||||
const nextConfig = readFileSync(
|
||||
resolve(process.cwd(), "next.config.ts"),
|
||||
"utf8"
|
||||
);
|
||||
const deployScript = readFileSync(
|
||||
resolve(process.cwd(), "scripts/deploy-pascal-production.sh"),
|
||||
"utf8"
|
||||
);
|
||||
|
||||
expect(nextConfig).toContain(
|
||||
'distDir: process.env.NEXT_DIST_DIR?.trim() || ".next"'
|
||||
);
|
||||
expect(deployScript).toContain("NEXT_DIST_DIR=.next-build");
|
||||
expect(deployScript).toContain(
|
||||
'rsync -a --ignore-existing "$current_build/static/" "$staged_build/static/"'
|
||||
);
|
||||
expect(deployScript).toContain('curl -fsS "$MAIN_INTERNAL_URL/ping"');
|
||||
});
|
||||
|
||||
@ -29,7 +29,9 @@
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
"next.config.js",
|
||||
".next/dev/types/**/*.ts"
|
||||
".next/dev/types/**/*.ts",
|
||||
".next-build/types/**/*.ts",
|
||||
".next-build/dev/types/**/*.ts"
|
||||
],
|
||||
"exclude": ["node_modules", "services/pascal-editor"]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user