[CmdletBinding()] param( [string] $OutputPath = 'artifacts\crm-release.zip' ) $ErrorActionPreference = 'Stop' Set-StrictMode -Version Latest $root = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path $output = if ([System.IO.Path]::IsPathRooted($OutputPath)) { [System.IO.Path]::GetFullPath($OutputPath) } else { [System.IO.Path]::GetFullPath((Join-Path $root $OutputPath)) } & git -C $root rev-parse --is-inside-work-tree | Out-Null if ($LASTEXITCODE -ne 0) { throw 'Release packaging requires a Git worktree.' } $status = @(& git -C $root status --porcelain --untracked-files=all) if ($LASTEXITCODE -ne 0) { throw 'Unable to inspect the Git worktree.' } if ($status.Count -gt 0) { throw "The worktree is not clean. Commit the intended release first.`n$($status -join "`n")" } $tracked = @(& git -C $root ls-files) if ($LASTEXITCODE -ne 0) { throw 'Unable to list tracked release files.' } $forbiddenPathPatterns = @( '(^|/)\.env($|\.(?!example$))', '(^|/)(vendor|node_modules)(/|$)', '(^|/)(storage/logs|bootstrap/cache)(/|$)', '\.(sqlite|sqlite3|db)$', '(^|/)(auth\.json|cookies[^/]*)$' ) $forbidden = $tracked | Where-Object { $path = $_ $forbiddenPathPatterns | Where-Object { $path -match $_ } | Select-Object -First 1 } if ($forbidden) { throw "Tracked forbidden files would enter the release:`n$($forbidden -join "`n")" } $secretPatterns = @( 'APP_KEY=base64:[A-Za-z0-9+/=]{20,}', '(sk|ghp|github_pat)_[A-Za-z0-9_-]{20,}', '-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----' ) foreach ($pattern in $secretPatterns) { $matches = @(& git -C $root grep -I -n -E -- $pattern -- . 2>$null) if ($pattern.StartsWith('APP_KEY=')) { $matches = @($matches | Where-Object { $_ -notmatch 'APP_KEY=base64:A{43}=' }) } if ($LASTEXITCODE -eq 0 -and $matches.Count -gt 0) { throw "A possible secret was detected in tracked content:`n$($matches -join "`n")" } if ($LASTEXITCODE -gt 1) { throw 'Secret scan failed.' } } $outputDirectory = Split-Path -Parent $output New-Item -ItemType Directory -Force -Path $outputDirectory | Out-Null if (Test-Path -LiteralPath $output) { Remove-Item -LiteralPath $output -Force } & git -C $root archive --format=zip --output=$output HEAD if ($LASTEXITCODE -ne 0) { throw 'git archive failed.' } $hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $output).Hash Write-Host "Release archive: $output" Write-Host "SHA256: $hash"