Skip to content

Commit 9cc7ec9

Browse files
authored
feat(runtime): print exit code info and signal on error (#43)
1 parent 2059567 commit 9cc7ec9

File tree

3 files changed

+137
-23
lines changed

3 files changed

+137
-23
lines changed

src/runtime/lib/exit_code_info.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
export function getExitCodeInfo(exitCode: number): string | undefined {
2+
return {
3+
2: "Misuse of shell builtins",
4+
126: "Invoked command not executable",
5+
127: "Command not found",
6+
128: "Invalid exit argument",
7+
// SIGHUP
8+
129: "Hangup",
9+
// SIGINT
10+
130: "Interrupt (Ctrl + C)",
11+
// SIGQUIT
12+
131: "Quit and dump core",
13+
// SIGILL
14+
132: "Illegal instruction",
15+
// SIGTRAP
16+
133: "Trace/breakpoint trap",
17+
// SIGABRT
18+
134: "Process aborted",
19+
// SIGEMT
20+
135: 'Bus error: "access to undefined portion of memory object"',
21+
// SIGFPE
22+
136: 'Floating point exception: "erroneous arithmetic operation"',
23+
// SIGKILL
24+
137: "Kill (terminate immediately)",
25+
// SIGBUS
26+
138: "Bus error (bad memory access)",
27+
// SIGSEGV
28+
139: "Segmentation violation",
29+
// SIGSYS
30+
140: "Bad system call (SVr4)",
31+
// SIGPIPE
32+
141: "Write to pipe with no one reading",
33+
// SIGALRM
34+
142: "Signal raised by alarm",
35+
// SIGTERM
36+
143: "Termination (request to terminate)",
37+
145: "Child process terminated, stopped (or continued*)",
38+
146: "Continue if stopped",
39+
147: "Stop executing temporarily",
40+
148: "Terminal stop signal",
41+
149: 'Background process attempting to read from tty ("in")',
42+
150: 'Background process attempting to write to tty ("out")',
43+
151: "Urgent data available on socket",
44+
// SIGXCPU
45+
152: "CPU time limit exceeded",
46+
// SIGXFSZ
47+
153: "File size limit exceeded",
48+
// SIGVTALRM
49+
154:
50+
'Signal raised by timer counting virtual time: "virtual timer expired"',
51+
// SIGPROF
52+
155: "Profiling timer expired",
53+
157: "Pollable event",
54+
// SIGUSR1
55+
158: "User-defined 1",
56+
// SIGUSR2
57+
159: "User-defined 2",
58+
}[exitCode];
59+
}

src/runtime/process_error.ts

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,71 @@
1-
import { ProcessOutput, ProcessOutputOptions } from "./process_output.ts";
1+
import { colors } from "./deps.ts";
2+
import { getExitCodeInfo } from "./lib/exit_code_info.ts";
3+
import { ProcessOutputOptions } from "./process_output.ts";
24

3-
export class ProcessError extends Error implements ProcessOutput {
4-
readonly stdout: string;
5-
readonly stderr: string;
6-
readonly combined: string;
7-
readonly status: Deno.ProcessStatus;
5+
export type ProcessErrorOptions = ProcessOutputOptions;
86

9-
constructor({ stdout, stderr, combined, status }: ProcessOutputOptions) {
7+
export class ProcessError extends Error {
8+
#name = "ProcessError";
9+
#stdout!: string;
10+
#stderr!: string;
11+
#combined!: string;
12+
#status!: Deno.ProcessStatus;
13+
14+
constructor(options: ProcessErrorOptions) {
1015
super();
1116
Object.setPrototypeOf(this, ProcessError.prototype);
12-
this.stdout = stdout;
13-
this.stderr = stderr;
14-
this.combined = combined;
15-
this.status = status;
16-
this.message = combined;
17+
this.#stdout = options.stdout;
18+
this.#stderr = options.stderr;
19+
this.#combined = options.combined;
20+
this.#status = options.status;
21+
this.message = this.#getErrorMessage();
22+
}
23+
24+
get name(): string {
25+
return this.#name;
26+
}
27+
28+
get stdout(): string {
29+
return this.#stdout;
30+
}
31+
32+
get stderr(): string {
33+
return this.#stderr;
34+
}
35+
36+
get combined(): string {
37+
return this.#combined;
38+
}
39+
40+
get status(): Deno.ProcessStatus {
41+
return this.#status;
42+
}
43+
44+
#getErrorMessage(): string {
45+
let message = colors.bold("Command failed.");
46+
47+
if (this.#combined.trim()) {
48+
message += "\n" + this.#combined.trim();
49+
}
50+
51+
const exitCodeInfo = getExitCodeInfo(this.#status.code)
52+
? ` (${colors.italic(getExitCodeInfo(this.#status.code)!)})`
53+
: "";
54+
55+
message += colors.bold(
56+
`\n${colors.white("Exit code:")} ${
57+
colors.yellow(this.#status.code.toString())
58+
}${exitCodeInfo}`,
59+
);
60+
61+
if (typeof this.#status.signal === "number") {
62+
message += colors.bold(
63+
`\n${colors.white("Signal:")} ${
64+
colors.yellow(this.#status.signal.toString())
65+
}`,
66+
);
67+
}
68+
69+
return colors.red(message);
1770
}
1871
}

src/runtime/process_error_test.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ function createError(): ProcessError {
1212
stderr: "bar",
1313
combined: "baz",
1414
status: {
15-
code: 0,
16-
success: true,
15+
code: 1,
16+
success: false,
17+
signal: undefined,
1718
},
1819
});
1920
}
@@ -31,14 +32,13 @@ Deno.test({
3132
name: "[process error] should have all properties defined",
3233
fn() {
3334
const error = createError();
34-
assertObjectMatch(error, {
35-
stdout: "foo",
36-
stderr: "bar",
37-
combined: "baz",
38-
status: {
39-
code: 0,
40-
success: true,
41-
},
35+
assertEquals(error.stdout, "foo");
36+
assertEquals(error.stderr, "bar");
37+
assertEquals(error.combined, "baz");
38+
assertObjectMatch(error.status, {
39+
code: 1,
40+
success: false,
41+
signal: undefined,
4242
});
4343
},
4444
});
@@ -60,7 +60,9 @@ Deno.test({
6060
Deno.test({
6161
name: "[process error] should have correct exit code",
6262
async fn() {
63-
const statusCode = await $`exit 2`.catch((error) => error.status.code);
63+
const statusCode = await $`exit 2`.catch((error) =>
64+
error instanceof ProcessError ? error.status.code : null
65+
);
6466
assertEquals(statusCode, 2);
6567
},
6668
});

0 commit comments

Comments
 (0)