Move VS Code extension Marketplace publishing to release-publish-nuget pipeline#14847
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 14847Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 14847" |
There was a problem hiding this comment.
Pull request overview
Moves VS Code extension publishing out of the main build pipeline and into the release publishing pipeline so that all external publishing happens in release-publish-nuget.yml.
Changes:
- Removes VS Code Marketplace publishing parameters/steps from the main build pipeline templates while keeping extension build/sign/verify and artifact publishing.
- Adds VS Code extension publishing parameters and a dedicated
VSCodeExtensionJobto the release pipeline, including artifact download + publish flow. - Updates release pipeline parameter validation output and the end-of-job summary to include VS Code extension publishing status.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| eng/pipelines/templates/BuildAndTest.yml | Removes Marketplace publishing steps/parameters while retaining signed VSIX artifact production for the release pipeline to consume. |
| eng/pipelines/release-publish-nuget.yml | Adds parameters and a new job to download the signed VSIX artifact and publish it to the VS Code Marketplace. |
| eng/pipelines/azure-pipelines.yml | Removes runtime parameters and variable-group wiring previously used for Marketplace publishing in the main build pipeline. |
| Write-Host "║ NuGet Publish: " -NoNewline | ||
| if ("${{ parameters.SkipNuGetPublish }}" -eq "true") { | ||
| Write-Host " (SKIPPED)" | ||
| Write-Host "SKIPPED" | ||
| } else { | ||
| Write-Host " (EXECUTED)" | ||
| Write-Host "EXECUTED" | ||
| } | ||
| Write-Host "║ Channel Promo: ${{ parameters.SkipChannelPromotion }}" -NoNewline | ||
| Write-Host "║ VS Code Ext: " -NoNewline | ||
| if ("${{ parameters.PublishVSCodeExtension }}" -eq "false") { | ||
| Write-Host "NOT REQUESTED" | ||
| } elseif ("${{ parameters.SkipExtensionPublish }}" -eq "true") { | ||
| Write-Host "SKIPPED" | ||
| } else { | ||
| $preRelease = if ("${{ parameters.VSCodeExtensionPreRelease }}" -eq "true") { " (pre-release)" } else { "" } | ||
| Write-Host "EXECUTED$preRelease" | ||
| } |
There was a problem hiding this comment.
The summary prints "NuGet Publish: EXECUTED" whenever SkipNuGetPublish is false, even when DryRun is true (in which case no publish occurs). Similarly, the VS Code extension summary prints EXECUTED even in dry-run mode. Consider updating the summary logic to account for DryRun and emit something like "DRY RUN" to avoid misleading release logs.
| # Verify PAT is valid | ||
| Write-Host "Verifying VS Code Marketplace PAT..." | ||
| $publisher = "microsoft-aspire" | ||
| npx vsce verify-pat $publisher | ||
| if ($LASTEXITCODE -ne 0) { | ||
| Write-Error "PAT verification failed for publisher '$publisher'. Ensure the token has 'Marketplace: Manage' scope." | ||
| exit 1 |
There was a problem hiding this comment.
Before running vsce verify-pat, consider explicitly validating that the VSCE_PAT environment variable is set/non-empty and failing with a clear message if it isn't. This makes failures easier to diagnose than relying on verify-pat's generic exit code.
| $manifestPath = Join-Path $extensionPath "$baseName.manifest" | ||
| $signaturePath = Join-Path $extensionPath "$baseName.signature.p7s" |
There was a problem hiding this comment.
$vsixFiles is collected with -Recurse, but the manifest/signature paths are constructed under $extensionPath (root). If the artifact ever contains nested folders, this will look in the wrong place and fail publishing. Consider either (1) not using -Recurse, matching the build artifact layout, or (2) deriving the manifest/signature paths from $vsix.DirectoryName (or searching adjacent files) so the trio stays together.
| $manifestPath = Join-Path $extensionPath "$baseName.manifest" | |
| $signaturePath = Join-Path $extensionPath "$baseName.signature.p7s" | |
| $vsixDirectory = $vsix.DirectoryName | |
| $manifestPath = Join-Path $vsixDirectory "$baseName.manifest" | |
| $signaturePath = Join-Path $vsixDirectory "$baseName.signature.p7s" |
| - powershell: | | ||
| Write-Host "Installing vsce CLI..." | ||
| npm install -g @vscode/vsce@3.7.1 | ||
| vsce --version | ||
| displayName: 'Install vsce' | ||
|
|
||
| - task: DownloadBuildArtifacts@0 | ||
| displayName: 'Download VS Code Extension Artifact' | ||
| inputs: | ||
| buildType: specific | ||
| buildVersionToDownload: specific | ||
| project: internal | ||
| pipeline: dotnet-aspire | ||
| buildId: $(resources.pipeline.aspire-build.runID) | ||
| artifactName: aspire-vscode-extension | ||
| downloadPath: '$(Pipeline.Workspace)/extension' | ||
| checkDownloadedFiles: true | ||
|
|
||
| - powershell: | | ||
| $extensionPath = "$(Pipeline.Workspace)/extension/aspire-vscode-extension" | ||
| Write-Host "=== VS Code Extension Inventory ===" | ||
|
|
||
| $vsixFiles = Get-ChildItem -Path $extensionPath -Filter "*.vsix" -Recurse | ||
| Write-Host "Found $($vsixFiles.Count) .vsix files:" | ||
|
|
||
| foreach ($vsix in $vsixFiles) { | ||
| $sizeMB = [math]::Round($vsix.Length / 1MB, 2) | ||
| Write-Host " - $($vsix.Name) ($sizeMB MB)" | ||
| } | ||
|
|
||
| if ($vsixFiles.Count -eq 0) { | ||
| Write-Error "No .vsix files found in artifacts!" | ||
| exit 1 | ||
| } | ||
|
|
||
| # Check for signature files | ||
| $manifestFiles = Get-ChildItem -Path $extensionPath -Filter "*.manifest" -Recurse | ||
| $signatureFiles = Get-ChildItem -Path $extensionPath -Filter "*.signature.p7s" -Recurse | ||
| Write-Host "" | ||
| Write-Host "Found $($manifestFiles.Count) manifest files" | ||
| Write-Host "Found $($signatureFiles.Count) signature files" | ||
|
|
||
| Write-Host "===========================" | ||
| displayName: 'List Extension Files' | ||
|
|
||
| - powershell: | | ||
| $extensionPath = "$(Pipeline.Workspace)/extension/aspire-vscode-extension" | ||
| $dryRun = [System.Convert]::ToBoolean("${{ parameters.DryRun }}") | ||
| $preRelease = [System.Convert]::ToBoolean("${{ parameters.VSCodeExtensionPreRelease }}") | ||
|
|
||
| $vsixFiles = Get-ChildItem -Path $extensionPath -Filter "*.vsix" -Recurse | ||
|
|
||
| Write-Host "=== Publishing VS Code Extension to Marketplace ===" | ||
|
|
||
| if ($dryRun) { | ||
| Write-Host "DRY RUN MODE - Extension will not actually be published" | ||
| } | ||
|
|
||
| foreach ($vsix in $vsixFiles) { | ||
| $baseName = [System.IO.Path]::GetFileNameWithoutExtension($vsix.FullName) | ||
| $manifestPath = Join-Path $extensionPath "$baseName.manifest" | ||
| $signaturePath = Join-Path $extensionPath "$baseName.signature.p7s" | ||
|
|
||
| # Verify required files exist | ||
| if (-not (Test-Path $manifestPath)) { | ||
| Write-Error "Manifest file not found: $manifestPath" | ||
| exit 1 | ||
| } | ||
| if (-not (Test-Path $signaturePath)) { | ||
| Write-Error "Signature file not found: $signaturePath" | ||
| exit 1 | ||
| } | ||
|
|
||
| # Verify signature file is valid PKCS#7 format | ||
| $bytes = [System.IO.File]::ReadAllBytes($signaturePath) | ||
| if ($bytes.Length -eq 0 -or $bytes[0] -ne 0x30) { | ||
| $firstByte = if ($bytes.Length -gt 0) { "0x{0:X2}" -f $bytes[0] } else { "empty" } | ||
| Write-Error "$baseName.signature.p7s does NOT appear to be signed. First byte: $firstByte (expected 0x30 for PKCS#7)" | ||
| exit 1 | ||
| } | ||
| Write-Host "Signature file format verified" | ||
|
|
||
| if ($dryRun) { | ||
| Write-Host "[DRY RUN] Would publish: $($vsix.Name)" | ||
| if ($preRelease) { | ||
| Write-Host "[DRY RUN] Would publish as PRE-RELEASE" | ||
| } | ||
| continue | ||
| } | ||
|
|
||
| # Verify PAT is valid | ||
| Write-Host "Verifying VS Code Marketplace PAT..." | ||
| $publisher = "microsoft-aspire" | ||
| npx vsce verify-pat $publisher | ||
| if ($LASTEXITCODE -ne 0) { | ||
| Write-Error "PAT verification failed for publisher '$publisher'. Ensure the token has 'Marketplace: Manage' scope." | ||
| exit 1 | ||
| } | ||
| Write-Host "PAT verified successfully" | ||
|
|
||
| # Build publish arguments | ||
| $publishArgs = @("vsce", "publish", "--packagePath", $vsix.FullName, "--manifestPath", $manifestPath, "--signaturePath", $signaturePath) | ||
| if ($preRelease) { | ||
| $publishArgs += "--pre-release" | ||
| Write-Host "Publishing $($vsix.Name) to VS Code Marketplace as PRE-RELEASE..." | ||
| } else { | ||
| Write-Host "Publishing $($vsix.Name) to VS Code Marketplace..." | ||
| } | ||
|
|
||
| & npx @publishArgs |
There was a problem hiding this comment.
The script installs @vscode/vsce globally, but later uses npx vsce ... / npx for publishing. This can execute a different vsce than the one you pinned (or trigger an on-the-fly download), which makes the release less deterministic. Prefer invoking the installed vsce binary directly, or use npx @vscode/vsce@<pinnedVersion> consistently.
| # Verify PAT is valid | ||
| Write-Host "Verifying VS Code Marketplace PAT..." | ||
| $publisher = "microsoft-aspire" | ||
| npx vsce verify-pat $publisher | ||
| if ($LASTEXITCODE -ne 0) { | ||
| Write-Error "PAT verification failed for publisher '$publisher'. Ensure the token has 'Marketplace: Manage' scope." | ||
| exit 1 | ||
| } | ||
| Write-Host "PAT verified successfully" |
There was a problem hiding this comment.
PAT verification is performed inside the loop for every .vsix. This adds redundant network calls and increases the chance of transient failures/rate limiting. Verify the PAT once before iterating through packages, then reuse that result for all publishes in the job.
| # Verify PAT is valid | |
| Write-Host "Verifying VS Code Marketplace PAT..." | |
| $publisher = "microsoft-aspire" | |
| npx vsce verify-pat $publisher | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Error "PAT verification failed for publisher '$publisher'. Ensure the token has 'Marketplace: Manage' scope." | |
| exit 1 | |
| } | |
| Write-Host "PAT verified successfully" | |
| # Verify PAT is valid once per job (cache result in script scope) | |
| if (-not $script:VscePatVerified) { | |
| Write-Host "Verifying VS Code Marketplace PAT..." | |
| $publisher = "microsoft-aspire" | |
| npx vsce verify-pat $publisher | |
| if ($LASTEXITCODE -ne 0) { | |
| Write-Error "PAT verification failed for publisher '$publisher'. Ensure the token has 'Marketplace: Manage' scope." | |
| exit 1 | |
| } | |
| Write-Host "PAT verified successfully" | |
| $script:VscePatVerified = $true | |
| } |
| default: false | ||
|
|
||
| # VS Code Extension Publishing Parameters | ||
| - name: PublishVSCodeExtension |
There was a problem hiding this comment.
Do we need this when we have SkipExtensionPublish, which follows the pattern in this pipeline?
mitchdenny
left a comment
There was a problem hiding this comment.
Clean move of extension publishing from build to release pipeline. Good separation of concerns, idempotency flags for re-runs, and dry-run support carried over.
|
What's missing on this one @adamint? Is it ready to go? |
Description
This PR moves VS Code extension Marketplace publishing from the main build pipeline (
azure-pipelines.yml) to the release pipeline (release-publish-nuget.yml). This centralizes all release publishing in one place.Changes
release-publish-nuget.yml:
PublishVSCodeExtensionparameter to enable extension publishingVSCodeExtensionPreReleaseparameter for pre-release publishingSkipExtensionPublishidempotency flag for re-runsVSCodeExtensionJobthat:aspire-vscode-extensionartifact from the source buildSummarysection to show extension publishing statusAspire-Release-Secretsalready contains bothNuGetApiKeyandVscePublishTokenazure-pipelines.yml:
publishVSCodeExtensionandvscePublishPreReleaseparametersAspire-Release-Secretsvariable group referenceBuildAndTest.yml:
publishVSCodeExtension,vscePublishToken,vscePublishPreRelease)Checklist