In a previous post 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

The source for this project is here and the diff for changes mentioned in this post is here. The extension itself is on the Visual Studio Marketplace here.

Development and Release Versions

Initially I followed a colleague’s post on setting up a pipeline and this devblog from Microsoft 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
  |-- 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 the master branch, and the new development instance is triggered 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.


"version": "1.2.3"


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

The solution proposed by microsoft 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.

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 & deploy with tfx-cli. For the latter we’ll need a PAT from our Azure Devops tenant, instructions can be found here.

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.


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