Skip to content

Commit 1d27c09

Browse files
feat: include a --no-wrap flag for table displays (#3613)
* Include a --no-wrap flag for table displays * feat: use shared no-wrap flag and table helper Made-with: Cursor * fix: use valid color exports in log colorize Made-with: Cursor * fix(addons): flatten add-on cell for --no-wrap Made-with: Cursor
1 parent 0df7705 commit 1d27c09

File tree

13 files changed

+175
-31
lines changed

13 files changed

+175
-31
lines changed

src/commands/addons/index.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {ux} from '@oclif/core/ux'
55
import _ from 'lodash'
66

77
import {formatPrice, formatState, grandfatheredPrice} from '../../lib/addons/util.js'
8+
import {huxTableNoWrapOptions} from '../../lib/utils/tableUtils.js'
89

910
const topic = 'addons'
1011

@@ -25,6 +26,7 @@ export default class Addons extends Command {
2526
all: flags.boolean({char: 'A', description: 'show add-ons and attachments for all accessible apps'}),
2627
app: flags.app(),
2728
json: flags.boolean({description: 'return add-ons in json format'}),
29+
'no-wrap': flags.noWrap(),
2830
remote: flags.remote(),
2931
}
3032

@@ -41,13 +43,13 @@ export default class Addons extends Command {
4143
if (json)
4244
displayJSON(addons)
4345
else
44-
displayForApp(app, addons)
46+
displayForApp(app, addons, flags['no-wrap'])
4547
} else {
4648
const addons = await addonGetter(this.heroku)
4749
if (json)
4850
displayJSON(addons)
4951
else
50-
displayAll(addons)
52+
displayAll(addons, flags['no-wrap'])
5153
}
5254
}
5355
}
@@ -125,7 +127,7 @@ async function addonGetter(api: APIClient, app?: string) {
125127
return addons
126128
}
127129

128-
function displayAll(addons: Heroku.AddOn[]) {
130+
function displayAll(addons: Heroku.AddOn[], noWrap = false) {
129131
addons = _.sortBy(addons, 'app.name', 'plan.name', 'addon.name')
130132
if (addons.length === 0) {
131133
ux.stdout('No add-ons.')
@@ -187,14 +189,12 @@ function displayAll(addons: Heroku.AddOn[]) {
187189
},
188190
},
189191
},
190-
{
191-
overflow: 'wrap',
192-
},
192+
huxTableNoWrapOptions(noWrap),
193193
)
194194
/* eslint-enable perfectionist/sort-objects */
195195
}
196196

197-
function displayForApp(app: string, addons: Heroku.AddOn[]) {
197+
function displayForApp(app: string, addons: Heroku.AddOn[], noWrap = false) {
198198
if (addons.length === 0) {
199199
ux.stdout(`No add-ons for app ${app}.`)
200200
return
@@ -210,13 +210,16 @@ function displayForApp(app: string, addons: Heroku.AddOn[]) {
210210

211211
const addonLine = `${service} (${name})`
212212
const atts = _.sortBy(addon.attachments, isForeignApp, 'app.name', 'name')
213-
// render each attachment under the add-on
214213
const attLines = atts.map((attachment, idx) => {
215214
const isLast = (idx === addon.attachments.length - 1)
216215
return renderAttachment(attachment, app, isLast)
217216
})
218-
return [addonLine].concat(attLines)
219-
.join('\n') + '\n' // Separate each add-on row by a blank line
217+
const lines = [addonLine, ...attLines]
218+
if (noWrap) {
219+
return lines.join(' ')
220+
}
221+
222+
return `${lines.join('\n')}\n`
220223
}
221224

222225
addons = _.sortBy(addons, isForeignApp, 'plan.name', 'name')
@@ -253,9 +256,7 @@ function displayForApp(app: string, addons: Heroku.AddOn[]) {
253256
get: ({state}) => formatState(state || ''),
254257
},
255258
},
256-
{
257-
overflow: 'wrap',
258-
},
259+
huxTableNoWrapOptions(noWrap),
259260
)
260261
ux.stdout(`The table above shows add-ons and the attachments to the current app (${color.app(app)}) or other apps.\n `)
261262
}

