diff --git a/.github/cwf.ps1 b/.github/cwf.ps1 index 2bd929bf..46923f71 100644 --- a/.github/cwf.ps1 +++ b/.github/cwf.ps1 @@ -2,17 +2,60 @@ param( [switch]$Local ) -$localCwf = Join-Path (Get-Location) "..\cs-webview" -$localPackage = Join-Path $localCwf "package.json" +function Exit-WithError($Message) { + Write-Error $Message + exit 1 +} + +function Get-CwfVersionOverride { + $candidate = "$env:CWF_VERSION".TrimStart([char]0xFEFF).Trim() + if ($candidate) { return $candidate } + return $null +} + +function Read-CwfPinFile { + if (-not (Test-Path cwf.version)) { + Exit-WithError "No CWF version found: cwf.version does not exist and CWF_VERSION is not set." + } + return "$(Get-Content cwf.version -Raw)".TrimStart([char]0xFEFF).Trim() +} + +function Resolve-CwfVersion { + $value = Get-CwfVersionOverride + $source = "CWF_VERSION" + if (-not $value) { + $value = Read-CwfPinFile + $source = "cwf.version" + } + + if ($value -cnotmatch '^v?[0-9]+\.[0-9]+\.[0-9]+$') { + Exit-WithError "Invalid CWF version '$value' from $source. Expected MAJOR.MINOR.PATCH, optionally prefixed with v." + } + + return $value.TrimStart('v') +} + +function Get-HttpStatus($err) { + if ($err.Exception.Response) { + return [int]$err.Exception.Response.StatusCode + } + return $null +} -if ($Local) { - if (-not (Test-Path $localPackage)) { - Write-Error "Local CWF checkout not found at $localCwf" - exit 1 +function Format-CwfFailure($Step, $Err, $Status) { + if ($null -eq $Status) { + return "$Step failed: $($Err.Exception.Message)" + } + $message = "$Step failed with HTTP $Status." + if ($Status -eq 401 -or $Status -eq 403) { + $message += " Check that CODESCENE_IDE_DOCS_AND_WEBVIEW_TOKEN is valid and has access to empear-analytics/cs-webview." } + return $message +} - Write-Host "Building CWF locally from $localCwf..." - Push-Location $localCwf +function Invoke-LocalBuild($LocalCwf) { + Write-Host "Building CWF locally from $LocalCwf..." + Push-Location $LocalCwf try { & npm run build if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } @@ -20,56 +63,131 @@ if ($Local) { finally { Pop-Location } +} - $assetsPath = Join-Path $localCwf "build\assets" - if (-not (Test-Path $assetsPath)) { - Write-Error "Local CWF build did not produce build\assets." - exit 1 - } - +function Copy-LocalBuild($AssetsPath) { if (Test-Path cs-cwf) { Remove-Item cs-cwf -Recurse -Force } New-Item -ItemType Directory -Path cs-cwf | Out-Null - Copy-Item (Join-Path $assetsPath "*") cs-cwf + Copy-Item (Join-Path $AssetsPath "*") cs-cwf $webDir = "Codescene.VSExtension.VS2022/Codescene.VSExtension.VS2022/ToolWindows/WebComponent" Copy-Item cs-cwf/index.css $webDir -Force Copy-Item cs-cwf/index.js $webDir -Force if (Test-Path cs-cwf.zip) { Remove-Item cs-cwf.zip -Force } Compress-Archive -Path "cs-cwf\*" -DestinationPath cs-cwf.zip - exit 0 + Set-Content -Path cs-cwf/.cwf-version -Value "local" -Encoding ascii -NoNewline +} + +function Install-LocalCwf { + $localCwf = Join-Path (Get-Location) "..\cs-webview" + if (-not (Test-Path (Join-Path $localCwf "package.json"))) { + Exit-WithError "Local CWF checkout not found at $localCwf" + } + + Invoke-LocalBuild $localCwf + + $assetsPath = Join-Path $localCwf "build\assets" + if (-not (Test-Path $assetsPath)) { + Exit-WithError "Local CWF build did not produce build\assets." + } + Copy-LocalBuild $assetsPath +} + +function Test-CwfPresent($Version) { + $stampPath = "cs-cwf/.cwf-version" + if (-not ((Test-Path $stampPath) -and (Test-Path "cs-cwf/index.js"))) { + return $false + } + return ([string](Get-Content $stampPath -Raw)).Trim() -eq $Version +} + +function Get-CwfToken($Version) { + $token = $env:CODESCENE_IDE_DOCS_AND_WEBVIEW_TOKEN + if ([string]::IsNullOrWhiteSpace($token)) { + Exit-WithError "CODESCENE_IDE_DOCS_AND_WEBVIEW_TOKEN is not set. It is required to download cs-webview v$Version." + } + return $token +} + +function New-CwfHeaders($Token, $Accept) { + return @{ + Authorization = "token $Token" + Accept = $Accept + "User-Agent" = "actions" + } +} + +function Get-CwfRelease($Tag, $Token) { + Write-Host "Fetching cs-webview release $Tag..." + try { + return Invoke-RestMethod -ErrorAction Stop -Uri "https://api.github.com/repos/empear-analytics/cs-webview/releases/tags/$Tag" -Headers (New-CwfHeaders $Token "application/vnd.github+json") + } + catch { + $status = Get-HttpStatus $_ + if ($status -eq 404) { + Exit-WithError "Release tag $Tag not found in empear-analytics/cs-webview (HTTP 404)." + } + Exit-WithError (Format-CwfFailure -Step "release lookup" -Err $_ -Status $status) + } } -$Token = $env:CODESCENE_IDE_DOCS_AND_WEBVIEW_TOKEN +function Get-CwfAsset($Release, $Tag) { + $assetName = "cs-webview-$Tag.zip" + $asset = $Release.assets | Where-Object { $_.name -eq $assetName } | Select-Object -First 1 + if (-not $asset) { Exit-WithError "Asset $assetName not found in release $Tag." } + return $asset +} -$headers = @{ -Authorization = "token $Token" -Accept = "application/vnd.github+json" -"User-Agent" = "actions" +function Save-CwfAsset($Asset, $Token) { + Write-Host "Downloading asset via API: $($Asset.name)" + try { + Invoke-WebRequest -ErrorAction Stop -Uri $Asset.url -Headers (New-CwfHeaders $Token "application/octet-stream") -OutFile cs-cwf.zip + } + catch { + Exit-WithError (Format-CwfFailure -Step "asset download" -Err $_ -Status (Get-HttpStatus $_)) + } } -Write-Host "Fetching latest release info..." +function Expand-CwfArchive { + if (Test-Path cs-cwf-temp) { Remove-Item cs-cwf-temp -Recurse -Force } + Expand-Archive -Path cs-cwf.zip -DestinationPath cs-cwf-temp -Force + if (Test-Path cs-cwf) { Remove-Item cs-cwf -Recurse -Force } + New-Item -ItemType Directory -Path cs-cwf | Out-Null + + $assetsPath = Join-Path "cs-cwf-temp" "assets" + if (Test-Path $assetsPath) { + Get-ChildItem $assetsPath -File | Move-Item -Destination cs-cwf + } -$release = Invoke-RestMethod -Uri "https://api.github.com/repos/empear-analytics/cs-webview/releases/latest" -Headers $headers + Remove-Item cs-cwf-temp -Recurse -Force +} -$asset = $release.assets | Select-Object -First 1 -if (-not $asset) { Write-Error "No assets found in the latest release."; exit 1 } +function Install-ReleaseCwf($Version) { + $token = Get-CwfToken $Version + $tag = "v$Version" + $release = Get-CwfRelease $tag $token + Save-CwfAsset (Get-CwfAsset $release $tag) $token -Write-Host "Downloading asset via API: $($asset.name)" -$downloadHeaders = @{ -Authorization = "token $Token" -Accept = "application/octet-stream" -"User-Agent" = "actions" + $stampPath = "cs-cwf/.cwf-version" + if (Test-Path $stampPath) { Remove-Item $stampPath -Force } + Expand-CwfArchive + if (-not (Test-Path "cs-cwf/index.js")) { + Exit-WithError "CWF $Version extraction did not produce cs-cwf/index.js." + } + Set-Content -Path $stampPath -Value $Version -Encoding ascii -NoNewline } -Invoke-WebRequest -Uri "https://api.github.com/repos/empear-analytics/cs-webview/releases/assets/$($asset.id)" ` - -Headers $downloadHeaders ` - -OutFile cs-cwf.zip -Expand-Archive -Path cs-cwf.zip -DestinationPath cs-cwf-temp -Force -if (Test-Path cs-cwf) { Remove-Item cs-cwf -Recurse -Force } -New-Item -ItemType Directory -Path cs-cwf | Out-Null +function Main($UseLocal) { + if ($UseLocal) { + Install-LocalCwf + exit 0 + } -$assetsPath = Join-Path "cs-cwf-temp" "assets" -if (Test-Path $assetsPath) { - Get-ChildItem $assetsPath -File | Move-Item -Destination cs-cwf + $version = Resolve-CwfVersion + if (Test-CwfPresent $version) { + Write-Host "CWF $version already present, skipping download" + exit 0 + } + Install-ReleaseCwf $version } -Remove-Item cs-cwf-temp -Recurse -Force \ No newline at end of file +Main $Local.IsPresent diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 6d4d1b7d..aff001eb 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -20,7 +20,7 @@ jobs: - name: Download latest cs-webview release (private repo) shell: pwsh - run: powershell.exe -File .github/cwf.ps1 -Token "$env:CODESCENE_IDE_DOCS_AND_WEBVIEW_TOKEN" + run: powershell.exe -File .github/cwf.ps1 env: CODESCENE_IDE_DOCS_AND_WEBVIEW_TOKEN: ${{ secrets.CODESCENE_IDE_DOCS_AND_WEBVIEW_TOKEN }} diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml index a49293ed..156a0b6d 100644 --- a/.github/workflows/github-release.yml +++ b/.github/workflows/github-release.yml @@ -21,7 +21,7 @@ jobs: - name: Download latest cs-webview release (private repo) shell: pwsh - run: powershell.exe -File .github/cwf.ps1 -Token "$env:CODESCENE_IDE_DOCS_AND_WEBVIEW_TOKEN" + run: powershell.exe -File .github/cwf.ps1 env: CODESCENE_IDE_DOCS_AND_WEBVIEW_TOKEN: ${{ secrets.CODESCENE_IDE_DOCS_AND_WEBVIEW_TOKEN }} diff --git a/Makefile b/Makefile index 14c9db16..2a58ae67 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ PROJ_FILES_RAW := $(wildcard Codescene.VSExtension.VS2022/*.csproj) \ $(wildcard Codescene.VSExtension.VS2022/**/**/*.csproj) PROJ_FILES := $(filter-out %_wpftmp.csproj,$(PROJ_FILES_RAW)) -cs-cwf.zip: +cs-cwf.zip: cwf.version @pwsh.exe .github/cwf.ps1 > cs-cwf.log 2>&1 && del cs-cwf.log || (type cs-cwf.log && del cs-cwf.log && exit /b 1) updatecwf: diff --git a/SETUP.md b/SETUP.md index c57d2010..48528ce4 100644 --- a/SETUP.md +++ b/SETUP.md @@ -39,7 +39,14 @@ The Centralized Webviews Framework (CWF) used by the extension, which provides s **Note:** Access to the CWF repository requires appropriate permissions. -A normal build downloads the latest GitHub release. `make updatecwf` builds the sibling checkout at `../cs-webview` and copies it into the extension instead. +A normal build downloads the release pinned in `cwf.version` (at the repository root). Bumping the pin is a one-line commit to that file. + +`make updatecwf` builds the sibling checkout at `../cs-webview` and copies it into the extension instead. + +Limitations to be aware of: + +- `CWF_VERSION` overrides the pin only when `.github/cwf.ps1` is run directly (for example `$env:CWF_VERSION = '2.5.9'; pwsh .github/cwf.ps1`). Make does not see environment overrides, so it will not re-run the download when only the variable changes. +- After `make updatecwf`, returning to the pinned release needs `.github/cwf.ps1` run directly, or `cs-cwf.zip` deleted so Make downloads it again. ## Feature Flags diff --git a/cwf.version b/cwf.version new file mode 100644 index 00000000..ecd7ee50 --- /dev/null +++ b/cwf.version @@ -0,0 +1 @@ +2.5.8