async function runCommandAndWaitForPattern(command, pattern, opts = {}) {
  const {timeout = 5000} = opts;

  /** @type {string|null} */
  let patternMatch = null;
  /** @type {() => void} */
  let resolve;
  /** @type {(e: Error) => void} */
  let reject;
  const timeoutPromise = new Promise(r => setTimeout(r, timeout));
  /** @type {Promise<void>} */
  const foundStringPromise = new Promise((r1, r2) => {
    resolve = r1;
    reject = r2;
  });
  const output = {stdout: '', stderr: ''};

  const child = childProcess.spawn(command, {stdio: 'pipe', shell: true});
  /** @param {'stdout'|'stderr'} channel @return {(chunk: Buffer) => void}  */
  const stringListener = channel => chunk => {
    const data = chunk.toString();
    output[channel] += data;
    const match = chunk.toString().match(pattern);
    patternMatch = patternMatch || (match && match[0]);
    if (match) resolve();
  };
  /** @param {number|null} code */
  const exitListener = code => {
    if (code !== 0) {
      const err = new Error(`Command exited with code ${code}`);
      Object.assign(err, output);
      reject(err);
    }
  };

  const stdoutListener = stringListener('stdout');
  const stderrListener = stringListener('stderr');

  child.on('exit', exitListener);
  child.stdout.on('data', stdoutListener);
  child.stderr.on('data', stderrListener);
  await Promise.race([timeoutPromise, foundStringPromise]);
  child.stdout.off('data', stdoutListener);
  child.stderr.off('data', stderrListener);
  child.off('exit', exitListener);

  return {child, patternMatch, ...output};
}
@param {string} command
@param {RegExp} pattern
@param {{timeout?: number}} [opts]
@return {Promise<{child: import('child_process').ChildProcess, patternMatch: string|null, stdout: string, stderr: string}>}