fix: prevent Pascal chunk load failures
This commit is contained in:
parent
7ccbc87afd
commit
aedf590e1d
@ -213,10 +213,15 @@ sync_pascal_source() {
|
||||
--exclude '.env.production' \
|
||||
--exclude '.git/' \
|
||||
--exclude '.next/' \
|
||||
--exclude '.next-build/' \
|
||||
--exclude '.next-previous/' \
|
||||
--exclude '.turbo/' \
|
||||
--exclude 'node_modules/' \
|
||||
"$PASCAL_SOURCE_DIR/" "$PASCAL_INSTALL_DIR/"
|
||||
|
||||
}
|
||||
|
||||
write_pascal_source_version() {
|
||||
if git -C "$MAIN_APP_DIR" rev-parse --verify HEAD >/dev/null 2>&1; then
|
||||
git -C "$MAIN_APP_DIR" rev-parse HEAD >"$PASCAL_INSTALL_DIR/.pascal-source-version"
|
||||
fi
|
||||
@ -248,10 +253,21 @@ write_pascal_env() {
|
||||
}
|
||||
|
||||
build_pascal() {
|
||||
local editor_dir="$PASCAL_INSTALL_DIR/apps/editor"
|
||||
local current_build="$editor_dir/.next"
|
||||
local staged_build="$editor_dir/.next-build"
|
||||
local previous_build="$editor_dir/.next-previous"
|
||||
|
||||
mkdir -p "$PASCAL_DATA_DIR"
|
||||
chmod 700 "$PASCAL_DATA_DIR"
|
||||
export PATH="$(dirname "$PASCAL_BUN_BIN"):$PATH"
|
||||
log "Installing and building vendored Pascal service"
|
||||
|
||||
[ "$staged_build" != "$current_build" ] || die "Pascal staged build path is invalid"
|
||||
[ "$previous_build" != "$current_build" ] || die "Pascal previous build path is invalid"
|
||||
[ ! -e "$staged_build" ] || rm -r "$staged_build"
|
||||
[ ! -e "$previous_build" ] || rm -r "$previous_build"
|
||||
|
||||
(
|
||||
cd "$PASCAL_INSTALL_DIR"
|
||||
"$PASCAL_BUN_BIN" install --frozen-lockfile
|
||||
@ -259,19 +275,55 @@ build_pascal() {
|
||||
# shellcheck disable=SC1091
|
||||
source .env.production
|
||||
set +a
|
||||
"$PASCAL_BUN_BIN" x turbo run build --filter=editor --output-logs=errors-only
|
||||
PASCAL_NEXT_DIST_DIR=.next-build \
|
||||
"$PASCAL_BUN_BIN" x turbo run build --filter=editor --output-logs=errors-only --force
|
||||
)
|
||||
|
||||
[ -f "$staged_build/BUILD_ID" ] || die "Pascal staged build did not produce a BUILD_ID"
|
||||
if [ -d "$current_build/static" ]; then
|
||||
log "Retaining static chunks from the active Pascal build"
|
||||
mkdir -p "$staged_build/static"
|
||||
rsync -a --ignore-existing "$current_build/static/" "$staged_build/static/"
|
||||
find "$staged_build/static" -type f -mtime +7 -delete
|
||||
find "$staged_build/static" -depth -type d -empty -delete
|
||||
fi
|
||||
}
|
||||
|
||||
activate_pascal_build() {
|
||||
local editor_dir="$PASCAL_INSTALL_DIR/apps/editor"
|
||||
local current_build="$editor_dir/.next"
|
||||
local staged_build="$editor_dir/.next-build"
|
||||
local previous_build="$editor_dir/.next-previous"
|
||||
|
||||
[ -f "$staged_build/BUILD_ID" ] || die "Pascal staged build is unavailable"
|
||||
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 Pascal build"
|
||||
fi
|
||||
}
|
||||
|
||||
rollback_pascal_build() {
|
||||
local editor_dir="$PASCAL_INSTALL_DIR/apps/editor"
|
||||
local current_build="$editor_dir/.next"
|
||||
local previous_build="$editor_dir/.next-previous"
|
||||
|
||||
[ -e "$previous_build" ] || return 1
|
||||
[ ! -e "$current_build" ] || rm -r "$current_build"
|
||||
mv "$previous_build" "$current_build"
|
||||
}
|
||||
|
||||
start_pascal() {
|
||||
require_command pm2
|
||||
if pm2 describe "$PASCAL_PM2_NAME" >/dev/null 2>&1; then
|
||||
pm2 restart "$PASCAL_PM2_NAME"
|
||||
pm2 restart "$PASCAL_PM2_NAME" || return 1
|
||||
else
|
||||
pm2 start /usr/bin/bash \
|
||||
--name "$PASCAL_PM2_NAME" \
|
||||
--cwd "$PASCAL_INSTALL_DIR" \
|
||||
-- -lc "set -a; source '$PASCAL_INSTALL_DIR/.env.production'; set +a; export PATH='$(dirname "$PASCAL_BUN_BIN")':\$PATH; exec '$PASCAL_BUN_BIN' run --cwd apps/editor start -- -p '$PASCAL_PORT' -H 127.0.0.1"
|
||||
-- -lc "set -a; source '$PASCAL_INSTALL_DIR/.env.production'; set +a; export PATH='$(dirname "$PASCAL_BUN_BIN")':\$PATH; exec '$PASCAL_BUN_BIN' run --cwd apps/editor start -- -p '$PASCAL_PORT' -H 127.0.0.1" || return 1
|
||||
fi
|
||||
pm2 save
|
||||
}
|
||||
@ -397,10 +449,17 @@ main() {
|
||||
sync_pascal_source
|
||||
write_pascal_env "$secret"
|
||||
build_pascal
|
||||
start_pascal
|
||||
activate_pascal_build
|
||||
if ! start_pascal; then
|
||||
log "Pascal restart failed; rolling back the previous build"
|
||||
rollback_pascal_build || true
|
||||
start_pascal || true
|
||||
die "Pascal service failed to start after deployment"
|
||||
fi
|
||||
render_nginx "$secret"
|
||||
sleep 3
|
||||
check_deployment "$secret"
|
||||
write_pascal_source_version
|
||||
|
||||
if is_true "$RUN_AGENT_SMOKE_TEST"; then
|
||||
PASCAL_SMOKE_CONFIRM=1 \
|
||||
|
||||
2
services/pascal-editor/.gitignore
vendored
2
services/pascal-editor/.gitignore
vendored
@ -29,6 +29,8 @@ supabase/.temp/
|
||||
|
||||
# Build Outputs
|
||||
.next/
|
||||
.next-build/
|
||||
.next-previous/
|
||||
out/
|
||||
build
|
||||
dist
|
||||
|
||||
@ -2,9 +2,48 @@ import { Agentation } from 'agentation'
|
||||
import { GeistPixelSquare } from 'geist/font/pixel'
|
||||
import { Barlow } from 'next/font/google'
|
||||
import localFont from 'next/font/local'
|
||||
import Script from 'next/script'
|
||||
import { ClientBootstrap } from './client-bootstrap'
|
||||
import './globals.css'
|
||||
|
||||
const chunkRecoveryScript = `
|
||||
(() => {
|
||||
const marker = 'pascal:chunk-recovery-at'
|
||||
const recoveryParam = '__pascal_reload'
|
||||
const chunkError = /ChunkLoadError|Failed to load chunk|Loading chunk .* failed|CSS_CHUNK_LOAD_FAILED/i
|
||||
|
||||
const recover = (event) => {
|
||||
const reason = event?.reason
|
||||
const message = [
|
||||
event?.message,
|
||||
reason?.message,
|
||||
typeof reason === 'string' ? reason : '',
|
||||
].filter(Boolean).join(' ')
|
||||
if (!chunkError.test(message)) return
|
||||
|
||||
const now = Date.now()
|
||||
const lastRecovery = Number(sessionStorage.getItem(marker) || 0)
|
||||
if (now - lastRecovery < 60000) return
|
||||
|
||||
sessionStorage.setItem(marker, String(now))
|
||||
const url = new URL(window.location.href)
|
||||
url.searchParams.set(recoveryParam, String(now))
|
||||
window.location.replace(url.toString())
|
||||
}
|
||||
|
||||
window.addEventListener('error', recover, true)
|
||||
window.addEventListener('unhandledrejection', recover)
|
||||
|
||||
window.setTimeout(() => {
|
||||
sessionStorage.removeItem(marker)
|
||||
const url = new URL(window.location.href)
|
||||
if (!url.searchParams.has(recoveryParam)) return
|
||||
url.searchParams.delete(recoveryParam)
|
||||
window.history.replaceState(window.history.state, '', url.toString())
|
||||
}, 10000)
|
||||
})()
|
||||
`
|
||||
|
||||
const geistSans = localFont({
|
||||
src: './fonts/GeistVF.woff',
|
||||
variable: '--font-geist-sans',
|
||||
@ -32,6 +71,9 @@ export default function RootLayout({
|
||||
lang="zh-CN"
|
||||
>
|
||||
<head>
|
||||
<Script id="pascal-chunk-recovery" strategy="beforeInteractive">
|
||||
{chunkRecoveryScript}
|
||||
</Script>
|
||||
{process.env.NODE_ENV === 'development' && (
|
||||
<script async crossOrigin="anonymous" src="//unpkg.com/react-scan/dist/auto.global.js" />
|
||||
)}
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const configuredBasePath = process.env.NEXT_PUBLIC_PASCAL_BASE_PATH?.trim() ?? ''
|
||||
const basePath = configuredBasePath ? `/${configuredBasePath.replace(/^\/+|\/+$/g, '')}` : undefined
|
||||
const pascalRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..')
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
basePath,
|
||||
distDir: process.env.PASCAL_NEXT_DIST_DIR?.trim() || '.next',
|
||||
logging: {
|
||||
browserToTerminal: true,
|
||||
},
|
||||
@ -21,6 +25,7 @@ const nextConfig: NextConfig = {
|
||||
'@dgreenheck/ez-tree',
|
||||
],
|
||||
turbopack: {
|
||||
root: pascalRoot,
|
||||
resolveAlias: {
|
||||
react: './node_modules/react',
|
||||
three: './node_modules/three',
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { Howl } from 'howler'
|
||||
import useAudio from '../store/use-audio'
|
||||
import { withEditorBasePath } from './public-asset-path'
|
||||
|
||||
// Per-sound variation config. Playback rate also shifts pitch (one semitone ≈ 1.0595×),
|
||||
// so a rate range of ~0.88–1.12 reads as a subtle ±2 semitones — enough to kill the
|
||||
@ -136,14 +137,14 @@ const lastVariation = new Map<SFXName, number>()
|
||||
// Initialize all sounds
|
||||
Object.entries(SFX).forEach(([name, config]) => {
|
||||
const sources = Array.isArray(config.src) ? config.src : [config.src]
|
||||
const sounds = sources.map(
|
||||
(src) =>
|
||||
new Howl({
|
||||
src: [src],
|
||||
preload: true,
|
||||
volume: 0.5, // Will be adjusted by the bus
|
||||
}),
|
||||
)
|
||||
const sounds = sources.map((src) => {
|
||||
const resolvedSrc = withEditorBasePath(src)
|
||||
return new Howl({
|
||||
src: [resolvedSrc],
|
||||
preload: true,
|
||||
volume: 0.5, // Will be adjusted by the bus
|
||||
})
|
||||
})
|
||||
sfxCache.set(name as SFXName, sounds)
|
||||
})
|
||||
|
||||
|
||||
@ -5,7 +5,13 @@
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"inputs": ["$TURBO_DEFAULT$", ".env*"],
|
||||
"outputs": [".next/**", "!.next/cache/**", "dist/**"],
|
||||
"outputs": [
|
||||
".next/**",
|
||||
"!.next/cache/**",
|
||||
".next-build/**",
|
||||
"!.next-build/cache/**",
|
||||
"dist/**"
|
||||
],
|
||||
"env": [
|
||||
"SKIP_ENV_VALIDATION",
|
||||
"BETTER_AUTH_SECRET",
|
||||
@ -28,6 +34,7 @@
|
||||
"PASCAL_DB_PATH",
|
||||
"PASCAL_MCP_COMMAND",
|
||||
"PASCAL_MCP_ENTRY",
|
||||
"PASCAL_NEXT_DIST_DIR",
|
||||
"NEXT_PUBLIC_PASCAL_BASE_PATH",
|
||||
"PASCAL_REQUIRE_HOST_USER"
|
||||
]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user