src/commands/data/pg/credentials/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {CredentialInfo, CredentialsInfo} from '../../../../lib/data/types.j
88
import BaseCommand from '../../../../lib/data/baseCommand.js'
99
import {sortByOwnerAndName} from '../../../../lib/data/credentialUtils.js'
1010
import {presentCredentialAttachments} from '../../../../lib/pg/util.js'
11+
import {huxTableNoWrapOptions} from '../../../../lib/utils/tableUtils.js'
1112

1213
export default class DataPgCredentialsIndex extends BaseCommand {
1314
static args = {
@@ -25,6 +26,7 @@ export default class DataPgCredentialsIndex extends BaseCommand {
2526

2627
static flags = {
2728
app: Flags.app({required: true}),
29+
'no-wrap': Flags.noWrap(),
2830
remote: Flags.remote(),
2931
}
3032

@@ -74,8 +76,6 @@ export default class DataPgCredentialsIndex extends BaseCommand {
7476
State: {
7577
get: cred => cred.state,
7678
},
77-
}, {
78-
overflow: 'wrap',
79-
})
79+
}, huxTableNoWrapOptions(flags['no-wrap']))
8080
}
8181
}

src/commands/domains/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Uri from 'urijs'
77

88
import parseKeyValue from '../../lib/utils/keyValueParser.js'
99
import {paginateRequest} from '../../lib/utils/paginator.js'
10+
import {huxTableNoWrapOptions} from '../../lib/utils/tableUtils.js'
1011

1112
export default class DomainsIndex extends Command {
1213
static description = 'list domains for an app'
@@ -30,6 +31,7 @@ www.example.com CNAME www.example.herokudns.com`]
3031
extended: flags.boolean({char: 'x', description: 'show extra columns'}),
3132
filter: flags.string({description: 'filter property by partial string matching, ex: name=foo'}),
3233
json: flags.boolean({char: 'j', description: 'output in json format'}),
34+
'no-wrap': flags.noWrap(),
3335
remote: flags.remote(),
3436
sort: flags.string({description: 'sort by property'}),
3537
}
@@ -215,7 +217,7 @@ www.example.com CNAME www.example.herokudns.com`]
215217
this.outputCSV(customDomains, tableConfig, sortProperty)
216218
} else {
217219
hux.table(customDomains, tableConfig, {
218-
overflow: 'wrap',
220+
...huxTableNoWrapOptions(flags['no-wrap']),
219221
sort: flags.sort ? {[sortProperty]: 'asc'} : undefined,
220222
})
221223
}

src/commands/pg/credentials.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {Args} from '@oclif/core'
66
import type {NonAdvancedCredentialInfo} from '../../lib/data/types.js'
77

88
import {presentCredentialAttachments} from '../../lib/pg/util.js'
9+
import {huxTableNoWrapOptions} from '../../lib/utils/tableUtils.js'
910
import {nls} from '../../nls.js'
1011

1112
export default class Credentials extends Command {
@@ -16,6 +17,7 @@ export default class Credentials extends Command {
1617
static description = 'show information on credentials in the database'
1718
static flags = {
1819
app: flags.app({required: true}),
20+
'no-wrap': flags.noWrap(),
1921
remote: flags.remote(),
2022
}
2123

@@ -62,9 +64,7 @@ export default class Credentials extends Command {
6264
State: {
6365
get: cred => cred.state,
6466
},
65-
}, {
66-
overflow: 'wrap',
67-
})
67+
}, huxTableNoWrapOptions(flags['no-wrap']))
6868
}
6969

