import { Either, IEither, type ITraceable, LogLevel, LogMetricTraceSupplier, Metric, TraceUtil, } from '@emprespresso/pengueno'; import { exec } from 'node:child_process'; export type Command = string[] | string; export type StdStreams = { stdout: string; stderr: string }; export const CmdMetric = Metric.fromName('Exec').asResult(); type Environment = Record; type Options = { streamTraceable?: Array<'stdout' | 'stderr'>; env?: Environment; clearEnv?: boolean }; export const getStdout = ( cmd: ITraceable, options: Options = { streamTraceable: [] }, ): Promise> => cmd .flatMap(TraceUtil.withFunctionTrace(getStdout)) .flatMap((tCmd) => tCmd.traceScope(() => `Command = ${tCmd.get()}`)) .map((tCmd) => { const cmd = tCmd.get(); const _exec = typeof cmd === 'string' ? cmd : cmd.join(' '); const env = options.clearEnv ? options.env : { ...process.env, ...options.env }; return Either.fromFailableAsync( new Promise((res, rej) => { const proc = exec(_exec, { env }); let stdout = ''; proc.stdout?.on('data', (d) => { const s = d.toString(); stdout += s; if (options.streamTraceable?.includes('stdout')) { tCmd.trace.trace(s); } }); let stderr = ''; proc.stderr?.on('data', (d) => { const s = d.toString(); stdout += s; if (options.streamTraceable?.includes('stderr')) { tCmd.trace.trace(s); } }); proc.on('exit', (code) => { const streams = { stdout, stderr }; if (code === 0) { res(streams); } else { rej(new Error(`exited with non-zero code: ${code}`)); } }); }), ); }) .map( TraceUtil.promiseify((tEitherStdStreams) => tEitherStdStreams.get().mapRight(({ stderr, stdout }) => { if (stderr) tEitherStdStreams.trace.traceScope(LogLevel.DEBUG).trace(`StdErr = ${stderr}`); return stdout; }), ), ) .peek(TraceUtil.promiseify(TraceUtil.traceResultingEither(CmdMetric))) .get(); export const getStdoutMany = ( cmds: ITraceable, LogMetricTraceSupplier>, options: Options = { streamTraceable: [] }, ): Promise>> => cmds .coExtend((t) => t.get()) .reduce( async (_result, tCmd) => { const result = await _result; return result.joinRightAsync( () => tCmd.map((cmd) => getStdout(cmd, options)).get(), (stdout, pre) => pre.concat(stdout), ); }, Promise.resolve(Either.right>([])), );