# Pack production upload bundle (上传到服务器) # Usage: powershell -ExecutionPolicy Bypass -File deploy/pack-server-upload.ps1 $ErrorActionPreference = "Stop" $Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) $Out = Join-Path $Root ([char]0x4e0a + [char]0x4f20 + [char]0x5230 + [char]0x670d + [char]0x52a1 + [char]0x5668) $Code = Join-Path $Out ([char]0x4ee3 + [char]0x7801) $Db = Join-Path $Out ([char]0x6570 + [char]0x636e + [char]0x5e93) Write-Host "pack: clean $Out" if (Test-Path $Out) { Remove-Item -Recurse -Force $Out } New-Item -ItemType Directory -Path $Code -Force | Out-Null New-Item -ItemType Directory -Path $Db -Force | Out-Null # PowerShell 将 [quote_id] 等路径当作通配符,必须用 -LiteralPath 递归复制 function Copy-TreeLiteral { param( [Parameter(Mandatory = $true)][string]$SourceDir, [Parameter(Mandatory = $true)][string]$DestDir ) $stack = New-Object System.Collections.Stack $stack.Push(@($SourceDir, $DestDir)) while ($stack.Count -gt 0) { $pair = $stack.Pop() $currentSrc = $pair[0] $currentDst = $pair[1] if (-not (Test-Path -LiteralPath $currentDst)) { New-Item -ItemType Directory -Path $currentDst -Force | Out-Null } Get-ChildItem -LiteralPath $currentSrc -Force | ForEach-Object { if ($_.PSIsContainer) { $stack.Push(@($_.FullName, (Join-Path $currentDst $_.Name))) } elseif ($_.Extension -ne ".md") { Copy-Item -LiteralPath $_.FullName -Destination (Join-Path $currentDst $_.Name) -Force } } } } $RequiredRoutes = @( "app\api\quotes\[quote_id]\route.ts", "app\api\markup-configs\[customer_id]\route.ts", "app\api\admin\markup-configs\[customer_id]\route.ts", "app\api\admin\queues\[[...slug]]\route.ts", "app\api\alerts\[id]\resolve\route.ts", "app\api\admin\customers\[customer_id]\provider-credentials\route.ts", "app\api\flock\quotes\route.ts", "app\api\addresses\mothership-suggest\route.ts" ) function Convert-ShFilesToLf { param([Parameter(Mandatory = $true)][string]$BaseDir) Get-ChildItem -Path $BaseDir -Filter "*.sh" -Recurse -File -ErrorAction SilentlyContinue | ForEach-Object { $text = [System.IO.File]::ReadAllText($_.FullName) $fixed = $text -replace "`r`n", "`n" -replace "`r", "`n" if ($text -ne $fixed) { [System.IO.File]::WriteAllText($_.FullName, $fixed, [System.Text.UTF8Encoding]::new($false)) Write-Host "pack: LF $($_.FullName.Substring($BaseDir.Length).TrimStart('\','/'))" } } } function Test-RequiredRoutes { param([string]$BaseDir) $missing = @() foreach ($rel in $RequiredRoutes) { $path = Join-Path $BaseDir $rel if (-not (Test-Path -LiteralPath $path)) { $missing += $rel } } if ($missing.Count -gt 0) { throw "pack: missing dynamic API routes (bracket paths): $($missing -join ', ')" } } $CodeDirs = @( "app", "components", "hooks", "lib", "modules", "workers", "prisma", "public" ) foreach ($dir in $CodeDirs) { $src = Join-Path $Root $dir if (-not (Test-Path $src)) { throw "missing dir: $dir" } Write-Host "pack: copy $dir" $dstDir = Join-Path $Code $dir Copy-TreeLiteral -SourceDir $src -DestDir $dstDir } $deploySrc = Join-Path $Root "deploy" $deployDst = Join-Path $Code "deploy" New-Item -ItemType Directory -Path $deployDst -Force | Out-Null Get-ChildItem -Path $deploySrc -Force | Where-Object { $_.Name -ne "database" -and $_.Name -ne "pack-server-upload.ps1" -and $_.Name -ne "pack-readme-template.md" } | ForEach-Object { Copy-Item -Path $_.FullName -Destination $deployDst -Recurse -Force } $CodeFiles = @( "package.json", "package-lock.json", "next.config.ts", "tsconfig.json", "middleware.ts", "next-env.d.ts", "postcss.config.mjs", "tailwind.config.ts", "Dockerfile", "Dockerfile.worker", ".dockerignore" ) foreach ($file in $CodeFiles) { $src = Join-Path $Root $file if (Test-Path $src) { Copy-Item -Path $src -Destination (Join-Path $Code $file) -Force Write-Host "pack: copy $file" } } $envExample = Join-Path $Root "deploy\.env.bt-host-mysql.example" if (Test-Path $envExample) { Copy-Item -Path $envExample -Destination (Join-Path $deployDst ".env.bt-host-mysql.example") -Force Write-Host "pack: copy deploy/.env.bt-host-mysql.example" } $staleEnvPath = Join-Path $Code ".env" if ((Test-Path -LiteralPath $Code) -and (Test-Path -LiteralPath $staleEnvPath)) { Remove-Item -LiteralPath $staleEnvPath -Force Write-Host "pack: removed stale 代码/.env" } $DbFiles = @("chajia.sql", "chajia.sql.gz", "README.md") foreach ($file in $DbFiles) { $src = Join-Path $Root "deploy\database\$file" if (Test-Path $src) { Copy-Item -Path $src -Destination (Join-Path $Db $file) -Force Write-Host "pack: database $file" } } function Get-DirSizeBytes($path) { (Get-ChildItem -Path $path -Recurse -File -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum).Sum } $codeSize = [math]::Round((Get-DirSizeBytes $Code) / 1MB, 2) $dbSize = [math]::Round((Get-DirSizeBytes $Db) / 1KB, 1) $fileCount = (Get-ChildItem -Path $Out -Recurse -File).Count Write-Host "" Write-Host "pack: normalize shell scripts (CRLF -> LF)" Convert-ShFilesToLf -BaseDir $Code Write-Host "" Write-Host "pack: verify dynamic API routes" Test-RequiredRoutes -BaseDir $Code # 安全:上传包禁止夹带机密/构建产物(避免覆盖服务器 .env 或浪费磁盘) $ForbiddenInCode = @( (Join-Path $Code ".env"), (Join-Path $Code "node_modules"), (Join-Path $Code ".next"), (Join-Path $Code ".rpa"), (Join-Path $Code ".git"), (Join-Path $Code ".dev") ) foreach ($bad in $ForbiddenInCode) { if (Test-Path -LiteralPath $bad) { throw "pack: forbidden path present in 代码/: $bad" } } Write-Host "" Write-Host "pack: done -> $Out" Write-Host "pack: files=$fileCount codeMB=$codeSize dbKB=$dbSize" Write-Host "pack: SAFETY ok (no .env/node_modules/.next/.rpa/.git in 代码/)" # Restore top-level README (removed by clean) $readmeSrc = Join-Path $Root "deploy\pack-readme-template.md" if (Test-Path $readmeSrc) { Copy-Item $readmeSrc (Join-Path $Out "README.md") -Force } $auxDir = Join-Path $Out ([char]0x8f85 + [char]0x52a9 + [char]0x811a + [char]0x672c) New-Item -ItemType Directory -Path $auxDir -Force | Out-Null $testScriptsSrc = Join-Path $Root "deploy\test-scripts" if (Test-Path $testScriptsSrc) { Get-ChildItem -Path $testScriptsSrc -Filter "*.sh" -File | ForEach-Object { Copy-Item -LiteralPath $_.FullName -Destination (Join-Path $auxDir $_.Name) -Force Write-Host "pack: aux $($_.Name)" } Convert-ShFilesToLf -BaseDir $auxDir } $releaseNotesCandidates = @( (Join-Path $Root "deploy\RELEASE-NOTES.md"), (Join-Path $Root "deploy\服务器更新说明.md") ) foreach ($releaseNotesSrc in $releaseNotesCandidates) { if (-not (Test-Path -LiteralPath $releaseNotesSrc)) { continue } $releaseNotesDst = Join-Path $Out "RELEASE-NOTES.md" Copy-Item -LiteralPath $releaseNotesSrc -Destination $releaseNotesDst -Force Write-Host "pack: RELEASE-NOTES.md" break }