7070
protected sortByDefaultAndName(credentials: NonAdvancedCredentialInfo[]) {

src/commands/ps/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {AccountQuota} from '../../lib/types/account_quota.js'
88
import {AppProcessTier} from '../../lib/types/app_process_tier.js'
99
import {DynoExtended} from '../../lib/types/dyno_extended.js'
1010
import {Account} from '../../lib/types/fir.js'
11+
import {huxTableNoWrapOptions} from '../../lib/utils/tableUtils.js'
1112

1213
const heredoc = tsheredoc.default
1314

@@ -29,6 +30,7 @@ export default class Index extends Command {
2930
app: flags.app({required: true}),
3031
extended: flags.boolean({char: 'x', hidden: true}), // only works with sudo privileges
3132
json: flags.boolean({description: 'display as json'}),
33+
'no-wrap': flags.noWrap(),
3234
remote: flags.remote(),
3335
}
3436

@@ -79,7 +81,7 @@ export default class Index extends Command {
7981
if (json)
8082
hux.styledJSON(selectedDynos)
8183
else if (extended)
82-
printExtended(selectedDynos)
84+
printExtended(selectedDynos, flags['no-wrap'])
8385
else {
8486
await printAccountQuota(this.heroku, appInfo, accountInfo)
8587
if (selectedDynos.length === 0)
@@ -207,7 +209,7 @@ function printDynos(dynos: DynoExtended[]) : void {
207209
})
208210
}
209211

210-
function printExtended(dynos: DynoExtended[]) {
212+
function printExtended(dynos: DynoExtended[], noWrap = false) {
211213
const sortedDynos = dynos.sort(byProcessTypeAndNumber)
212214

213215
/* eslint-disable perfectionist/sort-objects */
@@ -229,9 +231,7 @@ function printExtended(dynos: DynoExtended[]) {
229231
Route: {get: (dyno: DynoExtended) => dyno.extended?.route ?? ''},
230232
Size: {get: (dyno: DynoExtended) => dyno.size},
231233
},
232-
{
233-
overflow: 'wrap',
234-
},
234+
huxTableNoWrapOptions(noWrap),
235235
)
236236
/* eslint-enable perfectionist/sort-objects */
237237
}

src/lib/run/colorize.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ export const COLORS: Array<(s: string) => string> = [
77
s => color.cyan(s),
88
s => color.magenta(s),
99
s => color.blue(s),
10-
s => color.teal(s),
11-
s => color.pink(s),
12-
s => color.gold(s),
13-
s => color.purple(s),
14-
s => color.orange(s),
10+
s => color.info(s),
11+
s => color.name(s),
12+
s => color.addon(s),
13+
s => color.pipeline(s),
14+
s => color.warning(s),
1515
]
1616
const assignedColors: any = {}
1717
let isInitialized = false

src/lib/utils/tableUtils.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@ import {ux} from '@oclif/core/ux'
22

33
import parseKeyValue from './keyValueParser.js'
44

5+
export function huxTableNoWrapOptions(noWrap: boolean): {
6+
maxWidth: 'none' | undefined
7+
overflow: 'truncate' | 'wrap'
8+
} {
9+
return {
10+
maxWidth: noWrap ? 'none' : undefined,
11+
overflow: noWrap ? 'truncate' : 'wrap',
12+
}
13+
}
14+
515
export const constructSortFilterTableOptions = (flags: Record<string, string>, tableColumns: Record<string, any>) => {
616
const {filter, sort} = flags
717
const columnNames = new Set(Object.keys(tableColumns).map(key => key.toLowerCase()))

test/unit/commands/addons/index.unit.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
/* eslint-disable max-nested-callbacks */
22
import * as Heroku from '@heroku-cli/schema'
33
import {expect} from 'chai'
4+
import {hux} from '@heroku/heroku-cli-util'
45
import nock from 'nock'
6+
import sinon from 'sinon'
57

68
import Cmd from '../../../../src/commands/addons/index.js'
79
import * as fixtures from '../../../fixtures/addons/fixtures.js'
@@ -24,6 +26,7 @@ describe('addons', function () {
2426
afterEach(function () {
2527
api.done()
2628
nock.cleanAll()
29+
sinon.restore()
2730
})
2831

2932
describe('--all', function () {
@@ -59,6 +62,14 @@ describe('addons', function () {
5962
expect(stdout.indexOf('acme-inc-api')).to.be.lt(stdout.indexOf('acme-inc-www'))
6063
expect(stdout.indexOf('www-db')).to.be.lt(stdout.indexOf('www-redis'))
6164
})
65+
it('passes no-wrap option through to table rendering', async function () {
66+
const tableStub = sinon.stub(hux, 'table')
67+
68+
await runCommand(Cmd, ['--all', '--no-wrap'])
69+
70+
const callArgs = tableStub.firstCall.args
71+
expect(callArgs[2]).to.include({maxWidth: 'none', overflow: 'truncate'})
72+
})
6273
context('--json', function () {
6374
it('prints the output in json format', async function () {
6475
const {stdout} = await runCommand(Cmd, [

test/unit/commands/data/pg/credentials/index.unit.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import ansis from 'ansis'
22
import {expect} from 'chai'
3+
import {hux} from '@heroku/heroku-cli-util'
34
import nock from 'nock'
5+
import sinon from 'sinon'
46
import {stdout} from 'stdout-stderr'
57
import tsheredoc from 'tsheredoc'
68

@@ -18,6 +20,10 @@ import removeAllWhitespace from '../../../../../helpers/utils/remove-whitespaces
1820
const heredoc = tsheredoc.default
1921

2022
describe('data:pg:credentials:index', function () {
23+
afterEach(function () {
24+
sinon.restore()
25+
})
26+
2127
it('shows error for non-advanced databases', async function () {
2228
const herokuApi = nock('https://api.heroku.com')
2329
.post('/actions/addons/resolve')
@@ -97,4 +103,25 @@ describe('data:pg:credentials:index', function () {
97103
herokuApi.done()
98104
dataApi.done()
99105
})
106+
107+
it('passes no-wrap option through to table rendering', async function () {
108+
const herokuApi = nock('https://api.heroku.com')
109+
.post('/actions/addons/resolve')
110+
.reply(200, [addon])
111+
.get(`/addons/${addon.id}/addon-attachments`)
112+
.reply(200, advancedCredentialsAttachmentsResponse)
113+
114+
const dataApi = nock('https://api.data.heroku.com')
115+
.get(`/data/postgres/v1/${addon.id}/credentials`)
116+
.reply(200, advancedCredentialsResponse)
117+
118+
const tableStub = sinon.stub(hux, 'table')
119+
await runCommand(DataPgCredentialsIndex, ['DATABASE', '--app=myapp', '--no-wrap'])
120+
121+
const callArgs = tableStub.firstCall.args
122+
expect(callArgs[2]).to.include({maxWidth: 'none', overflow: 'truncate'})
123+
124+
herokuApi.done()
125+
dataApi.done()
126+
})
100127
})

test/unit/commands/domains/index.unit.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {expect} from 'chai'
22
import nock from 'nock'
33
import sinon from 'sinon'
44
import {stderr, stdout} from 'stdout-stderr'
5+
import {hux} from '@heroku/heroku-cli-util'
56

67
import DomainsIndex from '../../../../src/commands/domains/index.js'
78
import {runCommand} from '../../../helpers/run-command.js'
@@ -195,4 +196,17 @@ describe('domains', function () {
195196
'Warning: This app has over 100 domains. Your terminal may not be configured to display the total amount of domains.',
196197
)
197198
})
199+
200+
it('passes no-wrap option through to table rendering', async function () {
201+
api.get('/apps/myapp/domains').reply(200, herokuAndCustomDomainsResponse)
202+
const tableStub = sinon.stub(hux, 'table')
203+
204+
await runCommand(DomainsIndex, ['--app', 'myapp', '--no-wrap'])
205+
206+
expect(tableStub.calledOnce).to.equal(true)
207+
const callArgs = tableStub.firstCall.args
208+
expect(callArgs[2]).to.include({maxWidth: 'none', overflow: 'truncate'})
209+
210+
tableStub.restore()
211+
})
198212
})

0 commit comments

Comments
 (0)