@@ -6,6 +6,10 @@ import (
66 "fmt"
77 "io"
88 "os"
9+ "runtime"
10+ "runtime/debug"
11+ "strconv"
12+ "strings"
913)
1014
1115// RunOptions specifies options for running a command.
@@ -46,9 +50,15 @@ func run(ctx context.Context, cmd *Command, state *State) (retErr error) {
4650 if r := recover (); r != nil {
4751 switch err := r .(type ) {
4852 case error :
49- retErr = fmt .Errorf ("internal: %v" , err )
53+ // If error is from cli package (e.g., flag type mismatch), don't add location info
54+ var intErr * internalError
55+ if errors .As (err , & intErr ) {
56+ retErr = err
57+ } else {
58+ retErr = fmt .Errorf ("panic: %v\n \n %s" , err , location (4 ))
59+ }
5060 default :
51- retErr = fmt .Errorf ("recover : %v" , r )
61+ retErr = fmt .Errorf ("panic : %v" , r )
5262 }
5363 }
5464 }()
@@ -82,3 +92,34 @@ func checkAndSetRunOptions(opt *RunOptions) *RunOptions {
8292 }
8393 return opt
8494}
95+
96+ var (
97+ goModuleName string
98+ )
99+
100+ // GoModuleName returns the Go module name of the current application.
101+ func GoModuleName () string {
102+ if goModuleName == "" {
103+ if info , ok := debug .ReadBuildInfo (); ok && info .Main .Path != "" {
104+ goModuleName = info .Main .Path
105+ }
106+ }
107+ return goModuleName
108+ }
109+
110+ func location (skip int ) string {
111+ var pcs [1 ]uintptr
112+ // Need to add 2 to skip to account for this function and runtime.Callers.
113+ n := runtime .Callers (skip + 2 , pcs [:])
114+ if n == 0 {
115+ return "unknown:0"
116+ }
117+
118+ frame , _ := runtime .CallersFrames (pcs [:n ]).Next ()
119+
120+ // Trim the module name from both function and file paths for cleaner output
121+ fn := strings .TrimPrefix (frame .Function , GoModuleName ()+ "/" )
122+ file := strings .TrimPrefix (frame .File , GoModuleName ()+ "/" )
123+
124+ return fn + " " + file + ":" + strconv .Itoa (frame .Line )
125+ }
0 commit comments