66 * found in the LICENSE file at https://angular.dev/license
77 */
88
9- import nodeModule from 'node:module' ;
10- import { resolve } from 'node:path' ;
11- import { Argv } from 'yargs' ;
9+ import type { Argv } from 'yargs' ;
1210import { CommandModule , CommandModuleImplementation } from '../../command-builder/command-module' ;
1311import { colors } from '../../utilities/color' ;
1412import { RootCommands } from '../command-config' ;
15-
16- interface PartialPackageInfo {
17- name : string ;
18- version : string ;
19- dependencies ?: Record < string , string > ;
20- devDependencies ?: Record < string , string > ;
21- }
13+ import { VersionInfo , gatherVersionInfo } from './version-info' ;
2214
2315/**
24- * Major versions of Node.js that are officially supported by Angular .
16+ * The Angular CLI logo, displayed as ASCII art .
2517 */
26- const SUPPORTED_NODE_MAJORS = [ 20 , 22 , 24 ] ;
27-
28- const PACKAGE_PATTERNS = [
29- / ^ @ a n g u l a r \/ .* / ,
30- / ^ @ a n g u l a r - d e v k i t \/ .* / ,
31- / ^ @ n g t o o l s \/ .* / ,
32- / ^ @ s c h e m a t i c s \/ .* / ,
33- / ^ r x j s $ / ,
34- / ^ t y p e s c r i p t $ / ,
35- / ^ n g - p a c k a g r $ / ,
36- / ^ w e b p a c k $ / ,
37- / ^ z o n e \. j s $ / ,
38- ] ;
18+ const ASCII_ART = `
19+ _ _ ____ _ ___
20+ / \\ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
21+ / △ \\ | '_ \\ / _\` | | | | |/ _\` | '__| | | | | | |
22+ / ___ \\| | | | (_| | |_| | | (_| | | | |___| |___ | |
23+ /_/ \\_\\_| |_|\\__, |\\__,_|_|\\__,_|_| \\____|_____|___|
24+ |___/
25+ `
26+ . split ( '\n' )
27+ . map ( ( x ) => colors . red ( x ) )
28+ . join ( '\n' ) ;
3929
30+ /**
31+ * The command-line module for the `ng version` command.
32+ */
4033export default class VersionCommandModule
4134 extends CommandModule
4235 implements CommandModuleImplementation
@@ -46,146 +39,108 @@ export default class VersionCommandModule
4639 describe = 'Outputs Angular CLI version.' ;
4740 longDescriptionPath ?: string | undefined ;
4841
42+ /**
43+ * Builds the command-line options for the `ng version` command.
44+ * @param localYargs The `yargs` instance to configure.
45+ * @returns The configured `yargs` instance.
46+ */
4947 builder ( localYargs : Argv ) : Argv {
5048 return localYargs ;
5149 }
5250
51+ /**
52+ * The main execution logic for the `ng version` command.
53+ */
5354 async run ( ) : Promise < void > {
54- const { packageManager, logger, root } = this . context ;
55- const localRequire = nodeModule . createRequire ( resolve ( __filename , '../../../' ) ) ;
56- // Trailing slash is used to allow the path to be treated as a directory
57- const workspaceRequire = nodeModule . createRequire ( root + '/' ) ;
58-
59- const cliPackage : PartialPackageInfo = localRequire ( './package.json' ) ;
60- let workspacePackage : PartialPackageInfo | undefined ;
61- try {
62- workspacePackage = workspaceRequire ( './package.json' ) ;
63- } catch { }
64-
65- const [ nodeMajor ] = process . versions . node . split ( '.' ) . map ( ( part ) => Number ( part ) ) ;
66- const unsupportedNodeVersion = ! SUPPORTED_NODE_MAJORS . includes ( nodeMajor ) ;
67-
68- const packageNames = new Set (
69- Object . keys ( {
70- ...cliPackage . dependencies ,
71- ...cliPackage . devDependencies ,
72- ...workspacePackage ?. dependencies ,
73- ...workspacePackage ?. devDependencies ,
74- } ) ,
75- ) ;
76-
77- const versions : Record < string , string > = { } ;
78- for ( const name of packageNames ) {
79- if ( PACKAGE_PATTERNS . some ( ( p ) => p . test ( name ) ) ) {
80- versions [ name ] = this . getVersion ( name , workspaceRequire , localRequire ) ;
81- }
82- }
55+ const { logger } = this . context ;
56+ const versionInfo = gatherVersionInfo ( this . context ) ;
57+ const {
58+ ngCliVersion,
59+ nodeVersion,
60+ unsupportedNodeVersion,
61+ packageManagerName,
62+ packageManagerVersion,
63+ os,
64+ arch,
65+ versions,
66+ } = versionInfo ;
67+
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, '' ) ;
8374
84- const ngCliVersion = cliPackage . version ;
85- let angularCoreVersion = '' ;
86- const angularSameAsCore : string [ ] = [ ] ;
87-
88- if ( workspacePackage ) {
89- // Filter all angular versions that are the same as core.
90- angularCoreVersion = versions [ '@angular/core' ] ;
91- if ( angularCoreVersion ) {
92- for ( const [ name , version ] of Object . entries ( versions ) ) {
93- if ( version === angularCoreVersion && name . startsWith ( '@angular/' ) ) {
94- angularSameAsCore . push ( name . replace ( / ^ @ a n g u l a r \/ / , '' ) ) ;
95- delete versions [ name ] ;
96- }
97- }
75+ const angularPackages = this . formatAngularPackages ( versionInfo ) ;
76+ const packageTable = this . formatPackageTable ( versions ) ;
9877
99- // Make sure we list them in alphabetical order.
100- angularSameAsCore . sort ( ) ;
101- }
102- }
103-
104- const namePad = ' ' . repeat (
105- Object . keys ( versions ) . sort ( ( a , b ) => b . length - a . length ) [ 0 ] . length + 3 ,
106- ) ;
107- const asciiArt = `
108- _ _ ____ _ ___
109- / \\ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
110- / △ \\ | '_ \\ / _\` | | | | |/ _\` | '__| | | | | | |
111- / ___ \\| | | | (_| | |_| | | (_| | | | |___| |___ | |
112- /_/ \\_\\_| |_|\\__, |\\__,_|_|\\__,_|_| \\____|_____|___|
113- |___/
114- `
115- . split ( '\n' )
116- . map ( ( x ) => colors . red ( x ) )
117- . join ( '\n' ) ;
118-
119- logger . info ( asciiArt ) ;
120- logger . info (
121- `
122- Angular CLI: ${ ngCliVersion }
123- Node: ${ process . versions . node } ${ unsupportedNodeVersion ? ' (Unsupported)' : '' }
124- Package Manager: ${ packageManager . name } ${ packageManager . version ?? '<error>' }
125- OS: ${ process . platform } ${ process . arch }
126-
127- Angular: ${ angularCoreVersion }
128- ... ${ angularSameAsCore
129- . reduce < string [ ] > ( ( acc , name ) => {
130- // Perform a simple word wrap around 60.
131- if ( acc . length == 0 ) {
132- return [ name ] ;
133- }
134- const line = acc [ acc . length - 1 ] + ', ' + name ;
135- if ( line . length > 60 ) {
136- acc . push ( name ) ;
137- } else {
138- acc [ acc . length - 1 ] = line ;
139- }
140-
141- return acc ;
142- } , [ ] )
143- . join ( '\n... ' ) }
144-
145- Package${ namePad . slice ( 7 ) } Version
146- -------${ namePad . replace ( / / g, '-' ) } ------------------
147- ${ Object . keys ( versions )
148- . map ( ( module ) => `${ module } ${ namePad . slice ( module . length ) } ${ versions [ module ] } ` )
149- . sort ( )
150- . join ( '\n' ) }
151- ` . replace ( / ^ { 6 } / gm, '' ) ,
152- ) ;
78+ logger . info ( [ ASCII_ART , header , angularPackages , packageTable ] . join ( '\n\n' ) ) ;
15379
15480 if ( unsupportedNodeVersion ) {
15581 logger . warn (
156- `Warning: The current version of Node (${ process . versions . node } ) is not supported by Angular.` ,
82+ `Warning: The current version of Node (${ nodeVersion } ) is not supported by Angular.` ,
15783 ) ;
15884 }
15985 }
16086
161- private getVersion (
162- moduleName : string ,
163- workspaceRequire : NodeRequire ,
164- localRequire : NodeRequire ,
165- ) : string {
166- let packageInfo : PartialPackageInfo | undefined ;
167- let cliOnly = false ;
168-
169- // Try to find the package in the workspace
170- try {
171- packageInfo = workspaceRequire ( `${ moduleName } /package.json` ) ;
172- } catch { }
173-
174- // If not found, try to find within the CLI
175- if ( ! packageInfo ) {
176- try {
177- packageInfo = localRequire ( `${ moduleName } /package.json` ) ;
178- cliOnly = true ;
179- } catch { }
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>' ;
18096 }
18197
182- // If found, attempt to get the version
183- if ( packageInfo ) {
184- try {
185- return packageInfo . version + ( cliOnly ? ' (cli-only)' : '' ) ;
186- } catch { }
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+
117+ /**
118+ * Formats the package table section of the version output.
119+ * @param versions A map of package names to their versions.
120+ * @returns A string containing the formatted package table.
121+ */
122+ private formatPackageTable ( versions : Record < string , string > ) : string {
123+ const versionKeys = Object . keys ( versions ) ;
124+ if ( versionKeys . length === 0 ) {
125+ return '' ;
187126 }
188127
189- return '<error>' ;
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 ) ;
131+
132+ const tableHeader = `${ header } ${ namePad } Version` ;
133+ const separator = '-' . repeat ( tableHeader . length ) ;
134+
135+ const tableRows = versionKeys
136+ . map ( ( module ) => {
137+ const padding = ' ' . repeat ( maxNameLength - module . length + 3 ) ;
138+
139+ return `${ module } ${ padding } ${ versions [ module ] } ` ;
140+ } )
141+ . sort ( )
142+ . join ( '\n' ) ;
143+
144+ return `${ tableHeader } \n${ separator } \n${ tableRows } ` ;
190145 }
191146}
0 commit comments