Skip to content

Commit 4dbe922

Browse files
committed
feat(shell): provide which utility for finding binaries
1 parent 7e6c713 commit 4dbe922

2 files changed

Lines changed: 45 additions & 0 deletions

File tree

packages/@ionic/cli-framework/src/utils/array.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,11 @@ export async function map<T, U>(array: T[] | ReadonlyArray<T>, callback: (curren
4949

5050
export async function reduce<T>(array: T[], callback: (accumulator: T, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => Promise<T>): Promise<T>;
5151
export async function reduce<T>(array: T[], callback: (accumulator: T, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => Promise<T>, initialValue: T): Promise<T>;
52+
export async function reduce<T, R>(array: T[], callback: (accumulator: R, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => Promise<R>): Promise<R>;
5253
export async function reduce<T, U>(array: T[], callback: (accumulator: U, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => Promise<U>, initialValue: U): Promise<U>;
5354
export async function reduce<T>(array: ReadonlyArray<T>, callback: (accumulator: T, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => Promise<T>): Promise<T>;
5455
export async function reduce<T>(array: ReadonlyArray<T>, callback: (accumulator: T, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => Promise<T>, initialValue: T): Promise<T>;
56+
export async function reduce<T, R>(array: ReadonlyArray<T>, callback: (accumulator: R, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => Promise<R>): Promise<R>;
5557
export async function reduce<T, U>(array: ReadonlyArray<T>, callback: (accumulator: U, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => Promise<U>, initialValue: U): Promise<U>;
5658
export async function reduce<T, U>(array: T[] | ReadonlyArray<T>, callback: (accumulator: T | U, currentValue: T, currentIndex: number, array: ReadonlyArray<T>) => Promise<T | U>, initialValue?: T | U): Promise<T | U> {
5759
const hadInitialValue = typeof initialValue === 'undefined';

packages/@ionic/cli-framework/src/utils/shell.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import { statSafe } from '@ionic/utils-fs';
12
import { ChildProcess, ForkOptions, SpawnOptions, fork as _fork } from 'child_process';
23
import * as os from 'os';
34
import * as path from 'path';
45

56
import * as crossSpawn from 'cross-spawn';
67

78
import { ERROR_SHELL_COMMAND_NOT_FOUND, ERROR_SHELL_NON_ZERO_EXIT, ShellCommandError } from '../errors';
9+
import { reduce } from '../utils/array';
810
import { createProcessEnv } from '../utils/process';
911
import { WritableStreamBuffer } from '../utils/streams';
1012

@@ -157,3 +159,44 @@ export function spawn(command: string, args: ReadonlyArray<string> = [], options
157159
export function fork(modulePath: string, args: ReadonlyArray<string> = [], options: ForkOptions & Pick<SpawnOptions, 'stdio'> = {}): ChildProcess {
158160
return _fork(modulePath, [...args], options);
159161
}
162+
163+
export interface WhichOptions {
164+
PATH?: string;
165+
}
166+
167+
export async function which(command: string, { PATH = process.env.PATH || '' }: WhichOptions = {}): Promise<string> {
168+
if (command.includes(path.sep)) {
169+
return command;
170+
}
171+
172+
const pathParts = PATH.split(path.delimiter);
173+
174+
// tslint:disable:no-null-keyword
175+
176+
const value = await reduce<string, string | null>(pathParts, async (acc, v) => {
177+
// acc is no longer null, so we found the first match already
178+
if (acc) {
179+
return acc;
180+
}
181+
182+
const p = path.join(v, command);
183+
const stats = await statSafe(p);
184+
185+
if (stats && stats.isFile()) {
186+
// TODO: check if file is executable
187+
return p;
188+
}
189+
190+
return null;
191+
}, null);
192+
193+
// tslint:enable:no-null-keyword
194+
195+
if (!value) {
196+
const err: NodeJS.ErrnoException = new Error(`${command} cannot be found within PATH`);
197+
err.code = 'ENOENT';
198+
throw err;
199+
}
200+
201+
return value;
202+
}

0 commit comments

Comments
 (0)