6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
9
- import { logging } from '@angular-devkit/core' ;
10
- import { spawnSync } from 'child_process' ;
9
+ import { spawn , spawnSync } from 'child_process' ;
11
10
import { existsSync , mkdtempSync , readFileSync , realpathSync , writeFileSync } from 'fs' ;
12
11
import { tmpdir } from 'os' ;
13
12
import { join , resolve } from 'path' ;
14
13
import * as rimraf from 'rimraf' ;
15
14
import { PackageManager } from '../lib/config/workspace-schema' ;
16
- import { colors } from '../utilities/color' ;
17
15
import { NgAddSaveDepedency } from '../utilities/package-metadata' ;
16
+ import { Spinner } from './spinner' ;
18
17
19
18
interface PackageManagerOptions {
20
19
silent : string ;
@@ -24,14 +23,13 @@ interface PackageManagerOptions {
24
23
noLockfile : string ;
25
24
}
26
25
27
- export function installPackage (
26
+ export async function installPackage (
28
27
packageName : string ,
29
- logger : logging . Logger | undefined ,
30
28
packageManager : PackageManager = PackageManager . Npm ,
31
29
save : Exclude < NgAddSaveDepedency , false > = true ,
32
30
extraArgs : string [ ] = [ ] ,
33
31
cwd = process . cwd ( ) ,
34
- ) {
32
+ ) : Promise < 1 | 0 > {
35
33
const packageManagerArgs = getPackageManagerArguments ( packageManager ) ;
36
34
37
35
const installArgs : string [ ] = [
@@ -40,40 +38,48 @@ export function installPackage(
40
38
packageManagerArgs . silent ,
41
39
] ;
42
40
43
- logger ?. info ( colors . green ( `Installing packages for tooling via ${ packageManager } .` ) ) ;
41
+ const spinner = new Spinner ( ) ;
42
+ spinner . start ( 'Installing package...' ) ;
44
43
45
44
if ( save === 'devDependencies' ) {
46
45
installArgs . push ( packageManagerArgs . saveDev ) ;
47
46
}
47
+ const bufferedOutput : { stream : NodeJS . WriteStream ; data : Buffer } [ ] = [ ] ;
48
48
49
- const { status, stderr, stdout, error } = spawnSync (
50
- packageManager ,
51
- [ ...installArgs , ...extraArgs ] ,
52
- {
49
+ return new Promise ( ( resolve , reject ) => {
50
+ const childProcess = spawn ( packageManager , [ ...installArgs , ...extraArgs ] , {
53
51
stdio : 'pipe' ,
54
52
shell : true ,
55
- encoding : 'utf8' ,
56
53
cwd,
57
- } ,
58
- ) ;
59
-
60
- if ( status !== 0 ) {
61
- let errorMessage = ( ( error && error . message ) || stderr || stdout || '' ) . trim ( ) ;
62
- if ( errorMessage ) {
63
- errorMessage += '\n' ;
64
- }
65
- throw new Error ( errorMessage + `Package install failed${ errorMessage ? ', see above' : '' } .` ) ;
66
- }
67
-
68
- logger ?. info ( colors . green ( `Installed packages for tooling via ${ packageManager } .` ) ) ;
54
+ } ) . on ( 'close' , ( code : number ) => {
55
+ if ( code === 0 ) {
56
+ spinner . succeed ( 'Package successfully installed.' ) ;
57
+ resolve ( 0 ) ;
58
+ } else {
59
+ spinner . stop ( ) ;
60
+ bufferedOutput . forEach ( ( { stream, data } ) => stream . write ( data ) ) ;
61
+ spinner . fail ( 'Package install failed, see above.' ) ;
62
+ reject ( 1 ) ;
63
+ }
64
+ } ) ;
65
+
66
+ childProcess . stdout ?. on ( 'data' , ( data : Buffer ) =>
67
+ bufferedOutput . push ( { stream : process . stdout , data : data } ) ,
68
+ ) ;
69
+ childProcess . stderr ?. on ( 'data' , ( data : Buffer ) =>
70
+ bufferedOutput . push ( { stream : process . stderr , data : data } ) ,
71
+ ) ;
72
+ } ) ;
69
73
}
70
74
71
- export function installTempPackage (
75
+ export async function installTempPackage (
72
76
packageName : string ,
73
- logger : logging . Logger | undefined ,
74
77
packageManager : PackageManager = PackageManager . Npm ,
75
78
extraArgs ?: string [ ] ,
76
- ) : string {
79
+ ) : Promise < {
80
+ status : 1 | 0 ;
81
+ tempPath : string ;
82
+ } > {
77
83
const tempPath = mkdtempSync ( join ( realpathSync ( tmpdir ( ) ) , 'angular-cli-packages-' ) ) ;
78
84
79
85
// clean up temp directory on process exit
@@ -113,23 +119,26 @@ export function installTempPackage(
113
119
packageManagerArgs . noLockfile ,
114
120
] ;
115
121
116
- installPackage ( packageName , logger , packageManager , true , installArgs , tempPath ) ;
117
-
118
- return tempNodeModules ;
122
+ return {
123
+ status : await installPackage ( packageName , packageManager , true , installArgs , tempPath ) ,
124
+ tempPath,
125
+ } ;
119
126
}
120
127
121
- export function runTempPackageBin (
128
+ export async function runTempPackageBin (
122
129
packageName : string ,
123
- logger : logging . Logger ,
124
130
packageManager : PackageManager = PackageManager . Npm ,
125
131
args : string [ ] = [ ] ,
126
- ) : number {
127
- const tempNodeModulesPath = installTempPackage ( packageName , logger , packageManager ) ;
132
+ ) : Promise < number > {
133
+ const { status : code , tempPath } = await installTempPackage ( packageName , packageManager ) ;
134
+ if ( code !== 0 ) {
135
+ return code ;
136
+ }
128
137
129
138
// Remove version/tag etc... from package name
130
139
// Ex: @angular /cli@latest -> @angular/cli
131
140
const packageNameNoVersion = packageName . substring ( 0 , packageName . lastIndexOf ( '@' ) ) ;
132
- const pkgLocation = join ( tempNodeModulesPath , packageNameNoVersion ) ;
141
+ const pkgLocation = join ( tempPath , packageNameNoVersion ) ;
133
142
const packageJsonPath = join ( pkgLocation , 'package.json' ) ;
134
143
135
144
// Get a binary location for this package
0 commit comments