Skip to content

Commit 50453fd

Browse files
committed
feat(@angular/cli): overhaul ng version command output
This commit completely revamps the `ng version` command's output for a more modern and readable experience. The key improvements include: - A single, unified table for all packages, removing the separate Angular section. - A polished table format using box-drawing characters and consistent padding. - An improved header section with aligned, bolded, and more formal labels. - The use of color to highlight version numbers and other key information, improving scannability. - Version lookup errors are now highlighted in red for better visibility. The header generation logic is also refactored to be more maintainable by removing repeated label strings.
1 parent 0881c4f commit 50453fd

File tree

2 files changed

+45
-74
lines changed

2 files changed

+45
-74
lines changed

packages/angular/cli/src/commands/version/cli.ts

Lines changed: 44 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type { Argv } from 'yargs';
1010
import { CommandModule, CommandModuleImplementation } from '../../command-builder/command-module';
1111
import { colors } from '../../utilities/color';
1212
import { RootCommands } from '../command-config';
13-
import { VersionInfo, gatherVersionInfo } from './version-info';
13+
import { gatherVersionInfo } from './version-info';
1414

1515
/**
1616
* The Angular CLI logo, displayed as ASCII art.
@@ -65,17 +65,31 @@ export default class VersionCommandModule
6565
versions,
6666
} = versionInfo;
6767

68-
const header = `
69-
Angular CLI: ${ngCliVersion}
70-
Node: ${nodeVersion}${unsupportedNodeVersion ? ' (Unsupported)' : ''}
71-
Package Manager: ${packageManagerName} ${packageManagerVersion ?? '<error>'}
72-
OS: ${os} ${arch}
73-
`.replace(/^ {6}/gm, '');
68+
const headerInfo = [
69+
{ label: 'Angular CLI', value: ngCliVersion },
70+
{
71+
label: 'Node.js',
72+
value: `${nodeVersion}${unsupportedNodeVersion ? colors.yellow(' (Unsupported)') : ''}`,
73+
},
74+
{
75+
label: 'Package Manager',
76+
value: `${packageManagerName} ${packageManagerVersion ?? '<error>'}`,
77+
},
78+
{ label: 'Operating System', value: `${os} ${arch}` },
79+
];
80+
81+
const maxHeaderLabelLength = Math.max(...headerInfo.map((l) => l.label.length));
82+
83+
const header = headerInfo
84+
.map(
85+
({ label, value }) =>
86+
colors.bold(label.padEnd(maxHeaderLabelLength + 2)) + `: ${colors.cyan(value)}`,
87+
)
88+
.join('\n');
7489

75-
const angularPackages = this.formatAngularPackages(versionInfo);
7690
const packageTable = this.formatPackageTable(versions);
7791

78-
logger.info([ASCII_ART, header, angularPackages, packageTable].join('\n\n'));
92+
logger.info([ASCII_ART, header, packageTable].join('\n\n'));
7993

8094
if (unsupportedNodeVersion) {
8195
logger.warn(
@@ -84,36 +98,6 @@ export default class VersionCommandModule
8498
}
8599
}
86100

87-
/**
88-
* Formats the Angular packages section of the version output.
89-
* @param versionInfo An object containing the version information.
90-
* @returns A string containing the formatted Angular packages information.
91-
*/
92-
private formatAngularPackages(versionInfo: VersionInfo): string {
93-
const { angularCoreVersion, angularSameAsCore } = versionInfo;
94-
if (!angularCoreVersion) {
95-
return 'Angular: <error>';
96-
}
97-
98-
const wrappedPackages = angularSameAsCore
99-
.reduce<string[]>((acc, name) => {
100-
if (acc.length === 0) {
101-
return [name];
102-
}
103-
const line = acc[acc.length - 1] + ', ' + name;
104-
if (line.length > 60) {
105-
acc.push(name);
106-
} else {
107-
acc[acc.length - 1] = line;
108-
}
109-
110-
return acc;
111-
}, [])
112-
.join('\n... ');
113-
114-
return `Angular: ${angularCoreVersion}\n... ${wrappedPackages}`;
115-
}
116-
117101
/**
118102
* Formats the package table section of the version output.
119103
* @param versions A map of package names to their versions.
@@ -125,22 +109,33 @@ export default class VersionCommandModule
125109
return '';
126110
}
127111

128-
const header = 'Package';
129-
const maxNameLength = Math.max(...versionKeys.map((key) => key.length));
130-
const namePad = ' '.repeat(Math.max(0, maxNameLength - header.length) + 3);
112+
const nameHeader = 'Package';
113+
const versionHeader = 'Version';
131114

132-
const tableHeader = `${header}${namePad}Version`;
133-
const separator = '-'.repeat(tableHeader.length);
115+
const maxNameLength = Math.max(nameHeader.length, ...versionKeys.map((key) => key.length));
116+
const maxVersionLength = Math.max(
117+
versionHeader.length,
118+
...versionKeys.map((key) => versions[key].length),
119+
);
134120

135121
const tableRows = versionKeys
136122
.map((module) => {
137-
const padding = ' '.repeat(maxNameLength - module.length + 3);
123+
const name = module.padEnd(maxNameLength);
124+
const version = versions[module];
125+
const coloredVersion = version === '<error>' ? colors.red(version) : colors.cyan(version);
126+
const padding = ' '.repeat(maxVersionLength - version.length);
138127

139-
return `${module}${padding}${versions[module]}`;
128+
return `${name}${coloredVersion}${padding}`;
140129
})
141-
.sort()
142-
.join('\n');
130+
.sort();
131+
132+
const top = `┌─${'─'.repeat(maxNameLength)}─┬─${'─'.repeat(maxVersionLength)}─┐`;
133+
const header = `│ ${nameHeader.padEnd(maxNameLength)}${versionHeader.padEnd(
134+
maxVersionLength,
135+
)} │`;
136+
const separator = `├─${'─'.repeat(maxNameLength)}─┼─${'─'.repeat(maxVersionLength)}─┤`;
137+
const bottom = `└─${'─'.repeat(maxNameLength)}─┴─${'─'.repeat(maxVersionLength)}─┘`;
143138

144-
return `${tableHeader}\n${separator}\n${tableRows}`;
139+
return [top, header, separator, ...tableRows, bottom].join('\n');
145140
}
146141
}

packages/angular/cli/src/commands/version/version-info.ts

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ interface PartialPackageInfo {
2424
*/
2525
export interface VersionInfo {
2626
ngCliVersion: string;
27-
angularCoreVersion: string;
28-
angularSameAsCore: string[];
2927
versions: Record<string, string>;
3028
unsupportedNodeVersion: boolean;
3129
nodeVersion: string;
@@ -90,30 +88,8 @@ export function gatherVersionInfo(context: {
9088
}
9189
}
9290

93-
const ngCliVersion = VERSION.full;
94-
let angularCoreVersion = '';
95-
const angularSameAsCore: string[] = [];
96-
97-
if (workspacePackage) {
98-
// Filter all angular versions that are the same as core.
99-
angularCoreVersion = versions['@angular/core'];
100-
if (angularCoreVersion) {
101-
for (const [name, version] of Object.entries(versions)) {
102-
if (version === angularCoreVersion && name.startsWith('@angular/')) {
103-
angularSameAsCore.push(name.replace(/^@angular\//, ''));
104-
delete versions[name];
105-
}
106-
}
107-
108-
// Make sure we list them in alphabetical order.
109-
angularSameAsCore.sort();
110-
}
111-
}
112-
11391
return {
114-
ngCliVersion,
115-
angularCoreVersion,
116-
angularSameAsCore,
92+
ngCliVersion: VERSION.full,
11793
versions,
11894
unsupportedNodeVersion,
11995
nodeVersion: process.versions.node,

0 commit comments

Comments
 (0)