From 3df37084ab1ae55f873fcbb449ad0d7df8bc328f Mon Sep 17 00:00:00 2001 From: Kinland <16787581+kinland@users.noreply.github.com> Date: Thu, 28 Sep 2023 22:08:09 -0700 Subject: [PATCH 1/2] Add support for pnpm (#117) --- lib/run-task.js | 39 ++++++++++++++++++++++++++++++++++----- lib/spawn-posix.js | 18 +++++++++++++++++- lib/spawn-win32.js | 18 +++++++++++++++++- 3 files changed, 68 insertions(+), 7 deletions(-) diff --git a/lib/run-task.js b/lib/run-task.js index 5a12931..7916f61 100644 --- a/lib/run-task.js +++ b/lib/run-task.js @@ -10,6 +10,7 @@ // Requirements // ------------------------------------------------------------------------------ +const fs = require('fs') const path = require('path') const parseArgs = require('shell-quote').parse const createHeader = require('./create-header') @@ -128,6 +129,9 @@ function cleanTaskArg (arg) { * An array of options which are inserted before the task name. * @param {object} options.labelState - A state object for printing labels. * @param {boolean} options.printName - The flag to print task names before running each task. + * @param {object} options.packageInfo - A package.json's information. + * @param {object} options.packageInfo.body - A package.json's JSON object. + * @param {string} options.packageInfo.path - A package.json's file path. * @returns {Promise} * A promise object which becomes fullfilled when the npm-script is completed. * This promise object has an extra method: `abort()`. @@ -156,12 +160,37 @@ module.exports = function runTask (task, options) { } // Execute. - const npmPath = options.npmPath || path.basename(process.env.npm_execpath).startsWith('npx') // eslint-disable-line no-process-env - ? path.join(path.dirname(process.env.npm_execpath), path.basename(process.env.npm_execpath).replace('npx', 'npm')) // eslint-disable-line no-process-env - : process.env.npm_execpath // eslint-disable-line no-process-env - const npmPathIsJs = typeof npmPath === 'string' && /\.m?js/.test(path.extname(npmPath)) - const execPath = (npmPathIsJs ? process.execPath : npmPath || 'npm') + let npmPath = options.npmPath + if (!npmPath && process.env.npm_execpath) { + const basename = path.basename(process.env.npm_execpath) + let newBasename = basename + if (basename.startsWith('npx')) { + newBasename = basename.replace('npx', 'npm') // eslint-disable-line no-process-env + } else if (basename.startsWith('pnpx')) { + newBasename = basename.replace('pnpx', 'pnpm') // eslint-disable-line no-process-env + } + + npmPath = newBasename === basename + ? path.join(path.dirname(process.env.npm_execpath), newBasename) + : process.env.npm_execpath // eslint-disable-line no-process-env + } + + const npmPathIsJs = typeof npmPath === 'string' && /\.(c|m)?js/.test(path.extname(npmPath)) + let execPath = (npmPathIsJs ? process.execPath : npmPath || 'npm') + + if (!npmPath && !process.env.npm_execpath) { + // When a script is being run via pnpm, npmPath and npm_execpath will be null or undefined + // Attempt to figure out whether we're running via pnpm + const projectRoot = path.dirname(options.packageInfo.path) + const hasPnpmLockfile = fs.existsSync(path.join(projectRoot, 'pnpm-lock.yaml')) + const { status: pnpmFound, output } = spawn.sync('which', 'pnpm', { silent: true }) + if (hasPnpmLockfile && __dirname.split(path.delimiter).includes('.pnpm') && pnpmFound) { + execPath = output + } + } + const isYarn = process.env.npm_config_user_agent && process.env.npm_config_user_agent.startsWith('yarn') // eslint-disable-line no-process-env + const spawnArgs = ['run'] if (npmPathIsJs) { diff --git a/lib/spawn-posix.js b/lib/spawn-posix.js index 6d63549..ca5a851 100644 --- a/lib/spawn-posix.js +++ b/lib/spawn-posix.js @@ -55,9 +55,25 @@ function kill () { * @returns {ChildProcess} A ChildProcess instance of new process. * @private */ -module.exports = function spawn (command, args, options) { +function spawn (command, args, options) { const child = crossSpawn(command, args, options) child.kill = kill return child } + +/** + * Launches a new process synchronously with the given command. + * This is almost same as `child_process.spawnSync`. + * + * This returns a `SpawnSyncReturns` object. + * + * @param {string} command - The command to run. + * @param {string[]} args - List of string arguments. + * @param {object} options - Options. + * @returns {SpawnSyncReturns} A ChildProcess instance of new process. + * @private + */ +spawn.sync = crossSpawn.sync + +module.exports = spawn diff --git a/lib/spawn-win32.js b/lib/spawn-win32.js index 1198d98..4cc26b5 100644 --- a/lib/spawn-win32.js +++ b/lib/spawn-win32.js @@ -42,9 +42,25 @@ function kill () { * @returns {ChildProcess} A ChildProcess instance of new process. * @private */ -module.exports = function spawn (command, args, options) { +function spawn (command, args, options) { const child = crossSpawn(command, args, options) child.kill = kill return child } + +/** + * Launches a new process synchronously with the given command. + * This is almost same as `child_process.spawnSync`. + * + * This returns a `SpawnSyncReturns` object. + * + * @param {string} command - The command to run. + * @param {string[]} args - List of string arguments. + * @param {object} options - Options. + * @returns {SpawnSyncReturns} A ChildProcess instance of new process. + * @private + */ +spawn.sync = crossSpawn.sync + +module.exports = spawn From a3ee6cd9e051471bfd7b1b4d153aa260fc9b6634 Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Tue, 3 Oct 2023 22:02:51 -0700 Subject: [PATCH 2/2] Lets avoid spawn.sync --- lib/run-task.js | 141 +++++++++++++++++++++++---------------------- lib/spawn-posix.js | 18 +----- lib/spawn-win32.js | 18 +----- 3 files changed, 75 insertions(+), 102 deletions(-) diff --git a/lib/run-task.js b/lib/run-task.js index 7916f61..512ea18 100644 --- a/lib/run-task.js +++ b/lib/run-task.js @@ -139,83 +139,86 @@ function cleanTaskArg (arg) { */ module.exports = function runTask (task, options) { let cp = null - const promise = new Promise((resolve, reject) => { - ansiStylesPromise.then(({ default: ansiStyles }) => { - const stdin = options.stdin - const stdout = wrapLabeling(task, options.stdout, options.labelState, ansiStyles) - const stderr = wrapLabeling(task, options.stderr, options.labelState, ansiStyles) - const stdinKind = detectStreamKind(stdin, process.stdin) - const stdoutKind = detectStreamKind(stdout, process.stdout) - const stderrKind = detectStreamKind(stderr, process.stderr) - const spawnOptions = { stdio: [stdinKind, stdoutKind, stderrKind] } - - // Print task name. - if (options.printName && stdout != null) { - stdout.write(createHeader( - task, - options.packageInfo, - options.stdout.isTTY, - ansiStyles - )) - } - // Execute. - let npmPath = options.npmPath - if (!npmPath && process.env.npm_execpath) { - const basename = path.basename(process.env.npm_execpath) - let newBasename = basename - if (basename.startsWith('npx')) { - newBasename = basename.replace('npx', 'npm') // eslint-disable-line no-process-env - } else if (basename.startsWith('pnpx')) { - newBasename = basename.replace('pnpx', 'pnpm') // eslint-disable-line no-process-env - } - - npmPath = newBasename === basename - ? path.join(path.dirname(process.env.npm_execpath), newBasename) - : process.env.npm_execpath // eslint-disable-line no-process-env + async function asyncRunTask () { + const { default: ansiStyles } = await ansiStylesPromise + + const stdin = options.stdin + const stdout = wrapLabeling(task, options.stdout, options.labelState, ansiStyles) + const stderr = wrapLabeling(task, options.stderr, options.labelState, ansiStyles) + const stdinKind = detectStreamKind(stdin, process.stdin) + const stdoutKind = detectStreamKind(stdout, process.stdout) + const stderrKind = detectStreamKind(stderr, process.stderr) + const spawnOptions = { stdio: [stdinKind, stdoutKind, stderrKind] } + + // Print task name. + if (options.printName && stdout != null) { + stdout.write(createHeader( + task, + options.packageInfo, + options.stdout.isTTY, + ansiStyles + )) + } + + // Execute. + let npmPath = options.npmPath + if (!npmPath && process.env.npm_execpath) { + const basename = path.basename(process.env.npm_execpath) + let newBasename = basename + if (basename.startsWith('npx')) { + newBasename = basename.replace('npx', 'npm') // eslint-disable-line no-process-env + } else if (basename.startsWith('pnpx')) { + newBasename = basename.replace('pnpx', 'pnpm') // eslint-disable-line no-process-env } - const npmPathIsJs = typeof npmPath === 'string' && /\.(c|m)?js/.test(path.extname(npmPath)) - let execPath = (npmPathIsJs ? process.execPath : npmPath || 'npm') - - if (!npmPath && !process.env.npm_execpath) { - // When a script is being run via pnpm, npmPath and npm_execpath will be null or undefined - // Attempt to figure out whether we're running via pnpm - const projectRoot = path.dirname(options.packageInfo.path) - const hasPnpmLockfile = fs.existsSync(path.join(projectRoot, 'pnpm-lock.yaml')) - const { status: pnpmFound, output } = spawn.sync('which', 'pnpm', { silent: true }) - if (hasPnpmLockfile && __dirname.split(path.delimiter).includes('.pnpm') && pnpmFound) { - execPath = output - } + npmPath = newBasename === basename + ? path.join(path.dirname(process.env.npm_execpath), newBasename) + : process.env.npm_execpath // eslint-disable-line no-process-env + } + + const npmPathIsJs = typeof npmPath === 'string' && /\.(c|m)?js/.test(path.extname(npmPath)) + let execPath = (npmPathIsJs ? process.execPath : npmPath || 'npm') + + if (!npmPath && !process.env.npm_execpath) { + // When a script is being run via pnpm, npmPath and npm_execpath will be null or undefined + // Attempt to figure out whether we're running via pnpm + const projectRoot = path.dirname(options.packageInfo.path) + const hasPnpmLockfile = fs.existsSync(path.join(projectRoot, 'pnpm-lock.yaml')) + const { status: pnpmFound, output: pnpmWhichOutput } = await spawn('which', 'pnpm', { silent: true }) + if (hasPnpmLockfile && __dirname.split(path.delimiter).includes('.pnpm') && pnpmFound) { + execPath = pnpmWhichOutput } + } - const isYarn = process.env.npm_config_user_agent && process.env.npm_config_user_agent.startsWith('yarn') // eslint-disable-line no-process-env + const isYarn = process.env.npm_config_user_agent && process.env.npm_config_user_agent.startsWith('yarn') // eslint-disable-line no-process-env - const spawnArgs = ['run'] + const spawnArgs = ['run'] - if (npmPathIsJs) { - spawnArgs.unshift(npmPath) - } - if (!isYarn) { - Array.prototype.push.apply(spawnArgs, options.prefixOptions) - } else if (options.prefixOptions.indexOf('--silent') !== -1) { - spawnArgs.push('--silent') - } - Array.prototype.push.apply(spawnArgs, parseArgs(task).map(cleanTaskArg)) + if (npmPathIsJs) { + spawnArgs.unshift(npmPath) + } + if (!isYarn) { + Array.prototype.push.apply(spawnArgs, options.prefixOptions) + } else if (options.prefixOptions.indexOf('--silent') !== -1) { + spawnArgs.push('--silent') + } + Array.prototype.push.apply(spawnArgs, parseArgs(task).map(cleanTaskArg)) - cp = spawn(execPath, spawnArgs, spawnOptions) + cp = spawn(execPath, spawnArgs, spawnOptions) - // Piping stdio. - if (stdinKind === 'pipe') { - stdin.pipe(cp.stdin) - } - if (stdoutKind === 'pipe') { - cp.stdout.pipe(stdout, { end: false }) - } - if (stderrKind === 'pipe') { - cp.stderr.pipe(stderr, { end: false }) - } + // Piping stdio. + if (stdinKind === 'pipe') { + stdin.pipe(cp.stdin) + } + if (stdoutKind === 'pipe') { + cp.stdout.pipe(stdout, { end: false }) + } + if (stderrKind === 'pipe') { + cp.stderr.pipe(stderr, { end: false }) + } + return new Promise((resolve, reject) => { // Register cp.on('error', (err) => { cp = null @@ -226,7 +229,9 @@ module.exports = function runTask (task, options) { resolve({ task, code, signal }) }) }) - }) + } + + const promise = asyncRunTask() promise.abort = function abort () { if (cp != null) { diff --git a/lib/spawn-posix.js b/lib/spawn-posix.js index ca5a851..6d63549 100644 --- a/lib/spawn-posix.js +++ b/lib/spawn-posix.js @@ -55,25 +55,9 @@ function kill () { * @returns {ChildProcess} A ChildProcess instance of new process. * @private */ -function spawn (command, args, options) { +module.exports = function spawn (command, args, options) { const child = crossSpawn(command, args, options) child.kill = kill return child } - -/** - * Launches a new process synchronously with the given command. - * This is almost same as `child_process.spawnSync`. - * - * This returns a `SpawnSyncReturns` object. - * - * @param {string} command - The command to run. - * @param {string[]} args - List of string arguments. - * @param {object} options - Options. - * @returns {SpawnSyncReturns} A ChildProcess instance of new process. - * @private - */ -spawn.sync = crossSpawn.sync - -module.exports = spawn diff --git a/lib/spawn-win32.js b/lib/spawn-win32.js index 4cc26b5..1198d98 100644 --- a/lib/spawn-win32.js +++ b/lib/spawn-win32.js @@ -42,25 +42,9 @@ function kill () { * @returns {ChildProcess} A ChildProcess instance of new process. * @private */ -function spawn (command, args, options) { +module.exports = function spawn (command, args, options) { const child = crossSpawn(command, args, options) child.kill = kill return child } - -/** - * Launches a new process synchronously with the given command. - * This is almost same as `child_process.spawnSync`. - * - * This returns a `SpawnSyncReturns` object. - * - * @param {string} command - The command to run. - * @param {string[]} args - List of string arguments. - * @param {object} options - Options. - * @returns {SpawnSyncReturns} A ChildProcess instance of new process. - * @private - */ -spawn.sync = crossSpawn.sync - -module.exports = spawn