diff --git a/packages/angular/cli/src/commands/version/cli.ts b/packages/angular/cli/src/commands/version/cli.ts index 9ba53a902ebd..7dfb138aa382 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 { gatherVersionInfo } from './version-info'; +import { PackageVersionInfo, gatherVersionInfo } from './version-info'; /** * The Angular CLI logo, displayed as ASCII art. @@ -67,6 +67,7 @@ export default class VersionCommandModule const { cli: { version: ngCliVersion }, + framework, system: { node: { version: nodeVersion, unsupported: unsupportedNodeVersion }, os: { platform: os, architecture: arch }, @@ -75,8 +76,13 @@ export default class VersionCommandModule packages, } = versionInfo; - const headerInfo = [ - { label: 'Angular CLI', value: ngCliVersion }, + const headerInfo = [{ label: 'Angular CLI', value: ngCliVersion }]; + + if (framework.version) { + headerInfo.push({ label: 'Angular', value: framework.version }); + } + + headerInfo.push( { label: 'Node.js', value: `${nodeVersion}${unsupportedNodeVersion ? colors.yellow(' (Unsupported)') : ''}`, @@ -86,7 +92,7 @@ export default class VersionCommandModule value: `${packageManagerName} ${packageManagerVersion ?? ''}`, }, { label: 'Operating System', value: `${os} ${arch}` }, - ]; + ); const maxHeaderLabelLength = Math.max(...headerInfo.map((l) => l.label.length)); @@ -113,38 +119,56 @@ export default class VersionCommandModule * @param versions A map of package names to their versions. * @returns A string containing the formatted package table. */ - private formatPackageTable(versions: Record): string { + private formatPackageTable(versions: Record): string { const versionKeys = Object.keys(versions); if (versionKeys.length === 0) { return ''; } - const nameHeader = 'Package'; - const versionHeader = 'Version'; + const headers = { + name: 'Package', + installed: 'Installed Version', + requested: 'Requested Version', + }; - const maxNameLength = Math.max(nameHeader.length, ...versionKeys.map((key) => key.length)); - const maxVersionLength = Math.max( - versionHeader.length, - ...versionKeys.map((key) => versions[key].length), + const maxNameLength = Math.max(headers.name.length, ...versionKeys.map((key) => key.length)); + const maxInstalledLength = Math.max( + headers.installed.length, + ...versionKeys.map((key) => versions[key].installed.length), + ); + const maxRequestedLength = Math.max( + headers.requested.length, + ...versionKeys.map((key) => versions[key].requested.length), ); const tableRows = versionKeys .map((module) => { + const { requested, installed } = versions[module]; 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 `│ ${name} │ ${coloredVersion}${padding} │`; + const coloredInstalled = + installed === '' ? colors.red(installed) : colors.cyan(installed); + const installedPadding = ' '.repeat(maxInstalledLength - installed.length); + + return `│ ${name} │ ${coloredInstalled}${installedPadding} │ ${requested.padEnd( + maxRequestedLength, + )} │`; }) .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)}─┘`; + const top = `┌─${'─'.repeat(maxNameLength)}─┬─${'─'.repeat( + maxInstalledLength, + )}─┬─${'─'.repeat(maxRequestedLength)}─┐`; + const header = + `│ ${headers.name.padEnd(maxNameLength)} │ ` + + `${headers.installed.padEnd(maxInstalledLength)} │ ` + + `${headers.requested.padEnd(maxRequestedLength)} │`; + const separator = `├─${'─'.repeat(maxNameLength)}─┼─${'─'.repeat( + maxInstalledLength, + )}─┼─${'─'.repeat(maxRequestedLength)}─┤`; + const bottom = `└─${'─'.repeat(maxNameLength)}─┴─${'─'.repeat( + maxInstalledLength, + )}─┴─${'─'.repeat(maxRequestedLength)}─┘`; 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 1e8b6b557514..ff3186c7b7ed 100644 --- a/packages/angular/cli/src/commands/version/version-info.ts +++ b/packages/angular/cli/src/commands/version/version-info.ts @@ -19,6 +19,14 @@ interface PartialPackageInfo { devDependencies?: Record; } +/** + * An object containing version information for a single package. + */ +export interface PackageVersionInfo { + requested: string; + installed: string; +} + /** * An object containing all the version information that will be displayed by the command. */ @@ -26,6 +34,9 @@ export interface VersionInfo { cli: { version: string; }; + framework: { + version: string | undefined; + }; system: { node: { version: string; @@ -40,7 +51,7 @@ export interface VersionInfo { version: string | undefined; }; }; - packages: Record; + packages: Record; } /** @@ -84,24 +95,31 @@ export function gatherVersionInfo(context: { const [nodeMajor] = process.versions.node.split('.').map((part) => Number(part)); const unsupportedNodeVersion = !SUPPORTED_NODE_MAJORS.includes(nodeMajor); - const packageNames = new Set( - Object.keys({ - ...workspacePackage?.dependencies, - ...workspacePackage?.devDependencies, - }), - ); + const allDependencies = { + ...workspacePackage?.dependencies, + ...workspacePackage?.devDependencies, + }; + const packageNames = new Set(Object.keys(allDependencies)); - const packages: Record = {}; + const packages: Record = {}; for (const name of packageNames) { if (PACKAGE_PATTERNS.some((p) => p.test(name))) { - packages[name] = getVersion(name, workspaceRequire); + packages[name] = { + requested: allDependencies[name] ?? 'error', + installed: getVersion(name, workspaceRequire), + }; } } + const angularCoreVersion = packages['@angular/core']; + return { cli: { version: VERSION.full, }, + framework: { + version: angularCoreVersion?.installed, + }, system: { node: { version: process.versions.node,