diff --git a/src/spawn.ts b/src/spawn.ts index 51141697..97d3e926 100644 --- a/src/spawn.ts +++ b/src/spawn.ts @@ -19,10 +19,10 @@ const debug = makeDebug('@oclif/plugin-plugins:spawn') export async function spawn(modulePath: string, args: string[] = [], {cwd, logLevel}: ExecOptions): Promise { return new Promise((resolve, reject) => { - // 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' + if (modulePath.endsWith('.js')) { + const quote = process.platform === 'win32' ? `"${modulePath}"` : modulePath + args.unshift(quote) + modulePath = process.execPath } debug('modulePath', modulePath) diff --git a/test/spawn.test.ts b/test/spawn.test.ts new file mode 100644 index 00000000..39eb6cff --- /dev/null +++ b/test/spawn.test.ts @@ -0,0 +1,60 @@ +import {expect} from 'chai' +import {chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync} from 'node:fs' +import {tmpdir} from 'node:os' +import {join} from 'node:path' + +import {spawn} from '../src/spawn.js' + +describe('spawn', () => { + let tempDir: string + + beforeEach(() => { + tempDir = mkdtempSync(join(tmpdir(), 'spawn-test-')) + }) + + afterEach(() => { + rmSync(tempDir, {force: true, recursive: true}) + }) + + it('should invoke .js module paths via process.execPath', async () => { + const script = join(tempDir, 'test-script.js') + writeFileSync(script, '#!/usr/bin/env nonexistent-node-binary\nconsole.log("spawned-ok")\n') + chmodSync(script, '755') + + const result = await spawn(script, [], {cwd: tempDir, logLevel: 'silent'}) + + expect(result.stdout).to.include('spawned-ok') + }) + + it('should pass args after the .js module path', async () => { + const script = join(tempDir, 'echo-args.js') + writeFileSync(script, 'console.log(JSON.stringify(process.argv.slice(2)))\n') + chmodSync(script, '755') + + const result = await spawn(script, ['--flag', 'value'], {cwd: tempDir, logLevel: 'silent'}) + + expect(result.stdout).to.include('["--flag","value"]') + }) + + it('should handle .js module paths with spaces in the path', async () => { + const dir = join(tempDir, 'dir with spaces') + mkdirSync(dir) + const script = join(dir, 'my script.js') + writeFileSync(script, 'console.log("spaces-ok")\n') + chmodSync(script, '755') + + const result = await spawn(script, [], {cwd: tempDir, logLevel: 'silent'}) + + expect(result.stdout).to.include('spaces-ok') + }) + + it('should not modify non-.js module paths', async () => { + const script = join(tempDir, 'test-bin') + writeFileSync(script, `#!/usr/bin/env bash\necho "bin-ok"\n`) + chmodSync(script, '755') + + const result = await spawn(script, [], {cwd: tempDir, logLevel: 'silent'}) + + expect(result.stdout).to.include('bin-ok') + }) +})