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 = {
|
const nextConfig: NextConfig = {
|
||||||
|
distDir: process.env.NEXT_DIST_DIR?.trim() || ".next",
|
||||||
poweredByHeader: false,
|
poweredByHeader: false,
|
||||||
allowedDevOrigins: ["127.0.0.1"],
|
allowedDevOrigins: ["127.0.0.1"],
|
||||||
// 禁用 cacheComponents 以支持 dynamic = "force-dynamic"
|
// 禁用 cacheComponents 以支持 dynamic = "force-dynamic"
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
"prebuild": "node scripts/security/auto-enable-perf-guard.mjs",
|
"prebuild": "node scripts/security/auto-enable-perf-guard.mjs",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"build:with-migrate": "pnpm db:migrate && 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",
|
"start": "next start",
|
||||||
"lint": "pnpm exec ultracite check",
|
"lint": "pnpm exec ultracite check",
|
||||||
"check:logs": "node scripts/check-log-hygiene.mjs && tsx scripts/check-log-redaction.ts",
|
"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_INTERNAL_URL="${MAIN_INTERNAL_URL:-http://127.0.0.1:3125}"
|
||||||
MAIN_NGINX_UPSTREAM="${MAIN_NGINX_UPSTREAM:-127.0.0.1:3125}"
|
MAIN_NGINX_UPSTREAM="${MAIN_NGINX_UPSTREAM:-127.0.0.1:3125}"
|
||||||
BUILD_MAIN_APP="${BUILD_MAIN_APP:-true}"
|
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_INSTALL_DIR="${PASCAL_INSTALL_DIR:-/opt/pascal-editor}"
|
||||||
PASCAL_SOURCE_DIR="${PASCAL_SOURCE_DIR:-$MAIN_APP_DIR/services/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
|
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() {
|
configure_main_app() {
|
||||||
local secret="$1"
|
local secret="$1"
|
||||||
local next_env_backup=""
|
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"
|
mkdir -p "$PASCAL_BACKUP_DIR"
|
||||||
if [ -f "$MAIN_ENV_FILE" ]; then
|
if [ -f "$MAIN_ENV_FILE" ]; then
|
||||||
cp "$MAIN_ENV_FILE" "$PASCAL_BACKUP_DIR/main-env-$(date +%Y%m%d%H%M%S).bak"
|
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 pnpm
|
||||||
require_command pm2
|
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"
|
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
|
if [ -f "$MAIN_APP_DIR/next-env.d.ts" ]; then
|
||||||
next_env_backup="$(mktemp)"
|
next_env_backup="$(mktemp)"
|
||||||
cp "$MAIN_APP_DIR/next-env.d.ts" "$next_env_backup"
|
cp "$MAIN_APP_DIR/next-env.d.ts" "$next_env_backup"
|
||||||
@ -169,21 +185,56 @@ configure_main_app() {
|
|||||||
source "$MAIN_ENV_FILE"
|
source "$MAIN_ENV_FILE"
|
||||||
set +a
|
set +a
|
||||||
DISABLE_PERF_GUARD_AUTO_INSTALL=1 pnpm install --frozen-lockfile --prod=false
|
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=$?
|
) || build_status=$?
|
||||||
set -e
|
set -e
|
||||||
if [ -n "$next_env_backup" ]; then
|
if [ -n "$next_env_backup" ]; then
|
||||||
cp "$next_env_backup" "$MAIN_APP_DIR/next-env.d.ts"
|
cp "$next_env_backup" "$MAIN_APP_DIR/next-env.d.ts"
|
||||||
rm -f "$next_env_backup"
|
rm -f "$next_env_backup"
|
||||||
fi
|
fi
|
||||||
[ "$build_status" -eq 0 ] || die "Main application build failed"
|
[ "$build_status" -eq 0 ] || {
|
||||||
(
|
rm -rf "$staged_build"
|
||||||
set -a
|
die "Main application build failed"
|
||||||
# shellcheck disable=SC1090
|
}
|
||||||
source "$MAIN_ENV_FILE"
|
[ -f "$staged_build/BUILD_ID" ] || die "Main staged build did not produce a BUILD_ID"
|
||||||
set +a
|
|
||||||
pm2 restart "$MAIN_PM2_NAME" --update-env
|
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() {
|
sync_pascal_source() {
|
||||||
@ -444,6 +495,7 @@ main() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
[ "$(id -u)" -eq 0 ] || die "Production deployment must run as root"
|
[ "$(id -u)" -eq 0 ] || die "Production deployment must run as root"
|
||||||
|
install_rsync
|
||||||
configure_main_app "$secret"
|
configure_main_app "$secret"
|
||||||
install_bun
|
install_bun
|
||||||
sync_pascal_source
|
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",
|
"**/*.tsx",
|
||||||
".next/types/**/*.ts",
|
".next/types/**/*.ts",
|
||||||
"next.config.js",
|
"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"]
|
"exclude": ["node_modules", "services/pascal-editor"]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user