diff --git a/src/helpers/utilsLocal.ts b/src/helpers/utilsLocal.ts index d0bc3fb..57dd5b6 100644 --- a/src/helpers/utilsLocal.ts +++ b/src/helpers/utilsLocal.ts @@ -1,4 +1,4 @@ -import { exec } from 'child_process'; +import { execFile } from 'child_process'; import { SystemType } from '../types/SystemType.js'; import { Architecture } from '../types/Architecture.js'; @@ -21,9 +21,14 @@ export function isTests() { return jest || vitest; } +// Only ever called with literal values today ('dpkg'/'rpm' in ManagerLocal.install()'s Linux +// branch), but uses execFile (no shell) rather than exec with a shell string on principle - +// every other command execution in this codebase avoids building shell strings from values that +// could someday trace back to registry/package metadata, and this should be no exception for +// whoever calls it next. export function commandExists(cmd: string): Promise { return new Promise(resolve => { - exec(`command -v ${cmd}`, (error, stdout) => { + execFile('which', [cmd], (error, stdout) => { resolve(Boolean(stdout.trim()) && !error); }); }); diff --git a/tests/helpers/utilsLocal.test.ts b/tests/helpers/utilsLocal.test.ts index 12bfa69..8526aad 100644 --- a/tests/helpers/utilsLocal.test.ts +++ b/tests/helpers/utilsLocal.test.ts @@ -1,5 +1,5 @@ import { expect, test } from 'vitest'; -import { getArchitecture, getSystem, isTests } from '../../src/helpers/utilsLocal'; +import { commandExists, getArchitecture, getSystem, isTests } from '../../src/helpers/utilsLocal'; test('Get Architecture', () => { if (process.arch === 'arm') { @@ -26,3 +26,12 @@ test('Get System', () => { test('Is tests', () => { expect(isTests()).toEqual(true); }); + +test('Command exists', async () => { + // `which` isn't available as a standalone command on plain Windows (unlike Linux/Mac, which + // is the only place commandExists() is actually used - ManagerLocal.install()'s dpkg/rpm + // check). + if (process.platform === 'win32') return; + expect(await commandExists('node')).toEqual(true); + expect(await commandExists('this-command-should-not-exist-xyz123')).toEqual(false); +});