1import spawnAsync, { SpawnOptions, SpawnPromise } from '@expo/spawn-async'; 2import sudo from 'sudo-prompt'; 3 4/** 5 * The pending spawn promise is similar to the spawn promise from `@expo/spawn-async`. 6 * Instead of the `child` process being available immediately, the `child` is behind another promise. 7 * We need this to perform async tasks before running the actual spawn promise. 8 * Use it like: `await manager.installAsync().child` 9 */ 10export interface PendingSpawnPromise<T> extends Promise<T> { 11 /** 12 * The child process from the delayed spawn. 13 * This is `null` whenever the promise before the spawn promise is rejected. 14 */ 15 child: Promise<SpawnPromise<T>['child'] | null>; 16} 17 18export function createPendingSpawnAsync<V, T>( 19 actionAsync: () => Promise<V>, 20 spawnAsync: (result: V) => SpawnPromise<T> 21): PendingSpawnPromise<T> { 22 // Manually rsolve the child promise whenever the prepending async action is resolved. 23 // Avoid `childReject` to prevent "unhandled promise rejection" for one of the two promises. 24 let childResolve: (child: SpawnPromise<T>['child'] | null) => void; 25 const child: Promise<SpawnPromise<T>['child'] | null> = new Promise((resolve, reject) => { 26 childResolve = resolve; 27 }); 28 29 const pendingPromise = new Promise<T>((spawnResolve, spawnReject) => { 30 actionAsync() 31 .then((result) => { 32 const spawnPromise = spawnAsync(result); 33 childResolve(spawnPromise.child); 34 spawnPromise.then(spawnResolve).catch(spawnReject); 35 }) 36 .catch((error) => { 37 childResolve(null); 38 spawnReject(error); 39 }); 40 }); 41 42 (pendingPromise as PendingSpawnPromise<T>).child = child; 43 return pendingPromise as PendingSpawnPromise<T>; 44} 45 46/** 47 * Spawn a command with sudo privileges. 48 * On windows, this uses the `sudo-prompt` package. 49 * on other systems, this uses the `sudo` binary. 50 */ 51export async function spawnSudoAsync(command: string[], spawnOptions: SpawnOptions): Promise<void> { 52 // sudo prompt only seems to work on win32 machines. 53 if (process.platform === 'win32') { 54 return new Promise((resolve, reject) => { 55 sudo.exec(command.join(' '), { name: 'pod install' }, (error) => { 56 if (error) { 57 reject(error); 58 } 59 resolve(); 60 }); 61 }); 62 } else { 63 // Attempt to use sudo to run the command on Mac and Linux. 64 // TODO(Bacon): Make a v of sudo-prompt that's win32 only for better bundle size. 65 console.log( 66 'Your password might be needed to install CocoaPods CLI: https://guides.cocoapods.org/using/getting-started.html#installation' 67 ); 68 await spawnAsync('sudo', command, spawnOptions); 69 } 70} 71