10
10
// Requirements
11
11
// ------------------------------------------------------------------------------
12
12
13
+ const fs = require ( 'fs' )
13
14
const path = require ( 'path' )
14
15
const parseArgs = require ( 'shell-quote' ) . parse
15
16
const createHeader = require ( './create-header' )
@@ -128,65 +129,96 @@ function cleanTaskArg (arg) {
128
129
* An array of options which are inserted before the task name.
129
130
* @param {object } options.labelState - A state object for printing labels.
130
131
* @param {boolean } options.printName - The flag to print task names before running each task.
132
+ * @param {object } options.packageInfo - A package.json's information.
133
+ * @param {object } options.packageInfo.body - A package.json's JSON object.
134
+ * @param {string } options.packageInfo.path - A package.json's file path.
131
135
* @returns {Promise }
132
136
* A promise object which becomes fullfilled when the npm-script is completed.
133
137
* This promise object has an extra method: `abort()`.
134
138
* @private
135
139
*/
136
140
module . exports = function runTask ( task , options ) {
137
141
let cp = null
138
- const promise = new Promise ( ( resolve , reject ) => {
139
- ansiStylesPromise . then ( ( { default : ansiStyles } ) => {
140
- const stdin = options . stdin
141
- const stdout = wrapLabeling ( task , options . stdout , options . labelState , ansiStyles )
142
- const stderr = wrapLabeling ( task , options . stderr , options . labelState , ansiStyles )
143
- const stdinKind = detectStreamKind ( stdin , process . stdin )
144
- const stdoutKind = detectStreamKind ( stdout , process . stdout )
145
- const stderrKind = detectStreamKind ( stderr , process . stderr )
146
- const spawnOptions = { stdio : [ stdinKind , stdoutKind , stderrKind ] }
147
-
148
- // Print task name.
149
- if ( options . printName && stdout != null ) {
150
- stdout . write ( createHeader (
151
- task ,
152
- options . packageInfo ,
153
- options . stdout . isTTY ,
154
- ansiStyles
155
- ) )
142
+
143
+ async function asyncRunTask ( ) {
144
+ const { default : ansiStyles } = await ansiStylesPromise
145
+
146
+ const stdin = options . stdin
147
+ const stdout = wrapLabeling ( task , options . stdout , options . labelState , ansiStyles )
148
+ const stderr = wrapLabeling ( task , options . stderr , options . labelState , ansiStyles )
149
+ const stdinKind = detectStreamKind ( stdin , process . stdin )
150
+ const stdoutKind = detectStreamKind ( stdout , process . stdout )
151
+ const stderrKind = detectStreamKind ( stderr , process . stderr )
152
+ const spawnOptions = { stdio : [ stdinKind , stdoutKind , stderrKind ] }
153
+
154
+ // Print task name.
155
+ if ( options . printName && stdout != null ) {
156
+ stdout . write ( createHeader (
157
+ task ,
158
+ options . packageInfo ,
159
+ options . stdout . isTTY ,
160
+ ansiStyles
161
+ ) )
162
+ }
163
+
164
+ // Execute.
165
+ let npmPath = options . npmPath
166
+ if ( ! npmPath && process . env . npm_execpath ) {
167
+ const basename = path . basename ( process . env . npm_execpath )
168
+ let newBasename = basename
169
+ if ( basename . startsWith ( 'npx' ) ) {
170
+ newBasename = basename . replace ( 'npx' , 'npm' ) // eslint-disable-line no-process-env
171
+ } else if ( basename . startsWith ( 'pnpx' ) ) {
172
+ newBasename = basename . replace ( 'pnpx' , 'pnpm' ) // eslint-disable-line no-process-env
156
173
}
157
174
158
- // Execute.
159
- const npmPath = options . npmPath || path . basename ( process . env . npm_execpath ) . startsWith ( 'npx' ) // eslint-disable-line no-process-env
160
- ? path . join ( path . dirname ( process . env . npm_execpath ) , path . basename ( process . env . npm_execpath ) . replace ( 'npx' , 'npm' ) ) // eslint-disable-line no-process-env
175
+ npmPath = newBasename === basename
176
+ ? path . join ( path . dirname ( process . env . npm_execpath ) , newBasename )
161
177
: process . env . npm_execpath // eslint-disable-line no-process-env
162
- const npmPathIsJs = typeof npmPath === 'string' && / \. m ? j s / . test ( path . extname ( npmPath ) )
163
- const execPath = ( npmPathIsJs ? process . execPath : npmPath || 'npm' )
164
- const isYarn = process . env . npm_config_user_agent && process . env . npm_config_user_agent . startsWith ( 'yarn' ) // eslint-disable-line no-process-env
165
- const spawnArgs = [ 'run' ]
178
+ }
166
179
167
- if ( npmPathIsJs ) {
168
- spawnArgs . unshift ( npmPath )
169
- }
170
- if ( ! isYarn ) {
171
- Array . prototype . push . apply ( spawnArgs , options . prefixOptions )
172
- } else if ( options . prefixOptions . indexOf ( '--silent' ) !== - 1 ) {
173
- spawnArgs . push ( '--silent' )
180
+ const npmPathIsJs = typeof npmPath === 'string' && / \. ( c | m ) ? j s / . test ( path . extname ( npmPath ) )
181
+ let execPath = ( npmPathIsJs ? process . execPath : npmPath || 'npm' )
182
+
183
+ if ( ! npmPath && ! process . env . npm_execpath ) {
184
+ // When a script is being run via pnpm, npmPath and npm_execpath will be null or undefined
185
+ // Attempt to figure out whether we're running via pnpm
186
+ const projectRoot = path . dirname ( options . packageInfo . path )
187
+ const hasPnpmLockfile = fs . existsSync ( path . join ( projectRoot , 'pnpm-lock.yaml' ) )
188
+ const { status : pnpmFound , output : pnpmWhichOutput } = await spawn ( 'which' , 'pnpm' , { silent : true } )
189
+ if ( hasPnpmLockfile && __dirname . split ( path . delimiter ) . includes ( '.pnpm' ) && pnpmFound ) {
190
+ execPath = pnpmWhichOutput
174
191
}
175
- Array . prototype . push . apply ( spawnArgs , parseArgs ( task ) . map ( cleanTaskArg ) )
192
+ }
176
193
177
- cp = spawn ( execPath , spawnArgs , spawnOptions )
194
+ const isYarn = process . env . npm_config_user_agent && process . env . npm_config_user_agent . startsWith ( 'yarn' ) // eslint-disable-line no-process-env
178
195
179
- // Piping stdio.
180
- if ( stdinKind === 'pipe' ) {
181
- stdin . pipe ( cp . stdin )
182
- }
183
- if ( stdoutKind === 'pipe' ) {
184
- cp . stdout . pipe ( stdout , { end : false } )
185
- }
186
- if ( stderrKind === 'pipe' ) {
187
- cp . stderr . pipe ( stderr , { end : false } )
188
- }
196
+ const spawnArgs = [ 'run' ]
189
197
198
+ if ( npmPathIsJs ) {
199
+ spawnArgs . unshift ( npmPath )
200
+ }
201
+ if ( ! isYarn ) {
202
+ Array . prototype . push . apply ( spawnArgs , options . prefixOptions )
203
+ } else if ( options . prefixOptions . indexOf ( '--silent' ) !== - 1 ) {
204
+ spawnArgs . push ( '--silent' )
205
+ }
206
+ Array . prototype . push . apply ( spawnArgs , parseArgs ( task ) . map ( cleanTaskArg ) )
207
+
208
+ cp = spawn ( execPath , spawnArgs , spawnOptions )
209
+
210
+ // Piping stdio.
211
+ if ( stdinKind === 'pipe' ) {
212
+ stdin . pipe ( cp . stdin )
213
+ }
214
+ if ( stdoutKind === 'pipe' ) {
215
+ cp . stdout . pipe ( stdout , { end : false } )
216
+ }
217
+ if ( stderrKind === 'pipe' ) {
218
+ cp . stderr . pipe ( stderr , { end : false } )
219
+ }
220
+
221
+ return new Promise ( ( resolve , reject ) => {
190
222
// Register
191
223
cp . on ( 'error' , ( err ) => {
192
224
cp = null
@@ -197,7 +229,9 @@ module.exports = function runTask (task, options) {
197
229
resolve ( { task, code, signal } )
198
230
} )
199
231
} )
200
- } )
232
+ }
233
+
234
+ const promise = asyncRunTask ( )
201
235
202
236
promise . abort = function abort ( ) {
203
237
if ( cp != null ) {
0 commit comments