Skip to content

Commit

Permalink
[cli] Swap custom table code with cli-table3 (vercel#10333)
Browse files Browse the repository at this point in the history
Co-authored-by: Trek Glowacki <[email protected]>
  • Loading branch information
trek and trek authored Aug 14, 2023
1 parent 94c93df commit 78fa022
Show file tree
Hide file tree
Showing 6 changed files with 276 additions and 301 deletions.
5 changes: 5 additions & 0 deletions .changeset/beige-pets-smell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'vercel': minor
---

Update help output to use cli-table3
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
"chalk": "4.1.0",
"chance": "1.1.7",
"chokidar": "3.3.1",
"cli-table3": "0.6.3",
"codecov": "3.8.2",
"cpy": "7.2.0",
"date-fns": "1.29.0",
Expand Down
154 changes: 74 additions & 80 deletions packages/cli/src/commands/help.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import chalk from 'chalk';
import stripAnsi from 'strip-ansi';
import { LOGO, NAME } from '@vercel-internals/constants';
import Table, { CellOptions } from 'cli-table3';

const INDENT = ' '.repeat(2);
const NEWLINE = '\n';
Expand Down Expand Up @@ -30,6 +30,12 @@ export interface Command {
examples: CommandExample[];
}

// https://github.com/cli-table/cli-table3/pull/303 adds
// word wrapping per cell but did not include updated types.
type _CellOptions = CellOptions & {
wordWrap?: boolean;
};

const globalCommandOptions: CommandOption[] = [
{
name: 'help',
Expand Down Expand Up @@ -110,10 +116,6 @@ const globalCommandOptions: CommandOption[] = [
},
];

export function calcLineLength(line: string[]) {
return stripAnsi(lineToString(line)).length;
}

// Insert spaces in between non-whitespace items only
export function lineToString(line: string[]) {
let string = '';
Expand Down Expand Up @@ -166,97 +168,89 @@ export function buildCommandOptionLines(
options: BuildHelpOutputOptions,
sectionTitle: String
) {
// Filter out deprecated and intentionally undocumented options
commandOptions = commandOptions.filter(
option => !option.deprecated && option.description !== undefined
);

if (commandOptions.length === 0) {
return null;
}

// Initialize output array with header and empty line
const outputArray: string[] = [`${INDENT}${chalk.dim(sectionTitle)}:`, ''];
// Filter out deprecated and intentionally undocumented options
commandOptions = commandOptions.filter(
option => !option.deprecated && option.description !== undefined
);

// Start building option lines
const optionLines: string[][] = [];
// Sort command options alphabetically
commandOptions.sort((a, b) =>
a.name < b.name ? -1 : a.name > b.name ? 1 : 0
);
// Keep track of longest "start" of an option line to determine description spacing
let maxLineStartLength = 0;
// Iterate over options and create the "start" of each option (e.g. ` -b, --build-env <key=value>`)

// word wrapping requires the wrapped cell to have a fixed width.
// We need to track cell sizes to make the final column of cells is
// equal to the remaindner of unused horizontal space.
let maxWidthOfUnwrappedColumns = 0;
const rows: (string | undefined | _CellOptions)[][] = [];
for (const option of commandOptions) {
const startLine: string[] = [INDENT, INDENT, INDENT];
if (option.shorthand) {
startLine.push(`-${option.shorthand},`);
}
startLine.push(`--${option.name}`);
const shorthandCell = option.shorthand
? `${INDENT}-${option.shorthand},`
: '';
let longhandCell = `${INDENT}--${option.name}`;

if (option.argument) {
startLine.push(`<${option.argument}>`);
longhandCell += ` <${option.argument}>`;
}
// the length includes the INDENT
const lineLength = calcLineLength(startLine);
maxLineStartLength = Math.max(lineLength, maxLineStartLength);
optionLines.push(startLine);
}
/*
* Iterate over in-progress option lines to add space-filler and description
* For Example:
* | --archive My description starts here.
* |
* | -b, --build-env <key=value> Start of description here then
* | it wraps here.
* |
* | -e, --env <key=value> My description is short.
*
* Breaking down option lines:
* | -b, --build-env <key=value> Start of description here then
* |[][ ][][ ]
* |↑ ↑ ↑ ↑
* |1 2 3 4
* | it wraps here.
* |[][ ][ ]
* |↑ ↑ ↑
* |5 6 7
* | 1, 5 = indent
* | 2 = start
* | 3, 6 = space-filler
* | 4, 7 = description
*/
for (let i = 0; i < optionLines.length; i++) {
const optionLine = optionLines[i];
const option = commandOptions[i];
// Add only 2 spaces to the longest line, and then make all shorter lines the same length.
optionLine.push(
' '.repeat(2 + (maxLineStartLength - calcLineLength(optionLine)))

longhandCell += INDENT;

const widthOfUnwrappedColumns = shorthandCell.length + longhandCell.length;
maxWidthOfUnwrappedColumns = Math.max(
widthOfUnwrappedColumns,
maxWidthOfUnwrappedColumns
);

// Descriptions may be longer than max line length. Wrap them to the same column as the first description line
const lines: string[][] = [optionLine];
if (option.description) {
for (const descriptionWord of option.description.split(' ')) {
// insert a new line when the next word would match or exceed the maximum line length
if (
calcLineLength(lines[lines.length - 1]) +
stripAnsi(descriptionWord).length >=
options.columns
) {
// initialize the new line with the necessary whitespace. The INDENT is apart of `maxLineStartLength`
lines.push([' '.repeat(maxLineStartLength + 2)]);
}
// insert the word to the current last line
lines[lines.length - 1].push(descriptionWord);
}
}
// for every line, transform into a string and push it to the output
for (const line of lines) {
outputArray.push(lineToString(line));
}
rows.push([
shorthandCell,
longhandCell,
{
content: option.description,
wordWrap: true,
},
]);
}

return `${outputArrayToString(outputArray)}${NEWLINE}`;
const finalColumnWidth = options.columns - maxWidthOfUnwrappedColumns;

const table = new Table({
chars: {
top: '',
'top-mid': '',
'top-left': '',
'top-right': '',
bottom: '',
'bottom-mid': '',
'bottom-left': '',
'bottom-right': '',
left: '',
'left-mid': '',
mid: '',
'mid-mid': '',
right: '',
'right-mid': '',
middle: '',
},
style: {
'padding-left': 0,
'padding-right': 0,
},
colWidths: [null, null, finalColumnWidth],
});

table.push(...rows);
return [
`${INDENT}${chalk.dim(sectionTitle)}:`,
NEWLINE,
NEWLINE,
table.toString(),
NEWLINE,
NEWLINE,
].join('');
}

export function buildCommandExampleLines(command: Command) {
Expand Down
Loading

0 comments on commit 78fa022

Please sign in to comment.