167 lines
5.5 KiB
PowerShell
167 lines
5.5 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Compresses work/ into a ZIP and uploads it to Ka-Note AI endpoint.
|
|
|
|
.PARAMETER Prod
|
|
Target production server.
|
|
|
|
.PARAMETER Token
|
|
Bearer token for prod auth. Falls back to $env:KA_NOTE_TOKEN.
|
|
|
|
.PARAMETER Force
|
|
Overwrite conflicting entities (adds ?force=true).
|
|
|
|
.EXAMPLE
|
|
.\upload.ps1
|
|
.\upload.ps1 -Force
|
|
.\upload.ps1 -Prod -Token eyJ...
|
|
#>
|
|
param(
|
|
[switch]$Prod,
|
|
[string]$Token,
|
|
[switch]$Force
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
# --- Helpers -----------------------------------------------------------------
|
|
|
|
function Write-Header([string]$Text) {
|
|
Write-Host ""
|
|
Write-Host " $Text" -ForegroundColor White
|
|
Write-Host (" " + ("-" * $Text.Length)) -ForegroundColor DarkGray
|
|
}
|
|
|
|
function Write-KV([string]$Key, [string]$Value, [string]$Color = "Gray") {
|
|
Write-Host (" {0,-14} " -f $Key) -NoNewline -ForegroundColor DarkGray
|
|
Write-Host $Value -ForegroundColor $Color
|
|
}
|
|
|
|
function Write-Ok([string]$Msg) { Write-Host " [OK] $Msg" -ForegroundColor Green }
|
|
function Write-Warn([string]$Msg) { Write-Host " [!!] $Msg" -ForegroundColor Yellow }
|
|
function Write-Err([string]$Msg) { Write-Host " [!!] $Msg" -ForegroundColor Red }
|
|
function Write-Hint([string]$Msg) { Write-Host " -> $Msg" -ForegroundColor DarkCyan }
|
|
|
|
# --- Paths -------------------------------------------------------------------
|
|
|
|
$RepoRoot = Resolve-Path "$PSScriptRoot\..\.."
|
|
$WorkDir = Join-Path $RepoRoot "work"
|
|
$ManifestPath = Join-Path $WorkDir "manifest.json"
|
|
$ZipPath = Join-Path $WorkDir "_upload.zip"
|
|
|
|
if (-not (Test-Path $ManifestPath)) {
|
|
Write-Err "manifest.json not found in work/. Run .\download.ps1 first."
|
|
exit 1
|
|
}
|
|
|
|
$Manifest = Get-Content $ManifestPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
$LockToken = $Manifest.lockToken
|
|
if (-not $LockToken) {
|
|
Write-Err "No lockToken found in manifest.json."
|
|
exit 1
|
|
}
|
|
|
|
# --- Server ------------------------------------------------------------------
|
|
|
|
if ($Prod) {
|
|
$BaseUrl = "https://ka-note.azurewebsites.net"
|
|
$BearerToken = if ($Token) { $Token } elseif ($env:KA_NOTE_TOKEN) { $env:KA_NOTE_TOKEN } else {
|
|
Write-Host " Acquiring token via MSAL..." -ForegroundColor DarkGray
|
|
& "$PSScriptRoot\get-token.ps1"
|
|
}
|
|
if (-not $BearerToken) {
|
|
Write-Err "Failed to acquire a Bearer token."
|
|
exit 1
|
|
}
|
|
$Headers = @{ Authorization = "Bearer $BearerToken"; "Content-Type" = "application/octet-stream" }
|
|
$Env = "prod"
|
|
} else {
|
|
$BaseUrl = "http://localhost:3001"
|
|
$Headers = @{ "Content-Type" = "application/octet-stream" }
|
|
$Env = "dev"
|
|
}
|
|
|
|
# --- Compress work/ → ZIP ----------------------------------------------------
|
|
|
|
Write-Header "Ka-Note AI Upload"
|
|
Write-KV "server" $BaseUrl $(if ($Prod) { "Yellow" } else { "Cyan" })
|
|
Write-KV "mode" $Env
|
|
Write-KV "lock" $LockToken "Yellow"
|
|
Write-KV "force" $Force.IsPresent
|
|
Write-Host ""
|
|
Write-Host " Compressing work/..." -ForegroundColor DarkGray
|
|
|
|
if (Test-Path $ZipPath) { Remove-Item $ZipPath -Force }
|
|
|
|
# Use .NET ZipArchive directly to ensure forward-slash entry names (Compress-Archive uses backslashes on Windows)
|
|
Add-Type -Assembly 'System.IO.Compression'
|
|
Add-Type -Assembly 'System.IO.Compression.FileSystem'
|
|
$zipStream = [System.IO.File]::Open($ZipPath, [System.IO.FileMode]::Create)
|
|
$archive = [System.IO.Compression.ZipArchive]::new($zipStream, [System.IO.Compression.ZipArchiveMode]::Create)
|
|
Get-ChildItem -Path $WorkDir -Recurse -File | Where-Object { $_.FullName -ne $ZipPath } | ForEach-Object {
|
|
$entryName = $_.FullName.Substring($WorkDir.Length + 1).Replace('\', '/')
|
|
$entry = $archive.CreateEntry($entryName, [System.IO.Compression.CompressionLevel]::Optimal)
|
|
$entryStream = $entry.Open()
|
|
$fileStream = [System.IO.File]::OpenRead($_.FullName)
|
|
$fileStream.CopyTo($entryStream)
|
|
$fileStream.Dispose()
|
|
$entryStream.Dispose()
|
|
}
|
|
$archive.Dispose()
|
|
$zipStream.Dispose()
|
|
|
|
$ZipSizeKb = [math]::Round((Get-Item $ZipPath).Length / 1KB, 1)
|
|
Write-KV "zip size" "${ZipSizeKb} KB"
|
|
Write-Host ""
|
|
Write-Host " Uploading..." -ForegroundColor DarkGray
|
|
|
|
# --- Upload ------------------------------------------------------------------
|
|
|
|
$Url = "$BaseUrl/api/ai/upload"
|
|
if ($Force) { $Url += "?force=true" }
|
|
|
|
try {
|
|
$Response = Invoke-WebRequest -Uri $Url `
|
|
-Method POST -Headers $Headers -InFile $ZipPath -UseBasicParsing
|
|
|
|
Remove-Item $ZipPath -Force
|
|
|
|
$Result = $Response.Content | ConvertFrom-Json
|
|
|
|
Write-Header "Result"
|
|
Write-KV "accepted" $Result.accepted "Green"
|
|
Write-KV "skipped" $Result.skipped
|
|
Write-KV "conflicts" $Result.conflicts.Count
|
|
|
|
Write-Host ""
|
|
Write-Ok "Upload complete. Lock released."
|
|
Write-Host ""
|
|
} catch {
|
|
if (Test-Path $ZipPath) { Remove-Item $ZipPath -Force }
|
|
|
|
$StatusCode = $_.Exception.Response.StatusCode.value__
|
|
$ErrBody = $_.ErrorDetails.Message | ConvertFrom-Json -ErrorAction SilentlyContinue
|
|
|
|
Write-Host ""
|
|
|
|
if ($StatusCode -eq 409) {
|
|
Write-Header "Version Conflicts (409)"
|
|
$ErrBody.conflicts | ForEach-Object {
|
|
Write-Host (" {0,-12} {1}" -f $_.entityType, $_.entityId) -ForegroundColor White -NoNewline
|
|
Write-Host " client v$($_.clientVersion) server v$($_.serverVersion)" -ForegroundColor DarkGray
|
|
}
|
|
Write-Host ""
|
|
Write-Warn "Nothing was applied."
|
|
Write-Hint "Re-run with -Force to overwrite server versions."
|
|
} elseif ($StatusCode -eq 401) {
|
|
Write-Err "401 Unauthorized - lock token invalid or expired."
|
|
Write-Hint "Run .\download.ps1 again to acquire a new lock."
|
|
} elseif ($StatusCode -eq 423) {
|
|
Write-Err "423 Locked - no active lock for this token."
|
|
} else {
|
|
Write-Err "HTTP $StatusCode - $($_.Exception.Message)"
|
|
}
|
|
Write-Host ""
|
|
exit 1
|
|
}
|