Example #1
0
function* run (context) {
  let host = process.env.HEROKU_STATUS_HOST || 'https://status.heroku.com';
  let response = (yield cli.got(host + '/api/v3/current-status', {
    path: '/api/v3/current-status',
    json: true,
    headers: { 'Accept': 'application/vnd.heroku+json;' },
  })).body;

  if (context.flags.json) {
    cli.styledJSON(response);
    return;
  }

  cli.log(`Production:   ${printStatus(response.status.Production)}`);
  cli.log(`Development:  ${printStatus(response.status.Development)}`);

  response.issues.forEach(function(incident) {
    cli.log();
    cli.styledHeader(`${incident.title} ${cli.color.yellow(moment(incident.created_at).format('LT'))} ${cli.color.cyan(incident.full_url)}`);

    let padding = _.maxBy(incident.updates, 'update_type.length').update_type.length+1;
    incident.updates.forEach(u => {
      cli.log(`${cli.color.yellow(_.padEnd(u.update_type, padding))} ${moment(u.updated_at).format('LT')} (${moment(u.updated_at).fromNow()})`);
      cli.log(`${u.contents}\n`);
    });
  });
}
Example #2
0
function * run (context, heroku) {
  const fetcher = require('../../lib/fetcher')(heroku)
  const host = require('../../lib/host')
  const util = require('../../lib/util')

  const { app, args, flags } = context
  let cred = flags.name

  if (cred === 'default') {
    throw new Error('Default credential cannot be destroyed.')
  }

  let db = yield fetcher.addon(app, args.database)
  if (util.starterPlan(db)) {
    throw new Error(`Only one default credential is supported for Hobby tier databases.`)
  }

  let attachments = yield heroku.get(`/addons/${db.name}/addon-attachments`)
  let credAttachments = attachments.filter(a => a.namespace === `credential:${flags.name}`)
  let credAttachmentApps = Array.from(new Set(credAttachments.map(a => a.app.name)))
  if (credAttachmentApps.length > 0) throw new Error(`Credential ${flags.name} must be detached from the app${credAttachmentApps.length > 1 ? 's' : ''} ${credAttachmentApps.map(name => cli.color.app(name)).join(', ')} before destroying.`)

  yield cli.confirmApp(app, flags.confirm, `WARNING: Destructive action`)

  yield cli.action(`Destroying credential ${cli.color.cmd(cred)}`, co(function * () {
    yield heroku.delete(`/postgres/v0/databases/${db.name}/credentials/${encodeURIComponent(cred)}`, { host: host(db) })
  }))

  cli.log(`The credential has been destroyed within ${db.name}.`)
  cli.log(`Database objects owned by ${cred} will be assigned to the default credential.`)
}
 run: function (context) {
   if (context.flags.user) {
     cli.log(`Hello, ${context.flags.user}!`);
   } else {
     cli.log('Hello, World!');
   }
 }
Example #4
0
  run: cli.command(co.wrap(function * (context, heroku) {
    let mappingResults
    let didDisplayAnything = false
    let connection = yield api.withConnection(context, heroku)
    context.region = connection.region_url
    let results = yield cli.action('Diagnosing connection', co(function * () {
      let url = '/api/v3/connections/' + connection.id + '/validations'
      return yield api.request(context, 'GET', url)
    }))

    cli.log() // Blank line to separate each section
    cli.styledHeader(`Connection: ${connection.name || connection.internal_name}`)
    if (shouldDisplay(results.data, context.flags)) {
      didDisplayAnything = true
      displayResults(results.data, context.flags)
    }

    for (let objectName in results.data.mappings) {
      mappingResults = results.data.mappings[objectName]
      if (shouldDisplay(mappingResults, context.flags)) {
        didDisplayAnything = true
        cli.log() // Blank line to separate each section
        cli.styledHeader(objectName)
        displayResults(mappingResults, context.flags)
      }
    }

    if (!didDisplayAnything && !context.flags.verbose) {
      cli.log(cli.color['green']('Everything appears to be fine'))
    }
  })),
Example #5
0
  function print (apps, user) {
    if (apps.length === 0) {
      if (space) cli.log(`There are no apps in space ${cli.color.green(space)}.`)
      else if (org) cli.log(`There are no apps in organization ${cli.color.magenta(org)}.`)
      else cli.log('You have no apps.')
      return
    } else if (space) {
      cli.styledHeader(`Apps in space ${cli.color.green(space)}`)
      listApps(apps)
    } else if (org) {
      cli.styledHeader(`Apps in organization ${cli.color.magenta(org)}`)
      listApps(apps)
    } else {
      apps = _.partition(apps, (app) => app.owner.email === user.email)
      if (apps[0].length > 0) {
        cli.styledHeader(`${cli.color.cyan(user.email)} Apps`)
        listApps(apps[0])
      }

      if (apps[1].length > 0) {
        cli.styledHeader('Collaborated Apps')
        cli.table(apps[1], {
          printHeader: false,
          columns: [
            {key: 'name', get: regionizeAppName},
            {key: 'owner.email'}
          ]
        })
      }
    }
  }
Example #6
0
  run: cli.command(co.wrap(function* (context, heroku) {
    const kolkrabbi = new KolkrabbiAPI(context.version, heroku.options.token)

    const settings = {
      pull_requests: {
        enabled: true
      }
    }

    if (context.flags.autodeploy) {
      cli.log('Enabling auto deployment...')
      settings.pull_requests.auto_deploy = true
    }
    if (context.flags.autodestroy) {
      cli.log('Enabling auto destroy...')
      settings.pull_requests.auto_destroy = true
    }

    let app = yield api.getApp(heroku, context.flags.app)

    yield cli.action(
      'Configuring pipeline',
      kolkrabbi.updateAppLink(app.id, settings)
    )
  }))
Example #7
0
function * run (context, heroku) {
  function favoriteApps () {
    return heroku.request({
      host: 'longboard.heroku.com',
      path: '/favorites',
      headers: {Range: ''}
    }).then((apps) => apps.map((app) => app.app_name))
  }

  function fetchMetrics (apps) {
    const NOW = new Date().toISOString()
    const YESTERDAY = new Date(new Date().getTime() - (24 * 60 * 60 * 1000)).toISOString()
    let date = `start_time=${YESTERDAY}&end_time=${NOW}&step=1h`
    return apps.map((app) => {
      let types = app.formation.map((p) => p.type)
      return {
        dynoErrors: types.map((type) => heroku.request({host: 'api.metrics.herokai.com', path: `/apps/${app.app.name}/formation/${type}/metrics/errors?${date}`, headers: {Range: ''}}).catch(() => null)),
        routerLatency: heroku.request({host: 'api.metrics.herokai.com', path: `/apps/${app.app.name}/router-metrics/latency?${date}&process_type=${types[0]}`, headers: {Range: ''}}).catch(() => null),
        routerErrors: heroku.request({host: 'api.metrics.herokai.com', path: `/apps/${app.app.name}/router-metrics/errors?${date}&process_type=${types[0]}`, headers: {Range: ''}}).catch(() => null),
        routerStatus: heroku.request({host: 'api.metrics.herokai.com', path: `/apps/${app.app.name}/router-metrics/status?${date}&process_type=${types[0]}`, headers: {Range: ''}}).catch(() => null)
      }
    })
  }

  let apps, data, metrics

  yield cli.action('Loading', {success: false}, co(function * () {
    apps = yield favoriteApps()

    data = yield {
      orgs: heroku.request({path: '/organizations'}),
      notifications: heroku.request({host: 'telex.heroku.com', path: '/user/notifications'}),
      apps: apps.map((app) => ({
        app: heroku.get(`/apps/${app}`),
        formation: heroku.get(`/apps/${app}/formation`),
        pipeline: heroku.get(`/apps/${app}/pipeline-couplings`).catch(() => null)
      }))
    }
    metrics = yield fetchMetrics(data.apps)
  }))

  process.stderr.write('\r')
  if (process.stderr.clearLine) process.stderr.clearLine()
  else cli.console.error('\n')

  if (apps.length > 0) displayApps(data.apps, metrics)
  else cli.warn(`Add apps to this dashboard by favoriting them with ${cli.color.cmd('heroku apps:favorites:add')}`)

  cli.log(`
See all add-ons with ${cli.color.cmd('heroku addons')}`)
  let sampleOrg = _.sortBy(data.orgs.filter((o) => o.role !== 'collaborator'), (o) => new Date(o.created_at))[0]
  if (sampleOrg) cli.log(`See all apps in ${cli.color.yellow.dim(sampleOrg.name)} with ${cli.color.cmd('heroku apps --org ' + sampleOrg.name)}`)
  cli.log(`See all apps with ${cli.color.cmd('heroku apps --all')}`)
  displayNotifications(data.notifications)
  cli.log(`
See other CLI commands with ${cli.color.cmd('heroku help')}
`)
}
Example #8
0
function * run (context, heroku) {
  const statusHelper = require('./status_helper')
  const time = require('../../lib/time')
  const truncate = require('lodash.truncate')

  let descriptionWithStatus = function (d, r) {
    const width = () => process.stdout.columns > 80 ? process.stdout.columns : 80
    const trunc = (s, l) => truncate(s, {length: width() - (60 + l), omission: '…'})

    let status = statusHelper(r.status)
    let sc = ''
    if (status.content !== undefined) {
      sc = cli.color[status.color](status.content)
    }
    return trunc(d, sc.length) + ' ' + sc
  }

  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`
    }
  })

  if (context.flags.json) {
    cli.log(JSON.stringify(releases, null, 2))
  } else if (context.flags.extended) {
    cli.styledHeader(`${context.app} Releases`)
    cli.table(releases, {
      printHeader: false,
      columns: [
        {key: 'version', format: (v, r) => cli.color[statusHelper(r.status).color]('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'}
      ]
    })
  } else if (releases.length === 0) {
    cli.log(`${context.app} has no releases.`)
  } else {
    cli.styledHeader(`${context.app} Releases`)
    cli.table(releases, {
      printHeader: false,
      columns: [
        {key: 'version', label: '', format: (v, r) => cli.color[statusHelper(r.status).color]('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))}
      ]
    })
  }
}
Example #9
0
 return function (result) {
   cli.log(cli.color[color](`${label}: ${result.display_name}`))
   if (displayMessages) {
     cli.log(result.message)
     if (result.doc_url) {
       cli.log(result.doc_url)
     }
   }
 }
Example #10
0
BuildpackCommand.prototype.displayUpdate = function (buildpacks) {
  if (buildpacks.length === 1) {
    cli.log(`Buildpack ${this.action}. Next release on ${this.app} will use ${buildpacks[0].buildpack.url}.`)
    cli.log(`Run ${cli.color.magenta('git push heroku master')} to create a new release using this buildpack.`)
  } else {
    cli.log(`Buildpack ${this.action}. Next release on ${this.app} will use:`)
    this.display(buildpacks, '  ')
    cli.log(`Run ${cli.color.magenta('git push heroku master')} to create a new release using these buildpacks.`)
  }
}
Example #11
0
 function createRemoteSlug(slug) {
   var lang = `heroku-docker (${ slug.name || 'unknown'})`;
   cli.log(`creating remote slug...`);
   cli.log(`language-pack: ${ lang }`);
   cli.log('remote process types:', modifiedProc);
   var slugInfo = app.slugs().create({
     process_types: modifiedProc,
     buildpack_provided_description: lang
   });
   return Promise.all([slug.path, slugInfo])
 }
  run: cli.command(async (context, heroku) => {
    const api = require('../lib/shared')(context, heroku)
    const addon = await api.getRedisAddon()

    if (context.flags.reset) {
      cli.log(`Resetting credentials for ${addon.name}`)
      await api.request(`/redis/v0/databases/${addon.name}/credentials_rotation`, 'POST')
    } else {
      let redis = await api.request(`/redis/v0/databases/${addon.name}`)
      cli.log(redis.resource_url)
    }
  })
Example #13
0
function * run (context, heroku) {
  let id = context.args.addon.split(':')[0]
  let addon = yield heroku.get(`/addon-services/${encodeURIComponent(id)}`).catch(() => null)
  if (!addon) addon = (yield resolve.addon(heroku, context.app, id)).addon_service
  let url = `https://devcenter.heroku.com/articles/${addon.name}`

  if (context.flags['show-url']) {
    cli.log(url)
  } else {
    cli.log(`Opening ${cli.color.cyan(url)}...`)
    yield cli.open(url)
  }
}
Example #14
0
 dbs.forEach(db => {
   if (once) cli.log()
   else once = true
   cli.styledHeader(cli.color.addon(db.name))
   if (db.links.message) return cli.log(db.links.message)
   if (!db.links.length) return cli.log('No data sources are linked into this database')
   db.links.forEach(link => {
     cli.log(`\n * ${cli.color.cyan(link.name)}`)
     link.remote = `${cli.color.configVar(link.remote.attachment_name)} (${cli.color.addon(link.remote.name)})`
     delete link.id
     delete link.name
     cli.styledObject(link)
   })
 })
Example #15
0
  run: cli.command(co.wrap(function * (context, heroku) {
    if (!context.flags.seconds) {
      cli.exit(1, 'Please specify a valid timeout value.')
    }

    let addon = yield api.getRedisAddon(context, heroku)

    let config = yield api.request(context, `/redis/v0/databases/${addon.name}/config`, 'PATCH', { timeout: parseInt(context.flags.seconds, 10) })
    cli.log(`Timeout for ${addon.name} (${addon.config_vars.join(', ')}) set to ${config.timeout.value} seconds.`)
    if (config.timeout.value === 0) {
      cli.log('Connections to the Redis instance can idle indefinitely.')
    } else {
      cli.log(`Connections to the Redis instance will be stopped after idling for ${config.timeout.value} seconds.`)
    }
  }))
Example #16
0
 function createRemoteSlug(slugPath) {
   cli.log('creating remote slug...');
   var slugInfo = app.slugs().create({
     process_types: procfile
   });
   return Promise.all([slugPath, slugInfo])
 }
Example #17
0
function * run (context, heroku) {
  let drain = yield heroku.request({
    method: 'delete',
    path: `/apps/${context.app}/log-drains/${encodeURIComponent(context.args.url)}`
  })
  cli.log(`Successfully removed drain ${cli.color.cyan(drain.url)}`)
}
Example #18
0
  run: cli.command(async (context, heroku) => {
    const api = require('../lib/shared')(context, heroku)
    let addonsList = heroku.get(`/apps/${context.app}/addons`)

    let addon = await api.getRedisAddon(addonsList)

    let redisFilter = api.makeAddonsFilter('REDIS_URL')
    let redis = redisFilter(await addonsList)

    // Check if REDIS_URL is singlehandly assigned
    if (redis.length === 1 && redis[0].config_vars.length === 1) {
      let attachment = redis[0]
      await heroku.post('/addon-attachments', { body: {
        app: { name: context.app },
        addon: { name: attachment.name },
        confirm: context.app
      } })
    }

    cli.log(`Promoting ${addon.name} to REDIS_URL on ${context.app}`)
    await heroku.post('/addon-attachments', { body: {
      app: { name: context.app },
      addon: { name: addon.name },
      confirm: context.app,
      name: 'REDIS'
    } })
  })
Example #19
0
function displayApps (apps, appsMetrics) {
  let owner = (owner) => owner.email.endsWith('@herokumanager.com') ? owner.email.split('@')[0] : owner.email

  for (let a of _.zip(apps, appsMetrics)) {
    cli.log()
    let app = a[0]
    let metrics = a[1]
    cli.log(cli.color.app(app.app.name))
    let pipeline = app.pipeline ? ` Pipeline: ${cli.color.blue.bold(app.pipeline.pipeline.name)}` : ''
    cli.log(`  Owner: ${cli.color.blue.bold(owner(app.app.owner))}${pipeline}`)
    displayFormation(app.formation)
    cli.log(`  Last release: ${cli.color.green(time.ago(new Date(app.app.released_at)))}`)
    displayMetrics(metrics)
    displayErrors(metrics)
  }
}
Example #20
0
function displayMetrics (metrics) {
  const flatten = require('lodash.flatten')
  const mean = require('lodash.mean')
  const pad = require('lodash.pad')
  const round = require('lodash.round')
  const sum = require('lodash.sum')
  const values = require('lodash.values')

  function rpmSparkline () {
    if (process.platform === 'win32') return
    let sparkline = require('sparkline')
    let points = []
    values(metrics.routerStatus.data).forEach((cur) => {
      for (let i = 0; i < cur.length; i++) {
        let j = Math.floor(i / 3)
        points[j] = (points[j] || 0) + cur[i]
      }
    })
    points.pop()
    return dim(sparkline(points)) + ' last 24 hours rpm'
  }
  let ms = ''
  let rpm = ''
  if (metrics.routerLatency && !empty(metrics.routerLatency.data)) {
    ms = pad(`${round(mean(metrics.routerLatency.data.latency_p50))} ms`, 6)
  }
  if (metrics.routerStatus && !empty(metrics.routerStatus.data)) {
    rpm = `${round(sum(flatten(values(metrics.routerStatus.data))) / 24 / 60)} rpm ${rpmSparkline()}`
  }
  if (rpm || ms) cli.log(`  ${label('Metrics:')} ${ms}${rpm}`)
}
Example #21
0
function * run (context, heroku) {
  let createAddon = require('../../lib/create_addon')

  let { app, flags, args } = context
  if (args.length === 0) {
    throw new Error('Usage: heroku addons:create SERVICE:PLAN')
  }

  let { name, as } = flags
  let config = parseConfig(args.slice(1))
  let addon

  try {
    addon = yield createAddon(heroku, app, args[0], context.flags.confirm, context.flags.wait, { config, name, as })
    if (context.flags.wait) {
      notify(`heroku addons:create ${addon.name}`, 'Add-on successfully provisioned')
    }
  } catch (error) {
    if (context.flags.wait) {
      notify(`heroku addons:create ${args[0]}`, 'Add-on failed to provision', false)
    }
    throw error
  }

  yield context.config.runHook('recache', { type: 'addon', app, addon })
  cli.log(`Use ${cli.color.cmd('heroku addons:docs ' + addon.addon_service.name)} to view documentation`)
}
Example #22
0
function * run (context, heroku) {
  let auth = yield cli.action('Revoking OAuth Authorization', {success: false}, heroku.request({
    method: 'DELETE',
    path: `/oauth/authorizations/${encodeURIComponent(context.args.id)}`
  }))
  cli.log(`done, revoked authorization from ${cli.color.cyan(auth.description)}`)
}
Example #23
0
function * run (ctx, api) {
  const resolve = require('../../lib/resolve')

  let addons
  if (ctx.args.addon) {
    addons = [yield resolve.addon(api, ctx.app, ctx.args.addon)]
  } else {
    if (ctx.app) {
      addons = yield api.get(`/apps/${ctx.app}/addons`)
    } else {
      addons = yield api.get('/addons')
    }
    addons = addons.filter(addon => addon.state === 'provisioning')
  }

  let interval = parseInt(ctx.flags['wait-interval'])
  if (!interval || interval < 0) { interval = 5 }

  for (let addon of addons) {
    addon = yield waitForAddonProvisioning(api, addon, interval)

    let configVars = (addon.config_vars || [])
    if (configVars.length > 0) {
      configVars = configVars.map(c => cli.color.configVar(c)).join(', ')
      cli.log(`Created ${cli.color.addon(addon.name)} as ${configVars}`)
    }
  }
}
Example #24
0
 fastly.purgeKey(config.FASTLY_SERVICE_ID, context.args.key, function(err, obj) {
   if (err) {
     hk.error(err);
   } else {
     hk.log(obj);
   }
 });
Example #25
0
 db.links.forEach(link => {
   cli.log(`\n * ${cli.color.cyan(link.name)}`)
   link.remote = `${cli.color.configVar(link.remote.attachment_name)} (${cli.color.addon(link.remote.name)})`
   delete link.id
   delete link.name
   cli.styledObject(link)
 })
Example #26
0
 yield exec.createSocksProxy(context, heroku, configVars, function(dynoIp, dynoName, socksPort) {
   cli.log(`Listening on ${cli.color.white.bold(localPort)} and forwarding to ${cli.color.white.bold(`${dynoName}:${remotePort}`)}`)
   cli.log(`Use ${cli.color.magenta('CTRL+C')} to stop port fowarding`)
   net.createServer(function(connIn) {
     socks.connect({
       host: '0.0.0.0',
       port: remotePort,
       proxyHost: '127.0.0.1',
       proxyPort: socksPort,
       auths: [ socks.auth.None() ]
     }, function(socket) {
       connIn.pipe(socket);
       socket.pipe(connIn);
     });
   }).listen(localPort);
 });
Example #27
0
function displayNotifications (notifications) {
  notifications = notifications.filter((n) => !n.read)
  if (notifications.length > 0) {
    cli.log(`
You have ${cli.color.yellow(notifications.length)} unread notifications. Read them with ${cli.color.cmd('heroku notifications')}`)
  }
}
Example #28
0
function * run (context, heroku) {
  function getRelease (id) {
    return heroku.get(`/apps/${context.app}/releases/${id}`)
  }

  function getLatestRelease () {
    return heroku.request({
      path: `/apps/${context.app}/releases`,
      partial: true,
      headers: { 'Range': 'version ..; max=2, order=desc' }
    }).then((releases) => releases[1])
  }

  let release
  if (context.args.release) {
    let id = context.args.release.toLowerCase()
    id = id.startsWith('v') ? id.slice(1) : id
    release = yield getRelease(id)
  } else {
    release = yield getLatestRelease()
  }

  let p = heroku.post(`/apps/${context.app}/releases`, {release: release.id})
  let latest = yield cli.action(`Rolling back ${cli.color.app(context.app)} to ${cli.color.green('v' + release.version)}`, p, {success: false})
  cli.log(`done, ${cli.color.green('v' + latest.version)}`)
  cli.warn(`Rollback affects code and config vars; it doesn't add or remove addons.
To undo, run: ${cli.color.cmd('heroku rollback v' + (latest.version - 1))}`)
}
Example #29
0
  function displayAll (addons) {
    addons = sortBy(addons, 'app.name', 'plan.name', 'addon.name')

    if (addons.length === 0) {
      cli.log('No add-ons.')
      return
    }

    table(addons, {
      headerAnsi: cli.color.bold,
      columns: [{
        key: 'app.name',
        label: 'Owning App',
        format: style('app')
      }, {
        key: 'name',
        label: 'Add-on',
        format: style('addon')
      }, {
        key: 'plan.name',
        label: 'Plan',
        format: function (plan) {
          if (typeof plan === 'undefined') return style('dim', '?')
          return plan
        }
      }, {
        key: 'plan.price',
        label: 'Price',
        format: function (price) {
          if (typeof price === 'undefined') return style('dim', '?')
          return formatPrice(price)
        }
      }]
    })
  }
Example #30
0
let release = async function (context, heroku) {
  if (context.flags.verbose) debug.enabled = true

  if (context.args.length === 0) {
    cli.exit(1, `Error: Requires one or more process types\n ${usage}`)
    return
  }
  await heroku.get(`/apps/${context.app}`)

  let herokuHost = process.env.HEROKU_HOST || 'heroku.com'

  let updateData = []
  for (let process of context.args) {
    let image = `${context.app}/${process}`
    let tag = 'latest'

    let imageResp = await heroku.request({
      host: `registry.${herokuHost}`,
      path: `/v2/${image}/manifests/${tag}`,
      headers: {
        Accept: 'application/vnd.docker.distribution.manifest.v2+json'
      }
    })

    let imageID
    switch (imageResp.schemaVersion) {
      case 1:
        let v1Comp = JSON.parse(imageResp.history[0].v1Compatibility)
        imageID = v1Comp.id
        break
      case 2:
        imageID = imageResp.config.digest
        break
    }

    updateData.push({
      type: process,
      docker_image: imageID
    })
  }

  let req = heroku.patch(`/apps/${context.app}/formation`, {
    body: { updates: updateData },
    headers: {
      'Accept': 'application/vnd.heroku+json; version=3.docker-releases'
    }
  })
  await cli.action(`Releasing images ${context.args.join(',')} to ${context.app}`, req)

  let release = await heroku.request({
    path: `/apps/${context.app}/releases`,
    partial: true,
    headers: { 'Range': 'version ..; max=2, order=desc' }
  }).then((releases) => releases[0])

  if (release.output_stream_url && release.status === 'pending') {
    cli.log('Running release command...')
    await streamer(release.output_stream_url, process.stdout)
  }
}