param( [string]$ProjectRoot = "D:\AI Code\3danzhuang-wuyongjie-mainline\furniture-3d-system", [string]$OutputRoot = "" ) $ErrorActionPreference = 'Stop' if ([string]::IsNullOrWhiteSpace($OutputRoot)) { $OutputRoot = Join-Path $env:TEMP '3danzhuang-deploy-artifacts\wyj-test' } if (-not (Test-Path -LiteralPath $OutputRoot)) { New-Item -ItemType Directory -Path $OutputRoot -Force | Out-Null } if (-not (Test-Path -LiteralPath $ProjectRoot)) { throw "项目目录不存在: $ProjectRoot" } function Get-EnvValue { param([string]$Path, [string]$Key) if (-not (Test-Path -LiteralPath $Path)) { return $null } $line = Get-Content -LiteralPath $Path | Where-Object { $_ -match "^$([regex]::Escape($Key))=" } | Select-Object -First 1 if (-not $line) { return $null } $value = $line.Substring($line.IndexOf('=') + 1).Trim() if ($value.Length -ge 2) { if (($value.StartsWith('"') -and $value.EndsWith('"')) -or ($value.StartsWith("'") -and $value.EndsWith("'"))) { $value = $value.Substring(1, $value.Length - 2) } } return $value } $localEnvPath = Join-Path $ProjectRoot '.env' $jwtSecret = Get-EnvValue -Path $localEnvPath -Key 'JWT_SECRET' if ([string]::IsNullOrWhiteSpace($jwtSecret) -or $jwtSecret.Length -lt 32) { $jwtSecret = 'wyj-test-jwt-secret-20260410-solidjoin-3d' } $nextAuthSecret = Get-EnvValue -Path $localEnvPath -Key 'NEXTAUTH_SECRET' if ([string]::IsNullOrWhiteSpace($nextAuthSecret)) { $nextAuthSecret = $jwtSecret } $timestamp = Get-Date -Format 'yyyyMMdd-HHmmss' $archiveName = "deploy-furniture-wyj-test-$timestamp.tar.gz" $scriptName = "deploy-furniture-wyj-test-$timestamp.sh" $archivePath = Join-Path $OutputRoot $archiveName $scriptPath = Join-Path $OutputRoot $scriptName $includePaths = @( 'furniture-3d-system/package.json' 'furniture-3d-system/package-lock.json' 'furniture-3d-system/.npmrc' 'furniture-3d-system/next.config.js' 'furniture-3d-system/postcss.config.mjs' 'furniture-3d-system/tsconfig.json' 'furniture-3d-system/next-env.d.ts' 'furniture-3d-system/prisma.config.ts' 'furniture-3d-system/src' 'furniture-3d-system/public' 'furniture-3d-system/prisma' 'furniture-3d-system/scripts' 'furniture-3d-system/lib' ) Push-Location (Split-Path $ProjectRoot -Parent) try { if (Test-Path -LiteralPath $archivePath) { Remove-Item -LiteralPath $archivePath -Force } tar -czf $archivePath @includePaths } finally { Pop-Location } $shellScript = @' set -e BASE=/root/codex-workspaces/furniture-3d-system-wyj-test APP=$BASE/furniture-3d-system ARCHIVE=/root/codex-workspaces/__ARCHIVE_NAME__ DB_NAME=furniture3d_wyj_test DB_USER=furniture3d_wyj_test DB_PASS='F3dWyjTest_3200_20260407' APP_PORT=3200 BUCKET=furniture-3d-models-wyj-test MINIO_USER=furniture3dtest MINIO_PASS='F3dMinio_39100_20260331' S3_ENDPOINT='http://127.0.0.1:39100' S3_PUBLIC_ENDPOINT='http://192.168.42.134:39100' mkdir -p $BASE mkdir -p $APP OLD_LOCK_HASH="" if [ -f "$APP/package-lock.json" ]; then OLD_LOCK_HASH=$(sha256sum "$APP/package-lock.json" | awk '{print $1}') fi if [ -d "$APP/.next/cache" ]; then mv "$APP/.next/cache" /tmp/next-cache-backup-$APP_PORT || true fi rm -rf $APP/src $APP/public $APP/prisma $APP/scripts $APP/lib $APP/.next rm -f $APP/package.json $APP/package-lock.json $APP/.npmrc $APP/next.config.js $APP/postcss.config.mjs $APP/tsconfig.json $APP/next-env.d.ts $APP/prisma.config.ts cd $BASE tar -xzf $ARCHIVE if [ -d /tmp/next-cache-backup-$APP_PORT ]; then mkdir -p "$APP/.next" mv /tmp/next-cache-backup-$APP_PORT "$APP/.next/cache" || true fi NEW_LOCK_HASH="" if [ -f "$APP/package-lock.json" ]; then NEW_LOCK_HASH=$(sha256sum "$APP/package-lock.json" | awk '{print $1}') fi sudo -u postgres psql -d postgres </dev/null && mc mb -p local/$BUCKET >/dev/null 2>&1 || true && mc anonymous set download local/$BUCKET >/dev/null" cd $APP export PUPPETEER_SKIP_DOWNLOAD=true if [ ! -d "$APP/node_modules" ] || [ "$OLD_LOCK_HASH" != "$NEW_LOCK_HASH" ]; then npm install --no-audit --no-fund fi sudo -u postgres psql -d $DB_NAME </dev/null 2>&1 || true pm2 start /bin/bash --name furniture-3d-system-wyj-test --cwd $APP -- -lc "set -a; source ./.env; set +a; mkdir -p .next/standalone/.next; rm -rf .next/standalone/.next/static .next/standalone/public; cp -R .next/static .next/standalone/.next/static; cp -R public .next/standalone/public; exec env PORT=$APP_PORT HOSTNAME=0.0.0.0 node .next/standalone/server.js" pm2 save >/dev/null sleep 3 echo '=== DONE ===' pm2 describe furniture-3d-system-wyj-test | sed -n '1,35p' '@ $finalScript = $shellScript.Replace('__ARCHIVE_NAME__', $archiveName) $finalScript = $finalScript.Replace('__JWT_SECRET__', $jwtSecret) $finalScript = $finalScript.Replace('__NEXTAUTH_SECRET__', $nextAuthSecret) # 使用最终极的 [System.IO.File] 写法,杜绝 0 字节和路径错误 $absoluteScriptPath = [System.IO.Path]::GetFullPath($scriptPath) $finalScript = $finalScript -replace "`r`n", "`n" [System.IO.File]::WriteAllText($absoluteScriptPath, $finalScript, [System.Text.UTF8Encoding]::new($false)) [PSCustomObject]@{ Archive = $archivePath Script = $absoluteScriptPath Size = (Get-Item $absoluteScriptPath).Length }