An Azure Pipeline for Visual Studio Marketplace
Tags: azuredevops, azurepipelines
Published: 2019-11-05 08:50:49

---
title: An Azure Pipeline for Visual Studio Marketplace
published: true
tags: azure-devops,azure-pipelines
canonical_url: http://staffordwilliams.com/blog/2019/10/28/an-azure-pipeline-for-visual-studio-marketplace/
---

In a [previous post](https://staffordwilliams.com/blog/2019/05/19/publishing-a-custom-azure-pipeline-release-task/) I built a custom Azure Pipeline release task and manually uploaded it to the Visual Studio Marketplace. In this post I’ll add new features and have DEV and PROD versions of the extension automatically deploy to the Marketplace on commit.

![Purge Cache for Cloudflare - an Azure Pipelines Task](http://staffordwilliams.com/assets/tfx-header.png)

The [source for this project is here](https://github.com/staff0rd/tfx-cloudflare-purge) and the [diff for changes mentioned in this post is here](https://github.com/staff0rd/tfx-cloudflare-purge/compare/9976e5366975092149d2b2b064c3c86f6f5ebd1b...f876e6e9807763cf4a8d7738feb3f10ba2a632c3).

## Development and Release Versions

Initially I followed a [colleague’s post on setting up a pipeline](https://blog.raph.ws/2018/03/build-and-release-pipeline-for-your-own-custom-vsts-tasks/) and [this devblog from Microsoft](https://devblogs.microsoft.com/devops/streamlining-azure-devops-extension-development/) in an attempt to have a dev (and prod) version of the release task. However, I later determined that there’s a difference between the _extension_ that is defined and published by `vss-extension.json` and the (one or more) tasks that are packaged with the extension and defined by `taskName/task.json`. I could successfully push a unique-id extension to my existing publisher, but the deployment would then fail due to the marketplace already having an existing version of the _task_.

As such, the approach was to name and key both `vss-extension.json` (now renamed `manifest-*.json`) and `task.json` specifically for dev/prod. The core changes to achieve this result in a structure like this:

```
azure-pipelines-dev.yml # dev pipeline
azure-pipelines-release.yml # release pipeline
manifest-dev.json # dev extension definition
manifest-release.json # release extension definition
cloudflarePurge
  |-- task.json # dev & release task definition

```

_NB: Actually `manifest-dev.json` and `manifest-release.json` could be merged and token-replaced similar to `task.json`._

The result is two extensions and their associated tasks:

- Purge Cache for Cloudflare and;
- Purge Cache for Cloudflare (Development)

The first is the existing, already published version, and the second is a brand new instance specifically for development and testing and with `public: false` set. The production instance continues to reflect and is now [triggered from](https://github.com/staff0rd/tfx-cloudflare-purge/blob/f876e6e9807763cf4a8d7738feb3f10ba2a632c3/azure-pipelines-release.yml#L1-L2) the `master` branch, and the new development instance is [triggered](https://github.com/staff0rd/tfx-cloudflare-purge/blob/f876e6e9807763cf4a8d7738feb3f10ba2a632c3/azure-pipelines-dev.yml#L1-L2) from the `dev` branch.

## Token Replacement

An immediate issue with my proposed pipeline was the difference in how `vss-extension.json` and `task.json` deal with `version` and how JSON variable substitution works within the standard File Transform task.

**vss-extension.json**

```
"version": "1.2.3"

```

**task.json**

```
"version": {
    "Major": 1,
    "Minor": 2,
    "Patch": 3
}

```

The solution [proposed by microsoft](https://github.com/microsoft/azure-pipelines-tasks/issues/11344) was to split the two variable transforms across jobs. I’ll give this approach a go in the future, however for now I’ve just used a single job and [token replacement](https://github.com/staff0rd/tfx-cloudflare-purge/blob/f876e6e9807763cf4a8d7738feb3f10ba2a632c3/azure-pipelines-release.yml#L30-L35).

This same token replacement is used for setting the `name` and `id` of the extension and task.

## Build and deploy

Outside token-replacement the only steps in both dev/prod pipelines are;

1. Install node
2. Install dependencies
3. Compile typescript
4. Deploy to marketplace

We’ll use `npm` to install the dependencies and use `npx` to [compile with typescript](https://github.com/staff0rd/tfx-cloudflare-purge/blob/f876e6e9807763cf4a8d7738feb3f10ba2a632c3/azure-pipelines-dev.yml#L39) & [deploy with tfx-cli](https://github.com/staff0rd/tfx-cloudflare-purge/blob/f876e6e9807763cf4a8d7738feb3f10ba2a632c3/azure-pipelines-dev.yml#L43). For the latter we’ll need a PAT from our Azure Devops tenant, [instructions can be found here](https://blog.raph.ws/2018/03/build-and-release-pipeline-for-your-own-custom-vsts-tasks/#generate-pat).

Note that before installing `npm` dependencies, it’s important to set the working directory to the task directory. Doing so installs dependencies adjacent to the task and ensures they are shipped with `tfx-cli extension publish`. Originally I had not done this, such that the extension was building and shipping without the dependencies, meaning users of the extension needed to `npm install` themself - my thanks to Patrycjusz Jaskurzyński who reported, and helped me diagnose, this issue.

## Further differences

An issue with the previous version of the extension was that the provided icon was only visibile on the marketplace, but once installed into an Azure DevOps tenant, a default icon rather than the one supplied was visible when adding the task to a release pipeline.

Where `vss-manifest.json` defines an `icons` property, `task.json` does not, and instead by convention checks for `icon.png` in the same directory. Additionally some documentation indicates an icon size of 32x32 is required - this will however result in a lower quality image once installed as the icon will be scaled up in size. To resolve this supply the icon at 128x128.

## Conclusion

The last thing I needed to do was actually install the new Development version of the extension. This is an entirely different extension as far as Visual Studio Marketplace is concerned, but I can have both of them installed on my single Azure Devops tenant. The dev version’s `public: false` property in the extension definition and `--share-with myUserName` in the [pipeline defintion](https://github.com/staff0rd/tfx-cloudflare-purge/blob/f876e6e9807763cf4a8d7738feb3f10ba2a632c3/azure-pipelines-dev.yml#L43) means only my Azure Devops tenant can see the development version. While developing and testing, I commit directly to the `dev` branch and the development version of the extension is deployed to the Visual Studio Marketplace and updated on my Azure Devops tenant. Once i’m satisfied, I merge `dev` into `master`, which triggers the production build & deploy.

As for the extension itself, now you can specify individual URLs instead of purging everything:

![files to purge](http://staffordwilliams.com/assets/tfx-files-to-purge.png)