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