#requires -Version 5.1 <# .SYNOPSIS 병원 배포용 산출물 생성 — 비밀값 하드 게이트 + SHA256 매니페스트 + zip. .DESCRIPTION 산출물에 실접속 정보가 섞이면 60여 개 병원 배포본에 자격증명이 평문으로 나간다. .gitignore 는 커밋만 막을 뿐 퍼블리시 산출물은 못 막으므로, 여기서 실패시켜 차단한다. .EXAMPLE pwsh tools/publish.ps1 pwsh tools/publish.ps1 -SelfContained # .NET 10 Desktop 런타임이 없는 단말용 #> [CmdletBinding()] param( [string]$OutputRoot = "$PSScriptRoot\..\publish", [switch]$SelfContained ) $ErrorActionPreference = 'Stop' $repo = Resolve-Path "$PSScriptRoot\.." $project = Join-Path $repo 'src\SheetMe.Designer\SheetMe.Designer.csproj' $stage = Join-Path $OutputRoot 'SheetMe' Write-Host "== SheetMe 배포 산출물 생성 ==" -ForegroundColor Cyan if (Test-Path $stage) { Remove-Item -Recurse -Force -LiteralPath $stage } New-Item -ItemType Directory -Force -Path $stage | Out-Null $args = @( 'publish', $project, '-c', 'Release', '-p:PublishProfile=Production', '-o', $stage, '--nologo' ) if ($SelfContained) { $args += @('-p:SelfContained=true') } & dotnet @args if ($LASTEXITCODE -ne 0) { throw "퍼블리시 실패 (exit $LASTEXITCODE)" } # ---- 하드 게이트: 비밀값이 산출물에 섞였으면 여기서 중단 ---- Write-Host "`n-- 비밀값 검사 --" -ForegroundColor Cyan $violations = @() $devConfig = Join-Path $stage 'appsettings.Development.json' if (Test-Path $devConfig) { $violations += "appsettings.Development.json 이 산출물에 포함됨" } Get-ChildItem -LiteralPath $stage -Recurse -File -Include *.json, *.config, *.ini, *.txt | ForEach-Object { $text = Get-Content -LiteralPath $_.FullName -Raw -ErrorAction SilentlyContinue if ($null -eq $text) { return } # 플레이스홀더(__PASSWORD__)는 통과, 실제 값이 채워진 것만 잡는다 if ($text -match 'Password\s*=\s*(?!__)[^;"\s]{1,}') { $violations += "$($_.Name): Password= 에 실제 값이 들어 있음" } if ($text -match 'User\s*Id\s*=\s*(?!__)[^;"\s]{1,}') { $violations += "$($_.Name): User Id= 에 실제 값이 들어 있음" } } if ($violations.Count -gt 0) { Write-Host "산출물에 자격증명이 포함되어 있습니다:" -ForegroundColor Red $violations | ForEach-Object { Write-Host " - $_" -ForegroundColor Red } throw "비밀값 게이트 실패 — 배포를 중단합니다." } Write-Host " OK — 자격증명 없음(접속 정보는 [000]bin 의 ServerInfo ini 에서 읽습니다)" -ForegroundColor Green # ---- 충돌 경고: [000]bin 평면 배치 금지 확인용 ---- $binRoot = 'C:\MsystechHIS_Ver.2\[000]Bin' if (Test-Path -LiteralPath $binRoot) { $existing = @{} Get-ChildItem -LiteralPath $binRoot -File -Filter *.dll | ForEach-Object { $existing[$_.Name] = $true } $clash = Get-ChildItem -LiteralPath $stage -File -Filter *.dll | Where-Object { $existing.ContainsKey($_.Name) } if ($clash) { Write-Host "`n-- 참고: [000]bin 최상위와 이름이 겹치는 DLL $($clash.Count)건 --" -ForegroundColor Yellow $clash | ForEach-Object { Write-Host " $($_.Name)" -ForegroundColor Yellow } Write-Host " → 반드시 [000]bin\SheetMe\ 하위 폴더에 배치하세요(평면 복사 금지)." -ForegroundColor Yellow } } # ---- 매니페스트 ---- $manifest = Join-Path $stage 'MANIFEST.sha256' Get-ChildItem -LiteralPath $stage -Recurse -File | Sort-Object FullName | ForEach-Object { "{0} {1}" -f (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash, $_.FullName.Substring($stage.Length + 1) } | Set-Content -LiteralPath $manifest -Encoding utf8 $files = Get-ChildItem -LiteralPath $stage -Recurse -File Write-Host ("`n산출물: {0}개 파일 / {1:N1} MB" -f $files.Count, (($files | Measure-Object Length -Sum).Sum / 1MB)) # ---- zip ---- $zip = Join-Path $OutputRoot 'SheetMe.zip' if (Test-Path $zip) { Remove-Item -Force -LiteralPath $zip } Compress-Archive -Path $stage -DestinationPath $zip Write-Host "패키지: $zip" -ForegroundColor Green Write-Host "`n배포 절차는 docs/DEPLOYMENT.md 를 따르세요." -ForegroundColor Cyan