2019-07-23 12:39:15 +00:00
|
|
|
import { promisify } from 'util';
|
2019-12-23 14:59:57 +00:00
|
|
|
import {
|
|
|
|
exec as cpExec,
|
|
|
|
ExecOptions as ChildProcessExecOptions,
|
|
|
|
} from 'child_process';
|
2020-01-12 18:47:39 +00:00
|
|
|
import { dockerCmd, DockerOptions, setDockerConfig } from './docker';
|
2020-01-10 14:18:41 +00:00
|
|
|
import { getChildProcessEnv } from './env';
|
2019-07-23 12:39:15 +00:00
|
|
|
|
2020-01-12 18:47:39 +00:00
|
|
|
let localDir;
|
|
|
|
|
|
|
|
export function setExecConfig(config): void {
|
|
|
|
localDir = config.localDir;
|
|
|
|
setDockerConfig(config);
|
|
|
|
}
|
|
|
|
|
2019-12-23 14:59:57 +00:00
|
|
|
const pExec: (
|
|
|
|
cmd: string,
|
|
|
|
opts: ChildProcessExecOptions & { encoding: string }
|
|
|
|
) => Promise<ExecResult> = promisify(cpExec);
|
|
|
|
|
2020-01-14 11:12:03 +00:00
|
|
|
type ExtraEnv<T = unknown> = Record<string, T>;
|
|
|
|
|
2019-12-23 14:59:57 +00:00
|
|
|
export interface ExecOptions extends ChildProcessExecOptions {
|
2020-01-14 11:12:03 +00:00
|
|
|
extraEnv?: ExtraEnv | null | undefined;
|
|
|
|
docker?: DockerOptions | null | undefined;
|
2019-12-23 14:59:57 +00:00
|
|
|
}
|
2019-07-23 12:39:15 +00:00
|
|
|
|
|
|
|
export interface ExecResult {
|
|
|
|
stdout: string;
|
|
|
|
stderr: string;
|
|
|
|
}
|
|
|
|
|
2020-01-14 11:12:03 +00:00
|
|
|
function createChildEnv(
|
|
|
|
env: NodeJS.ProcessEnv,
|
|
|
|
extraEnv: ExtraEnv
|
|
|
|
): ExtraEnv<string> {
|
|
|
|
const extraEnvKeys = Object.keys(extraEnv || {});
|
|
|
|
|
|
|
|
const childEnv =
|
|
|
|
env || extraEnv
|
|
|
|
? {
|
|
|
|
...extraEnv,
|
|
|
|
...getChildProcessEnv(extraEnvKeys),
|
|
|
|
...env,
|
|
|
|
}
|
|
|
|
: getChildProcessEnv();
|
|
|
|
|
|
|
|
const result: ExtraEnv<string> = {};
|
|
|
|
Object.entries(childEnv).forEach(([key, val]) => {
|
|
|
|
if (val === null) return;
|
|
|
|
if (val === undefined) return;
|
|
|
|
result[key] = val.toString();
|
|
|
|
});
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
function dockerEnvVars(
|
|
|
|
extraEnv: ExtraEnv,
|
|
|
|
childEnv: ExtraEnv<string>
|
|
|
|
): string[] {
|
|
|
|
const extraEnvKeys = Object.keys(extraEnv || {});
|
|
|
|
return extraEnvKeys.filter(key => typeof childEnv[key] !== 'undefined');
|
|
|
|
}
|
|
|
|
|
|
|
|
export function exec(
|
|
|
|
cmd: string | string[],
|
|
|
|
opts: ExecOptions = {}
|
|
|
|
): Promise<ExecResult> {
|
|
|
|
const { env, extraEnv, docker } = opts;
|
2020-01-12 18:47:39 +00:00
|
|
|
const cwd = opts.cwd || localDir;
|
2020-01-14 11:12:03 +00:00
|
|
|
const childEnv = createChildEnv(env, extraEnv);
|
|
|
|
|
|
|
|
const execOptions: ExecOptions = { ...opts };
|
|
|
|
delete execOptions.extraEnv;
|
|
|
|
delete execOptions.docker;
|
2020-01-10 14:18:41 +00:00
|
|
|
|
2020-01-14 11:12:03 +00:00
|
|
|
const pExecOptions: ChildProcessExecOptions & { encoding: string } = {
|
2020-01-10 14:18:41 +00:00
|
|
|
encoding: 'utf-8',
|
2020-01-14 11:12:03 +00:00
|
|
|
...execOptions,
|
|
|
|
env: childEnv,
|
2020-01-12 18:47:39 +00:00
|
|
|
cwd,
|
2020-01-10 14:18:41 +00:00
|
|
|
};
|
2020-01-14 11:12:03 +00:00
|
|
|
|
|
|
|
// TODO: simplify after callers' refactoring
|
|
|
|
const singleCommand = typeof cmd === 'string' ? cmd : cmd.join(' && ');
|
|
|
|
let pExecCommand = singleCommand.startsWith('docker run')
|
|
|
|
? singleCommand
|
|
|
|
: `bash -l -c "${singleCommand.replace(/"/g, '\\"')}"`;
|
2020-01-10 14:18:41 +00:00
|
|
|
|
|
|
|
if (docker) {
|
|
|
|
const dockerOptions = {
|
|
|
|
...docker,
|
|
|
|
cwd,
|
2020-01-14 11:12:03 +00:00
|
|
|
envVars: dockerEnvVars(extraEnv, childEnv),
|
2020-01-10 14:18:41 +00:00
|
|
|
};
|
2019-12-23 14:59:57 +00:00
|
|
|
|
2020-01-14 11:12:03 +00:00
|
|
|
pExecCommand = dockerCmd(pExecCommand, dockerOptions);
|
2019-12-23 14:59:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return pExec(pExecCommand, pExecOptions);
|
2019-07-23 12:39:15 +00:00
|
|
|
}
|