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 '.env.production' \
|
||||||
--exclude '.git/' \
|
--exclude '.git/' \
|
||||||
--exclude '.next/' \
|
--exclude '.next/' \
|
||||||
|
--exclude '.next-build/' \
|
||||||
|
--exclude '.next-previous/' \
|
||||||
--exclude '.turbo/' \
|
--exclude '.turbo/' \
|
||||||
--exclude 'node_modules/' \
|
--exclude 'node_modules/' \
|
||||||
"$PASCAL_SOURCE_DIR/" "$PASCAL_INSTALL_DIR/"
|
"$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
|
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"
|
git -C "$MAIN_APP_DIR" rev-parse HEAD >"$PASCAL_INSTALL_DIR/.pascal-source-version"
|
||||||
fi
|
fi
|
||||||
@ -248,10 +253,21 @@ write_pascal_env() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
build_pascal() {
|
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"
|
mkdir -p "$PASCAL_DATA_DIR"
|
||||||
chmod 700 "$PASCAL_DATA_DIR"
|
chmod 700 "$PASCAL_DATA_DIR"
|
||||||
export PATH="$(dirname "$PASCAL_BUN_BIN"):$PATH"
|
export PATH="$(dirname "$PASCAL_BUN_BIN"):$PATH"
|
||||||
log "Installing and building vendored Pascal service"
|
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"
|
cd "$PASCAL_INSTALL_DIR"
|
||||||
"$PASCAL_BUN_BIN" install --frozen-lockfile
|
"$PASCAL_BUN_BIN" install --frozen-lockfile
|
||||||
@ -259,19 +275,55 @@ build_pascal() {
|
|||||||
# shellcheck disable=SC1091
|
# shellcheck disable=SC1091
|
||||||
source .env.production
|
source .env.production
|
||||||
set +a
|
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() {
|
start_pascal() {
|
||||||
require_command pm2
|
require_command pm2
|
||||||
if pm2 describe "$PASCAL_PM2_NAME" >/dev/null 2>&1; then
|
if pm2 describe "$PASCAL_PM2_NAME" >/dev/null 2>&1; then
|
||||||
pm2 restart "$PASCAL_PM2_NAME"
|
pm2 restart "$PASCAL_PM2_NAME" || return 1
|
||||||
else
|
else
|
||||||
pm2 start /usr/bin/bash \
|
pm2 start /usr/bin/bash \
|
||||||
--name "$PASCAL_PM2_NAME" \
|
--name "$PASCAL_PM2_NAME" \
|
||||||
--cwd "$PASCAL_INSTALL_DIR" \
|
--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
|
fi
|
||||||
pm2 save
|
pm2 save
|
||||||
}
|
}
|
||||||
@ -397,10 +449,17 @@ main() {
|
|||||||
sync_pascal_source
|
sync_pascal_source
|
||||||
write_pascal_env "$secret"
|
write_pascal_env "$secret"
|
||||||
build_pascal
|
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"
|
render_nginx "$secret"
|
||||||
sleep 3
|
sleep 3
|
||||||
check_deployment "$secret"
|
check_deployment "$secret"
|
||||||
|
write_pascal_source_version
|
||||||
|
|
||||||
if is_true "$RUN_AGENT_SMOKE_TEST"; then
|
if is_true "$RUN_AGENT_SMOKE_TEST"; then
|
||||||
PASCAL_SMOKE_CONFIRM=1 \
|
PASCAL_SMOKE_CONFIRM=1 \
|
||||||
|
|||||||
2
services/pascal-editor/.gitignore
vendored
2
services/pascal-editor/.gitignore
vendored
@ -29,6 +29,8 @@ supabase/.temp/
|
|||||||
|
|
||||||
# Build Outputs
|
# Build Outputs
|
||||||
.next/
|
.next/
|
||||||
|
.next-build/
|
||||||
|
.next-previous/
|
||||||
out/
|
out/
|
||||||
build
|
build
|
||||||
dist
|
dist
|
||||||
|
|||||||
@ -2,9 +2,48 @@ import { Agentation } from 'agentation'
|
|||||||
import { GeistPixelSquare } from 'geist/font/pixel'
|
import { GeistPixelSquare } from 'geist/font/pixel'
|
||||||
import { Barlow } from 'next/font/google'
|
import { Barlow } from 'next/font/google'
|
||||||
import localFont from 'next/font/local'
|
import localFont from 'next/font/local'
|
||||||
|
import Script from 'next/script'
|
||||||
import { ClientBootstrap } from './client-bootstrap'
|
import { ClientBootstrap } from './client-bootstrap'
|
||||||
import './globals.css'
|
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({
|
const geistSans = localFont({
|
||||||
src: './fonts/GeistVF.woff',
|
src: './fonts/GeistVF.woff',
|
||||||
variable: '--font-geist-sans',
|
variable: '--font-geist-sans',
|
||||||
@ -32,6 +71,9 @@ export default function RootLayout({
|
|||||||
lang="zh-CN"
|
lang="zh-CN"
|
||||||
>
|
>
|
||||||
<head>
|
<head>
|
||||||
|
<Script id="pascal-chunk-recovery" strategy="beforeInteractive">
|
||||||
|
{chunkRecoveryScript}
|
||||||
|
</Script>
|
||||||
{process.env.NODE_ENV === 'development' && (
|
{process.env.NODE_ENV === 'development' && (
|
||||||
<script async crossOrigin="anonymous" src="//unpkg.com/react-scan/dist/auto.global.js" />
|
<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'
|
import type { NextConfig } from 'next'
|
||||||
|
|
||||||
const configuredBasePath = process.env.NEXT_PUBLIC_PASCAL_BASE_PATH?.trim() ?? ''
|
const configuredBasePath = process.env.NEXT_PUBLIC_PASCAL_BASE_PATH?.trim() ?? ''
|
||||||
const basePath = configuredBasePath ? `/${configuredBasePath.replace(/^\/+|\/+$/g, '')}` : undefined
|
const basePath = configuredBasePath ? `/${configuredBasePath.replace(/^\/+|\/+$/g, '')}` : undefined
|
||||||
|
const pascalRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..')
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
basePath,
|
basePath,
|
||||||
|
distDir: process.env.PASCAL_NEXT_DIST_DIR?.trim() || '.next',
|
||||||
logging: {
|
logging: {
|
||||||
browserToTerminal: true,
|
browserToTerminal: true,
|
||||||
},
|
},
|
||||||
@ -21,6 +25,7 @@ const nextConfig: NextConfig = {
|
|||||||
'@dgreenheck/ez-tree',
|
'@dgreenheck/ez-tree',
|
||||||
],
|
],
|
||||||
turbopack: {
|
turbopack: {
|
||||||
|
root: pascalRoot,
|
||||||
resolveAlias: {
|
resolveAlias: {
|
||||||
react: './node_modules/react',
|
react: './node_modules/react',
|
||||||
three: './node_modules/three',
|
three: './node_modules/three',
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Howl } from 'howler'
|
import { Howl } from 'howler'
|
||||||
import useAudio from '../store/use-audio'
|
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×),
|
// 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
|
// 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
|
// Initialize all sounds
|
||||||
Object.entries(SFX).forEach(([name, config]) => {
|
Object.entries(SFX).forEach(([name, config]) => {
|
||||||
const sources = Array.isArray(config.src) ? config.src : [config.src]
|
const sources = Array.isArray(config.src) ? config.src : [config.src]
|
||||||
const sounds = sources.map(
|
const sounds = sources.map((src) => {
|
||||||
(src) =>
|
const resolvedSrc = withEditorBasePath(src)
|
||||||
new Howl({
|
return new Howl({
|
||||||
src: [src],
|
src: [resolvedSrc],
|
||||||
preload: true,
|
preload: true,
|
||||||
volume: 0.5, // Will be adjusted by the bus
|
volume: 0.5, // Will be adjusted by the bus
|
||||||
}),
|
})
|
||||||
)
|
})
|
||||||
sfxCache.set(name as SFXName, sounds)
|
sfxCache.set(name as SFXName, sounds)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,13 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"dependsOn": ["^build"],
|
"dependsOn": ["^build"],
|
||||||
"inputs": ["$TURBO_DEFAULT$", ".env*"],
|
"inputs": ["$TURBO_DEFAULT$", ".env*"],
|
||||||
"outputs": [".next/**", "!.next/cache/**", "dist/**"],
|
"outputs": [
|
||||||
|
".next/**",
|
||||||
|
"!.next/cache/**",
|
||||||
|
".next-build/**",
|
||||||
|
"!.next-build/cache/**",
|
||||||
|
"dist/**"
|
||||||
|
],
|
||||||
"env": [
|
"env": [
|
||||||
"SKIP_ENV_VALIDATION",
|
"SKIP_ENV_VALIDATION",
|
||||||
"BETTER_AUTH_SECRET",
|
"BETTER_AUTH_SECRET",
|
||||||
@ -28,6 +34,7 @@
|
|||||||
"PASCAL_DB_PATH",
|
"PASCAL_DB_PATH",
|
||||||
"PASCAL_MCP_COMMAND",
|
"PASCAL_MCP_COMMAND",
|
||||||
"PASCAL_MCP_ENTRY",
|
"PASCAL_MCP_ENTRY",
|
||||||
|
"PASCAL_NEXT_DIST_DIR",
|
||||||
"NEXT_PUBLIC_PASCAL_BASE_PATH",
|
"NEXT_PUBLIC_PASCAL_BASE_PATH",
|
||||||
"PASCAL_REQUIRE_HOST_USER"
|
"PASCAL_REQUIRE_HOST_USER"
|
||||||
]
|
]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user