Azure DevOps latest pipeline build status

Azure DevOps latest pipeline build status.

Image preview of Azure DevOps latest pipeline build status plugin.

build-status.1m.js

Edit
Open on GitHub
#!/usr/local/bin/node

/* 
In case of an error: "node no such file or directory", there is a chance you are using nvm.
In this case, execute the following commands to create a symlink to the node binary used by nvm:

```
nodepath=$(which node);
sudo mkdir -p /usr/local/bin;
sudo ln -s $nodepath /usr/local/bin/node;
```

Then replace the shebang on the first line with:
#!/usr/local/bin/node
*/

//  <xbar.title>Azure DevOps latest pipeline build status</xbar.title>
//  <xbar.version>v1.0</xbar.version>
// <xbar.image>https://raw.githubusercontent.com/stormoz/public-images/main/Azure%20DevOps%20latest%20build%20status.png</xbar.image>
//  <xbar.author>Kon Stormozhev</xbar.author>
//  <xbar.author.github>stormoz</xbar.author.github>
//  <xbar.desc>Azure DevOps latest pipeline build status.</xbar.desc>
//  <xbar.dependencies>nodejs</xbar.dependencies>

//  <xbar.var>string(USERNAME=""): Azure DevOps username string.</xbar.var>
//  <xbar.var>string(PAT=""): Azure DevOps user PAT string.</xbar.var>
//  <xbar.var>string(ORGANIZATION=""): Azure DevOps organization string.</xbar.var>
//  <xbar.var>string(PROJECT=""): Azure DevOps project string.</xbar.var>
//  <xbar.var>string(PIPELINE_ID=""): Azure DevOps pipeline configuration id string.</xbar.var>
//  <xbar.var>string(PIPELINE_ALIAS=""): Alias for readability.</xbar.var>
//  <xbar.var>string(SUCCESS_TEXT=""): Text for successful result.</xbar.var>
//  <xbar.var>string(NOTSUCCESS_TEXT=""): Text for notsuccessful result.</xbar.var>

const username = process.env.USERNAME;
const pat = process.env.PAT;
const organization = process.env.ORGANIZATION;
const project = process.env.PROJECT;
const pipelineId = process.env.PIPELINE_ID;
const pipelineAlias = process.env.PIPELINE_ALIAS;
const success = process.env.SUCCESS_TEXT || `✅`;
const notSuccess = process.env.NOTSUCCESS_TEXT || `❌`;

main();

async function main() {
    const url = `https://dev.azure.com/${organization}/${project}/_apis/build/latest/${pipelineId}?api-version=5.1-preview.1`;
    const lastBuildResponse = await queryDevOpsApi(url, { username, pat });

    const statusValue = lastBuildResponse.result == 'succeeded'
        ? success
        : notSuccess;

    console.log(`${pipelineAlias}: ${statusValue}`);

    console.log('---');
    console.log(url);
    console.log(lastBuildJson.result);
}

async function queryDevOpsApi(url, credentials) {
    const auth = credentials && 'Basic ' + Buffer.from(credentials.username + ':' + credentials.pat).toString('base64');

    const responseJson = await getJson(url, auth && { 'Authorization': auth });
    return responseJson;
}

async function getJson(requestUrl, headers) {
    const url = require('url');
    const parsedUrl = url.parse(requestUrl);
    const options = { host: parsedUrl.host, path: parsedUrl.path, headers: headers };

    const httpClient = parsedUrl.protocol.indexOf('https') != -1
        ? require('https')
        : require('http');

    return new Promise((resolve, reject) => {
        const optionsForJson = {
            ...options, headers: { ...options.headers, 'Accept': 'application/json' }
        };

        httpClient.get(optionsForJson, res => {
            let body = '';
            res.on('data', chunk => {
                body += chunk;
            }).on('end', () => {
                try {
                    const json = JSON.parse(body);
                    resolve(json);
                } catch (error) {
                    reject(error.message);
                };
            }).on('error', err => { reject(err); }
            );
        });
    });
}