3
votes

After reading VSCode Publish Extension docs, I've succeeded to publish a VSCode extension manually with vsce.

I'm wondering if there is a way to publish extensions automatically via Azure DevOps pipelines (build or release) instead of doing it manually.

I've tried to use vsce there but I'm getting an authentication error

Resource not available for anonymous access. Client authentication required.

Using vsce publish -p <access_token> is not possible because the pipeline is public and everyone can see the access token...

So, is there a way to publish a Visual Studio Code extension automatically via Azure DevOps Pipeline or even Travis CI?

3
And now is part of the official docs - code.visualstudio.com/api/working-with-extensions/… - Mosh Feu
@MoshFeu Any reason why in the docs you are publishing the extension with the Build pipeline and not the Release pipeline of Azure DevOps? - nrofis
@MoshFeu I tried your steps with the Release Pipeline (instead of build). All worked fine, but when I've added a dependency it started to fail in yarn deploy with the error Command failed: npm list --production --parseable --depth=99999 (my dependencies were missing). How can I solve this? - nrofis
Actually I never tried to use the release pipeline. I'll try and return with my conclusions - Mosh Feu

3 Answers

4
votes

You can add the Personal Access Token as a secret variable, then nobody can couldn't see it.

Go to Azure DevOps to your pipeline and click on "Edit", not in the top left click on "Variables":

enter image description here

Now click on the + icon and add the variable, mark the checkbox "Keep this value secret":

enter image description here

Now you can use it in this way: $(PAT), for example:

vsce publish -p $(PAT)

The variable value will not appear in the YAML :)

3
votes

Is there a way to publish a Visual Studio Code extension automatically via Azure DevOps Pipeline?

Of course yes!

To have a good experience for CI/CD in Azure Devops, I recommend you store the source code in Azure Devops or Github.

  • Build \ CI

In build, most of work is update the version which in manifest of VSIX, build\create package. For the version increased, here I use the counter expression feature which supported in VSTS to achieve that:

counter('name', seed)

Use this expression in variable declaration bloc. For detailed and completed build process, refer to my sample YAML code:

trigger:
- '*'

pool:
  vmImage: 'windows-2019'

variables:
  VersionPatch: $[counter('versioncount', 24)]
  solution: '**/*.sln'
  BuildPlatform: 'Any CPU'
  BuildConfiguration: 'Release'

name: 2.0.$(VersionPatch)

steps:
- task: UseDotNet@2
  inputs:
    packageType: 'sdk'
    version: '3.0.100'
    includePreviewVersions: true

- task: NuGetToolInstaller@1
  inputs:
    versionSpec: 5.1.0

- task: PowerShell@2
  displayName: Update version
  inputs:
    filePath: 'Build\VersionUpdate.ps1'
    arguments: '$(Build.BuildNumber)'
    pwsh: true

- task: NuGetCommand@2
  inputs:
    command: 'restore'

- task: DotNetCoreCLI@2
  displayName: 
  inputs:
    command: 'restore'
    projects: 'tests/**/*.csproj'
    vstsFeed: '{My feed ID}'
    includeNuGetOrg: false

- task: VSBuild@1
  inputs:
    solution: '**\*.sln'
    maximumCpuCount: true
    platform: '$(BuildPlatform)'
    configuration: '$(BuildConfiguration)'

- task: VSTest@2
  inputs:
    platform: '$(BuildPlatform)'
    configuration: '$(BuildConfiguration)'

- task: CopyFiles@2
  inputs:
    SourceFolder: '$(Build.SourcesDirectory)'
    Contents: |
      Build/**
      **/*.vsix
      **/*.nupkg
      README.md
    TargetFolder: '$(Build.ArtifactStagingDirectory)'

- task: PublishPipelineArtifact@0
  inputs:
    artifactName: 'ExtensionDrop'
    targetPath: '$(Build.ArtifactStagingDirectory)'

In UpdateVersion.ps1 file:

$VerbosePreference="Continue"
$version = $args[0]
if (!$version) {
    $version = "0.0.0"
}
Write-Host "This Version is: $version"
$FullPath = Resolve-Path $PSScriptRoot\..\src\Merlin.Compiler.Vsix\source.vsixmanifest
Write-Host $FullPath
[xml]$content = Get-Content $FullPath
$content.PackageManifest.Metadata.Identity.Version = $version
$content.Save($FullPath)
  • Release\ CD

After build succeed, set the release pipeline for this repos. In release, use powershell script and VsixPublisher.exe to publish the vsix file.

$PAToken = $args[0]
$VsixPath = "$PSScriptRoot\..\src\Merlin.Compiler.Vsix\bin\Release\Merlin.Compiler.Vsix"
$ManifestPath = "$PSScriptRoot\ExtensionManifest.json"
$Installation = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -prerelease -format json | ConvertFrom-Json
$Path = $Installation.installationPath
$VsixPublisher = Join-Path -Path $Path -ChildPath "VSSDK\VisualStudioIntegration\Tools\Bin\VsixPublisher.exe" -Resolve
& $VsixPublisher publish -payload $VsixPath -publishManifest $ManifestPath -personalAccessToken $PAToken -ignoreWarnings "VSIXValidatorWarning01,VSIXValidatorWarning02,VSIXValidatorWarning08"

In CD, use VsixPublisher.exe which exist in VS to publish the vsix file.


You can set the PAToken in Variable tab, then set it as secret. Thus it would not be public for others. Here PAT token is a necessary one which could not be replaced by others. And also, when generate the token, need choose All accessible organizations. Or it will cause the permission error.

enter image description here

2
votes

Further @Shayki's answer there are some more steps because you can't just run vsce publish -p $(PAT).

  1. The vsce should be installed (can be in devDependencies)
  2. Add a "deploy" (or name it as you like) script to the package.json scripts.
"deploy": "vsce publish -p"
  1. Add a "publish" step in the azure-pipeline.yml file. the condition is for running the publish script only on master so Pull Requests will not publish. Also run it only in Linux's build, in case you configured multiple platforms. If you configured only one (for example, windows) replace Linux with that platform
- bash: |
   echo ">>> Publish"
   yarn deploy $(token)
 displayName: Publish
 condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['Agent.OS'], 'Linux'))

Example azure-pipeline.yml