2022-08-07 01:08:16 +00:00
|
|
|
import { exec } from '../dependencies.ts';
|
2020-04-26 18:22:09 +00:00
|
|
|
|
|
|
|
|
class System {
|
2022-08-07 01:08:16 +00:00
|
|
|
static async shellRun(command) {
|
|
|
|
|
return System.newRun('sh', ['-c', command]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Example:
|
|
|
|
|
* System.newRun(sh, ['-c', 'echo something'])
|
|
|
|
|
*
|
|
|
|
|
* private for now, but could become public if this happens to be a great replacement for the other run method.
|
|
|
|
|
*/
|
|
|
|
|
private static async newRun(command, args: string | string[] = []) {
|
|
|
|
|
if (!Array.isArray(args)) args = [args];
|
|
|
|
|
|
|
|
|
|
const argsString = args.join(' ');
|
|
|
|
|
const process = Deno.run({
|
|
|
|
|
cmd: [command, ...args],
|
|
|
|
|
stdout: 'piped',
|
|
|
|
|
stderr: 'piped',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const status = await process.status();
|
|
|
|
|
const outputBuffer = await process.output();
|
|
|
|
|
const errorBuffer = await process.stderrOutput();
|
|
|
|
|
|
|
|
|
|
process.close();
|
|
|
|
|
|
|
|
|
|
const output = new TextDecoder().decode(outputBuffer);
|
|
|
|
|
const error = new TextDecoder().decode(errorBuffer);
|
|
|
|
|
|
|
|
|
|
const result = { status, output };
|
|
|
|
|
const symbol = status.success ? '✅' : '❗';
|
|
|
|
|
|
|
|
|
|
log.debug('Command:', command, argsString, symbol, result);
|
|
|
|
|
|
|
|
|
|
if (error) throw new Error(error);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-01 02:31:20 +00:00
|
|
|
static async run(command, arguments_: any = [], options = {}, shouldLog = true) {
|
2020-04-26 18:22:09 +00:00
|
|
|
let result = '';
|
|
|
|
|
let error = '';
|
2020-05-01 11:57:42 +00:00
|
|
|
let debug = '';
|
2020-04-26 18:22:09 +00:00
|
|
|
|
|
|
|
|
const listeners = {
|
2020-05-01 14:52:08 +00:00
|
|
|
stdout: (dataBuffer) => {
|
2020-04-26 18:22:09 +00:00
|
|
|
result += dataBuffer.toString();
|
|
|
|
|
},
|
2020-05-01 14:52:08 +00:00
|
|
|
stderr: (dataBuffer) => {
|
2020-04-26 18:22:09 +00:00
|
|
|
error += dataBuffer.toString();
|
|
|
|
|
},
|
2020-05-01 14:52:08 +00:00
|
|
|
debug: (dataString) => {
|
2020-05-01 11:57:42 +00:00
|
|
|
debug += dataString.toString();
|
|
|
|
|
},
|
2020-04-26 18:22:09 +00:00
|
|
|
};
|
|
|
|
|
|
2020-05-21 18:40:03 +00:00
|
|
|
const showOutput = () => {
|
2022-02-01 02:31:20 +00:00
|
|
|
if (debug !== '' && shouldLog) {
|
2022-08-06 20:43:27 +00:00
|
|
|
log.debug(debug);
|
2020-05-21 18:40:03 +00:00
|
|
|
}
|
2020-05-01 11:57:42 +00:00
|
|
|
|
2022-02-01 02:31:20 +00:00
|
|
|
if (result !== '' && shouldLog) {
|
2022-08-06 20:43:27 +00:00
|
|
|
log.info(result);
|
2020-05-21 18:40:03 +00:00
|
|
|
}
|
2020-05-01 11:57:42 +00:00
|
|
|
|
2022-02-01 02:31:20 +00:00
|
|
|
if (error !== '' && shouldLog) {
|
2022-08-06 20:43:27 +00:00
|
|
|
log.warning(error);
|
2020-05-21 18:40:03 +00:00
|
|
|
}
|
|
|
|
|
};
|
2020-05-01 11:57:42 +00:00
|
|
|
|
2022-08-06 20:43:27 +00:00
|
|
|
const throwContextualError = (message: string) => {
|
2020-05-21 18:40:03 +00:00
|
|
|
let commandAsString = command;
|
2020-05-21 18:13:33 +00:00
|
|
|
if (Array.isArray(arguments_)) {
|
2020-05-21 18:40:03 +00:00
|
|
|
commandAsString += ` ${arguments_.join(' ')}`;
|
|
|
|
|
} else if (typeof arguments_ === 'string') {
|
|
|
|
|
commandAsString += ` ${arguments_}`;
|
2020-05-21 18:13:33 +00:00
|
|
|
}
|
|
|
|
|
|
2020-05-21 18:40:03 +00:00
|
|
|
throw new Error(`Failed to run "${commandAsString}".\n ${message}`);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
try {
|
2022-03-27 23:23:32 +00:00
|
|
|
if (command.trim() === '') {
|
|
|
|
|
throw new Error(`Failed to execute empty command`);
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-07 01:08:16 +00:00
|
|
|
const { exitCode, success, output } = await exec(command, arguments_, { silent: true, listeners, ...options });
|
2020-05-21 18:40:03 +00:00
|
|
|
showOutput();
|
2022-08-07 01:08:16 +00:00
|
|
|
if (!success) {
|
|
|
|
|
throwContextualError(`Command returned non-zero exit code (${exitCode}).\nError: ${error}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Todo - remove this after verifying it works as expected
|
|
|
|
|
const trimmedResult = result.replace(/\n+$/, '');
|
|
|
|
|
if (!output && trimmedResult) {
|
|
|
|
|
log.warning('returning result instead of output for backward compatibility');
|
|
|
|
|
|
|
|
|
|
return trimmedResult;
|
2020-05-21 18:40:03 +00:00
|
|
|
}
|
2022-08-07 01:08:16 +00:00
|
|
|
|
|
|
|
|
return output;
|
2020-05-21 18:40:03 +00:00
|
|
|
} catch (inCommandError) {
|
|
|
|
|
showOutput();
|
2020-05-21 23:24:22 +00:00
|
|
|
throwContextualError(`In-command error caught: ${inCommandError}`);
|
2020-05-21 18:05:06 +00:00
|
|
|
}
|
2020-04-26 18:22:09 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default System;
|