/
kochkatech
/
CorpGame
Обзор
Документация
Войти
/
kochkatech
/
CorpGame
Код
Запросы
1
Задачи
Вики
Пакеты
0
Релизы
1
CI/CD
Аналитика
dev
scripts/build-portable.ps1
177 строк
10 KB
kochkareal
feat(devops): свод к одному процессу Node на SQLite, портативная сборка без Docker; аудит безопасности
06 авг 2026, 15:06
06 авг 2026, 15:06
3266bcd
Код
Авторство
О чём код?
# Собирает портативную поставку игры: backend + frontend + готовая SQLite-база в один ZIP. # Node.js в поставку НЕ входит — единственное системное требование к машине ведущего # (обычный установщик с nodejs.org, без прав администратора). Результат — папка, которую # ведущий распаковывает и запускает двойным кликом по start.bat. # # Запускать из корня репозитория (сам скрипт лежит в scripts/): .\scripts\build-portable.ps1 # # Раскладка результата (см. src/common/app-paths.ts — рантайм ожидает именно её): # dist/CorpGame-portable-<версия>.zip, при распаковке: # CorpGame/ # start.bat, stop.bat, fix-firewall.bat <- тонкие ASCII-обёртки # .env.example # scripts/ # start.ps1, stop.ps1, fix-firewall.ps1, ensure-env.ps1 <- логика, копии из корня репозитория # app/ # server/ <- backend/dist (nest build), # server/prisma/migrations/ <- миграции копирует nest-cli (assets) # client/ <- frontend/dist (vite build) # node_modules/ <- только prod-зависимости backend # prisma/template.db <- эталонная база $ErrorActionPreference = 'Stop' [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 $OutputEncoding = [System.Text.Encoding]::UTF8 $repoRoot = Split-Path -Parent $PSScriptRoot $buildDir = Join-Path $repoRoot 'build' $stageDir = Join-Path $buildDir 'stage' $appDir = Join-Path $stageDir 'app' $distDir = Join-Path $repoRoot 'dist' function Step([string]$title) { Write-Host "" Write-Host "=== $title ===" } function Assert-ExitCode([string]$what) { if ($LASTEXITCODE -ne 0) { throw "$what завершилось с кодом $LASTEXITCODE" } } if (-not (Get-Command node -ErrorAction SilentlyContinue)) { throw "Node.js не найден в PATH — он нужен для сборки (npm ci/build), даже если сама " + "поставка Node.js в себе не несёт. Установите: https://nodejs.org/" } Step "Очищаю предыдущую сборку" if (Test-Path $stageDir) { Remove-Item -Recurse -Force $stageDir } New-Item -ItemType Directory -Force -Path $appDir | Out-Null New-Item -ItemType Directory -Force -Path $distDir | Out-Null Step "Собираю backend" Push-Location (Join-Path $repoRoot 'backend') try { npm ci; Assert-ExitCode 'npm ci (backend)' npx prisma generate; Assert-ExitCode 'prisma generate' # npm run build = nest build && node scripts/generate-template-db.js — сборка dist/ и # эталонной базы одним шагом (см. backend/package.json, backend/scripts/generate-template-db.js). npm run build; Assert-ExitCode 'npm run build (backend)' } finally { Pop-Location } Step "Собираю frontend" Push-Location (Join-Path $repoRoot 'frontend') try { npm ci; Assert-ExitCode 'npm ci (frontend)' npm run build; Assert-ExitCode 'npm run build (frontend)' } finally { Pop-Location } Step "Раскладываю server/ и client/" # backend/dist уже содержит prisma/migrations и schema.prisma — их туда кладёт nest-cli # (см. nest-cli.json: assets копирует backend/prisma/**/* в dist/prisma). Отдельного шага # копирования миграций не нужно — это именно то, что читает PrismaService.assertSchemaIsCurrent() # для проверки дрейфа схемы. Copy-Item (Join-Path $repoRoot 'backend\dist') (Join-Path $appDir 'server') -Recurse Copy-Item (Join-Path $repoRoot 'frontend\dist') (Join-Path $appDir 'client') -Recurse Copy-Item (Join-Path $repoRoot 'backend\package.json') $appDir Copy-Item (Join-Path $repoRoot 'backend\package-lock.json') $appDir Step "Ставлю только прод-зависимости" Push-Location $appDir try { # --ignore-scripts: без него постинсталл @prisma/client попытался бы заново запустить # `prisma generate`, а это требует CLI prisma (devDependency, её здесь нет). Уже # сгенерированный клиент с движком копируем вручную ниже — так же, как делает Dockerfile. npm ci --omit=dev --ignore-scripts; Assert-ExitCode 'npm ci --omit=dev (app)' } finally { Pop-Location } # @prisma/client объявляет `prisma` и `typescript` как peerDependencies (не dependencies) — # npm 7+ доустанавливает пиры сам, и делает это НЕЗАВИСИМО от --omit=dev (--omit=peer тоже не # помогает: если пакет уже разрешён в package-lock.json как часть графа, npm ci ставит его # всё равно). Без явного удаления в поставку тянутся CLI prisma (~50 МБ) и typescript (~23 МБ) # со всем деревом зависимостей — рантайму они не нужны вообще, движок уже сгенерирован и # копируется отдельно ниже. Step "Убираю peer-зависимости prisma/typescript (не нужны в рантайме)" foreach ($pkg in 'prisma', 'typescript') { $p = Join-Path $appDir "node_modules\$pkg" if (Test-Path $p) { Remove-Item -Recurse -Force $p; Write-Host "удалено: node_modules\$pkg" } } Step "Копирую сгенерированный Prisma Client (с нативным движком)" $prismaClientSrc = Join-Path $repoRoot 'backend\node_modules\.prisma' $prismaClientDst = Join-Path $appDir 'node_modules\.prisma' Copy-Item $prismaClientSrc $prismaClientDst -Recurse $engine = Join-Path $prismaClientDst 'client\query_engine-windows.dll.node' if (-not (Test-Path $engine)) { throw "Не найден query_engine-windows.dll.node — сборка велась не на Windows/не под " + "правильный binaryTarget? Поставка не запустится на ноутбуке ведущего. Проверьте " + "generator.binaryTargets в backend/prisma/schema.prisma (должен включать 'windows')." } Write-Host "OK: $engine" Step "Копирую эталонную базу (template.db)" $templateDbSrc = Join-Path $repoRoot 'backend\prisma\template.db' if (-not (Test-Path $templateDbSrc)) { throw "Не найден backend/prisma/template.db — npm run build должен был его создать. " + "Проверьте backend/scripts/generate-template-db.js." } $templateDb = Join-Path $appDir 'prisma\template.db' New-Item -ItemType Directory -Force -Path (Split-Path -Parent $templateDb) | Out-Null Copy-Item $templateDbSrc $templateDb Write-Host "OK: $templateDb" Step "Копирую скрипты запуска и .env.example" foreach ($f in 'start.bat', 'stop.bat', 'fix-firewall.bat', '.env.example') { Copy-Item (Join-Path $repoRoot $f) (Join-Path $stageDir $f) } $stageScripts = Join-Path $stageDir 'scripts' New-Item -ItemType Directory -Force -Path $stageScripts | Out-Null foreach ($f in 'start.ps1', 'stop.ps1', 'fix-firewall.ps1', 'ensure-env.ps1') { Copy-Item (Join-Path $repoRoot "scripts\$f") (Join-Path $stageScripts $f) } Step "Проверяю сборку перед упаковкой (health-check вживую)" $testEnv = Join-Path $stageDir '.env' 'ADMIN_PASSWORD=build-check' | Set-Content $testEnv -Encoding utf8 "JWT_SECRET=$('x' * 40)" | Add-Content $testEnv -Encoding utf8 $testDataDir = Join-Path $buildDir 'smoke-data' if (Test-Path $testDataDir) { Remove-Item -Recurse -Force $testDataDir } New-Item -ItemType Directory -Force -Path $testDataDir | Out-Null $testDbPath = Join-Path $testDataDir 'corpgame.db' $env:PORT = '0' $env:DATABASE_URL = "file:$($testDbPath -replace '\\','/')" $env:NODE_ENV = 'production' $env:ENV_FILE = $testEnv $smokeProc = Start-Process -FilePath node -ArgumentList "`"$(Join-Path $appDir 'server\main.js')`"" ` -WorkingDirectory (Join-Path $appDir 'server') -NoNewWindow -PassThru ` -RedirectStandardOutput (Join-Path $buildDir 'smoke.log') -RedirectStandardError (Join-Path $buildDir 'smoke.err') Remove-Item Env:\PORT, Env:\DATABASE_URL, Env:\NODE_ENV, Env:\ENV_FILE Start-Sleep -Seconds 3 $smokeOk = -not $smokeProc.HasExited if ($smokeProc) { Stop-Process -Id $smokeProc.Id -Force -ErrorAction SilentlyContinue } Remove-Item $testEnv, $testDataDir -Recurse -Force -ErrorAction SilentlyContinue if (-not $smokeOk) { Write-Host "--- smoke.log ---"; Get-Content (Join-Path $buildDir 'smoke.log') -ErrorAction SilentlyContinue Write-Host "--- smoke.err ---"; Get-Content (Join-Path $buildDir 'smoke.err') -ErrorAction SilentlyContinue throw "Собранный сервер упал на старте — смотрите журнал выше. ZIP не создан." } Write-Host "OK: собранный сервер стартует" Step "Упаковываю в ZIP" $version = (Get-Content (Join-Path $repoRoot 'backend\package.json') | ConvertFrom-Json).version $zipPath = Join-Path $distDir "CorpGame-portable-$version.zip" if (Test-Path $zipPath) { Remove-Item $zipPath } # tar, а не Compress-Archive: на объёме под сотню мегабайт с тысячами файлов node_modules # разница в скорости упаковки не косметическая. bsdtar идёт в комплекте с Windows 10+. Push-Location $stageDir try { tar -a -c -f $zipPath * Assert-ExitCode 'tar' } finally { Pop-Location } $sizeMb = [Math]::Round((Get-Item $zipPath).Length / 1MB, 1) Write-Host "" Write-Host "==================================================" Write-Host " Готово: $zipPath ($sizeMb МБ)" Write-Host "=================================================="