'use strict' const cli = require('heroku-cli-util') const co = require('co') const stripAnsi = require('strip-ansi') function * run (context, heroku) { const statusHelper = require('../../status_helper') const time = require('../../time') const { truncate } = require('lodash') let optimizationWidth = 0 let descriptionWithStatus = function (d, r) { const width = () => process.stdout.columns > 80 ? process.stdout.columns : 80 const trunc = (s, l) => { if (process.stdout.isTTY) { return truncate(s, { length: width() - (optimizationWidth + l), omission: '…' }) } return s } const status = statusHelper.description(r, runningRelease, runningSlug) if (status) { const sc = cli.color[statusHelper.color(r.status)](status) return trunc(d, status.length + 1) + ' ' + sc } return trunc(d, 0) } let url = `/apps/${context.app}/releases` if (context.flags.extended) url = url + '?extended=true' let releases = yield heroku.request({ path: url, partial: true, headers: { 'Range': `version ..; max=${context.flags.num || 15}, order=desc` } }) let header = `${context.app} Releases` let currentRelease = releases.filter((r) => r.current === true)[0] if (currentRelease) { header += ' - ' + cli.color['blue'](`Current: v${currentRelease.version}`) } let runningRelease = releases.filter((r) => r.status === 'pending').slice(-1)[0] let runningSlug if (runningRelease && runningRelease.slug) { runningSlug = yield heroku.get(`/apps/${context.app}/slugs/${runningRelease.slug.id}`) } let optimizeWidth = function (releases, columns, optimizeKey) { for (let col of columns) { col.optimizationWidth = 0 } for (let row of releases) { for (let colKey in row) { if (colKey === optimizeKey) { continue } for (let col of columns) { let parts = col.key.split('.') let matchKey = parts[0] if (matchKey !== colKey) { continue } let colValue = row for (let part of parts) { colValue = colValue[part] } let formattedValue if (col.format) { formattedValue = col.format(colValue, row) } else { formattedValue = colValue.toString() } col.optimizationWidth = Math.max( col.optimizationWidth, stripAnsi(formattedValue).length ) } } } for (let col of columns) { if (col.key !== optimizeKey) { optimizationWidth += col.optimizationWidth + 2 } } } let handleColorStatus = function (options) { if (context.flags.forceColor !== true) { return } cli.color.enabled = true let concatArguments = function (args) { return Array.prototype.map.call(args, function (arg) { return arg + '' }).join(' ') } options.printLine = function (...args) { cli.stdout += concatArguments(args) + '\n' } } if (context.flags.json) { cli.log(JSON.stringify(releases, null, 2)) } else if (context.flags.extended) { cli.styledHeader(header) let options = { printHeader: false, columns: [ { key: 'version', format: (v, r) => cli.color[statusHelper.color(r.status)]('v' + v) }, { key: 'description', format: descriptionWithStatus }, { key: 'user', format: (u) => cli.color.magenta(u.email.replace(/@addons\.heroku\.com$/, '')) }, { key: 'created_at', format: (t) => time.ago(new Date(t)) }, { key: 'extended.slug_id' }, { key: 'extended.slug_uuid' } ] } handleColorStatus(options) optimizeWidth(releases, options.columns, 'description') cli.table(releases, options) } else if (releases.length === 0) { cli.log(`${context.app} has no releases.`) } else { cli.styledHeader(header) let options = { printHeader: false, columns: [ { key: 'version', label: '', format: (v, r) => cli.color[statusHelper.color(r.status)]('v' + v) }, { key: 'description', format: descriptionWithStatus }, { key: 'user', format: (u) => cli.color.magenta(u.email) }, { key: 'created_at', format: (t) => time.ago(new Date(t)) } ] } handleColorStatus(options) optimizeWidth(releases, options.columns, 'description') cli.table(releases, options) } } module.exports = { topic: 'releases', description: 'display the releases for an app', examples: `$ heroku releases === example Releases v1 Config add FOO_BAR email@example.com 2015/11/17 17:37:41 (~ 1h ago) v2 Config add BAR_BAZ email@example.com 2015/11/17 17:37:41 (~ 1h ago) v3 Config add BAZ_QUX email@example.com 2015/11/17 17:37:41 (~ 1h ago)`, needsApp: true, needsAuth: true, flags: [ { name: 'num', char: 'n', description: 'number of releases to show', hasValue: true }, { name: 'json', description: 'output releases in json format' }, { name: 'extended', char: 'x', hidden: true } ], run: cli.command(co.wrap(run)) }