From 94e2c832afa3485edbb57d12cb005736617787a2 Mon Sep 17 00:00:00 2001 From: Charles Lyding <19598772+clydin@users.noreply.github.com> Date: Mon, 29 Sep 2025 15:20:03 -0400 Subject: [PATCH] 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. --- .../angular/cli/src/commands/version/cli.ts | 93 +++++++++---------- .../cli/src/commands/version/version-info.ts | 26 +----- 2 files changed, 45 insertions(+), 74 deletions(-) diff --git a/packages/angular/cli/src/commands/version/cli.ts b/packages/angular/cli/src/commands/version/cli.ts index b3624a9a06ce..135358a4b7ba 100644 --- a/packages/angular/cli/src/commands/version/cli.ts +++ b/packages/angular/cli/src/commands/version/cli.ts @@ -10,7 +10,7 @@ import type { Argv } from 'yargs'; import { CommandModule, CommandModuleImplementation } from '../../command-builder/command-module'; import { colors } from '../../utilities/color'; import { RootCommands } from '../command-config'; -import { VersionInfo, gatherVersionInfo } from './version-info'; +import { gatherVersionInfo } from './version-info'; /** * The Angular CLI logo, displayed as ASCII art. @@ -65,17 +65,31 @@ export default class VersionCommandModule versions, } = versionInfo; - const header = ` - Angular CLI: ${ngCliVersion} - Node: ${nodeVersion}${unsupportedNodeVersion ? ' (Unsupported)' : ''} - Package Manager: ${packageManagerName} ${packageManagerVersion ?? ''} - OS: ${os} ${arch} - `.replace(/^ {6}/gm, ''); + const headerInfo = [ + { label: 'Angular CLI', value: ngCliVersion }, + { + label: 'Node.js', + value: `${nodeVersion}${unsupportedNodeVersion ? colors.yellow(' (Unsupported)') : ''}`, + }, + { + label: 'Package Manager', + value: `${packageManagerName} ${packageManagerVersion ?? ''}`, + }, + { label: 'Operating System', value: `${os} ${arch}` }, + ]; + + const maxHeaderLabelLength = Math.max(...headerInfo.map((l) => l.label.length)); + + const header = headerInfo + .map( + ({ label, value }) => + colors.bold(label.padEnd(maxHeaderLabelLength + 2)) + `: ${colors.cyan(value)}`, + ) + .join('\n'); - const angularPackages = this.formatAngularPackages(versionInfo); const packageTable = this.formatPackageTable(versions); - logger.info([ASCII_ART, header, angularPackages, packageTable].join('\n\n')); + logger.info([ASCII_ART, header, packageTable].join('\n\n')); if (unsupportedNodeVersion) { logger.warn( @@ -84,36 +98,6 @@ export default class VersionCommandModule } } - /** - * Formats the Angular packages section of the version output. - * @param versionInfo An object containing the version information. - * @returns A string containing the formatted Angular packages information. - */ - private formatAngularPackages(versionInfo: VersionInfo): string { - const { angularCoreVersion, angularSameAsCore } = versionInfo; - if (!angularCoreVersion) { - return 'Angular: '; - } - - const wrappedPackages = angularSameAsCore - .reduce((acc, name) => { - if (acc.length === 0) { - return [name]; - } - const line = acc[acc.length - 1] + ', ' + name; - if (line.length > 60) { - acc.push(name); - } else { - acc[acc.length - 1] = line; - } - - return acc; - }, []) - .join('\n... '); - - return `Angular: ${angularCoreVersion}\n... ${wrappedPackages}`; - } - /** * Formats the package table section of the version output. * @param versions A map of package names to their versions. @@ -125,22 +109,33 @@ export default class VersionCommandModule return ''; } - const header = 'Package'; - const maxNameLength = Math.max(...versionKeys.map((key) => key.length)); - const namePad = ' '.repeat(Math.max(0, maxNameLength - header.length) + 3); + const nameHeader = 'Package'; + const versionHeader = 'Version'; - const tableHeader = `${header}${namePad}Version`; - const separator = '-'.repeat(tableHeader.length); + const maxNameLength = Math.max(nameHeader.length, ...versionKeys.map((key) => key.length)); + const maxVersionLength = Math.max( + versionHeader.length, + ...versionKeys.map((key) => versions[key].length), + ); const tableRows = versionKeys .map((module) => { - const padding = ' '.repeat(maxNameLength - module.length + 3); + const name = module.padEnd(maxNameLength); + const version = versions[module]; + const coloredVersion = version === '' ? colors.red(version) : colors.cyan(version); + const padding = ' '.repeat(maxVersionLength - version.length); - return `${module}${padding}${versions[module]}`; + return `│ ${name} │ ${coloredVersion}${padding} │`; }) - .sort() - .join('\n'); + .sort(); + + const top = `┌─${'─'.repeat(maxNameLength)}─┬─${'─'.repeat(maxVersionLength)}─┐`; + const header = `│ ${nameHeader.padEnd(maxNameLength)} │ ${versionHeader.padEnd( + maxVersionLength, + )} │`; + const separator = `├─${'─'.repeat(maxNameLength)}─┼─${'─'.repeat(maxVersionLength)}─┤`; + const bottom = `└─${'─'.repeat(maxNameLength)}─┴─${'─'.repeat(maxVersionLength)}─┘`; - return `${tableHeader}\n${separator}\n${tableRows}`; + return [top, header, separator, ...tableRows, bottom].join('\n'); } } diff --git a/packages/angular/cli/src/commands/version/version-info.ts b/packages/angular/cli/src/commands/version/version-info.ts index 5d5f656fd2ea..c59a3b5728af 100644 --- a/packages/angular/cli/src/commands/version/version-info.ts +++ b/packages/angular/cli/src/commands/version/version-info.ts @@ -24,8 +24,6 @@ interface PartialPackageInfo { */ export interface VersionInfo { ngCliVersion: string; - angularCoreVersion: string; - angularSameAsCore: string[]; versions: Record; unsupportedNodeVersion: boolean; nodeVersion: string; @@ -90,30 +88,8 @@ export function gatherVersionInfo(context: { } } - const ngCliVersion = VERSION.full; - let angularCoreVersion = ''; - const angularSameAsCore: string[] = []; - - if (workspacePackage) { - // Filter all angular versions that are the same as core. - angularCoreVersion = versions['@angular/core']; - if (angularCoreVersion) { - for (const [name, version] of Object.entries(versions)) { - if (version === angularCoreVersion && name.startsWith('@angular/')) { - angularSameAsCore.push(name.replace(/^@angular\//, '')); - delete versions[name]; - } - } - - // Make sure we list them in alphabetical order. - angularSameAsCore.sort(); - } - } - return { - ngCliVersion, - angularCoreVersion, - angularSameAsCore, + ngCliVersion: VERSION.full, versions, unsupportedNodeVersion, nodeVersion: process.versions.node,