@@ -798,38 +798,78 @@ export async function parseOptionsAndRunCLI(argsToParse: string[]) {
798798 * plain objects (which lose their Error prototype during worker thread
799799 * transfer), and arbitrary values.
800800 */
801- function describeError ( error : unknown ) : string {
801+ export function describeError (
802+ error : unknown ,
803+ seen = new WeakSet < object > ( ) ,
804+ depth = 0
805+ ) : string {
806+ if ( depth > 10 ) {
807+ return '[Max error cause depth exceeded]' ;
808+ }
802809 if ( error instanceof Error ) {
803- return error . message ;
810+ return (
811+ error . message ||
812+ describeErrorObject ( error , seen , depth , {
813+ suppressGenericErrorName : true ,
814+ } )
815+ ) ;
804816 }
805817 if ( error && typeof error === 'object' ) {
806- // Comlink-serialized errors arrive as plain objects like
807- // { name: 'ErrnoError', errno: 20 } with no .message.
808- const parts = [ ] ;
809- const obj = error as Record < string , unknown > ;
810- if ( obj [ 'name' ] ) {
811- parts . push ( String ( obj [ 'name' ] ) ) ;
812- }
813- if ( obj [ 'message' ] ) {
814- parts . push ( String ( obj [ 'message' ] ) ) ;
815- }
816- if ( obj [ 'errno' ] !== undefined ) {
817- parts . push ( `errno: ${ obj [ 'errno' ] } ` ) ;
818- }
819- if ( parts . length > 0 ) {
820- return parts . join ( ' — ' ) ;
821- }
822- // Last resort: JSON-serialize the object so we at least see
823- // what fields it has.
824- try {
825- return JSON . stringify ( error ) ;
826- } catch {
827- return String ( error ) ;
828- }
818+ return describeErrorObject ( error , seen , depth ) ;
829819 }
830820 return String ( error ) ;
831821}
832822
823+ type ErrorLikeObject = object & {
824+ name ?: unknown ;
825+ message ?: unknown ;
826+ cause ?: unknown ;
827+ errno ?: unknown ;
828+ } ;
829+
830+ function describeErrorObject (
831+ error : ErrorLikeObject ,
832+ seen : WeakSet < object > ,
833+ depth : number ,
834+ options : { suppressGenericErrorName ?: boolean } = { }
835+ ) : string {
836+ if ( seen . has ( error ) ) {
837+ return '[Circular error cause]' ;
838+ }
839+ seen . add ( error ) ;
840+
841+ // Comlink-serialized errors arrive as plain objects like
842+ // { name: 'ErrnoError', errno: 20 } with no .message.
843+ const parts = [ ] ;
844+ if (
845+ error [ 'name' ] &&
846+ ! ( options . suppressGenericErrorName && error [ 'name' ] === 'Error' )
847+ ) {
848+ parts . push ( String ( error [ 'name' ] ) ) ;
849+ }
850+ if ( error [ 'message' ] ) {
851+ parts . push ( String ( error [ 'message' ] ) ) ;
852+ }
853+ if ( error [ 'cause' ] ) {
854+ parts . push (
855+ `caused by: ${ describeError ( error [ 'cause' ] , seen , depth + 1 ) } `
856+ ) ;
857+ }
858+ if ( error [ 'errno' ] !== undefined ) {
859+ parts . push ( `errno: ${ error [ 'errno' ] } ` ) ;
860+ }
861+ if ( parts . length > 0 ) {
862+ return parts . join ( ' — ' ) ;
863+ }
864+ // Last resort: JSON-serialize the object so we at least see
865+ // what fields it has.
866+ try {
867+ return JSON . stringify ( error ) ;
868+ } catch {
869+ return String ( error ) ;
870+ }
871+ }
872+
833873function getMountForVfsPath (
834874 mounts : Mount [ ] ,
835875 vfsPath : string
0 commit comments