Skip to content

Move VS Code extension Marketplace publishing to release-publish-nuget pipeline#14847

Closed
adamint wants to merge 1 commit intomicrosoft:release/13.2from
adamint:dev/adamint/vscode-extension-release-pipeline-13.2
Closed

Move VS Code extension Marketplace publishing to release-publish-nuget pipeline#14847
adamint wants to merge 1 commit intomicrosoft:release/13.2from
adamint:dev/adamint/vscode-extension-release-pipeline-13.2

Conversation

@adamint
Copy link
Copy Markdown
Member

@adamint adamint commented Mar 2, 2026

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:

  • Added PublishVSCodeExtension parameter to enable extension publishing
  • Added VSCodeExtensionPreRelease parameter for pre-release publishing
  • Added SkipExtensionPublish idempotency flag for re-runs
  • Added VSCodeExtensionJob that:
    • Downloads signed aspire-vscode-extension artifact from the source build
    • Verifies .vsix, manifest, and signature files exist
    • Validates PKCS#7 signature format
    • Verifies Marketplace PAT is valid
    • Publishes to VS Code Marketplace (with optional pre-release flag)
  • Updated Summary section to show extension publishing status
  • Variable group Aspire-Release-Secrets already contains both NuGetApiKey and VscePublishToken

azure-pipelines.yml:

  • Removed publishVSCodeExtension and vscePublishPreRelease parameters
  • Removed conditional Aspire-Release-Secrets variable group reference

BuildAndTest.yml:

  • Removed publishing parameters (publishVSCodeExtension, vscePublishToken, vscePublishPreRelease)
  • Removed PAT verification and Marketplace publishing steps
  • Kept: Build, signing, signature verification, and artifact publishing (required for release pipeline to have signed artifacts)

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No (pipeline YAML changes only - no code changes)
  • Did you add public API?
    • Yes
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
    • No
  • Does the change require an update in our Aspire docs?
    • Yes
    • No

Copilot AI review requested due to automatic review settings March 2, 2026 18:32
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 2, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 14847

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 14847"

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 VSCodeExtensionJob to 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.

Comment on lines +478 to +492
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"
}
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +675 to +681
# 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
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +645 to +646
$manifestPath = Join-Path $extensionPath "$baseName.manifest"
$signaturePath = Join-Path $extensionPath "$baseName.signature.p7s"
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$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.

Suggested change
$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"

Copilot uses AI. Check for mistakes.
Comment on lines +585 to +694
- 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
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +675 to +683
# 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"
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
# 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
}

Copilot uses AI. Check for mistakes.
default: false

# VS Code Extension Publishing Parameters
- name: PublishVSCodeExtension
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this when we have SkipExtensionPublish, which follows the pattern in this pipeline?

Copy link
Copy Markdown
Member

@mitchdenny mitchdenny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@joperezr
Copy link
Copy Markdown
Member

What's missing on this one @adamint? Is it ready to go?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants