heroku/heroku-apps

View on GitHub
src/commands/apps/info.js

Summary

Maintainability
C
1 day
Test Coverage
'use strict'

const co = require('co')
const cli = require('heroku-cli-util')

function * run (context, heroku) {
  const filesize = require('filesize')
  const util = require('util')
  const {countBy, snakeCase} = require('lodash')

  function * getInfo (app) {
    const pipelineCouplings = heroku.get(`/apps/${app}/pipeline-couplings`).catch(() => null)

    let promises = {
      addons: heroku.get(`/apps/${app}/addons`),
      app: heroku.request({
        path: `/apps/${app}`,
        headers: {'Accept': 'application/vnd.heroku+json; version=3.cedar-acm'}
      }),
      dynos: heroku.get(`/apps/${app}/dynos`).catch(() => []),
      collaborators: heroku.get(`/apps/${app}/collaborators`).catch(() => []),
      pipeline_coupling: pipelineCouplings,
      pipeline: pipelineCouplings // TODO: Remove this key once we feel comfortable with https://github.com/heroku/heroku-apps/pull/207#issuecomment-335775852.
    }

    if (context.flags.extended) {
      promises.appExtended = heroku.get(`/apps/${app}?extended=true`)
    }

    let data = yield promises

    if (context.flags.extended) {
      data.appExtended.acm = data.app.acm
      data.app = data.appExtended
      delete data['appExtended']
    }

    return data
  }

  let app = context.args.app || context.app
  if (!app) throw new Error('No app specified.\nUSAGE: heroku info my-app')

  context.app = app // make sure context.app is always set for herkou-cli-util

  let info = yield getInfo(app)
  let addons = info.addons.map(a => a.plan.name).sort()
  let collaborators = info.collaborators.map(c => c.user.email).filter(c => c !== info.app.owner.email).sort()

  function print () {
    let data = {}
    data.Addons = addons
    data.Collaborators = collaborators

    if (info.app.archived_at) data['Archived At'] = cli.formatDate(new Date(info.app.archived_at))
    if (info.app.cron_finished_at) data['Cron Finished At'] = cli.formatDate(new Date(info.app.cron_finished_at))
    if (info.app.cron_next_run) data['Cron Next Run'] = cli.formatDate(new Date(info.app.cron_next_run))
    if (info.app.database_size) data['Database Size'] = filesize(info.app.database_size, {round: 0})
    if (info.app.create_status !== 'complete') data['Create Status'] = info.app.create_status
    if (info.app.space) data['Space'] = info.app.space.name
    if (info.app.space && info.app.internal_routing) data['Internal Routing'] = info.app.internal_routing
    if (info.pipeline_coupling) data['Pipeline'] = `${info.pipeline_coupling.pipeline.name} - ${info.pipeline.stage}`

    data['Auto Cert Mgmt'] = info.app.acm
    data['Git URL'] = info.app.git_url
    data['Web URL'] = info.app.web_url
    data['Repo Size'] = filesize(info.app.repo_size, {round: 0})
    data['Slug Size'] = filesize(info.app.slug_size, {round: 0})
    data['Owner'] = info.app.owner.email
    data['Region'] = info.app.region.name
    data['Dynos'] = countBy(info.dynos, 'type')
    data['Stack'] = (function (app) {
      let stack = info.app.stack.name
      if (app.stack.name !== app.build_stack.name) {
        stack += ` (next build will use ${app.build_stack.name})`
      }
      return stack
    })(info.app)

    cli.styledHeader(info.app.name)
    cli.styledObject(data)

    if (context.flags.extended) {
      cli.log('\n\n--- Extended Information ---\n\n')
      if (info.app.extended) {
        cli.log(util.inspect(info.app.extended))
      }
    }
  }

  function shell () {
    function print (k, v) {
      cli.log(`${snakeCase(k)}=${v}`)
    }
    print('auto_cert_mgmt', info.app.acm)
    print('addons', addons)
    print('collaborators', collaborators)

    if (info.app.archived_at) print('archived_at', cli.formatDate(new Date(info.app.archived_at)))
    if (info.app.cron_finished_at) print('cron_finished_at', cli.formatDate(new Date(info.app.cron_finished_at)))
    if (info.app.cron_next_run) print('cron_next_run', cli.formatDate(new Date(info.app.cron_next_run)))
    if (info.app.database_size) print('database_size', filesize(info.app.database_size, {round: 0}))
    if (info.app.create_status !== 'complete') print('create_status', info.app.create_status)
    if (info.pipeline_coupling) print('pipeline', `${info.pipeline_coupling.pipeline.name}:${info.pipeline.stage}`)

    print('git_url', info.app.git_url)
    print('web_url', info.app.web_url)
    print('repo_size', filesize(info.app.repo_size, {round: 0}))
    print('slug_size', filesize(info.app.slug_size, {round: 0}))
    print('owner', info.app.owner.email)
    print('region', info.app.region.name)
    print('dynos', util.inspect(countBy(info.dynos, 'type')))
    print('stack', info.app.stack.name)
  }

  if (context.flags.shell) {
    shell()
  } else if (context.flags.json) {
    cli.styledJSON(info)
    cli.warn('DEPRECATION WARNING: `pipeline` key will be removed in favor of `pipeline_coupling`')
  } else {
    print()
  }
}

let cmd = {
  description: 'show detailed app information',
  help: `$ heroku apps:info
=== example
Git URL:   https://git.heroku.com/example.git
Repo Size: 5M
...

$ heroku apps:info --shell
git_url=https://git.heroku.com/example.git
repo_size=5000000
...`,
  wantsApp: true,
  needsAuth: true,
  args: [{name: 'app', hidden: true, optional: true}],
  flags: [
    {name: 'shell', char: 's', description: 'output more shell friendly key/value pairs'},
    {name: 'extended', char: 'x', hidden: true},
    {name: 'json', char: 'j'}
  ],
  run: cli.command({preauth: true}, co.wrap(run))
}

module.exports = [
  Object.assign({topic: 'apps', command: 'info'}, cmd),
  Object.assign({topic: 'info', hidden: true}, cmd)
]