Skip to content

Commit a5637f4

Browse files
authored
Track when error happens (#22)
* Track when error happens * added few tests * install.sh script updates
1 parent d402cf8 commit a5637f4

3 files changed

Lines changed: 121 additions & 6 deletions

File tree

cli/root.go

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import (
88
"time"
99

1010
"github.com/charmbracelet/fang"
11+
"github.com/spf13/cobra"
12+
13+
"github.com/duneanalytics/duneapi-client-go/config"
14+
"github.com/duneanalytics/duneapi-client-go/dune"
15+
1116
"github.com/duneanalytics/cli/authconfig"
1217
"github.com/duneanalytics/cli/cmd/auth"
1318
duneconfig "github.com/duneanalytics/cli/cmd/config"
@@ -18,9 +23,6 @@ import (
1823
"github.com/duneanalytics/cli/cmd/usage"
1924
"github.com/duneanalytics/cli/cmdutil"
2025
"github.com/duneanalytics/cli/tracking"
21-
"github.com/duneanalytics/duneapi-client-go/config"
22-
"github.com/duneanalytics/duneapi-client-go/dune"
23-
"github.com/spf13/cobra"
2426
)
2527

2628
var apiKeyFlag string
@@ -126,9 +128,40 @@ func Execute(version, commit, date, amplitudeKey string) {
126128
if err := fang.Execute(rootCmd.Context(), rootCmd,
127129
fang.WithVersion(versionStr),
128130
); err != nil {
129-
tracker.Track("unknown", tracking.StatusError, err.Error(), 0)
131+
// Build best-effort command path from os.Args (strip flags).
132+
commandPath := commandPathFromArgs(os.Args)
133+
tracker.Track(commandPath, tracking.StatusError, err.Error(), 0)
134+
// Flush the event before exiting — os.Exit does not run deferred funcs,
135+
// so defer tracker.Shutdown() above would never fire.
136+
tracker.Shutdown()
130137

131138
fmt.Fprintln(os.Stderr, err)
132139
os.Exit(1)
133140
}
134141
}
142+
143+
// commandPathFromArgs extracts the subcommand path from os.Args, skipping
144+
// the binary name, flags, and flag values so the tracked path is e.g.
145+
// "query list" even when invoked as "dune --api-key KEY query list --limit 10".
146+
func commandPathFromArgs(args []string) string {
147+
var parts []string
148+
skipNext := false
149+
for _, a := range args[1:] { // skip binary name
150+
if skipNext {
151+
skipNext = false
152+
continue
153+
}
154+
if strings.HasPrefix(a, "-") {
155+
// --flag=value is self-contained; --flag value needs to skip the next arg.
156+
if !strings.Contains(a, "=") {
157+
skipNext = true
158+
}
159+
continue
160+
}
161+
parts = append(parts, a)
162+
}
163+
if len(parts) == 0 {
164+
return "unknown"
165+
}
166+
return strings.Join(parts, " ")
167+
}

cli/root_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,58 @@ func TestPersistentPreRunEConfigFallback(t *testing.T) {
6969
err := rootCmd.PersistentPreRunE(cmd, nil)
7070
require.NoError(t, err)
7171
}
72+
73+
func TestCommandPathFromArgs(t *testing.T) {
74+
tests := []struct {
75+
name string
76+
args []string
77+
want string
78+
}{
79+
{
80+
name: "simple subcommand",
81+
args: []string{"dune", "query", "list"},
82+
want: "query list",
83+
},
84+
{
85+
name: "root flag before subcommand",
86+
args: []string{"dune", "--api-key", "KEY", "query", "list"},
87+
want: "query list",
88+
},
89+
{
90+
name: "flag with equals syntax",
91+
args: []string{"dune", "--api-key=KEY", "query", "list"},
92+
want: "query list",
93+
},
94+
{
95+
name: "trailing flags after subcommand",
96+
args: []string{"dune", "query", "list", "--limit", "10"},
97+
want: "query list",
98+
},
99+
{
100+
name: "flags before and after subcommand",
101+
args: []string{"dune", "--api-key", "KEY", "query", "list", "--limit", "10"},
102+
want: "query list",
103+
},
104+
{
105+
name: "binary only",
106+
args: []string{"dune"},
107+
want: "unknown",
108+
},
109+
{
110+
name: "only flags",
111+
args: []string{"dune", "--help"},
112+
want: "unknown",
113+
},
114+
{
115+
name: "single subcommand",
116+
args: []string{"dune", "auth"},
117+
want: "auth",
118+
},
119+
}
120+
121+
for _, tt := range tests {
122+
t.Run(tt.name, func(t *testing.T) {
123+
assert.Equal(t, tt.want, commandPathFromArgs(tt.args))
124+
})
125+
}
126+
}

install.sh

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Usage: curl -sSfL https://github.com/duneanalytics/cli/raw/main/install.sh | bash
44
#
55
# Environment variables:
6-
# INSTALL_DIR — installation directory (default: /usr/local/bin)
6+
# INSTALL_DIR — override installation directory (default: auto-detected)
77
# VERSION — specific version to install (default: latest)
88
# GITHUB_TOKEN — GitHub token for private repo access
99

@@ -30,7 +30,11 @@ main() {
3030
# Strip leading 'v' for archive name
3131
version_num="${version#v}"
3232

33-
install_dir="${INSTALL_DIR:-/usr/local/bin}"
33+
if [ -n "$INSTALL_DIR" ]; then
34+
install_dir="$INSTALL_DIR"
35+
else
36+
install_dir=$(detect_install_dir)
37+
fi
3438

3539
case "$os" in
3640
windows) ext="zip" ;;
@@ -68,16 +72,39 @@ main() {
6872

6973
chmod +x "$tmp/$binary_name"
7074

75+
mkdir -p "$install_dir" 2>/dev/null || true
76+
7177
if [ -w "$install_dir" ]; then
7278
mv "$tmp/$binary_name" "$install_dir/$binary_name"
7379
else
7480
log "Installing to ${install_dir} (requires sudo)..."
81+
sudo mkdir -p "$install_dir"
7582
sudo mv "$tmp/$binary_name" "$install_dir/$binary_name"
7683
fi
7784

7885
log "Installed ${BINARY} ${version} to ${install_dir}/${binary_name}"
7986
}
8087

88+
# Pick the best install directory by checking user-writable directories
89+
# already on PATH, falling back to /usr/local/bin (always on PATH).
90+
detect_install_dir() {
91+
for candidate in \
92+
"$HOME/.local/bin" \
93+
"$HOME/bin" \
94+
"$HOME/go/bin" \
95+
"$HOME/.cargo/bin"; do
96+
case ":$PATH:" in
97+
*":${candidate}:"*)
98+
if [ -d "$candidate" ] && [ -w "$candidate" ]; then
99+
echo "$candidate"
100+
return
101+
fi
102+
;;
103+
esac
104+
done
105+
echo "/usr/local/bin"
106+
}
107+
81108
detect_os() {
82109
os=$(uname -s | tr '[:upper:]' '[:lower:]')
83110
case "$os" in

0 commit comments

Comments
 (0)