070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
371 lines
15 KiB
PowerShell
371 lines
15 KiB
PowerShell
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateSet("win_x64")]
|
|
[string]$ReleaseTarget,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ReleaseNamespace,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ReleaseVersion,
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateSet("skip", "core", "full")]
|
|
[string]$SmokeMode,
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateSet("all", "dir", "nsis", "zip")]
|
|
[string]$BuildTarget,
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateSet("off", "on")]
|
|
[string]$SignMode,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$WorkRoot,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ToolsPackDir,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$CacheDir,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$BuildJsonPath,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$IndexPath,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ReportRoot,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$OutputsPath
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$startedAt = Get-Date
|
|
$timings = @()
|
|
$failureMessage = $null
|
|
$ReleaseChannel = [string]$env:RELEASE_CHANNEL
|
|
if ([string]::IsNullOrWhiteSpace($ReleaseChannel)) {
|
|
throw "RELEASE_CHANNEL is required"
|
|
}
|
|
|
|
function Format-Duration([int64]$Milliseconds) {
|
|
if ($Milliseconds -ge 60000) {
|
|
return "$([Math]::Round($Milliseconds / 60000, 1))m"
|
|
}
|
|
return "$([Math]::Round($Milliseconds / 1000, 1))s"
|
|
}
|
|
|
|
function Measure-Step([string]$Name, [scriptblock]$Script) {
|
|
Write-Host "##[group]$Name"
|
|
$started = Get-Date
|
|
try {
|
|
$result = & $Script
|
|
$durationMs = [int64]((Get-Date) - $started).TotalMilliseconds
|
|
$script:timings += [ordered]@{ step = $Name; status = "success"; durationMs = $durationMs }
|
|
Write-Host "[$Name] success in $(Format-Duration $durationMs)"
|
|
return $result
|
|
} catch {
|
|
$durationMs = [int64]((Get-Date) - $started).TotalMilliseconds
|
|
$script:timings += [ordered]@{ step = $Name; status = "failed"; durationMs = $durationMs; error = $_.Exception.Message }
|
|
$script:failureMessage = $_.Exception.Message
|
|
Write-Host "[$Name] failed in $(Format-Duration $durationMs)"
|
|
throw
|
|
} finally {
|
|
Write-Host "##[endgroup]"
|
|
}
|
|
}
|
|
|
|
function Write-JsonFile([string]$Path, [object]$Value, [int]$Depth = 10) {
|
|
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $Path) | Out-Null
|
|
$Value | ConvertTo-Json -Depth $Depth | Set-Content -LiteralPath $Path -Encoding utf8
|
|
}
|
|
|
|
function Read-BuildJson {
|
|
if (-not (Test-Path -LiteralPath $BuildJsonPath)) {
|
|
return $null
|
|
}
|
|
return Get-Content -LiteralPath $BuildJsonPath -Raw -Encoding utf8 | ConvertFrom-Json
|
|
}
|
|
|
|
function Get-SmokeSummary {
|
|
$summaryJsonPath = Join-Path $ReportRoot "summary.json"
|
|
if (-not (Test-Path -LiteralPath $summaryJsonPath)) {
|
|
return $null
|
|
}
|
|
return Get-Content -LiteralPath $summaryJsonPath -Raw -Encoding utf8 | ConvertFrom-Json
|
|
}
|
|
|
|
function Write-Index([string]$Status) {
|
|
$durationMs = [int64]((Get-Date) - $startedAt).TotalMilliseconds
|
|
$build = Read-BuildJson
|
|
$artifacts = $null
|
|
if ($build -ne $null) {
|
|
$artifacts = [ordered]@{
|
|
installerPath = $build.installerPath
|
|
latestYmlPath = $build.latestYmlPath
|
|
outputRoot = $build.outputRoot
|
|
payloadPath = $build.payloadPath
|
|
portableZipPath = $build.portableZipPath
|
|
}
|
|
}
|
|
$index = [ordered]@{
|
|
artifacts = $artifacts
|
|
branch = $env:RELEASE_BRANCH
|
|
buildJsonPath = $BuildJsonPath
|
|
cacheDir = $CacheDir
|
|
channel = $ReleaseChannel
|
|
commit = $env:RELEASE_COMMIT
|
|
durationMs = $durationMs
|
|
failure = $script:failureMessage
|
|
generatedAt = (Get-Date).ToUniversalTime().ToString("o")
|
|
namespace = $ReleaseNamespace
|
|
platform = "win"
|
|
releaseTarget = $ReleaseTarget
|
|
releaseVersion = $ReleaseVersion
|
|
reportDir = $ReportRoot
|
|
signed = $SignMode -eq "on"
|
|
smoke = Get-SmokeSummary
|
|
smokeMode = $SmokeMode
|
|
status = $Status
|
|
target = $BuildTarget
|
|
timings = $script:timings
|
|
toolsPackDir = $ToolsPackDir
|
|
}
|
|
Write-JsonFile -Path $IndexPath -Value $index -Depth 10
|
|
Write-JsonFile -Path $OutputsPath -Value ([ordered]@{
|
|
build_json_path = $BuildJsonPath
|
|
index_path = $IndexPath
|
|
release_target = $ReleaseTarget
|
|
release_version = $ReleaseVersion
|
|
}) -Depth 5
|
|
}
|
|
|
|
function Invoke-CommandChecked([string[]]$Arguments, [string]$WorkingDirectory = (Get-Location).Path) {
|
|
Push-Location -LiteralPath $WorkingDirectory
|
|
try {
|
|
& $Arguments[0] @($Arguments | Select-Object -Skip 1)
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "command failed with exit code ${LASTEXITCODE}: $($Arguments -join ' ')"
|
|
}
|
|
} finally {
|
|
Pop-Location
|
|
}
|
|
}
|
|
|
|
function Convert-ArchiveRelativePath([string]$Value) {
|
|
if ([string]::IsNullOrWhiteSpace($Value)) {
|
|
throw "archive relative path must not be empty"
|
|
}
|
|
if ([System.IO.Path]::IsPathRooted($Value) -or $Value.Contains("..")) {
|
|
throw "unsafe archive relative path: $Value"
|
|
}
|
|
return $Value.Replace("/", [System.IO.Path]::DirectorySeparatorChar).Replace("\", [System.IO.Path]::DirectorySeparatorChar)
|
|
}
|
|
|
|
function Test-JsonString([object]$Value, [string]$Name, [string]$Expected) {
|
|
$actual = [string]$Value
|
|
if ($actual -ne $Expected) {
|
|
throw "launcher payload manifest $Name expected '$Expected' but got '$actual'"
|
|
}
|
|
}
|
|
|
|
function Validate-WinLauncherPayloadArchive([string]$PayloadPath, [string]$ExpectedVersion, [string]$Label) {
|
|
if ([string]::IsNullOrWhiteSpace($PayloadPath) -or -not (Test-Path -LiteralPath $PayloadPath)) {
|
|
throw "expected launcher payload path for $Label not found at $PayloadPath"
|
|
}
|
|
|
|
$sevenZipExe = Join-Path (Get-Location).Path "tools\pack\resources\win\7zip\7z.exe"
|
|
if (-not (Test-Path -LiteralPath $sevenZipExe)) {
|
|
throw "bundled 7z.exe not found at $sevenZipExe"
|
|
}
|
|
|
|
$extractRoot = Join-Path $WorkRoot "validate-launcher-payload-$Label"
|
|
Remove-Item -LiteralPath $extractRoot -Recurse -Force -ErrorAction SilentlyContinue
|
|
New-Item -ItemType Directory -Force -Path $extractRoot | Out-Null
|
|
try {
|
|
& $sevenZipExe x $PayloadPath "-o$extractRoot" -y | Write-Host
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "launcher payload extraction failed with exit code $LASTEXITCODE for $PayloadPath"
|
|
}
|
|
|
|
$manifestPath = Join-Path $extractRoot "manifest.json"
|
|
if (-not (Test-Path -LiteralPath $manifestPath)) {
|
|
throw "launcher payload manifest not found after extraction: $manifestPath"
|
|
}
|
|
$manifest = Get-Content -LiteralPath $manifestPath -Raw -Encoding utf8 | ConvertFrom-Json
|
|
Test-JsonString $manifest.schemaVersion "schemaVersion" "1"
|
|
Test-JsonString $manifest.channel "channel" $ReleaseChannel
|
|
Test-JsonString $manifest.namespace "namespace" $ReleaseNamespace
|
|
Test-JsonString $manifest.version "version" $ExpectedVersion
|
|
Test-JsonString $manifest.platform "platform" "win32"
|
|
Test-JsonString $manifest.payloadRoot "payloadRoot" "payload"
|
|
Test-JsonString $manifest.entry.cwd "entry.cwd" "payload"
|
|
Test-JsonString $manifest.entry.executable "entry.executable" "payload/Open Design.exe"
|
|
|
|
$entryPath = Join-Path $extractRoot (Convert-ArchiveRelativePath "payload/Open Design.exe")
|
|
if (-not (Test-Path -LiteralPath $entryPath)) {
|
|
throw "launcher payload entry executable not found after extraction: $entryPath"
|
|
}
|
|
$configPath = Join-Path $extractRoot (Convert-ArchiveRelativePath "payload/resources/open-design-config.json")
|
|
if (-not (Test-Path -LiteralPath $configPath)) {
|
|
throw "launcher payload packaged config not found after extraction: $configPath"
|
|
}
|
|
} finally {
|
|
Remove-Item -LiteralPath $extractRoot -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
function Resolve-LocalUpdateVersion([string]$Channel, [string]$Version) {
|
|
if ($Channel -eq "stable") {
|
|
$stableMatch = [System.Text.RegularExpressions.Regex]::Match($Version, "^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)$")
|
|
if (-not $stableMatch.Success) {
|
|
throw "full Windows stable smoke requires stable version x.y.z; got $Version"
|
|
}
|
|
return "{0}.{1}.{2}" -f $stableMatch.Groups['major'].Value, $stableMatch.Groups['minor'].Value, ([int]$stableMatch.Groups['patch'].Value + 1)
|
|
}
|
|
|
|
$releaseChannelPattern = [System.Text.RegularExpressions.Regex]::Escape($Channel)
|
|
$match = [System.Text.RegularExpressions.Regex]::Match($Version, "^(?<base>\d+\.\d+\.\d+)-$releaseChannelPattern\.(?<number>\d+)$")
|
|
if (-not $match.Success) {
|
|
throw "full Windows smoke requires a counted version like x.y.z-$Channel.N; got $Version"
|
|
}
|
|
return "{0}-{1}.{2}" -f $match.Groups['base'].Value, $Channel, ([int]$match.Groups['number'].Value + 1)
|
|
}
|
|
|
|
New-Item -ItemType Directory -Force -Path $WorkRoot, $ToolsPackDir, $CacheDir, $ReportRoot, (Split-Path -Parent $BuildJsonPath), (Split-Path -Parent $IndexPath), (Split-Path -Parent $OutputsPath) | Out-Null
|
|
Remove-Item -LiteralPath $BuildJsonPath -Force -ErrorAction SilentlyContinue
|
|
|
|
try {
|
|
Measure-Step "clean tools-pack win namespace" {
|
|
Invoke-CommandChecked -Arguments @(
|
|
"pnpm.cmd", "exec", "tools-pack", "win", "cleanup",
|
|
"--dir", $ToolsPackDir,
|
|
"--namespace", $ReleaseNamespace,
|
|
"--json"
|
|
)
|
|
}
|
|
|
|
$buildArgs = @(
|
|
"pnpm.cmd", "exec", "tools-pack", "win", "build",
|
|
"--dir", $ToolsPackDir,
|
|
"--cache-dir", $CacheDir,
|
|
"--namespace", $ReleaseNamespace,
|
|
"--portable",
|
|
"--app-version", $ReleaseVersion,
|
|
"--to", $BuildTarget,
|
|
"--json"
|
|
)
|
|
if ($SignMode -eq "on") {
|
|
$buildArgs += "--signed"
|
|
}
|
|
|
|
Measure-Step "tools-pack win build" {
|
|
$buildOutput = & $buildArgs[0] @($buildArgs | Select-Object -Skip 1)
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "tools-pack win build failed with exit code $LASTEXITCODE"
|
|
}
|
|
$buildOutput | Set-Content -LiteralPath $BuildJsonPath -Encoding utf8
|
|
}
|
|
Measure-Step "validate launcher payload artifact" {
|
|
$build = Read-BuildJson
|
|
if ($build -eq $null) {
|
|
throw "build json missing before launcher payload validation: $BuildJsonPath"
|
|
}
|
|
Validate-WinLauncherPayloadArchive -PayloadPath ([string]$build.payloadPath) -ExpectedVersion $ReleaseVersion -Label "primary"
|
|
}
|
|
|
|
$localUpdateArtifactPath = $null
|
|
$localUpdateVersion = $null
|
|
$externalUpdateMetadataUrl = [string]$env:OD_PACKAGED_E2E_WIN_UPDATE_METADATA_URL
|
|
$externalUpdateArtifactPath = [string]$env:OD_PACKAGED_E2E_WIN_UPDATE_ARTIFACT_PATH
|
|
$externalUpdateVersion = [string]$env:OD_PACKAGED_E2E_WIN_UPDATE_VERSION
|
|
$hasExternalUpdateMetadata = -not [string]::IsNullOrWhiteSpace($externalUpdateMetadataUrl)
|
|
$hasExternalUpdateArtifactPair = -not [string]::IsNullOrWhiteSpace($externalUpdateArtifactPath) -and -not [string]::IsNullOrWhiteSpace($externalUpdateVersion)
|
|
|
|
if ($SmokeMode -eq "full" -and -not $hasExternalUpdateMetadata -and -not $hasExternalUpdateArtifactPair) {
|
|
$localUpdateVersion = Resolve-LocalUpdateVersion -Channel $ReleaseChannel -Version $ReleaseVersion
|
|
$fixtureDir = Join-Path $WorkRoot "tools-pack-update-fixture"
|
|
$fixtureJsonPath = Join-Path $WorkRoot "windows-tools-pack-update-build.json"
|
|
$updateArgs = @(
|
|
"pnpm.cmd", "exec", "tools-pack", "win", "build",
|
|
"--dir", $fixtureDir,
|
|
"--cache-dir", $CacheDir,
|
|
"--namespace", $ReleaseNamespace,
|
|
"--app-version", $localUpdateVersion,
|
|
"--to", "nsis",
|
|
"--json"
|
|
)
|
|
if ($SignMode -eq "on") {
|
|
$updateArgs += "--signed"
|
|
}
|
|
Measure-Step "tools-pack win build update fixture" {
|
|
$updateOutput = & $updateArgs[0] @($updateArgs | Select-Object -Skip 1)
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "tools-pack win update fixture build failed with exit code $LASTEXITCODE"
|
|
}
|
|
$updateOutput | Set-Content -LiteralPath $fixtureJsonPath -Encoding utf8
|
|
$updateBuild = Get-Content -LiteralPath $fixtureJsonPath -Raw | ConvertFrom-Json
|
|
$localUpdateArtifactPath = [string]$updateBuild.installerPath
|
|
if ([string]::IsNullOrWhiteSpace($localUpdateArtifactPath)) {
|
|
throw "tools-pack win build update fixture did not report installerPath"
|
|
}
|
|
}
|
|
Measure-Step "validate launcher payload update fixture" {
|
|
$updateBuild = Get-Content -LiteralPath $fixtureJsonPath -Raw | ConvertFrom-Json
|
|
Validate-WinLauncherPayloadArchive -PayloadPath ([string]$updateBuild.payloadPath) -ExpectedVersion $localUpdateVersion -Label "update-fixture"
|
|
}
|
|
}
|
|
|
|
if ($SmokeMode -eq "skip") {
|
|
Write-Host "Skipping Windows packaged runtime smoke: smoke mode skip"
|
|
} else {
|
|
$previous = @{
|
|
OD_PACKAGED_E2E_BUILD_JSON_PATH = $env:OD_PACKAGED_E2E_BUILD_JSON_PATH
|
|
OD_PACKAGED_E2E_WIN = $env:OD_PACKAGED_E2E_WIN
|
|
OD_PACKAGED_E2E_WIN_SMOKE_PROFILE = $env:OD_PACKAGED_E2E_WIN_SMOKE_PROFILE
|
|
OD_PACKAGED_E2E_NAMESPACE = $env:OD_PACKAGED_E2E_NAMESPACE
|
|
OD_PACKAGED_E2E_RELEASE_CHANNEL = $env:OD_PACKAGED_E2E_RELEASE_CHANNEL
|
|
OD_PACKAGED_E2E_RELEASE_VERSION = $env:OD_PACKAGED_E2E_RELEASE_VERSION
|
|
OD_PACKAGED_E2E_REPORT_DIR = $env:OD_PACKAGED_E2E_REPORT_DIR
|
|
OD_PACKAGED_E2E_TOOLS_PACK_DIR = $env:OD_PACKAGED_E2E_TOOLS_PACK_DIR
|
|
OD_PACKAGED_E2E_WIN_UPDATE_FIXTURE = $env:OD_PACKAGED_E2E_WIN_UPDATE_FIXTURE
|
|
OD_PACKAGED_E2E_WIN_UPDATE_ARTIFACT_PATH = $env:OD_PACKAGED_E2E_WIN_UPDATE_ARTIFACT_PATH
|
|
OD_PACKAGED_E2E_WIN_UPDATE_VERSION = $env:OD_PACKAGED_E2E_WIN_UPDATE_VERSION
|
|
OD_PACKAGED_E2E_WIN_UPDATE_BUILD_JSON_PATH = $env:OD_PACKAGED_E2E_WIN_UPDATE_BUILD_JSON_PATH
|
|
}
|
|
try {
|
|
$env:OD_PACKAGED_E2E_BUILD_JSON_PATH = $BuildJsonPath
|
|
$env:OD_PACKAGED_E2E_WIN = "1"
|
|
$env:OD_PACKAGED_E2E_WIN_SMOKE_PROFILE = $SmokeMode
|
|
$env:OD_PACKAGED_E2E_NAMESPACE = $ReleaseNamespace
|
|
$env:OD_PACKAGED_E2E_RELEASE_CHANNEL = $ReleaseChannel
|
|
$env:OD_PACKAGED_E2E_RELEASE_VERSION = $ReleaseVersion
|
|
$env:OD_PACKAGED_E2E_REPORT_DIR = $ReportRoot
|
|
$env:OD_PACKAGED_E2E_TOOLS_PACK_DIR = $ToolsPackDir
|
|
if (-not [string]::IsNullOrWhiteSpace($localUpdateArtifactPath)) {
|
|
$env:OD_PACKAGED_E2E_WIN_UPDATE_FIXTURE = "tools-serve"
|
|
$env:OD_PACKAGED_E2E_WIN_UPDATE_ARTIFACT_PATH = $localUpdateArtifactPath
|
|
$env:OD_PACKAGED_E2E_WIN_UPDATE_VERSION = $localUpdateVersion
|
|
$env:OD_PACKAGED_E2E_WIN_UPDATE_BUILD_JSON_PATH = Join-Path $WorkRoot "windows-tools-pack-update-build.json"
|
|
}
|
|
Measure-Step "release smoke win" {
|
|
Remove-Item -LiteralPath $ReportRoot -Recurse -Force -ErrorAction SilentlyContinue
|
|
Invoke-CommandChecked -Arguments @("pnpm.cmd", "exec", "tsx", "scripts/release-smoke.ts", "win", "specs/win.spec.ts") -WorkingDirectory (Join-Path (Get-Location).Path "e2e")
|
|
}
|
|
} finally {
|
|
foreach ($key in $previous.Keys) {
|
|
if ([string]::IsNullOrWhiteSpace([string]$previous[$key])) {
|
|
Remove-Item "Env:$key" -ErrorAction SilentlyContinue
|
|
} else {
|
|
[Environment]::SetEnvironmentVariable($key, [string]$previous[$key], "Process")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Write-Index "success"
|
|
Write-Host "$ReleaseChannel build index: $IndexPath"
|
|
} catch {
|
|
if ($script:failureMessage -eq $null) {
|
|
$script:failureMessage = $_.Exception.Message
|
|
}
|
|
try {
|
|
Write-Index "failed"
|
|
} catch {
|
|
Write-Warning "failed to write $ReleaseChannel build index: $($_.Exception.Message)"
|
|
}
|
|
throw
|
|
}
|