diff --git a/src/npm.ts b/src/npm.ts index 8b0459e8..7aaf77e3 100644 --- a/src/npm.ts +++ b/src/npm.ts @@ -4,8 +4,8 @@ import {readFile} from 'node:fs/promises' import {createRequire} from 'node:module' import {join, sep} from 'node:path' -import {ExecOptions, Output, fork} from './fork.js' import {LogLevel} from './log-level.js' +import {ExecOptions, Output, spawn} from './spawn.js' const debug = makeDebug('@oclif/plugin-plugins:npm') @@ -36,7 +36,7 @@ export class NPM { debug(`${options.cwd}: ${bin} ${args.join(' ')}`) try { - const output = await fork(bin, args, options) + const output = await spawn(bin, args, options) debug('npm done') return output } catch (error: unknown) { diff --git a/src/plugins.ts b/src/plugins.ts index 31699057..a4028d5c 100644 --- a/src/plugins.ts +++ b/src/plugins.ts @@ -7,9 +7,9 @@ import {basename, dirname, join, resolve} from 'node:path' import {fileURLToPath} from 'node:url' import {gt, valid, validRange} from 'semver' -import {Output} from './fork.js' import {LogLevel} from './log-level.js' import {NPM} from './npm.js' +import {Output} from './spawn.js' import {uniqWith} from './util.js' import {Yarn} from './yarn.js' diff --git a/src/fork.ts b/src/spawn.ts similarity index 66% rename from src/fork.ts rename to src/spawn.ts index 20e797d2..fe7f0012 100644 --- a/src/fork.ts +++ b/src/spawn.ts @@ -1,6 +1,6 @@ import {Errors, ux} from '@oclif/core' import makeDebug from 'debug' -import {fork as cpFork} from 'node:child_process' +import {spawn as cpSpawn} from 'node:child_process' import {npmRunPathEnv} from 'npm-run-path' import {LogLevel} from './log-level.js' @@ -15,11 +15,19 @@ export type Output = { stdout: string[] } -const debug = makeDebug('@oclif/plugin-plugins:fork') +const debug = makeDebug('@oclif/plugin-plugins:spawn') -export async function fork(modulePath: string, args: string[] = [], {cwd, logLevel}: ExecOptions): Promise { +export async function spawn(modulePath: string, args: string[] = [], {cwd, logLevel}: ExecOptions): Promise { return new Promise((resolve, reject) => { - const forked = cpFork(modulePath, args, { + // On windows, the global path to npm could be .cmd, .exe, or .js. If it's a .js file, we need to run it with node. + if (process.platform === 'win32' && modulePath.endsWith('.js')) { + args.unshift(`"${modulePath}"`) + modulePath = 'node' + } + + debug('modulePath', modulePath) + debug('args', args) + const spawned = cpSpawn(modulePath, args, { cwd, env: { ...npmRunPathEnv(), @@ -27,15 +35,9 @@ export async function fork(modulePath: string, args: string[] = [], {cwd, logLev // break the install since the install location isn't a .git directory. HUSKY: '0', }, - execArgv: process.execArgv - .join(' ') - // Remove --loader ts-node/esm from execArgv so that the subprocess doesn't fail if it can't find ts-node. - // The ts-node/esm loader isn't need to execute npm or yarn commands anyways. - .replace('--loader ts-node/esm', '') - .replace('--loader=ts-node/esm', '') - .split(' ') - .filter(Boolean), - stdio: [0, null, null, 'ipc'], + stdio: 'pipe', + windowsVerbatimArguments: true, + ...(process.platform === 'win32' && modulePath.toLowerCase().endsWith('.cmd') && {shell: true}), }) const possibleLastLinesOfNpmInstall = ['up to date', 'added'] @@ -56,8 +58,8 @@ export async function fork(modulePath: string, args: string[] = [], {cwd, logLev return logLevel !== 'silent' } - forked.stderr?.setEncoding('utf8') - forked.stderr?.on('data', (d: Buffer) => { + spawned.stderr?.setEncoding('utf8') + spawned.stderr?.on('data', (d: Buffer) => { const output = d.toString().trim() stderr.push(output) if (shouldPrint(output)) { @@ -66,8 +68,8 @@ export async function fork(modulePath: string, args: string[] = [], {cwd, logLev } else debug(output) }) - forked.stdout?.setEncoding('utf8') - forked.stdout?.on('data', (d: Buffer) => { + spawned.stdout?.setEncoding('utf8') + spawned.stdout?.on('data', (d: Buffer) => { const output = d.toString().trim() stdout.push(output) if (shouldPrint(output)) { @@ -76,8 +78,8 @@ export async function fork(modulePath: string, args: string[] = [], {cwd, logLev } else debug(output) }) - forked.on('error', reject) - forked.on('exit', (code: number) => { + spawned.on('error', reject) + spawned.on('exit', (code: number) => { if (code === 0) { resolve({stderr, stdout}) } else { diff --git a/src/yarn.ts b/src/yarn.ts index f70cf68b..b6aaba07 100644 --- a/src/yarn.ts +++ b/src/yarn.ts @@ -3,8 +3,8 @@ import makeDebug from 'debug' import {createRequire} from 'node:module' import {fileURLToPath} from 'node:url' -import {ExecOptions, Output, fork} from './fork.js' import {LogLevel} from './log-level.js' +import {ExecOptions, Output, spawn} from './spawn.js' const require = createRequire(import.meta.url) const debug = makeDebug('@oclif/plugin-plugins:yarn') @@ -32,7 +32,7 @@ export class Yarn { debug(`${options.cwd}: ${bin} ${args.join(' ')}`) try { - const output = await fork(bin, args, options) + const output = await spawn(bin, args, options) debug('yarn done') return output } catch (error: unknown) { @@ -54,7 +54,7 @@ export class Yarn { this.bin = await which('yarn') } - if (this.bin) { + if (!this.bin) { throw new Errors.CLIError('yarn not found') }