Creating an Azure Pipelines extension
Most of what an Azure Pipelines build or release should do can be accomplished by using the builtin tasks or tasks from the marketplace. Especially generic tasks like the Command Line task combined with the ability to install software on the build agents by using package managers (e.g. Chocolatey on Windows) provide a lot of creative freedom.
By implementing and publishing your own extension containing your custom tasks, you can consistently reuse functionality and make it easily accessible to other people inside and outside of your organization.
Sample extension
The extension used as an example in this blog post contains tasks to validate websites, namely a task that checks a website for broken links. Its source can be found in GitHub.
The code that makes up the essence of the extension’s task is implemented in TypeScript and is transpiled to JavaScript before the extension package is generated. Azure Pipelines runs it with Node.js when it executes the task.
For detailed information about the files and folders of the sample extension, check out the Appendix.
Create an extension using the sample extension
Prerequisites
- Nodejs
- TypeScript
TypeScript can be installed using npm by runningnpm install -g typescript
- TFS Cross Platform Command Line Interface (tfx-cli) to package your extensions.
tfx-cli can be installed using npm by runningnpm i -g tfx-cli
Initialize folders and files
- Clone or download the sample extension’s repository.
- Adapt file and folder names as well as contents of
package.json
,vss-extension.json
,task.json
. - Change contents of
README.md
,overview.md
andlicense.md
. - Change the icons.
- In the console, from the root directory, execute
npm install
- In the console, from each subfolder of
src/tasks
, execute
npm install
Implement and build
- Implement some awesome functionality (in your
*.ts
files). - To fill the
dist
folder with all the files needed to build extension package, execute in the console, from the root folder
npm run-script build
- To build the extension package, execute in the console, from the root folder
npm run-script package
(no need to executenpm run-script build
prior to that)
(Note: The definition of the build
and package
commands mentioned above can be found in the root directory’s package.json
file.)
Publish to the marketplace
To publish your extension to the marketplace, go to https://marketplace.visualstudio.com/azuredevops and click on Publish extension. You will need to sign in and create a Publisher.
Updating an extension that’s already published and in use
When an extension is updated in the marketplace, its instances that are installed in Azure DevOps accounts will not automatically get that update. This is good for the production scenario because nobody wants their build to fail because there’s a breaking change in an extension.
However, this means that you have to uninstall and reinstall the extension in the DevOps account you use for testing while you are developing and testing your extension.
If there are no breaking changes in the task definitions (e.g. new required input fields), you don’t have to revisit your build definitions containing the tasks. The existing task instances also don’t get removed from build definitions by uninstalling and reinstalling the extension so don’t worry.
To uninstall the extension go to Organization settings > Extensions > Manage (https://dev.azure.com/someAccount/_settings/extensions
). Hover over the extension and click More actions (the three dots).
Publish the extension using the tfx
tool
As an alternative to publishing or updating your extension with Microsoft’s web UI, you can also use the tfx
tool. Run tfx extension publish --help
in the command line to find out how.
CI/CD for the extension using Azure DevOps
Thanks to the awesome existing extension CI/CD Tools for VSTS Extensions you can also build and publish your extension using Azure DevOps itself. It can even update instances of your extension to facilitate your extension development and testing.
Appendix
Sample Extension folder structure
build build scripts
clean.js deletes the dist folder
build.js copies files to the dist folder and calls npminstall.js
npminstall.js calls npm install for each src/tasks/*/package.json files
dist build output goes here
node_modules node modules used during development solely
src the source of the Azure DevOps extension
tasks contains a subfolder for each build/release task
brokenLinkChecker brokenLinkChecker task subfolder
node_modules node modules used for the task
brokenLinkCheckerTask.ts brokenLinkChecker task source code
icon.png icon displayed in the UI where the task is configured
package.json lists node modules used for the task
task.json defines the task configuration UI and its startup file
anotherTask
...
extension-icon.png extension icon displayed in the market place (128*128)
license.md
overview.md content of the extension info page in the marketplace
vss-extension.json extension manifest file listing tasks, icon, version
package.json lists node modules used during development
README.md documentation about the repository (NOT the extension)
tsconfig.json TypeScript project file
Rationale behind the folder structure
As the extension is implemented in TypeScript which has to be transpiled to JavaScript, separate folders for the source (src
folder containing TypeScript) and the transpiled JavaScript (dist
folder) are used. Setting "rootDir":"src"
and "outDir":"dist"
in tsconfig.json
make sure the resulting folder structure in dist
is the same as in src
. This and programmatically copying all other files from src
to dist
make for an easy solution to end up with all the extension files in the dist
folder from which the package can then be created.
(The actual - but also complicated and not that important - reason for having an src
and a dist
folder as opposed to have neither of them is the following: All the files that make up a task must be self contained in a single folder. This includes the files in node_modules
. Because the node_modules
folder might potentially contain more files for development than for production, separating src
from dist
prevents bloating the extension package by including only the necessary modules.)
Additional information about some files
- Extension manifest
vss-extension.json
- Official Reference
- The
version
attribute must be changed before the extension can be updated in the marketplace. As an alternative the version can be provided when generating the extension package with thetfx
tool. - Including a
node_modules
folder in the package by referencing it in thefiles
attribute with the intention to have the contained modules available to all tasks does not work. Make sure all task subfolders are totally self contained.
- Extension icon
extension-icon.png
- Best displayed when it is 128px * 128px
- Task manifest
task.json
- Official JSON schema
- The official documentation on tasks is either sparse or difficult to find. This extension makes use of input fields of types
string
,multiline
andradio
so check out the task’s source code for hints about how to use fields of these types. - Before the extension package is created, the task’s subfolder must contain all necessary files. This especially includes the
node_modules
. Azure DevOps does not exectute npm install while the extension is published in the marketplace or when the task is used in a build pipeline.
- Task icon
icon.png
- Must be named
icon.png
- Must live in the same folder as the corresponding
task.json
- Best displayed when it is 64px * 64px
- Must be named
Hire me! Web frontend with JavaScript, TypeScript, React, Svelte, HTML, CSS. Backend with .Net/C#, Node.js, (MS-)SQL. I fill your resource gaps, take on whole projects and create prototypes. Yes, tell me more