Skip to content

Commit 081ce59

Browse files
committed
fix(W-18763150): width calculation when no process.stdout.columns
1 parent 0dcbd56 commit 081ce59

File tree

5 files changed

+155
-5
lines changed

5 files changed

+155
-5
lines changed

examples/no-width-table.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import {printTable} from '../src/index.js'
2+
3+
const data = [
4+
{
5+
actual: 'This is a string of the actual test results. ',
6+
expected: 'This is a long string of the expected test results. ',
7+
result: 'Passed',
8+
test: 'Topic',
9+
},
10+
]
11+
12+
process.stdout.columns = 0
13+
printTable({
14+
columns: ['test', 'result', 'expected', 'actual'],
15+
data,
16+
headerOptions: {
17+
formatter: 'capitalCase',
18+
},
19+
noStyle: true,
20+
overflow: 'wrap',
21+
title: 'process.stdout.columns is 0',
22+
titleOptions: {bold: true},
23+
})
24+
25+
// @ts-expect-error for testing
26+
process.stdout.columns = undefined
27+
printTable({
28+
columns: ['test', 'result', 'expected', 'actual'],
29+
data,
30+
headerOptions: {
31+
formatter: 'capitalCase',
32+
},
33+
noStyle: true,
34+
overflow: 'wrap',
35+
title: 'process.stdout.columns is undefined',
36+
titleOptions: {bold: true},
37+
})

