dax icon indicating copy to clipboard operation
dax copied to clipboard

By default, print out debug information when error was encountered

Open krzkaczor opened this issue 1 year ago • 6 comments

Simple code such:

await $`false`;

Throws totally unhelpful error message like

Uncaught Error: Exited with code: 1
    at CommandChild.pipedStdoutBuffer (file:///Users/krzkaczor/Workspace/spark/spark-alm-planner/node_modules/.pnpm/[email protected]/node_modules/dax-sh/esm/mod.js:8625:19)

(often even stack trace is not helpful).

I think the default behaviour should be to throw an error with command name that was executed and at least few final lines of stderr and stdout.

For reference, this is what bun/shell will do (output and error code both parts of the responses): image

krzkaczor avatar Aug 13 '24 10:08 krzkaczor

Hey @krzkaczor, hope you're doing well!

I think this is?

https://github.com/dsherret/dax/issues/150 https://github.com/dsherret/dax/issues/172

The stack trace right now is not helpful due to a v8 bug.

dsherret avatar Aug 13 '24 12:08 dsherret

Hey David, long time no talk ;)

Yeah it's definitely related to those tickets, I decided to create another ticket because I think this behaviour should be a default. Feel free to close this if you think the other tickets are enough.

krzkaczor avatar Aug 13 '24 19:08 krzkaczor

I came up with the following to work around this. It works decently well, although you lose out on the text, json, etc. helpers on CommandBuilder since they essentially spawn internally. Would be nice if this was somehow built in to dax.

Usage:

const result = await exec($`some-command`);
const json = result.stdoutJson;

Implementation:

export async function exec(
  commandBuilder: CommandBuilder,
  captureOutput = true,
): Promise<CommandResult> {
  if (captureOutput) {
    commandBuilder = commandBuilder
      .noThrow()
      .quiet()
      .captureCombined();
  }

  const output = await commandBuilder.spawn();

  if (output.code !== 0) {
    const outputText = captureOutput
      ? output.combined
        .split('\n')
        .map((l) => `${' '.repeat(4)}${l}`)
        .join('\n')
      : '';

    throw new Error(`Failed to execute command\n${outputText}`);
  }

  return output;
}

andrewthauer avatar Nov 24 '24 15:11 andrewthauer

@dsherret Any advice on how to move forward with building something into dax itself to help deal with this? I'd like to use it more broadly, but it can be really painful to trace errors when using it in a lot of different files and contexts.

andrewthauer avatar Jan 22 '25 23:01 andrewthauer

It would slow everything down and make it act differently. I think we need something like https://github.com/denoland/deno/issues/7586

dsherret avatar Jan 23 '25 04:01 dsherret

@dsherret - I can't say I fully understand the scope of the problem here. I presume https://github.com/denoland/deno/issues/7586 has to do with supporting pipes ala deno_task_shell?

Did some further experimenting and it seems using Error.captureStackTrace (from v8 in node/deno) can help a bit here as well. For example, the following retains more of the stack trace.

async function captureAsyncStackTrace<T>(fn: () => Promise<T>) {
  try {
    return await fn()
  } catch (error) {
    Error.captureStackTrace(error as Error);
    throw error;
  }
}

async function main() {
  await captureAsyncStackTrace(() => $`cmd-does-not-exist`.text());
}

await main();

I'm wondering if we could incorporate the Error.captureStackTrace to the Command.then, etc. to do this automatically?

andrewthauer avatar Jan 29 '25 02:01 andrewthauer