UNPKG

ado-release-notes

Version:

Creates release notes from two build numbers and uploads to a wiki

289 lines (244 loc) 7.83 kB
var request = require('request-promise'); const mustache = require('mustache'); const yargs = require('yargs'); const dot = require('dot-object'); dot.override = true; const args = yargs .option('proxy', { alias: 'p', description: 'e.g. http://proxy.root.local:8080', default: '' }) .option('pat', { description: 'personal access token from ADO', }) .option('organisation', { description: 'ADO organisation' }) .option('project', { description: 'ADO project' }) .option('from', { alias: 'f', description: 'From BuildId' }) .option('to', { alias: 't', description: 'To BuildId' }) .option('buildname', { alias: 'n', description: "Build Name" }) .option('template', { description: "template file (containing mustache template)" }) .option('wikiIdentifier', { description: "ADO wiki Identifier" }) .option('wikiPagePath', { description: "ADO page path (to create a wiki page for the release notes" }) .option('wikipat', { description: 'personal access token from ADO', }) .option('wikiorganisation', { description: 'ADO organisation' }) .option('wikiproject', { description: 'ADO project' }) .coerce('template', function (arg) { return require('fs').readFileSync(arg, 'utf8') }) .help() .alias('help', 'h') .demandOption(['pat', 'organisation', 'project', 'from', 'to', 'buildname']) .argv; // Default wiki orgs to main one if not provided if (!args.wikiorganisation) { args.wikiorganisation = args.organisation; } if (!args.wikiproject) { args.wikiproject = args.project; } if (!args.wikipat) { args.wikipat = args.pat; } const differentWiki = args.wikiorganisation !== args.organisation; // Replace <img src= etc async function resolveHtml(html) { if (differentWiki) { // Replace any img src let start = 0; while (start >= 0) { start = html.indexOf('<img ', start); if (start >= 0) { let end = html.indexOf('>', start); // Look for the src let startsrc = html.indexOf('src=', start); if (startsrc >= 0 && startsrc < end) { let startquote = startsrc + 5; let endquote = html.indexOf(html[startsrc + 4], startsrc + 5); src = html.substring(startquote, endquote); // If it points at azure - add our pat and go get it if (src.startsWith("https://dev.azure.com")) { src = src.replace('https://dev.azure.com', 'https://:' + args.pat + '@dev.azure.com'); var img; // Get the image await request({ 'url': src, 'method': "GET", 'encoding': null, 'resolveWithFullResponse': true, 'proxy': args.proxy, }).then(res => { img = res; }).catch(err => { throw err }); // Upload image it to the other wiki await request({ 'url': 'https://:' + args.wikipat + '@dev.azure.com/' + args.wikiorganisation + '/' + args.wikiproject + '/_apis/wit/attachments?api-version=5.1', 'method': "POST", 'proxy': args.proxy, 'content-type': img.headers['content-type'], body: img.body }).then(res => { res = JSON.parse(res); // Replace the src with new data uri html = html.substring(0, startquote) + res.url + html.substring(endquote); // Adjust the end of the tag accordingly end = end + (res.url.length - src.length); }).catch(err => { throw err }); } } start = end; } } } return html; } // Recursively replace async function resolveHtmlValues(obj) { // recurse down var i; if (obj instanceof Object) { for (i in obj) { if (obj.hasOwnProperty(i)) { //recursive call to scan property if (obj[i] instanceof Object) { await resolveHtmlValues(obj[i]); } else { //not an Object so obj[k] here is a value if (typeof obj[i] === "string" && obj[i].startsWith("<")) { obj[i] = await resolveHtml(obj[i]); } } } } } } // Do the template merge function merge(items) { // If we have a template use it otherwise just spit out json if (args.template) { var html = mustache.to_html(args.template, items); return html; } else { return (JSON.stringify(items, null, 2)); } } // Resolve a value's item async function resolveWorkItem(value) { await request({ 'url': 'https://:' + args.pat + '@dev.azure.com/' + args.organisation + '/' + args.project + '/_apis/wit/workitems/' + value.id + '?$expand=all', 'method': "GET", 'proxy': args.proxy, 'json': true }).then(item => { value.item = item; }).catch(err => { throw err }); // dot.object expands out fields with dots in it so mustache can handle it // E.g. "System.Title" become System: {Title: } if (value.item.fields) { value.item.fields = dot.object(value.item.fields); } // Look for any html in fields and resolve img tags await resolveHtmlValues(value.item); } // Report an error function reportError(error, response) { var message = ""; if (response) { message += "StatusCode: " + response.statusCode + "\r\n" } if (error) { message += "Error: " + error.message + "\r\n"; } return message; } let md = "" // Get all work items request({ 'url': 'https://:' + args.pat + '@dev.azure.com/' + args.organisation + '/' + args.project + '/_apis/build/workitems?fromBuildId=' + args.from + '&toBuildId=' + args.to + '&api-version=5.1-preview.2', 'method': "GET", 'proxy': args.proxy, 'json': true }).then(async items => { // Add additional item (build refs, build name) items.from = args.from; items.to = args.to; items.buildname = args.buildname; for (let i = 0; i < items.value.length; i++) { await resolveWorkItem(items.value[i]); } // Do the merge md = merge(items); // Upload it to a wiki? if (args.wikiIdentifier) { // Does this page already exist? let pageId = 0; let method = 'PUT'; let headers = { "Content-Type": "application/json" }; await request({ 'url': 'https://:' + args.wikipat + '@dev.azure.com/' + args.wikiorganisation + '/' + args.wikiproject + '/_apis/wiki/wikis/' + args.wikiIdentifier + '/pages?path=' + args.wikiPagePath + '&api-version=5.1-preview.1', 'method': "GET", 'proxy': args.proxy, 'headers': { "Content-Type": "application/json" }, resolveWithFullResponse: true, json: true }).then(resp => { pageId = resp.body.id; method = "PATCH"; headers["If-Match"] = resp.headers.etag; }).catch(err => { // It is ok if the page doesn't exist yet if (err.statusCode !== 404) { throw err; } }) let createUrl = 'https://:' + args.wikipat + '@dev.azure.com/' + args.wikiorganisation + '/' + args.wikiproject + '/_apis/wiki/wikis/' + args.wikiIdentifier + '/pages'; if (pageId) { createUrl += '/' + pageId + '?api-version=5.1-preview.1'; } else { createUrl += '?path=' + args.wikiPagePath + '&api-version=5.1-preview.1'; } request({ 'url': createUrl, 'method': method, 'proxy': args.proxy, 'headers': headers, json: { "content": md } }) } else { console.log(md) } }).catch(err => { throw err; })