summaryrefslogtreecommitdiff
path: root/packages/create-astro/src/shell.ts
blob: 4941d788c63a55441ff094a8d62f85e1d330fd84 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// This is an extremely simplified version of [`execa`](https://github.com/sindresorhus/execa)
// intended to keep our dependency size down
import type { StdioOptions } from 'node:child_process';
import type { Readable } from 'node:stream';

import { text as textFromStream } from 'node:stream/consumers';
import { spawn } from 'node:child_process';
import { setTimeout as sleep } from 'node:timers/promises';

export interface ExecaOptions {
    cwd?: string | URL;
    stdio?: StdioOptions;
    timeout?: number;
}
export interface Output {
    stdout: string;
    stderr: string;
    exitCode: number;
}
const text = (stream: NodeJS.ReadableStream | Readable | null) => stream ? textFromStream(stream).then(t => t.trimEnd()) : '';

export async function shell(command: string, flags: string[], opts: ExecaOptions = {}): Promise<Output> {
    const controller = opts.timeout ? new AbortController() : undefined;
    const child = spawn(command, flags, {
        cwd: opts.cwd,
        shell: true,
        stdio: opts.stdio,
        signal: controller?.signal
    })
    const stdout = await text(child.stdout);
    const stderr = await text(child.stderr);
    if (opts.timeout) {
        sleep(opts.timeout).then(() => {
            controller!.abort();
            throw { stdout, stderr, exitCode: 1 }
        })
    }
    await new Promise((resolve) => child.on('exit', resolve))
    const { exitCode } = child;
    if (exitCode !== 0) {
        throw { stdout, stderr, exitCode };
    }
    return { stdout, stderr, exitCode }
}