Spaces:
Running
Running
Eric Putnam
Claude Opus 5 (1M context)
feat: deploy simpledocreview as a browser-only static space
3d968d9 Download scripts/sync-upstream.ps1 from Hoctar77/SimpleDocReview: direct link, hf CLI and curl.
- Browser
- Download file 7.16 kB
-
https://huggingface.co/spaces/Hoctar77/SimpleDocReview/resolve/main/scripts/sync-upstream.ps1
- Command line
-
hf download hf://spaces/Hoctar77/SimpleDocReview/scripts/sync-upstream.ps1
-
curl -L -o sync-upstream.ps1 https://huggingface.co/spaces/Hoctar77/SimpleDocReview/resolve/main/scripts/sync-upstream.ps1
7.16 kB
| <# | |
| .SYNOPSIS | |
| Mirror the upstream SimpleDocReview checkout into ./app. | |
| .DESCRIPTION | |
| ./app is a vendored copy of upstream, trimmed to what the deployment needs: | |
| the server (app.py, kept for the Dockerfile escape hatch) and the front end | |
| (static/) that the Static Space actually serves. | |
| This script wipes and re-mirrors it, then records the upstream commit in | |
| UPSTREAM.json so the Space can always be traced back to a revision. | |
| Anything you edit under ./app is destroyed by the next run. Fix upstream | |
| instead, then re-sync. | |
| .PARAMETER UpstreamPath | |
| The local SimpleDocReview checkout. Defaults to C:\dev\SimpleDocReview. | |
| .PARAMETER AllowDirty | |
| Sync even when the upstream checkout has uncommitted changes. Without this, | |
| a dirty upstream is an error, because the recorded commit would not describe | |
| what actually got deployed. | |
| .EXAMPLE | |
| .\scripts\sync-upstream.ps1 | |
| #> | |
| [CmdletBinding()] | |
| param( | |
| [string] $UpstreamPath = 'C:\dev\SimpleDocReview', | |
| [switch] $AllowDirty | |
| ) | |
| $ErrorActionPreference = 'Stop' | |
| $repoRoot = Split-Path -Parent $PSScriptRoot | |
| $appDir = Join-Path $repoRoot 'app' | |
| # --- Validate upstream ----------------------------------------------------- | |
| if (-not (Test-Path -LiteralPath $UpstreamPath -PathType Container)) { | |
| throw "Upstream path not found: $UpstreamPath" | |
| } | |
| $upstreamAppPy = Join-Path $UpstreamPath 'app.py' | |
| if (-not (Test-Path -LiteralPath $upstreamAppPy -PathType Leaf)) { | |
| throw "No app.py at $UpstreamPath - is that really the SimpleDocReview checkout?" | |
| } | |
| $appPySource = Get-Content -LiteralPath $upstreamAppPy -Raw | |
| if ($appPySource -notmatch 'server_version\s*=\s*"SimpleDocReview/') { | |
| throw "app.py at $UpstreamPath does not identify itself as SimpleDocReview." | |
| } | |
| $serverVersion = ([regex]'server_version\s*=\s*"(SimpleDocReview/[^"]+)"').Match($appPySource).Groups[1].Value | |
| # --- Read upstream git provenance ----------------------------------------- | |
| $commit = 'unknown' | |
| $branch = 'unknown' | |
| $isDirty = $false | |
| $originUrl = 'unknown' | |
| if (Test-Path -LiteralPath (Join-Path $UpstreamPath '.git')) { | |
| Push-Location $UpstreamPath | |
| try { | |
| $commit = (& git rev-parse HEAD).Trim() | |
| $branch = (& git rev-parse --abbrev-ref HEAD).Trim() | |
| $status = & git status --porcelain | |
| $isDirty = -not [string]::IsNullOrWhiteSpace(($status -join '')) | |
| try { $originUrl = (& git remote get-url origin).Trim() } catch { $originUrl = 'none' } | |
| } | |
| finally { Pop-Location } | |
| } | |
| else { | |
| Write-Warning "$UpstreamPath is not a git repo - provenance will be recorded as 'unknown'." | |
| } | |
| if ($isDirty -and -not $AllowDirty) { | |
| throw @" | |
| Upstream checkout has uncommitted changes. | |
| Commit them in $UpstreamPath first so the deployed Space maps to a real revision, | |
| or re-run with -AllowDirty to deploy the working tree as-is. | |
| "@ | |
| } | |
| # --- Mirror upstream -> ./app --------------------------------------------- | |
| # Local review data is a user's documents, not source. It must never be copied | |
| # into a repo that gets pushed to a public Space. | |
| $excludeDirs = @( | |
| (Join-Path $UpstreamPath '.git'), | |
| (Join-Path $UpstreamPath '.github'), | |
| (Join-Path $UpstreamPath '.claude'), | |
| (Join-Path $UpstreamPath '.vscode'), | |
| (Join-Path $UpstreamPath '.idea'), | |
| (Join-Path $UpstreamPath 'tests'), | |
| (Join-Path $UpstreamPath 'docs'), | |
| (Join-Path $UpstreamPath 'scripts'), | |
| (Join-Path $UpstreamPath 'data'), | |
| (Join-Path $UpstreamPath 'uploads'), | |
| '__pycache__', '.pytest_cache', '.ruff_cache', '.mypy_cache' | |
| ) | |
| # Repo-hygiene files the deployment does not read. README.md in particular must | |
| # not land in app/, because the Space card at the repository root is the README | |
| # Hugging Face renders. | |
| $excludeFiles = @( | |
| 'README.md', 'CLAUDE.md', 'CHANGELOG.md', | |
| '.gitignore', '.gitattributes', | |
| '*.pyc', '*.log', '.env', '.env.local' | |
| ) | |
| Write-Host "Syncing $UpstreamPath -> $appDir" -ForegroundColor Cyan | |
| New-Item -ItemType Directory -Force -Path $appDir | Out-Null | |
| $robocopyArgs = @( | |
| $UpstreamPath, $appDir, | |
| '/MIR', # mirror: also deletes files removed upstream | |
| '/NFL', '/NDL', # quiet: no per-file or per-directory listing | |
| '/NJH', '/NP', | |
| '/R:2', '/W:1' | |
| ) + @('/XD') + $excludeDirs + @('/XF') + $excludeFiles | |
| & robocopy @robocopyArgs | Out-Null | |
| # Robocopy exit codes 0-7 are success (8+ means copy failures). | |
| if ($LASTEXITCODE -ge 8) { | |
| throw "robocopy failed with exit code $LASTEXITCODE" | |
| } | |
| # --- Sanity-check what landed -------------------------------------------- | |
| $required = @('app.py', 'static/index.html', 'static/app.js', 'static/styles.css') | |
| $missing = $required | Where-Object { -not (Test-Path -LiteralPath (Join-Path $appDir $_)) } | |
| if ($missing.Count -gt 0) { | |
| throw "Sync incomplete - missing from app/: $($missing -join ', ')" | |
| } | |
| # --- Warn if the Dockerfile's entry point no longer fits app.py ---------- | |
| # The container calls app.run('0.0.0.0', 7860) positionally, so a change to that | |
| # signature breaks the escape hatch without breaking the static build - exactly | |
| # the kind of drift nobody notices until they need the container. | |
| if ($appPySource -notmatch 'def\s+run\s*\(\s*host[^)]*port') { | |
| Write-Warning "app.py's run() signature changed. Check the CMD line in Dockerfile still matches." | |
| } | |
| # --- Record provenance --------------------------------------------------- | |
| function Get-Sha256([string] $path) { | |
| return (Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash.ToLowerInvariant() | |
| } | |
| $upstream = [ordered]@{ | |
| upstreamRepo = $originUrl | |
| upstreamPath = $UpstreamPath | |
| commit = $commit | |
| branch = $branch | |
| dirtyWorkingTree = $isDirty | |
| serverVersion = $serverVersion | |
| portedSources = [ordered]@{ | |
| 'app.py' = Get-Sha256 (Join-Path $appDir 'app.py') | |
| 'static/app.js' = Get-Sha256 (Join-Path $appDir 'static/app.js') | |
| } | |
| syncedAtUtc = (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') | |
| } | |
| # Written without a BOM: Windows PowerShell 5.1's -Encoding utf8 emits one, and | |
| # a BOM breaks strict JSON parsers that may read this file later. | |
| $upstreamJson = ($upstream | ConvertTo-Json -Depth 4) + [Environment]::NewLine | |
| [System.IO.File]::WriteAllText( | |
| (Join-Path $repoRoot 'UPSTREAM.json'), | |
| $upstreamJson, | |
| (New-Object System.Text.UTF8Encoding $false) | |
| ) | |
| # --- Summary ------------------------------------------------------------- | |
| $shortCommit = if ($commit.Length -ge 7) { $commit.Substring(0, 7) } else { $commit } | |
| Write-Host '' | |
| Write-Host "Synced $serverVersion @ $shortCommit ($branch)" -ForegroundColor Green | |
| if ($isDirty) { Write-Warning 'Synced from a dirty upstream working tree.' } | |
| Write-Host '' | |
| Push-Location $repoRoot | |
| try { | |
| $changes = & git status --porcelain | |
| if ([string]::IsNullOrWhiteSpace(($changes -join ''))) { | |
| Write-Host 'No changes - the Space already matches upstream.' -ForegroundColor Yellow | |
| } | |
| else { | |
| Write-Host 'Pending changes:' | |
| & git status --short | |
| Write-Host '' | |
| Write-Host 'Deploy with: .\scripts\deploy.ps1' -ForegroundColor Cyan | |
| } | |
| } | |
| finally { Pop-Location } | |