1
- import { Errors , Interfaces , ux } from '@oclif/core'
1
+ import { Interfaces , ux } from '@oclif/core'
2
2
import makeDebug from 'debug'
3
- import { fork as cpFork } from 'node:child_process'
4
3
import { readFile } from 'node:fs/promises'
5
4
import { createRequire } from 'node:module'
6
5
import { join , sep } from 'node:path'
7
- import { npmRunPathEnv } from 'npm-run-path'
8
6
7
+ import { ExecOptions , Output , fork } from './fork.js'
9
8
import { LogLevel } from './log-level.js'
10
9
11
10
const debug = makeDebug ( '@oclif/plugin-plugins:npm' )
12
11
13
- type ExecOptions = {
14
- cwd : string
15
- logLevel : LogLevel
16
- }
17
-
18
12
type InstallOptions = ExecOptions & {
19
13
prod ?: boolean
20
14
}
21
15
22
- export type NpmOutput = {
23
- stderr : string [ ]
24
- stdout : string [ ]
25
- }
26
-
27
- async function fork ( modulePath : string , args : string [ ] = [ ] , { cwd, logLevel} : ExecOptions ) : Promise < NpmOutput > {
28
- return new Promise ( ( resolve , reject ) => {
29
- const forked = cpFork ( modulePath , args , {
30
- cwd,
31
- env : {
32
- ...npmRunPathEnv ( ) ,
33
- // Disable husky hooks because a plugin might be trying to install them, which will
34
- // break the install since the install location isn't a .git directory.
35
- HUSKY : '0' ,
36
- } ,
37
- execArgv : process . execArgv
38
- . join ( ' ' )
39
- // Remove --loader ts-node/esm from execArgv so that the subprocess doesn't fail if it can't find ts-node.
40
- // The ts-node/esm loader isn't need to execute npm commands anyways.
41
- . replace ( '--loader ts-node/esm' , '' )
42
- . replace ( '--loader=ts-node/esm' , '' )
43
- . split ( ' ' )
44
- . filter ( Boolean ) ,
45
- stdio : [ 0 , null , null , 'ipc' ] ,
46
- } )
47
-
48
- const possibleLastLinesOfNpmInstall = [ 'up to date' , 'added' ]
49
- const stderr : string [ ] = [ ]
50
- const stdout : string [ ] = [ ]
51
- const loggedStderr : string [ ] = [ ]
52
- const loggedStdout : string [ ] = [ ]
53
-
54
- const shouldPrint = ( str : string ) : boolean => {
55
- // For ux cleanliness purposes, don't print the final line of npm install output if
56
- // the log level is 'notice' and there's no other output.
57
- const noOtherOutput = loggedStderr . length === 0 && loggedStdout . length === 0
58
- const isLastLine = possibleLastLinesOfNpmInstall . some ( ( line ) => str . startsWith ( line ) )
59
- if ( noOtherOutput && isLastLine && logLevel === 'notice' ) {
60
- return false
61
- }
62
-
63
- return logLevel !== 'silent'
64
- }
65
-
66
- forked . stderr ?. setEncoding ( 'utf8' )
67
- forked . stderr ?. on ( 'data' , ( d : Buffer ) => {
68
- const output = d . toString ( ) . trim ( )
69
- stderr . push ( output )
70
- if ( shouldPrint ( output ) ) {
71
- loggedStderr . push ( output )
72
- ux . log ( output )
73
- } else debug ( output )
74
- } )
75
-
76
- forked . stdout ?. setEncoding ( 'utf8' )
77
- forked . stdout ?. on ( 'data' , ( d : Buffer ) => {
78
- const output = d . toString ( ) . trim ( )
79
- stdout . push ( output )
80
- if ( shouldPrint ( output ) ) {
81
- loggedStdout . push ( output )
82
- ux . log ( output )
83
- } else debug ( output )
84
- } )
85
-
86
- forked . on ( 'error' , reject )
87
- forked . on ( 'exit' , ( code : number ) => {
88
- if ( code === 0 ) {
89
- resolve ( { stderr, stdout} )
90
- } else {
91
- reject (
92
- new Errors . CLIError ( `${ modulePath } ${ args . join ( ' ' ) } exited with code ${ code } ` , {
93
- suggestions : [ 'Run with DEBUG=@oclif/plugin-plugins* to see debug output.' ] ,
94
- } ) ,
95
- )
96
- }
97
- } )
98
- } )
99
- }
100
-
101
16
export class NPM {
102
17
private bin : string | undefined
103
18
private config : Interfaces . Config
@@ -108,7 +23,7 @@ export class NPM {
108
23
this . logLevel = logLevel
109
24
}
110
25
111
- async exec ( args : string [ ] = [ ] , options : ExecOptions ) : Promise < NpmOutput > {
26
+ async exec ( args : string [ ] = [ ] , options : ExecOptions ) : Promise < Output > {
112
27
const bin = await this . findNpm ( )
113
28
debug ( 'npm binary path' , bin )
114
29
@@ -130,20 +45,20 @@ export class NPM {
130
45
}
131
46
}
132
47
133
- async install ( args : string [ ] , opts : InstallOptions ) : Promise < NpmOutput > {
48
+ async install ( args : string [ ] , opts : InstallOptions ) : Promise < Output > {
134
49
const prod = opts . prod ? [ '--omit' , 'dev' ] : [ ]
135
50
return this . exec ( [ 'install' , ...args , ...prod , '--no-audit' ] , opts )
136
51
}
137
52
138
- async uninstall ( args : string [ ] , opts : ExecOptions ) : Promise < NpmOutput > {
53
+ async uninstall ( args : string [ ] , opts : ExecOptions ) : Promise < Output > {
139
54
return this . exec ( [ 'uninstall' , ...args ] , opts )
140
55
}
141
56
142
- async update ( args : string [ ] , opts : ExecOptions ) : Promise < NpmOutput > {
57
+ async update ( args : string [ ] , opts : ExecOptions ) : Promise < Output > {
143
58
return this . exec ( [ 'update' , ...args ] , opts )
144
59
}
145
60
146
- async view ( args : string [ ] , opts : ExecOptions ) : Promise < NpmOutput > {
61
+ async view ( args : string [ ] , opts : ExecOptions ) : Promise < Output > {
147
62
return this . exec ( [ 'view' , ...args ] , { ...opts , logLevel : 'silent' } )
148
63
}
149
64
0 commit comments