1 | 'use strict'
|
2 |
|
3 | const cli = require('heroku-cli-util')
|
4 | const co = require('co')
|
5 | const strftime = require('strftime')
|
6 | const _ = require('lodash')
|
7 |
|
8 | const getProcessNum = (s) => parseInt(s.split('.', 2)[1])
|
9 |
|
10 | function timeAgo (since) {
|
11 | let elapsed = Math.floor((new Date() - since) / 1000)
|
12 | let message = strftime('%Y/%m/%d %H:%M:%S %z', since)
|
13 | if (elapsed < 60) return `${message} (~ ${Math.floor(elapsed)}s ago)`
|
14 | else if (elapsed < 60 * 60) return `${message} (~ ${Math.floor(elapsed / 60)}m ago)`
|
15 | else if (elapsed < 60 * 60 * 25) return `${message} (~ ${Math.floor(elapsed / 60 / 60)}h ago)`
|
16 | else return message
|
17 | }
|
18 |
|
19 | function * run (context, heroku) {
|
20 | const spaceName = context.flags.space || context.args.space
|
21 | if (!spaceName) throw new Error('Space name required.\nUSAGE: heroku spaces:ps my-space')
|
22 |
|
23 | const [spaceDynos, space] = yield Promise.all([
|
24 | heroku.get(`/spaces/${spaceName}/dynos`),
|
25 | heroku.get(`/spaces/${spaceName}`)
|
26 | ])
|
27 |
|
28 | if (space.shield) {
|
29 | spaceDynos.forEach(spaceDyno => {
|
30 | spaceDyno.dynos.forEach(d => {
|
31 | if (d.size) {
|
32 | d.size = d.size.replace('Private-', 'Shield-')
|
33 | }
|
34 | })
|
35 | })
|
36 | }
|
37 |
|
38 | if (context.flags.json) {
|
39 | cli.styledJSON(spaceDynos)
|
40 | } else {
|
41 | render(spaceDynos)
|
42 | }
|
43 | }
|
44 |
|
45 | function render (spaceDynos) {
|
46 | _.forEach(spaceDynos, (spaceDyno) => {
|
47 | printDynos(spaceDyno.app_name, spaceDyno.dynos)
|
48 | })
|
49 | }
|
50 |
|
51 | function printDynos (appName, dynos) {
|
52 | let dynosByCommand = _.reduce(dynos, function (dynosByCommand, dyno) {
|
53 | let since = timeAgo(new Date(dyno.updated_at))
|
54 | let size = dyno.size || '1X'
|
55 |
|
56 | if (dyno.type === 'run') {
|
57 | let key = 'run: one-off processes'
|
58 | if (dynosByCommand[key] === undefined) dynosByCommand[key] = []
|
59 | dynosByCommand[key].push(`${dyno.name} (${size}): ${dyno.state} ${since}: ${dyno.command}`)
|
60 | } else {
|
61 | let key = `${cli.color.green(dyno.type)} (${cli.color.cyan(size)}): ${dyno.command}`
|
62 | if (dynosByCommand[key] === undefined) dynosByCommand[key] = []
|
63 | let state = dyno.state === 'up' ? cli.color.green(dyno.state) : cli.color.yellow(dyno.state)
|
64 | let item = `${dyno.name}: ${cli.color.green(state)} ${cli.color.dim(since)}`
|
65 | dynosByCommand[key].push(item)
|
66 | }
|
67 | return dynosByCommand
|
68 | }, {})
|
69 | _.forEach(dynosByCommand, function (dynos, key) {
|
70 | cli.styledHeader(`${appName} ${key} (${cli.color.yellow(dynos.length)})`)
|
71 | dynos = dynos.sort((a, b) => getProcessNum(a) - getProcessNum(b))
|
72 | for (let dyno of dynos) cli.log(dyno)
|
73 | cli.log()
|
74 | })
|
75 | }
|
76 |
|
77 | module.exports = {
|
78 | topic: 'spaces',
|
79 | command: 'ps',
|
80 | description: 'list dynos for a space',
|
81 | needsAuth: true,
|
82 | args: [{ name: 'space', optional: true, hidden: true }],
|
83 | flags: [
|
84 | { name: 'space', char: 's', hasValue: true, description: 'space to get dynos of' },
|
85 | { name: 'json', description: 'output in json format' }
|
86 | ],
|
87 | run: cli.command(co.wrap(run))
|
88 | }
|