Skip to content

Commit

Permalink
feat: apply formatting to $ error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
antongolub committed Dec 6, 2024
1 parent 0b6949b commit 6b1eb65
Show file tree
Hide file tree
Showing 8 changed files with 428 additions and 20 deletions.
6 changes: 3 additions & 3 deletions src/main/ts/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export function getExitCodeInfo(exitCode: number | null): string | undefined {
return EXIT_CODES[exitCode as keyof typeof EXIT_CODES]
}

export const getExitMessage = (
export const formatExitMessage = (
code: number | null,
signal: NodeJS.Signals | null,
stderr: string,
Expand All @@ -186,7 +186,7 @@ export const getExitMessage = (
return message
}

export const getErrorMessage = (err: NodeJS.ErrnoException, from: string) => {
export const formatErrorMessage = (err: NodeJS.ErrnoException, from: string) => {
return (
`${err.message}\n` +
` errno: ${err.errno} (${getErrnoMessage(err.errno)})\n` +
Expand All @@ -195,7 +195,7 @@ export const getErrorMessage = (err: NodeJS.ErrnoException, from: string) => {
)
}

export function getCallerLocation(err = new Error()) {
export function getCallerLocation(err = new Error('zurk error')) {
return getCallerLocationFromString(err.stack)
}

Expand Down
2 changes: 2 additions & 0 deletions src/main/ts/x.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
g,
immediate
} from './util.js'
import { getCallerLocation } from './error.ts'
import { pipeMixin } from './mixin/pipe.js'
import { killMixin } from './mixin/kill.js'
import { timeoutMixin } from './mixin/timeout.js'
Expand Down Expand Up @@ -90,6 +91,7 @@ export interface TShellSync {
export const $: TShell = function(this: any, pieces?: any, ...args: any): any {
const self = (this !== g) && this
const preset = self || {}
preset.stack = (preset.stack || getCallerLocation())

if (pieces === undefined) return applyMixins($, preset)

Expand Down
12 changes: 9 additions & 3 deletions src/main/ts/zurk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import {
type Promisified,
type TVoidCallback
} from './util.js'
import {
formatErrorMessage,
formatExitMessage
} from './error.js'

export const ZURK = Symbol('Zurk')
export const ZURKPROXY = Symbol('ZurkProxy')
Expand Down Expand Up @@ -91,6 +95,7 @@ export const zurkifyPromise = (target: Promise<TZurk> | TZurkPromise, ctx: TSpaw
if (p === 'stdio') return ctx.stdio
if (p === 'ctx') return ctx
if (p === 'child') return ctx.child
if (p === 'stack') return ctx.stack
if (p === 'on') return function (name: string, cb: VoidFunction){ ctx.ee.on(name, cb); return proxy }

if (p in target) return Reflect.get(target, p, receiver)
Expand All @@ -103,9 +108,10 @@ export const zurkifyPromise = (target: Promise<TZurk> | TZurkPromise, ctx: TSpaw
}

export const getError = (data: TSpawnResult): Error | null => {
if (data.error) return data.error
if (data.status) return new Error(`Command failed with exit code ${data.status}`)
if (data.signal) return new Error(`Command failed with signal ${data.signal}`)
if (data.error)
return new Error(formatErrorMessage(data.error, data.stack))
if (data.status || data.signal)
return new Error(formatExitMessage(data.status, data.signal, data.stderr, data.stack))

return null
}
Expand Down
28 changes: 28 additions & 0 deletions src/test/ts/error.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as assert from 'node:assert'
import { describe, it } from 'node:test'
import {
getCallerLocation,
getCallerLocationFromString,
getExitCodeInfo,
getErrnoMessage,
formatErrorMessage,
formatExitMessage,
EXIT_CODES,
ERRNO_CODES
} from '../../main/ts/error.js'

import * as all from '../../main/ts/error.js'

describe('error', () => {
it('has proper exports', () => {
assert.equal(typeof getCallerLocation, 'function')
assert.equal(typeof getCallerLocationFromString, 'function')
assert.equal(typeof getExitCodeInfo, 'function')
assert.equal(typeof getErrnoMessage, 'function')
assert.equal(typeof formatErrorMessage, 'function')
assert.equal(typeof formatExitMessage, 'function')
assert.equal(typeof EXIT_CODES, 'object')
assert.equal(typeof ERRNO_CODES, 'object')
assert.equal(Object.keys(all).length, 8)
})
})
14 changes: 8 additions & 6 deletions src/test/ts/x.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ describe('$()', () => {
try {
await $`exit 2`
} catch (error: unknown) {
assert.equal((error as Error).message, 'Command failed with exit code 2')
console.error(error)
assert.ok((error as Error).message.includes('exit code: 2 (Misuse of shell builtins)'))
}
})

Expand All @@ -40,7 +41,7 @@ describe('$()', () => {
try {
$({sync: true})`exit 2`
} catch (error: unknown) {
assert.equal((error as Error).message, 'Command failed with exit code 2')
assert.match((error as Error).message, /exit code: 2 \(Misuse of shell builtins\)/)
}
})

Expand Down Expand Up @@ -103,7 +104,7 @@ describe('mixins', () => {
const signal = await killed

assert.equal(signal, 'SIGTERM')
assert.equal(error.message, 'Command failed with signal SIGTERM')
assert.ok(error.message.includes('signal: SIGTERM'))
})

it('handles `abort`', async () => {
Expand All @@ -119,7 +120,8 @@ describe('mixins', () => {

const { error } = await p
assert.ok(getEventListeners(p.ctx.signal, 'abort').length < c)
assert.equal(error.message, 'The operation was aborted')
assert.ok(error.message.startsWith('The operation was aborted'))
assert.match(error.message, /code: ABORT_ERR/)
assert.deepEqual(events, ['abort', 'end'])
})
})
Expand All @@ -129,7 +131,7 @@ describe('mixins', () => {
const p = $({ timeout: 25, timeoutSignal: 'SIGALRM', nothrow: true })`sleep 10`

const { error } = await p
assert.equal(error.message, 'Command failed with signal SIGALRM')
assert.ok(error.message.includes('signal: SIGALRM'))
})

it('handles `timeout` as promise setter', async () => {
Expand All @@ -139,7 +141,7 @@ describe('mixins', () => {
p.ctx.nothrow = true

const { error } = await p
assert.equal(error.message, 'Command failed with signal SIGALRM')
assert.ok(error.message.includes('signal: SIGALRM'))
})
})

Expand Down
191 changes: 188 additions & 3 deletions target/cjs/zurk.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,190 @@ __export(zurk_exports, {
module.exports = __toCommonJS(zurk_exports);
var import_spawn = require("./spawn.cjs");
var import_util = require("./util.cjs");

// src/main/ts/error.ts
var EXIT_CODES = {
2: "Misuse of shell builtins",
126: "Invoked command cannot execute",
127: "Command not found",
128: "Invalid exit argument",
129: "Hangup",
130: "Interrupt",
131: "Quit and dump core",
132: "Illegal instruction",
133: "Trace/breakpoint trap",
134: "Process aborted",
135: 'Bus error: "access to undefined portion of memory object"',
136: 'Floating point exception: "erroneous arithmetic operation"',
137: "Kill (terminate immediately)",
138: "User-defined 1",
139: "Segmentation violation",
140: "User-defined 2",
141: "Write to pipe with no one reading",
142: "Signal raised by alarm",
143: "Termination (request to terminate)",
145: "Child process terminated, stopped (or continued*)",
146: "Continue if stopped",
147: "Stop executing temporarily",
148: "Terminal stop signal",
149: 'Background process attempting to read from tty ("in")',
150: 'Background process attempting to write to tty ("out")',
151: "Urgent data available on socket",
152: "CPU time limit exceeded",
153: "File size limit exceeded",
154: 'Signal raised by timer counting virtual time: "virtual timer expired"',
155: "Profiling timer expired",
157: "Pollable event",
159: "Bad syscall"
};
var ERRNO_CODES = {
0: "Success",
1: "Not super-user",
2: "No such file or directory",
3: "No such process",
4: "Interrupted system call",
5: "I/O error",
6: "No such device or address",
7: "Arg list too long",
8: "Exec format error",
9: "Bad file number",
10: "No children",
11: "No more processes",
12: "Not enough core",
13: "Permission denied",
14: "Bad address",
15: "Block device required",
16: "Mount device busy",
17: "File exists",
18: "Cross-device link",
19: "No such device",
20: "Not a directory",
21: "Is a directory",
22: "Invalid argument",
23: "Too many open files in system",
24: "Too many open files",
25: "Not a typewriter",
26: "Text file busy",
27: "File too large",
28: "No space left on device",
29: "Illegal seek",
30: "Read only file system",
31: "Too many links",
32: "Broken pipe",
33: "Math arg out of domain of func",
34: "Math result not representable",
35: "File locking deadlock error",
36: "File or path name too long",
37: "No record locks available",
38: "Function not implemented",
39: "Directory not empty",
40: "Too many symbolic links",
42: "No message of desired type",
43: "Identifier removed",
44: "Channel number out of range",
45: "Level 2 not synchronized",
46: "Level 3 halted",
47: "Level 3 reset",
48: "Link number out of range",
49: "Protocol driver not attached",
50: "No CSI structure available",
51: "Level 2 halted",
52: "Invalid exchange",
53: "Invalid request descriptor",
54: "Exchange full",
55: "No anode",
56: "Invalid request code",
57: "Invalid slot",
59: "Bad font file fmt",
60: "Device not a stream",
61: "No data (for no delay io)",
62: "Timer expired",
63: "Out of streams resources",
64: "Machine is not on the network",
65: "Package not installed",
66: "The object is remote",
67: "The link has been severed",
68: "Advertise error",
69: "Srmount error",
70: "Communication error on send",
71: "Protocol error",
72: "Multihop attempted",
73: "Cross mount point (not really error)",
74: "Trying to read unreadable message",
75: "Value too large for defined data type",
76: "Given log. name not unique",
77: "f.d. invalid for this operation",
78: "Remote address changed",
79: "Can access a needed shared lib",
80: "Accessing a corrupted shared lib",
81: ".lib section in a.out corrupted",
82: "Attempting to link in too many libs",
83: "Attempting to exec a shared library",
84: "Illegal byte sequence",
86: "Streams pipe error",
87: "Too many users",
88: "Socket operation on non-socket",
89: "Destination address required",
90: "Message too long",
91: "Protocol wrong type for socket",
92: "Protocol not available",
93: "Unknown protocol",
94: "Socket type not supported",
95: "Not supported",
96: "Protocol family not supported",
97: "Address family not supported by protocol family",
98: "Address already in use",
99: "Address not available",
100: "Network interface is not configured",
101: "Network is unreachable",
102: "Connection reset by network",
103: "Connection aborted",
104: "Connection reset by peer",
105: "No buffer space available",
106: "Socket is already connected",
107: "Socket is not connected",
108: "Can't send after socket shutdown",
109: "Too many references",
110: "Connection timed out",
111: "Connection refused",
112: "Host is down",
113: "Host is unreachable",
114: "Socket already connected",
115: "Connection already in progress",
116: "Stale file handle",
122: "Quota exceeded",
123: "No medium (in tape drive)",
125: "Operation canceled",
130: "Previous owner died",
131: "State not recoverable"
};
function getErrnoMessage(errno) {
return ERRNO_CODES[-errno] || "Unknown error";
}
function getExitCodeInfo(exitCode) {
return EXIT_CODES[exitCode];
}
var formatExitMessage = (code, signal, stderr, from) => {
let message = `exit code: ${code}`;
if (code != 0 || signal != null) {
message = `${stderr || "\n"} at ${from}`;
message += `
exit code: ${code}${getExitCodeInfo(code) ? " (" + getExitCodeInfo(code) + ")" : ""}`;
if (signal != null) {
message += `
signal: ${signal}`;
}
}
return message;
};
var formatErrorMessage = (err, from) => {
return `${err.message}
errno: ${err.errno} (${getErrnoMessage(err.errno)})
code: ${err.code}
at ${from}`;
};

// src/main/ts/zurk.ts
var ZURK = Symbol("Zurk");
var ZURKPROXY = Symbol("ZurkProxy");
var zurk = (opts) => opts.sync ? zurkSync(opts) : zurkAsync(opts);
Expand Down Expand Up @@ -77,9 +261,10 @@ var zurkifyPromise = (target, ctx) => {
return proxy;
};
var getError = (data) => {
if (data.error) return data.error;
if (data.status) return new Error(`Command failed with exit code ${data.status}`);
if (data.signal) return new Error(`Command failed with signal ${data.signal}`);
if (data.error)
return new Error(formatErrorMessage(data.error, data.stack));
if (data.status || data.signal)
return new Error(formatExitMessage(data.status, data.signal, data.stderr, data.stack));
return null;
};
var isZurkAny = (o) => (o == null ? void 0 : o[ZURK]) === ZURK;
Expand Down
4 changes: 2 additions & 2 deletions target/dts/error.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export declare const ERRNO_CODES: {
};
export declare function getErrnoMessage(errno?: number): string;
export declare function getExitCodeInfo(exitCode: number | null): string | undefined;
export declare const getExitMessage: (code: number | null, signal: NodeJS.Signals | null, stderr: string, from: string) => string;
export declare const getErrorMessage: (err: NodeJS.ErrnoException, from: string) => string;
export declare const formatExitMessage: (code: number | null, signal: NodeJS.Signals | null, stderr: string, from: string) => string;
export declare const formatErrorMessage: (err: NodeJS.ErrnoException, from: string) => string;
export declare function getCallerLocation(err?: Error): string;
export declare function getCallerLocationFromString(stackString?: string): string;
Loading

0 comments on commit 6b1eb65

Please sign in to comment.