src/table.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
determineConfiguredWidth,
2828
determineWidthOfWrappedText,
2929
getColumns,
30+
getColumnWidth,
3031
getHeadings,
3132
intersperse,
3233
maybeStripAnsi,
@@ -431,7 +432,7 @@ const createStdout = (): FakeStdout => {
431432
// https://github.com/vadimdemedes/ink/blob/v5.0.1/src/ink.tsx#L174
432433
// This might be a bad idea but it works.
433434
stdout.rows = 10_000
434-
stdout.columns = process.stdout.columns ?? 80
435+
stdout.columns = getColumnWidth()
435436
const frames: string[] = []
436437

437438
stdout.write = (data: string) => {
@@ -568,8 +569,7 @@ export function printTables<T extends Record<string, unknown>[]>(
568569
const output = new Output()
569570
const leftMargin = options?.marginLeft ?? options?.margin ?? 0
570571
const rightMargin = options?.marginRight ?? options?.margin ?? 0
571-
const columns = process.stdout.columns - (leftMargin + rightMargin)
572-
572+
const columns = getColumnWidth() - (leftMargin + rightMargin)
573573
const processed = tables.map((table) => ({
574574
...table,
575575
// adjust maxWidth to account for margin and columnGap

src/utils.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,25 @@ export function determineWidthOfWrappedText(text: string): number {
5151
return lines.reduce((max, line) => Math.max(max, line.length), 0)
5252
}
5353

54+
/**
55+
* Gets the width of the terminal column.
56+
* First checks for an override in the OCLIF_TABLE_COLUMN_OVERRIDE environment variable.
57+
* If no override is set or the override is 0, returns the actual terminal width from process.stdout.columns.
58+
*
59+
* It's possible that `process.stdout.columns` is undefined or 0, which is okay. We'll end up using the table's natural width
60+
* in that case. If that renders poorly for the user, they can set the OCLIF_TABLE_COLUMN_OVERRIDE environment variable to a
61+
* non-zero value.
62+
*
63+
* @returns {number} The width of the terminal column
64+
*/
65+
export function getColumnWidth(): number {
66+
return Number.parseInt(process.env.OCLIF_TABLE_COLUMN_OVERRIDE || '0', 10) || process.stdout.columns
67+
}
68+
5469
/**
5570
* Determines the configured width based on the provided width value.
5671
* If no width is provided, it returns the width of the current terminal.
72+
* - It's possible that `process.stdout.columns` is undefined, which is okay. We'll end up using the table's natural width.
5773
* If the provided width is a percentage, it calculates the width based on the percentage of the terminal width.
5874
* If the provided width is a number, it returns the provided width.
5975
* If the calculated width is greater than the terminal width, it returns the terminal width.
@@ -63,7 +79,7 @@ export function determineWidthOfWrappedText(text: string): number {
6379
*/
6480
export function determineConfiguredWidth(
6581
providedWidth: number | Percentage | undefined,
66-
columns = process.stdout.columns,
82+
columns = getColumnWidth(),
6783
): number {
6884
if (!providedWidth) return columns
6985

@@ -126,6 +142,9 @@ export function getColumns<T extends Record<string, unknown>>(config: Config<T>,
126142
const seen = new Set<string>()
127143

128144
const reduceColumnWidths = (calcMinWidth: (col: Column<T>) => number) => {
145+
// maxWidth === 0 is likely from a test environment where process.stdout.columns is undefined or 0
146+
// In that case, we don't want to reduce the column widths and just use the table's natural width.
147+
if (maxWidth === 0) return
129148
// If the table is too wide, reduce the width of the largest column as little as possible to fit the table.
130149
// If the table is still too wide, it will reduce the width of the next largest column and so on
131150
while (tableWidth > maxWidth) {

test/table.test.tsx

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,4 +718,80 @@ describe('printTable compatibility with @oclif/test', () => {
718718
)
719719
expect(stdout).to.equal(expected)
720720
})
721+
722+
it('prints full table with no set width', async () => {
723+
console.log(process.stdout.columns)
724+
const data = [
725+
{name: 'Foo', age: '1'.repeat(60)},
726+
{name: 'Bar', age: '2'.repeat(60)},
727+
]
728+
729+
const expected = `┌──────┬──────────────────────────────────────────────────────────────┐
730+
│ name │ age │
731+
├──────┼──────────────────────────────────────────────────────────────┤
732+
│ Foo │ 111111111111111111111111111111111111111111111111111111111111 │
733+
├──────┼──────────────────────────────────────────────────────────────┤
734+
│ Bar │ 222222222222222222222222222222222222222222222222222222222222 │
735+
└──────┴──────────────────────────────────────────────────────────────┘
736+
737+
`
738+
739+
const {stdout} = await captureOutput(async () =>
740+
printTable({
741+
data,
742+
columns: ['name', 'age'],
743+
}),
744+
)
745+
expect(stdout).to.equal(expected)
746+
})
747+
748+
it('should use natural width of 80 if process.stdout.columns is 0', async () => {
749+
const backupColumns = process.stdout.columns
750+
process.stdout.columns = 0
751+
const data = [
752+
{name: 'Foo', age: '1'.repeat(100)},
753+
{name: 'Bar', age: '2'.repeat(100)},
754+
]
755+
756+
const {stdout} = await captureOutput(async () =>
757+
printTable({
758+
data,
759+
columns: ['name', 'age'],
760+
}),
761+
)
762+
expect(stdout.length).to.equal(785)
763+
764+
process.stdout.columns = backupColumns
765+
})
766+
767+
it('should respect the OCLIF_TABLE_COLUMN_OVERRIDE env var', async () => {
768+
const backupColumns = process.stdout.columns
769+
process.stdout.columns = 0
770+
process.env.OCLIF_TABLE_COLUMN_OVERRIDE = '50'
771+
const data = [
772+
{name: 'Foo', age: '1'.repeat(100)},
773+
{name: 'Bar', age: '2'.repeat(100)},
774+
]
775+
776+
const expected = `┌──────┬─────────────────────────────────────────┐
777+
│ name │ age │
778+
├──────┼─────────────────────────────────────────┤
779+
│ Foo │ 11111111111111111111111111111111111111… │
780+
├──────┼─────────────────────────────────────────┤
781+
│ Bar │ 22222222222222222222222222222222222222… │
782+
└──────┴─────────────────────────────────────────┘
783+
784+
`
785+
786+
const {stdout} = await captureOutput(async () =>
787+
printTable({
788+
data,
789+
columns: ['name', 'age'],
790+
}),
791+
)
792+
expect(stdout).to.equal(expected)
793+
794+
delete process.env.OCLIF_TABLE_COLUMN_OVERRIDE
795+
process.stdout.columns = backupColumns
796+
})
721797
})

test/util.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {config, expect} from 'chai'
22

3-
import {intersperse, sortData} from '../src/utils.js'
3+
import {getColumnWidth, intersperse, sortData} from '../src/utils.js'
44

55
config.truncateThreshold = 0
66

@@ -64,3 +64,21 @@ describe('sortData', () => {
6464
expect(sortData(data, sort)).to.deep.equal(expected)
6565
})
6666
})
67+
68+
describe('should get the correct column width', () => {
69+
it('should return the value of OCLIF_TABLE_COLUMN_OVERRIDE', () => {
70+
process.env.OCLIF_TABLE_COLUMN_OVERRIDE = '100'
71+
expect(getColumnWidth()).to.equal(100)
72+
delete process.env.OCLIF_TABLE_COLUMN_OVERRIDE
73+
})
74+
75+
it('should return the value of process.stdout.columns', () => {
76+
if (process.env.CI && !process.stdout.columns) {
77+
// In GHA process.stdout.columns is undefined
78+
expect(getColumnWidth()).to.equal(80)
79+
} else {
80+
const currentColumns = process.stdout.columns
81+
expect(getColumnWidth()).to.equal(currentColumns)
82+
}
83+
})
84+
})

0 commit comments

Comments
 (0)