diff --git a/package.json b/package.json index 546e704..061415d 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,12 @@ "import": "./target/esm/index.mjs", "default": "./target/esm/index.mjs" }, + "./error": { + "types": "./target/dts/error.d.ts", + "require": "./target/cjs/error.cjs", + "import": "./target/esm/error.mjs", + "default": "./target/esm/error.mjs" + }, "./spawn": { "types": "./target/dts/spawn.d.ts", "require": "./target/cjs/spawn.cjs", diff --git a/src/main/ts/error.ts b/src/main/ts/error.ts new file mode 100644 index 0000000..6de5a5f --- /dev/null +++ b/src/main/ts/error.ts @@ -0,0 +1,209 @@ +export const 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', +} + +export const 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', +} + +export function getErrnoMessage(errno?: number): string { + return ( + ERRNO_CODES[-(errno as number) as keyof typeof ERRNO_CODES] || + 'Unknown error' + ) +} + +export function getExitCodeInfo(exitCode: number | null): string | undefined { + return EXIT_CODES[exitCode as keyof typeof EXIT_CODES] +} + +export const getExitMessage = ( + code: number | null, + signal: NodeJS.Signals | null, + stderr: string, + from: string +) => { + let message = `exit code: ${code}` + if (code != 0 || signal != null) { + message = `${stderr || '\n'} at ${from}` + message += `\n exit code: ${code}${ + getExitCodeInfo(code) ? ' (' + getExitCodeInfo(code) + ')' : '' + }` + if (signal != null) { + message += `\n signal: ${signal}` + } + } + + return message +} + +export const getErrorMessage = (err: NodeJS.ErrnoException, from: string) => { + return ( + `${err.message}\n` + + ` errno: ${err.errno} (${getErrnoMessage(err.errno)})\n` + + ` code: ${err.code}\n` + + ` at ${from}` + ) +} + +export function getCallerLocation(err = new Error()) { + return getCallerLocationFromString(err.stack) +} + +export function getCallerLocationFromString(stackString = 'unknown') { + return ( + stackString + .split(/^\s*(at\s)?/m) + .filter((s) => s?.includes(':'))[2] + ?.trim() || stackString + ) +} diff --git a/src/main/ts/spawn.ts b/src/main/ts/spawn.ts index c3f479b..4726c52 100644 --- a/src/main/ts/spawn.ts +++ b/src/main/ts/spawn.ts @@ -33,6 +33,7 @@ export type TSpawnResult = { ctx: TSpawnCtxNormalized error?: TSpawnError, child?: TChild + stack: string } export type TSpawnListeners = { @@ -77,6 +78,7 @@ export interface TSpawnCtxNormalized { fulfilled?: TSpawnResult error?: any run: (cb: () => void, ctx: TSpawnCtxNormalized) => void + stack: string } export const defaults: TSpawnCtxNormalized = { @@ -102,7 +104,8 @@ export const defaults: TSpawnCtxNormalized = { get stdout(){ return new VoidStream() }, get stderr(){ return new VoidStream() }, stdio: ['pipe', 'pipe', 'pipe'], - run: immediate + run: immediate, + stack: '' } export const normalizeCtx = (...ctxs: TSpawnCtx[]): TSpawnCtxNormalized => assign({ @@ -184,7 +187,8 @@ export const invoke = (c: TSpawnCtxNormalized): TSpawnCtxNormalized => { get stdall() { return c.store.stdall.join('') }, stdio, duration: Date.now() - now, - ctx: c + stack: c.stack, + ctx: c }) c.ee.emit('end', c.fulfilled, c) @@ -238,7 +242,8 @@ export const invoke = (c: TSpawnCtxNormalized): TSpawnCtxNormalized => { get stdall() { return c.store.stdall.join('') }, stdio, duration: Date.now() - now, - ctx: c + stack: c.stack, + ctx: c } opts.signal?.removeEventListener('abort', onAbort) c.callback(error, c.fulfilled) @@ -251,14 +256,15 @@ export const invoke = (c: TSpawnCtxNormalized): TSpawnCtxNormalized => { error, c.fulfilled = { error, - status: null, - signal: null, - stdout: '', - stderr: '', - stdall: '', + status: null, + signal: null, + stdout: '', + stderr: '', + stdall: '', stdio, duration: Date.now() - now, - ctx: c + stack: c.stack, + ctx: c } ) c.ee.emit('err', error, c) diff --git a/src/main/ts/zurk.ts b/src/main/ts/zurk.ts index dae616a..aa505d9 100644 --- a/src/main/ts/zurk.ts +++ b/src/main/ts/zurk.ts @@ -136,6 +136,7 @@ class Zurk implements TZurk { this.ctx.stdout, this.ctx.stderr ]} + get stack() { return this.ctx.stack } get duration() { return this.ctx.fulfilled?.duration ?? 0 } toString(){ return this.stdall.trim() } valueOf(){ return this.stdall.trim() } diff --git a/target/cjs/spawn.cjs b/target/cjs/spawn.cjs index 7255d91..759f090 100644 --- a/target/cjs/spawn.cjs +++ b/target/cjs/spawn.cjs @@ -71,7 +71,8 @@ var defaults = { return new VoidStream(); }, stdio: ["pipe", "pipe", "pipe"], - run: import_util.immediate + run: import_util.immediate, + stack: "" }; var normalizeCtx = (...ctxs) => (0, import_util.assign)( __spreadProps(__spreadValues({}, defaults), { @@ -154,6 +155,7 @@ var invoke = (c) => { }, stdio, duration: Date.now() - now, + stack: c.stack, ctx: c })); c.ee.emit("end", c.fulfilled, c); @@ -208,6 +210,7 @@ var invoke = (c) => { }, stdio, duration: Date.now() - now, + stack: c.stack, ctx: c }; (_a3 = opts.signal) == null ? void 0 : _a3.removeEventListener("abort", onAbort); @@ -228,6 +231,7 @@ var invoke = (c) => { stdall: "", stdio, duration: Date.now() - now, + stack: c.stack, ctx: c } ); diff --git a/target/cjs/zurk.cjs b/target/cjs/zurk.cjs index f8e2ee0..7b8fc21 100644 --- a/target/cjs/zurk.cjs +++ b/target/cjs/zurk.cjs @@ -132,6 +132,9 @@ var Zurk = class { this.ctx.stderr ]; } + get stack() { + return this.ctx.stack; + } get duration() { var _a2, _b; return (_b = (_a2 = this.ctx.fulfilled) == null ? void 0 : _a2.duration) != null ? _b : 0; diff --git a/target/dts/error.d.ts b/target/dts/error.d.ts new file mode 100644 index 0000000..7be5ac8 --- /dev/null +++ b/target/dts/error.d.ts @@ -0,0 +1,161 @@ +export declare const EXIT_CODES: { + 2: string; + 126: string; + 127: string; + 128: string; + 129: string; + 130: string; + 131: string; + 132: string; + 133: string; + 134: string; + 135: string; + 136: string; + 137: string; + 138: string; + 139: string; + 140: string; + 141: string; + 142: string; + 143: string; + 145: string; + 146: string; + 147: string; + 148: string; + 149: string; + 150: string; + 151: string; + 152: string; + 153: string; + 154: string; + 155: string; + 157: string; + 159: string; +}; +export declare const ERRNO_CODES: { + 0: string; + 1: string; + 2: string; + 3: string; + 4: string; + 5: string; + 6: string; + 7: string; + 8: string; + 9: string; + 10: string; + 11: string; + 12: string; + 13: string; + 14: string; + 15: string; + 16: string; + 17: string; + 18: string; + 19: string; + 20: string; + 21: string; + 22: string; + 23: string; + 24: string; + 25: string; + 26: string; + 27: string; + 28: string; + 29: string; + 30: string; + 31: string; + 32: string; + 33: string; + 34: string; + 35: string; + 36: string; + 37: string; + 38: string; + 39: string; + 40: string; + 42: string; + 43: string; + 44: string; + 45: string; + 46: string; + 47: string; + 48: string; + 49: string; + 50: string; + 51: string; + 52: string; + 53: string; + 54: string; + 55: string; + 56: string; + 57: string; + 59: string; + 60: string; + 61: string; + 62: string; + 63: string; + 64: string; + 65: string; + 66: string; + 67: string; + 68: string; + 69: string; + 70: string; + 71: string; + 72: string; + 73: string; + 74: string; + 75: string; + 76: string; + 77: string; + 78: string; + 79: string; + 80: string; + 81: string; + 82: string; + 83: string; + 84: string; + 86: string; + 87: string; + 88: string; + 89: string; + 90: string; + 91: string; + 92: string; + 93: string; + 94: string; + 95: string; + 96: string; + 97: string; + 98: string; + 99: string; + 100: string; + 101: string; + 102: string; + 103: string; + 104: string; + 105: string; + 106: string; + 107: string; + 108: string; + 109: string; + 110: string; + 111: string; + 112: string; + 113: string; + 114: string; + 115: string; + 116: string; + 122: string; + 123: string; + 125: string; + 130: string; + 131: string; +}; +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 function getCallerLocation(err?: Error): string; +export declare function getCallerLocationFromString(stackString?: string): string; diff --git a/target/dts/spawn.d.ts b/target/dts/spawn.d.ts index fdded45..3116c42 100644 --- a/target/dts/spawn.d.ts +++ b/target/dts/spawn.d.ts @@ -30,6 +30,7 @@ export type TSpawnResult = { ctx: TSpawnCtxNormalized; error?: TSpawnError; child?: TChild; + stack: string; }; export type TSpawnListeners = { start: (data: TChild, ctx: TSpawnCtxNormalized) => void; @@ -69,6 +70,7 @@ export interface TSpawnCtxNormalized { fulfilled?: TSpawnResult; error?: any; run: (cb: () => void, ctx: TSpawnCtxNormalized) => void; + stack: string; } export declare const defaults: TSpawnCtxNormalized; export declare const normalizeCtx: (...ctxs: TSpawnCtx[]) => TSpawnCtxNormalized; diff --git a/target/esm/spawn.mjs b/target/esm/spawn.mjs index 8105bd9..6e4a17a 100644 --- a/target/esm/spawn.mjs +++ b/target/esm/spawn.mjs @@ -47,7 +47,8 @@ var defaults = { return new VoidStream(); }, stdio: ["pipe", "pipe", "pipe"], - run: immediate + run: immediate, + stack: "" }; var normalizeCtx = (...ctxs) => assign( { @@ -133,6 +134,7 @@ var invoke = (c) => { }, stdio, duration: Date.now() - now, + stack: c.stack, ctx: c }); c.ee.emit("end", c.fulfilled, c); @@ -187,6 +189,7 @@ var invoke = (c) => { }, stdio, duration: Date.now() - now, + stack: c.stack, ctx: c }; (_a3 = opts.signal) == null ? void 0 : _a3.removeEventListener("abort", onAbort); @@ -207,6 +210,7 @@ var invoke = (c) => { stdall: "", stdio, duration: Date.now() - now, + stack: c.stack, ctx: c } ); diff --git a/target/esm/zurk.mjs b/target/esm/zurk.mjs index 19625f3..cb3c387 100644 --- a/target/esm/zurk.mjs +++ b/target/esm/zurk.mjs @@ -119,6 +119,9 @@ var Zurk = class { this.ctx.stderr ]; } + get stack() { + return this.ctx.stack; + } get duration() { var _a2, _b; return (_b = (_a2 = this.ctx.fulfilled) == null ? void 0 : _a2.duration) != null ? _b : 0;