From da6e8ca8fa6421e6df5357cd28c28af7f4253101 Mon Sep 17 00:00:00 2001 From: MABD Date: Tue, 14 Apr 2026 06:07:55 +0300 Subject: [PATCH 01/25] send mixpanel usage analytic --- cmd/gh-oss-stats/main.go | 31 ++++++++++++++++++++++++++++++- go.mod | 2 ++ go.sum | 2 ++ internal/analytic/analytics.go | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 go.sum create mode 100644 internal/analytic/analytics.go diff --git a/cmd/gh-oss-stats/main.go b/cmd/gh-oss-stats/main.go index 9ba9678..68db9df 100644 --- a/cmd/gh-oss-stats/main.go +++ b/cmd/gh-oss-stats/main.go @@ -3,14 +3,18 @@ package main import ( "fmt" "os" + "runtime" + + analytics "github.com/mabd-dev/gh-oss-stats/internal/analytic" ) -const version = "0.3.2" +const version = "0.3.4" func main() { args := os.Args[1:] if len(args) == 0 { + sendAnalyticsUsage() runMainCmd(args) return } @@ -18,13 +22,38 @@ func main() { // Route to sub-commands, or fallback to main command switch args[0] { case "badge": + sendAnalyticsUsage() runBadgeCmd(args[1:]) case "demo": + sendAnalyticsUsage() runDemoCmd(args[1:]) case "version": fmt.Printf("gh-oss-stats v%s\n", version) os.Exit(0) default: + sendAnalyticsUsage() runMainCmd(args) } } + +func sendAnalyticsUsage() { + // TODO: if first time, and no on CI tell the user we are collecting + // + save that user has been told to not tell him again + + isCI := os.Getenv("CI") != "" || os.Getenv("GITHUB_ACTIONS") != "" + firstTime := true + + if firstTime && !isCI { + println("TODO: show data collection message") + } + + analytics := analytics.CreateAnalytics() + + err := analytics.TrackToolUsage(runtime.GOOS, version, isCI) + if err != nil { + print("error sending analytics, err=") + println(err) + } else { + println("analytics sent") + } +} diff --git a/go.mod b/go.mod index 29fa8d0..67e521b 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/mabd-dev/gh-oss-stats go 1.25.4 + +require github.com/mixpanel/mixpanel-go v1.2.1 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0f31265 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/mixpanel/mixpanel-go v1.2.1 h1:iykbHKomTJjVoWU95Vt1sjZy4HLt8UOYacMEEEMFBok= +github.com/mixpanel/mixpanel-go v1.2.1/go.mod h1:mPGaNhBoZMJuLu8k7Y1KhU5n8Vw13rxQZZjHj+b9RLk= diff --git a/internal/analytic/analytics.go b/internal/analytic/analytics.go new file mode 100644 index 0000000..8cac5b0 --- /dev/null +++ b/internal/analytic/analytics.go @@ -0,0 +1,34 @@ +package analytics + +import ( + "context" + "os" + + "github.com/mixpanel/mixpanel-go" +) + +type Analytics struct { + Client *mixpanel.ApiClient +} + +func CreateAnalytics() Analytics { + mixpanelClient := mixpanel.NewApiClient(os.Getenv("MIXPANEL_PROJECT_TOKEN")) + return Analytics{Client: mixpanelClient} +} + +func (analytics Analytics) Track(name string, params map[string]any) error { + ctx := context.Background() + return analytics.Client.Track(ctx, []*mixpanel.Event{ + analytics.Client.NewEvent(name, "", params), + }) +} + +func (analytics Analytics) TrackToolUsage(os string, version string, ci bool) error { + params := map[string]any{ + "os": os, + "version": version, + "ci": ci, + "project": "gh-oss-stats", + } + return analytics.Track("usage", params) +} From 807d855b9acb8f6eb6f163489c4587f4f3bb24bb Mon Sep 17 00:00:00 2001 From: MABD Date: Tue, 14 Apr 2026 07:17:22 +0300 Subject: [PATCH 02/25] read env variable, save/read telemetry.json, show notice --- cmd/gh-oss-stats/main.go | 33 ++----------- internal/analytic/analytics.go | 8 ++-- internal/telemetry/main.go | 84 ++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 32 deletions(-) create mode 100644 internal/telemetry/main.go diff --git a/cmd/gh-oss-stats/main.go b/cmd/gh-oss-stats/main.go index 68db9df..40ebe6a 100644 --- a/cmd/gh-oss-stats/main.go +++ b/cmd/gh-oss-stats/main.go @@ -3,9 +3,8 @@ package main import ( "fmt" "os" - "runtime" - analytics "github.com/mabd-dev/gh-oss-stats/internal/analytic" + "github.com/mabd-dev/gh-oss-stats/internal/telemetry" ) const version = "0.3.4" @@ -14,7 +13,7 @@ func main() { args := os.Args[1:] if len(args) == 0 { - sendAnalyticsUsage() + telemetry.Send(version) runMainCmd(args) return } @@ -22,38 +21,16 @@ func main() { // Route to sub-commands, or fallback to main command switch args[0] { case "badge": - sendAnalyticsUsage() + telemetry.Send(version) runBadgeCmd(args[1:]) case "demo": - sendAnalyticsUsage() + telemetry.Send(version) runDemoCmd(args[1:]) case "version": fmt.Printf("gh-oss-stats v%s\n", version) os.Exit(0) default: - sendAnalyticsUsage() + telemetry.Send(version) runMainCmd(args) } } - -func sendAnalyticsUsage() { - // TODO: if first time, and no on CI tell the user we are collecting - // + save that user has been told to not tell him again - - isCI := os.Getenv("CI") != "" || os.Getenv("GITHUB_ACTIONS") != "" - firstTime := true - - if firstTime && !isCI { - println("TODO: show data collection message") - } - - analytics := analytics.CreateAnalytics() - - err := analytics.TrackToolUsage(runtime.GOOS, version, isCI) - if err != nil { - print("error sending analytics, err=") - println(err) - } else { - println("analytics sent") - } -} diff --git a/internal/analytic/analytics.go b/internal/analytic/analytics.go index 8cac5b0..5b1df6e 100644 --- a/internal/analytic/analytics.go +++ b/internal/analytic/analytics.go @@ -25,10 +25,10 @@ func (analytics Analytics) Track(name string, params map[string]any) error { func (analytics Analytics) TrackToolUsage(os string, version string, ci bool) error { params := map[string]any{ - "os": os, - "version": version, - "ci": ci, - "project": "gh-oss-stats", + "os": os, + "tool-version": version, + "ci": ci, + "project": "gh-oss-stats", } return analytics.Track("usage", params) } diff --git a/internal/telemetry/main.go b/internal/telemetry/main.go new file mode 100644 index 0000000..624333a --- /dev/null +++ b/internal/telemetry/main.go @@ -0,0 +1,84 @@ +package telemetry + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + analytics "github.com/mabd-dev/gh-oss-stats/internal/analytic" +) + +var ( + toolName = "gh-oss-stats" + telemetryFileName = "telemetry.json" +) + +type Telemetry struct { + NoticeShown bool `json:"noticeShown"` +} + +func Send(version string) { + sendTrackUsageEvent := func(isCI bool) error { + analytics := analytics.CreateAnalytics() + return analytics.TrackToolUsage(runtime.GOOS, version, isCI) + } + + isCI := os.Getenv("CI") != "" || os.Getenv("GITHUB_ACTIONS") != "" + fmt.Printf("isCI=%v\n", isCI) + + if isCI { + sendTrackUsageEvent(true) + return + } + + telemetryDisabled := os.Getenv("GH_OSS_STATS_TELEMETRY_DISABLED") + if strings.ToLower(telemetryDisabled) == "true" { + fmt.Println("telemetry disabled") + return + } + + firstRun := IsFirstRun() + + if firstRun { + PrintNotice() + if err := MarkFirstRunDone(); err != nil { + println(err) + } + } + + sendTrackUsageEvent(false) +} + +func PrintNotice() { + println("We send analytics data!") +} + +func IsFirstRun() bool { + configDir, _ := os.UserConfigDir() + markerPath := filepath.Join(configDir, toolName, telemetryFileName) + _, err := os.Stat(markerPath) + return os.IsNotExist(err) +} + +func MarkFirstRunDone() error { + telemetry := Telemetry{ + NoticeShown: true, + } + jsonData, err := json.Marshal(telemetry) + if err != nil { + return err + } + + configDir, _ := os.UserConfigDir() + dir := filepath.Join(configDir, toolName) + if err := os.MkdirAll(dir, 0755); err != nil { + return err + } + + filePath := filepath.Join(dir, telemetryFileName) + + return os.WriteFile(filePath, jsonData, 0644) +} From 2b9bf46edac895be21a1442de77b925ab8e4d1df Mon Sep 17 00:00:00 2001 From: MABD Date: Tue, 14 Apr 2026 07:26:29 +0300 Subject: [PATCH 03/25] updated first use message --- internal/telemetry/main.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/telemetry/main.go b/internal/telemetry/main.go index 624333a..31a7917 100644 --- a/internal/telemetry/main.go +++ b/internal/telemetry/main.go @@ -6,7 +6,6 @@ import ( "os" "path/filepath" "runtime" - "strings" analytics "github.com/mabd-dev/gh-oss-stats/internal/analytic" ) @@ -27,15 +26,13 @@ func Send(version string) { } isCI := os.Getenv("CI") != "" || os.Getenv("GITHUB_ACTIONS") != "" - fmt.Printf("isCI=%v\n", isCI) - if isCI { sendTrackUsageEvent(true) return } telemetryDisabled := os.Getenv("GH_OSS_STATS_TELEMETRY_DISABLED") - if strings.ToLower(telemetryDisabled) == "true" { + if telemetryDisabled == "1" { fmt.Println("telemetry disabled") return } @@ -53,7 +50,10 @@ func Send(version string) { } func PrintNotice() { - println("We send analytics data!") + fmt.Println("gh-oss-stats collects anonymous usage telemetry to help improve the tool.") + fmt.Println("No personal data or GitHub credentials are collected.") + fmt.Println("To disable: export GH_OSS_STATS_TELEMETRY_DISABLED=1") + fmt.Println("More info: https://github.com/mabd-dev/gh-oss-stats#telemetry") } func IsFirstRun() bool { From c84f08100bfd5193186e7dd9bf13d4d85178faf2 Mon Sep 17 00:00:00 2001 From: MABD Date: Tue, 14 Apr 2026 07:26:37 +0300 Subject: [PATCH 04/25] added telemetry section in readme --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/README.md b/README.md index 6f9a784..1a120f1 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,31 @@ For local development or custom integrations, see [docs/TECHNICAL.md](docs/TECHN 📖 **Full technical documentation:** [docs/TECHNICAL.md](docs/TECHNICAL.md) +*** + +## Telemetry + +`gh-oss-stats` collects anonymous usage data to help understand how the tool +is used and improve it over time. You'll see a one-time notice about this on +first run. + +**What is collected:** +- `os` — operating system (linux, windows, darwin) +- `version` — tool version being used +- `ci` — whether the tool is running in a CI environment + +Nothing personal is collected — no usernames, tokens, or file paths. +Events are sent to a [mixpanel](https://mixpanel.com/home/) (a third-party analytics service) and visible only to the maintainer. + +### Disable telemetry + +Add this to your shell config (`~/.zshrc` or `~/.bashrc`): +```sh +export GH_OSS_STATS_TELEMETRY_DISABLED=1 +``` + +*** + ## License See [LICENSE](LICENSE) file. From 2fd156fd7e4a86fface816ec33afd75c85c8d310 Mon Sep 17 00:00:00 2001 From: MABD Date: Tue, 14 Apr 2026 07:33:04 +0300 Subject: [PATCH 05/25] removed claude.md --- .gitignore | 1 + CLAUDE.md | 189 ------------------- internal/telemetry/{main.go => telemetry.go} | 5 +- 3 files changed, 3 insertions(+), 192 deletions(-) delete mode 100644 CLAUDE.md rename internal/telemetry/{main.go => telemetry.go} (94%) diff --git a/.gitignore b/.gitignore index 005c643..a0acbbf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .claude .serena .mcp.json +claude.local.md commands.nu /gh-oss-stats scripts/ diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 8edb248..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,189 +0,0 @@ -# CLAUDE.md - -> This file provides context for Claude Code CLI when working on this project. - -## Project Overview - -**gh-oss-stats** is a Go library + CLI tool that fetches a GitHub user's open source contributions to external repositories (repos they don't own). It outputs structured JSON for consumption by other tools (websites, badge services, etc.). - -**Organization:** `mabd-dev` -**Repository:** `github.com/mabd-dev/gh-oss-stats` -**Go Version:** 1.21+ - -## Architecture - -``` -gh-oss-stats/ -├── cmd/gh-oss-stats/main.go # CLI entry point (thin wrapper) -├── pkg/ossstats/ # PUBLIC API - importable by external projects -│ ├── client.go # Client struct + New() constructor -│ ├── contributions.go # Core logic: GetContributions() -│ ├── types.go # All exported types (Stats, Contribution, etc.) -│ └── options.go # Functional options (WithToken, WithLOC, etc.) -├── internal/github/ # PRIVATE - GitHub API implementation -│ ├── api.go # HTTP client, request helpers -│ ├── ratelimit.go # Rate limit handling, backoff -│ └── types.go # API response structs (internal only) -├── go.mod -└── README.md -``` - -**Key Principle:** Library-first design. All logic lives in `pkg/ossstats/`. The CLI in `cmd/` is just a thin wrapper that parses flags and calls the library. - -## Core Types - -```go -// pkg/ossstats/types.go - -type Stats struct { - Username string `json:"username"` - GeneratedAt time.Time `json:"generated_at"` - Summary Summary `json:"summary"` - Contributions []Contribution `json:"contributions"` -} - -type Summary struct { - TotalProjects int `json:"total_projects"` - TotalPRsMerged int `json:"total_prs_merged"` - TotalCommits int `json:"total_commits"` - TotalAdditions int `json:"total_additions"` - TotalDeletions int `json:"total_deletions"` -} - -type Contribution struct { - Repo string `json:"repo"` - Owner string `json:"owner"` - RepoName string `json:"repo_name"` - Description string `json:"description"` - RepoURL string `json:"repo_url"` - Stars int `json:"stars"` - PRsMerged int `json:"prs_merged"` - Commits int `json:"commits"` - Additions int `json:"additions"` - Deletions int `json:"deletions"` - FirstContribution time.Time `json:"first_contribution"` - LastContribution time.Time `json:"last_contribution"` -} -``` - -## GitHub API Strategy - -**Step 1: Find merged PRs to external repos** -``` -GET /search/issues?q=author:{username}+type:pr+is:merged+-user:{username}&per_page=100 -``` -*Note: Organizations can be excluded by appending `-org:{orgname}` to the query for each excluded org.* - -**Step 2: Get PR details (commits, additions, deletions)** -``` -GET /repos/{owner}/{repo}/pulls/{pull_number} -``` - -**Step 3: Get repo metadata (stars, description)** -``` -GET /repos/{owner}/{repo} -``` - -## Functional Options Pattern - -Always use this pattern for client configuration: - -```go -client := ossstats.New( - ossstats.WithToken(token), - ossstats.WithLOC(true), - ossstats.WithMinStars(100), - ossstats.WithExcludeOrgs([]string{"my-org", "my-company"}), -) -``` - -Required options to implement: -- `WithToken(string)` - GitHub PAT (required for reasonable rate limits) -- `WithLOC(bool)` - Include lines of code metrics (default: true) -- `WithPRDetails(bool)` - Include detailed PR list (default: false) -- `WithMinStars(int)` - Filter repos by minimum stars (default: 0) -- `WithMaxPRs(int)` - Limit PRs fetched (default: 500) -- `WithExcludeOrgs([]string)` - Exclude organizations from the report (default: none) -- `WithTimeout(time.Duration)` - Overall timeout (default: 5m) -- `WithLogger(Logger)` - Custom logger interface -- `WithHTTPClient(*http.Client)` - Custom HTTP client - -## CLI Flags - -``` ---user, -u string GitHub username (required) ---token, -t string GitHub token (default: $GITHUB_TOKEN) ---include-loc bool Include LOC metrics (default: true) ---include-prs bool Include PR details (default: false) ---min-stars int Minimum repo stars (default: 0) ---max-prs int Max PRs to fetch (default: 500) ---exclude-orgs string Comma-separated list of organizations to exclude ---output, -o string Output file (default: stdout) ---verbose, -v bool Verbose logging to stderr ---timeout duration Timeout (default: 5m) ---version bool Print version -``` - -## Rate Limit Handling - -GitHub limits: -- Core API: 5,000/hour (authenticated) -- Search API: 30/minute (authenticated) - -Implementation requirements: -1. Check `X-RateLimit-Remaining` and `X-RateLimit-Reset` headers -2. Exponential backoff on 429 responses -3. 2-second delay between search API calls -4. Return partial results with `ErrPartialResults` if rate limited mid-fetch - -## Error Types - -```go -type ErrRateLimited struct { ResetAt time.Time; Message string } -type ErrAuthentication struct { Message string } -type ErrNotFound struct { Username string } -type ErrPartialResults struct { Stats *Stats; Errors []error; Message string } -``` - -## Commands - -```bash -# Run tests -go test ./... - -# Build CLI -go build -o gh-oss-stats ./cmd/gh-oss-stats - -# Install locally -go install ./cmd/gh-oss-stats - -# Run CLI -./gh-oss-stats --user mabd-dev --token $GITHUB_TOKEN - -# Lint (if golangci-lint installed) -golangci-lint run -``` - -## Implementation Notes - -1. **All API calls must accept `context.Context`** for cancellation/timeout -2. **No external dependencies** in `pkg/ossstats/` - stdlib only -3. **Pagination:** GitHub returns max 100 items per page; handle `Link` header -4. **Concurrency:** Consider parallel PR fetching with `errgroup` (limit to 5 concurrent) -5. **Timestamps:** Always UTC, use `time.RFC3339` for JSON -6. **Logging:** Use the `Logger` interface, never `fmt.Print` in library code - -## Testing - -- Unit tests: Mock HTTP responses using `httptest.Server` -- Test data: Store fixtures in `pkg/ossstats/testdata/` -- Integration tests: Tag with `//go:build integration` and skip without token - -## Do NOT - -- Add external dependencies without strong justification -- Put business logic in `cmd/` - it belongs in `pkg/ossstats/` -- Make unauthenticated requests by default (rate limits too restrictive) -- Ignore context cancellation -- Return zero values on error - always return descriptive errors -- Use `log.Fatal` or `os.Exit` in library code diff --git a/internal/telemetry/main.go b/internal/telemetry/telemetry.go similarity index 94% rename from internal/telemetry/main.go rename to internal/telemetry/telemetry.go index 31a7917..9f0827a 100644 --- a/internal/telemetry/main.go +++ b/internal/telemetry/telemetry.go @@ -27,13 +27,12 @@ func Send(version string) { isCI := os.Getenv("CI") != "" || os.Getenv("GITHUB_ACTIONS") != "" if isCI { - sendTrackUsageEvent(true) + go sendTrackUsageEvent(true) return } telemetryDisabled := os.Getenv("GH_OSS_STATS_TELEMETRY_DISABLED") if telemetryDisabled == "1" { - fmt.Println("telemetry disabled") return } @@ -46,7 +45,7 @@ func Send(version string) { } } - sendTrackUsageEvent(false) + go sendTrackUsageEvent(false) } func PrintNotice() { From 1deb1f5e48c5b16f5fa71873e367320cc7c7f068 Mon Sep 17 00:00:00 2001 From: MABD Date: Tue, 14 Apr 2026 17:46:53 +0300 Subject: [PATCH 06/25] added file utils file --- internal/analytic/analytics.go | 12 +++-- internal/utils/file.go | 81 ++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 internal/utils/file.go diff --git a/internal/analytic/analytics.go b/internal/analytic/analytics.go index 5b1df6e..a74363e 100644 --- a/internal/analytic/analytics.go +++ b/internal/analytic/analytics.go @@ -8,18 +8,22 @@ import ( ) type Analytics struct { - Client *mixpanel.ApiClient + userUUID string + Client *mixpanel.ApiClient } -func CreateAnalytics() Analytics { +func CreateAnalytics(userUUID string) Analytics { mixpanelClient := mixpanel.NewApiClient(os.Getenv("MIXPANEL_PROJECT_TOKEN")) - return Analytics{Client: mixpanelClient} + return Analytics{ + userUUID: userUUID, + Client: mixpanelClient, + } } func (analytics Analytics) Track(name string, params map[string]any) error { ctx := context.Background() return analytics.Client.Track(ctx, []*mixpanel.Event{ - analytics.Client.NewEvent(name, "", params), + analytics.Client.NewEvent(name, analytics.userUUID, params), }) } diff --git a/internal/utils/file.go b/internal/utils/file.go new file mode 100644 index 0000000..a2bee8a --- /dev/null +++ b/internal/utils/file.go @@ -0,0 +1,81 @@ +package utils + +import ( + "errors" + "os" + "path/filepath" + "strings" +) + +// FileExists checks if a file exists at the given path. +// Returns (true, nil) if the file exists, +// (false, nil) if it does not exist, +// or (false, err) if an error other than "not exist" occurs. +func FileExists(path string) (bool, error) { + _, err := os.Stat(path) + if err == nil { + // file already exists, do nothing + return true, nil + } + + if errors.Is(err, os.ErrNotExist) { + // file not found + return false, nil + } + + return false, err +} + +// DirExists checks if a directory exists at the given path. +// Returns (true, nil) if the directory exists, +// (false, nil) if it does not exist, +// or (false, err) if an error other than "not exist" occurs. +func DirExists(path string) (bool, error) { + info, err := os.Stat(path) + if err == nil { + return info.IsDir(), nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} + +// WriteToFile Write data to file and create all parent folders if needed +func WriteToFile(data []byte, path string) error { + path, err := expandPath(path) + if err != nil { + return err + } + + // Create parent directories if needed + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + return err + } + + return os.WriteFile(path, data, 0o644) +} + +// expandPath expands a filesystem path that may start with '~' into an +// absolute path using the current user's home directory. +// +// Examples: +// +// expandPath("~/Documents/file.txt") -> "/Users/someone/Documents/file.txt" +// expandPath("/tmp/file.txt") -> "/tmp/file.txt" +// +// Only a leading '~' is expanded. If the path does not start with '~', +// it is returned unchanged. +// +// Returns the expanded absolute path or an error if the home directory +// cannot be determined. +func expandPath(path string) (string, error) { + if strings.HasPrefix(path, "~") { + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + return filepath.Join(home, path[1:]), nil + } + return path, nil +} From 51f880fd71e5e45e67955aab961f695c5fa636e7 Mon Sep 17 00:00:00 2001 From: MABD Date: Tue, 14 Apr 2026 17:47:23 +0300 Subject: [PATCH 07/25] add uuid to telemetry json file --- go.mod | 5 +- go.sum | 2 + internal/telemetry/telemetry.go | 105 ++++++++++++++++++++++++-------- 3 files changed, 84 insertions(+), 28 deletions(-) diff --git a/go.mod b/go.mod index 67e521b..9c9c3a0 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/mabd-dev/gh-oss-stats go 1.25.4 -require github.com/mixpanel/mixpanel-go v1.2.1 // indirect +require ( + github.com/google/uuid v1.6.0 + github.com/mixpanel/mixpanel-go v1.2.1 // indirect +) diff --git a/go.sum b/go.sum index 0f31265..679527f 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/mixpanel/mixpanel-go v1.2.1 h1:iykbHKomTJjVoWU95Vt1sjZy4HLt8UOYacMEEEMFBok= github.com/mixpanel/mixpanel-go v1.2.1/go.mod h1:mPGaNhBoZMJuLu8k7Y1KhU5n8Vw13rxQZZjHj+b9RLk= diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go index 9f0827a..c0dbd2d 100644 --- a/internal/telemetry/telemetry.go +++ b/internal/telemetry/telemetry.go @@ -7,7 +7,10 @@ import ( "path/filepath" "runtime" + "github.com/google/uuid" + analytics "github.com/mabd-dev/gh-oss-stats/internal/analytic" + "github.com/mabd-dev/gh-oss-stats/internal/utils" ) var ( @@ -16,57 +19,89 @@ var ( ) type Telemetry struct { - NoticeShown bool `json:"noticeShown"` + NoticeShown bool `json:"noticeShown"` + UserUUID string `json:"userUUID"` } func Send(version string) { - sendTrackUsageEvent := func(isCI bool) error { - analytics := analytics.CreateAnalytics() - return analytics.TrackToolUsage(runtime.GOOS, version, isCI) + telemetry, err := readOrCreateTelemetry() + if err != nil { + fmt.Printf("failed to read/create telemetry, %v\n", err.Error()) + return } isCI := os.Getenv("CI") != "" || os.Getenv("GITHUB_ACTIONS") != "" if isCI { - go sendTrackUsageEvent(true) + sendTrackUsageEvent(telemetry.UserUUID, version, true) return } telemetryDisabled := os.Getenv("GH_OSS_STATS_TELEMETRY_DISABLED") if telemetryDisabled == "1" { + println("telemetry disabled") return } - firstRun := IsFirstRun() - - if firstRun { - PrintNotice() - if err := MarkFirstRunDone(); err != nil { + if !telemetry.NoticeShown { + printNotice() + telemetry.NoticeShown = true + if err := storeTelemetry(*telemetry); err != nil { println(err) } } - go sendTrackUsageEvent(false) + sendTrackUsageEvent(telemetry.UserUUID, version, false) } -func PrintNotice() { - fmt.Println("gh-oss-stats collects anonymous usage telemetry to help improve the tool.") - fmt.Println("No personal data or GitHub credentials are collected.") - fmt.Println("To disable: export GH_OSS_STATS_TELEMETRY_DISABLED=1") - fmt.Println("More info: https://github.com/mabd-dev/gh-oss-stats#telemetry") +func readOrCreateTelemetry() (*Telemetry, error) { + t, err := readTelemetry() + if err != nil { + println("failed to read telemetry") + return nil, err + } + if t == nil { + userUUID := uuid.New().String() + telemetry := Telemetry{ + NoticeShown: false, + UserUUID: userUUID, + } + if err := storeTelemetry(telemetry); err != nil { + println("shit") + return nil, err + } + return &telemetry, nil + } + + return t, nil } -func IsFirstRun() bool { +func readTelemetry() (*Telemetry, error) { configDir, _ := os.UserConfigDir() - markerPath := filepath.Join(configDir, toolName, telemetryFileName) - _, err := os.Stat(markerPath) - return os.IsNotExist(err) -} + telementryPath := filepath.Join(configDir, toolName, telemetryFileName) + + exists, err := utils.FileExists(telementryPath) + if err != nil { + return nil, err + } + if !exists { + return nil, nil + } -func MarkFirstRunDone() error { - telemetry := Telemetry{ - NoticeShown: true, + data, err := os.ReadFile(telementryPath) + if err != nil { + return nil, err } - jsonData, err := json.Marshal(telemetry) + + var telemetry Telemetry + if err := json.Unmarshal(data, &telemetry); err != nil { + return nil, err + } + + return &telemetry, nil +} + +func storeTelemetry(t Telemetry) error { + jsonData, err := json.Marshal(t) if err != nil { return err } @@ -76,8 +111,24 @@ func MarkFirstRunDone() error { if err := os.MkdirAll(dir, 0755); err != nil { return err } - filePath := filepath.Join(dir, telemetryFileName) - return os.WriteFile(filePath, jsonData, 0644) + return utils.WriteToFile(jsonData, filePath) +} + +func printNotice() { + fmt.Println("gh-oss-stats collects anonymous usage telemetry to help improve the tool.") + fmt.Println("No personal data or GitHub credentials are collected.") + fmt.Println("To disable: export GH_OSS_STATS_TELEMETRY_DISABLED=1") + fmt.Println("More info: https://github.com/mabd-dev/gh-oss-stats#telemetry") +} + +func sendTrackUsageEvent(userUUID string, version string, isCI bool) error { + analytics := analytics.CreateAnalytics(userUUID) + println("analytics sent") + err := analytics.TrackToolUsage(runtime.GOOS, version, isCI) + if err != nil { + fmt.Printf("failed to send analytics, userUUID=%v, error=%v\n", userUUID, err.Error()) + } + return err } From f200924bc4b949eb8742e9c1022e9909169e0b82 Mon Sep 17 00:00:00 2001 From: MABD Date: Tue, 14 Apr 2026 17:56:19 +0300 Subject: [PATCH 08/25] first check if tool is disabled --- internal/telemetry/telemetry.go | 39 ++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go index c0dbd2d..83c3d6a 100644 --- a/internal/telemetry/telemetry.go +++ b/internal/telemetry/telemetry.go @@ -30,18 +30,18 @@ func Send(version string) { return } - isCI := os.Getenv("CI") != "" || os.Getenv("GITHUB_ACTIONS") != "" - if isCI { - sendTrackUsageEvent(telemetry.UserUUID, version, true) - return - } - telemetryDisabled := os.Getenv("GH_OSS_STATS_TELEMETRY_DISABLED") if telemetryDisabled == "1" { println("telemetry disabled") return } + isCI := os.Getenv("CI") != "" || os.Getenv("GITHUB_ACTIONS") != "" + if isCI { + sendTrackUsageEvent(telemetry.UserUUID, version, true) + return + } + if !telemetry.NoticeShown { printNotice() telemetry.NoticeShown = true @@ -59,20 +59,23 @@ func readOrCreateTelemetry() (*Telemetry, error) { println("failed to read telemetry") return nil, err } - if t == nil { - userUUID := uuid.New().String() - telemetry := Telemetry{ - NoticeShown: false, - UserUUID: userUUID, - } - if err := storeTelemetry(telemetry); err != nil { - println("shit") - return nil, err - } - return &telemetry, nil + + if t != nil { + return t, nil } - return t, nil + // Create + save new telemetry file + + userUUID := uuid.New().String() + telemetry := Telemetry{ + NoticeShown: false, + UserUUID: userUUID, + } + if err := storeTelemetry(telemetry); err != nil { + println("shit") + return nil, err + } + return &telemetry, nil } func readTelemetry() (*Telemetry, error) { From e1aa1f31f78035007eb5c554a4aafe421b0e1ec6 Mon Sep 17 00:00:00 2001 From: MABD Date: Tue, 14 Apr 2026 17:59:12 +0300 Subject: [PATCH 09/25] removed debugging prints --- internal/telemetry/telemetry.go | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go index 83c3d6a..ae2e038 100644 --- a/internal/telemetry/telemetry.go +++ b/internal/telemetry/telemetry.go @@ -26,13 +26,11 @@ type Telemetry struct { func Send(version string) { telemetry, err := readOrCreateTelemetry() if err != nil { - fmt.Printf("failed to read/create telemetry, %v\n", err.Error()) return } telemetryDisabled := os.Getenv("GH_OSS_STATS_TELEMETRY_DISABLED") if telemetryDisabled == "1" { - println("telemetry disabled") return } @@ -44,10 +42,9 @@ func Send(version string) { if !telemetry.NoticeShown { printNotice() + telemetry.NoticeShown = true - if err := storeTelemetry(*telemetry); err != nil { - println(err) - } + storeTelemetry(*telemetry) } sendTrackUsageEvent(telemetry.UserUUID, version, false) @@ -56,7 +53,6 @@ func Send(version string) { func readOrCreateTelemetry() (*Telemetry, error) { t, err := readTelemetry() if err != nil { - println("failed to read telemetry") return nil, err } @@ -72,7 +68,6 @@ func readOrCreateTelemetry() (*Telemetry, error) { UserUUID: userUUID, } if err := storeTelemetry(telemetry); err != nil { - println("shit") return nil, err } return &telemetry, nil @@ -128,10 +123,5 @@ func printNotice() { func sendTrackUsageEvent(userUUID string, version string, isCI bool) error { analytics := analytics.CreateAnalytics(userUUID) - println("analytics sent") - err := analytics.TrackToolUsage(runtime.GOOS, version, isCI) - if err != nil { - fmt.Printf("failed to send analytics, userUUID=%v, error=%v\n", userUUID, err.Error()) - } - return err + return analytics.TrackToolUsage(runtime.GOOS, version, isCI) } From 66c57de236d9d5e255c9706c5fdaf427a986fc36 Mon Sep 17 00:00:00 2001 From: MABD Date: Thu, 16 Apr 2026 07:18:41 +0300 Subject: [PATCH 10/25] add indentation to Telemetry parsing --- internal/telemetry/telemetry.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go index ae2e038..6865e53 100644 --- a/internal/telemetry/telemetry.go +++ b/internal/telemetry/telemetry.go @@ -99,7 +99,7 @@ func readTelemetry() (*Telemetry, error) { } func storeTelemetry(t Telemetry) error { - jsonData, err := json.Marshal(t) + jsonData, err := json.MarshalIndent(t, "", " ") if err != nil { return err } From 60fa97bdb2187ff8031130b46a721917c9861120 Mon Sep 17 00:00:00 2001 From: MABD Date: Thu, 16 Apr 2026 07:33:44 +0300 Subject: [PATCH 11/25] add ldflags to build script --- .github/workflows/release.yml | 6 +++--- .github/workflows/tests.yml | 6 +++--- internal/telemetry/telemetry.go | 2 ++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d8ee232..8b980a2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,9 +34,9 @@ jobs: run: | echo "Building gh-oss-stats ..." mkdir -p dist - GOOS=linux GOARCH=amd64 go build -o dist/gh-oss-stats-linux-amd64 ./cmd/gh-oss-stats/*.go - GOOS=linux GOARCH=arm64 go build -o dist/gh-oss-stats-linux-arm64 ./cmd/gh-oss-stats/*.go - GOOS=darwin GOARCH=arm64 go build -o dist/gh-oss-stats-darwin-arm64 ./cmd/gh-oss-stats/*.go + GOOS=linux GOARCH=amd64 go build -o dist/gh-oss-stats-linux-amd64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/telemetry/telemetry.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats/*.go + GOOS=linux GOARCH=arm64 go build -o dist/gh-oss-stats-linux-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/telemetry/telemetry.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats/*.go + GOOS=darwin GOARCH=arm64 go build -o dist/gh-oss-stats-darwin-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/telemetry/telemetry.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats/*.go - name: Trigger pkg.go.dev indexing run: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1874bac..333abf7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -59,9 +59,9 @@ jobs: run: | echo "Building gh-oss-stats ..." mkdir -p dist - GOOS=linux GOARCH=amd64 go build -o dist/gh-oss-stats-linux-amd64 ./cmd/gh-oss-stats/*.go - GOOS=linux GOARCH=arm64 go build -o dist/gh-oss-stats-linux-arm64 ./cmd/gh-oss-stats/*.go - GOOS=darwin GOARCH=arm64 go build -o dist/gh-oss-stats-darwin-arm64 ./cmd/gh-oss-stats/*.go + GOOS=linux GOARCH=amd64 go build -o dist/gh-oss-stats-linux-amd64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/telemetry/telemetry.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats/*.go + GOOS=linux GOARCH=arm64 go build -o dist/gh-oss-stats-linux-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/telemetry/telemetry.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats/*.go + GOOS=darwin GOARCH=arm64 go build -o dist/gh-oss-stats-darwin-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/telemetry/telemetry.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats/*.go - name: Verify binaries run: | diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go index 6865e53..be335e4 100644 --- a/internal/telemetry/telemetry.go +++ b/internal/telemetry/telemetry.go @@ -18,6 +18,8 @@ var ( telemetryFileName = "telemetry.json" ) +var mixpanelToken = "dev-token" + type Telemetry struct { NoticeShown bool `json:"noticeShown"` UserUUID string `json:"userUUID"` From d171ccf141354f2bc05a5567a5f7572ffb0d4bb9 Mon Sep 17 00:00:00 2001 From: MABD Date: Thu, 16 Apr 2026 07:37:04 +0300 Subject: [PATCH 12/25] handle if get configDir failed --- internal/telemetry/telemetry.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go index be335e4..217e6ad 100644 --- a/internal/telemetry/telemetry.go +++ b/internal/telemetry/telemetry.go @@ -76,7 +76,11 @@ func readOrCreateTelemetry() (*Telemetry, error) { } func readTelemetry() (*Telemetry, error) { - configDir, _ := os.UserConfigDir() + configDir, err := os.UserConfigDir() + if err != nil { + return nil, err + } + telementryPath := filepath.Join(configDir, toolName, telemetryFileName) exists, err := utils.FileExists(telementryPath) @@ -106,7 +110,11 @@ func storeTelemetry(t Telemetry) error { return err } - configDir, _ := os.UserConfigDir() + configDir, err := os.UserConfigDir() + if err != nil { + return err + } + dir := filepath.Join(configDir, toolName) if err := os.MkdirAll(dir, 0755); err != nil { return err From 75d89e7a12ba4cf5659f1861049d59e5d0b8d9c0 Mon Sep 17 00:00:00 2001 From: MABD Date: Thu, 16 Apr 2026 07:40:52 +0300 Subject: [PATCH 13/25] updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a120f1..5fbeeee 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ Customize your badge by passing inputs to the action: with: github-token: ${{ secrets.GITHUB_TOKEN }} badge-path: 'images/oss-badge.svg' - badge-style: 'detailed' # summary, compact, or detailed + badge-style: 'detailed' # summary, compact, or detailed badge-theme: 'nord' # dark, light, nord, dracula, gruvbox-light, gruvbox-dark, etc... badge-variant: 'text-based' # default or text-based min-stars: '100' # Filter repos by minimum stars From 5e3b7a2ccb3fbfbb51cf849d804b6436cf2511d6 Mon Sep 17 00:00:00 2001 From: MABD Date: Thu, 16 Apr 2026 07:41:10 +0300 Subject: [PATCH 14/25] added v0.3.5 release notes --- cmd/gh-oss-stats/main.go | 2 +- docs/release-notes/v0.3.5.md | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 docs/release-notes/v0.3.5.md diff --git a/cmd/gh-oss-stats/main.go b/cmd/gh-oss-stats/main.go index 40ebe6a..5ec3b27 100644 --- a/cmd/gh-oss-stats/main.go +++ b/cmd/gh-oss-stats/main.go @@ -7,7 +7,7 @@ import ( "github.com/mabd-dev/gh-oss-stats/internal/telemetry" ) -const version = "0.3.4" +const version = "0.3.5" func main() { args := os.Args[1:] diff --git a/docs/release-notes/v0.3.5.md b/docs/release-notes/v0.3.5.md new file mode 100644 index 0000000..cf86c79 --- /dev/null +++ b/docs/release-notes/v0.3.5.md @@ -0,0 +1,11 @@ +# Release Notes: v0.3.5 + +Release Date: April XX, 2026 +Codename: + + +## ✨ Features + +- NEW: analytics usage tracking + + From a7c90b78a0d286d848e4337629b814358c5bcf71 Mon Sep 17 00:00:00 2001 From: MABD Date: Thu, 16 Apr 2026 09:01:57 +0300 Subject: [PATCH 15/25] fixed analytics package name. fix build script --- .github/workflows/release.yml | 6 +++--- .github/workflows/tests.yml | 6 +++--- go.mod | 2 +- internal/{analytic => analytics}/analytics.go | 7 +++++-- internal/telemetry/telemetry.go | 4 +--- 5 files changed, 13 insertions(+), 12 deletions(-) rename internal/{analytic => analytics}/analytics.go (83%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8b980a2..ceb70ca 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,9 +34,9 @@ jobs: run: | echo "Building gh-oss-stats ..." mkdir -p dist - GOOS=linux GOARCH=amd64 go build -o dist/gh-oss-stats-linux-amd64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/telemetry/telemetry.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats/*.go - GOOS=linux GOARCH=arm64 go build -o dist/gh-oss-stats-linux-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/telemetry/telemetry.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats/*.go - GOOS=darwin GOARCH=arm64 go build -o dist/gh-oss-stats-darwin-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/telemetry/telemetry.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats/*.go + GOOS=linux GOARCH=amd64 go build -o dist/gh-oss-stats-linux-amd64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats + GOOS=linux GOARCH=arm64 go build -o dist/gh-oss-stats-linux-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats + GOOS=darwin GOARCH=arm64 go build -o dist/gh-oss-stats-darwin-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats - name: Trigger pkg.go.dev indexing run: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 333abf7..10d2206 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -59,9 +59,9 @@ jobs: run: | echo "Building gh-oss-stats ..." mkdir -p dist - GOOS=linux GOARCH=amd64 go build -o dist/gh-oss-stats-linux-amd64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/telemetry/telemetry.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats/*.go - GOOS=linux GOARCH=arm64 go build -o dist/gh-oss-stats-linux-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/telemetry/telemetry.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats/*.go - GOOS=darwin GOARCH=arm64 go build -o dist/gh-oss-stats-darwin-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/telemetry/telemetry.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats/*.go + GOOS=linux GOARCH=amd64 go build -o dist/gh-oss-stats-linux-amd64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats + GOOS=linux GOARCH=arm64 go build -o dist/gh-oss-stats-linux-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats + GOOS=darwin GOARCH=arm64 go build -o dist/gh-oss-stats-darwin-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats - name: Verify binaries run: | diff --git a/go.mod b/go.mod index 9c9c3a0..3a94ebb 100644 --- a/go.mod +++ b/go.mod @@ -4,5 +4,5 @@ go 1.25.4 require ( github.com/google/uuid v1.6.0 - github.com/mixpanel/mixpanel-go v1.2.1 // indirect + github.com/mixpanel/mixpanel-go v1.2.1 ) diff --git a/internal/analytic/analytics.go b/internal/analytics/analytics.go similarity index 83% rename from internal/analytic/analytics.go rename to internal/analytics/analytics.go index a74363e..0711331 100644 --- a/internal/analytic/analytics.go +++ b/internal/analytics/analytics.go @@ -2,18 +2,21 @@ package analytics import ( "context" - "os" + "fmt" "github.com/mixpanel/mixpanel-go" ) +var mixpanelToken = "dev-token" + type Analytics struct { userUUID string Client *mixpanel.ApiClient } func CreateAnalytics(userUUID string) Analytics { - mixpanelClient := mixpanel.NewApiClient(os.Getenv("MIXPANEL_PROJECT_TOKEN")) + fmt.Printf("creaeting analytics, token=%v\n", mixpanelToken) + mixpanelClient := mixpanel.NewApiClient(mixpanelToken) return Analytics{ userUUID: userUUID, Client: mixpanelClient, diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go index 217e6ad..67447da 100644 --- a/internal/telemetry/telemetry.go +++ b/internal/telemetry/telemetry.go @@ -9,7 +9,7 @@ import ( "github.com/google/uuid" - analytics "github.com/mabd-dev/gh-oss-stats/internal/analytic" + analytics "github.com/mabd-dev/gh-oss-stats/internal/analytics" "github.com/mabd-dev/gh-oss-stats/internal/utils" ) @@ -18,8 +18,6 @@ var ( telemetryFileName = "telemetry.json" ) -var mixpanelToken = "dev-token" - type Telemetry struct { NoticeShown bool `json:"noticeShown"` UserUUID string `json:"userUUID"` From 09b5799149ac8d80110df404eabfa24b8c7a3412 Mon Sep 17 00:00:00 2001 From: MABD Date: Thu, 16 Apr 2026 09:05:34 +0300 Subject: [PATCH 16/25] updated changelog --- docs/release-notes/v0.3.5.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/v0.3.5.md b/docs/release-notes/v0.3.5.md index cf86c79..d6123c9 100644 --- a/docs/release-notes/v0.3.5.md +++ b/docs/release-notes/v0.3.5.md @@ -6,6 +6,9 @@ Codename: ## ✨ Features -- NEW: analytics usage tracking +- **NEW** add anonymous usage telemetry via Mixpanel +Collects anonymous usage data (os, version, ci) on each run. +Disable with GH_OSS_STATS_TELEMETRY_DISABLED=1. +See README for full details. From 3a57704118fdce5fa805cc0e5f1ccaaa7029018f Mon Sep 17 00:00:00 2001 From: MABD Date: Thu, 16 Apr 2026 09:10:05 +0300 Subject: [PATCH 17/25] move disable check to top of send function --- internal/telemetry/telemetry.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go index 67447da..69e9359 100644 --- a/internal/telemetry/telemetry.go +++ b/internal/telemetry/telemetry.go @@ -24,13 +24,13 @@ type Telemetry struct { } func Send(version string) { - telemetry, err := readOrCreateTelemetry() - if err != nil { + telemetryDisabled := os.Getenv("GH_OSS_STATS_TELEMETRY_DISABLED") + if telemetryDisabled == "1" { return } - telemetryDisabled := os.Getenv("GH_OSS_STATS_TELEMETRY_DISABLED") - if telemetryDisabled == "1" { + telemetry, err := readOrCreateTelemetry() + if err != nil { return } @@ -79,9 +79,9 @@ func readTelemetry() (*Telemetry, error) { return nil, err } - telementryPath := filepath.Join(configDir, toolName, telemetryFileName) + telemetryPath := filepath.Join(configDir, toolName, telemetryFileName) - exists, err := utils.FileExists(telementryPath) + exists, err := utils.FileExists(telemetryPath) if err != nil { return nil, err } @@ -89,7 +89,7 @@ func readTelemetry() (*Telemetry, error) { return nil, nil } - data, err := os.ReadFile(telementryPath) + data, err := os.ReadFile(telemetryPath) if err != nil { return nil, err } From 515a2a5d43bc82fda9c9b96c78c677ce62789fff Mon Sep 17 00:00:00 2001 From: MABD Date: Thu, 16 Apr 2026 09:10:34 +0300 Subject: [PATCH 18/25] remove debugging print --- internal/analytics/analytics.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/analytics/analytics.go b/internal/analytics/analytics.go index 0711331..ada5a9c 100644 --- a/internal/analytics/analytics.go +++ b/internal/analytics/analytics.go @@ -2,7 +2,6 @@ package analytics import ( "context" - "fmt" "github.com/mixpanel/mixpanel-go" ) @@ -15,7 +14,6 @@ type Analytics struct { } func CreateAnalytics(userUUID string) Analytics { - fmt.Printf("creaeting analytics, token=%v\n", mixpanelToken) mixpanelClient := mixpanel.NewApiClient(mixpanelToken) return Analytics{ userUUID: userUUID, From c4dfdd6f2d6cf1a084e806a3dab82532d52365ec Mon Sep 17 00:00:00 2001 From: MABD Date: Thu, 16 Apr 2026 09:11:49 +0300 Subject: [PATCH 19/25] default mixpanelToken to empty string --- internal/analytics/analytics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/analytics/analytics.go b/internal/analytics/analytics.go index ada5a9c..15fdbfe 100644 --- a/internal/analytics/analytics.go +++ b/internal/analytics/analytics.go @@ -6,7 +6,7 @@ import ( "github.com/mixpanel/mixpanel-go" ) -var mixpanelToken = "dev-token" +var mixpanelToken = "" type Analytics struct { userUUID string From 3573c8ea99d28cca505d3171209e34980239c5af Mon Sep 17 00:00:00 2001 From: MABD Date: Thu, 16 Apr 2026 09:18:36 +0300 Subject: [PATCH 20/25] updated build script for test and release github actions --- .github/workflows/release.yml | 6 +++--- .github/workflows/tests.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ceb70ca..4b9353a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -34,9 +34,9 @@ jobs: run: | echo "Building gh-oss-stats ..." mkdir -p dist - GOOS=linux GOARCH=amd64 go build -o dist/gh-oss-stats-linux-amd64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats - GOOS=linux GOARCH=arm64 go build -o dist/gh-oss-stats-linux-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats - GOOS=darwin GOARCH=arm64 go build -o dist/gh-oss-stats-darwin-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats + GOOS=linux GOARCH=amd64 go build -o dist/gh-oss-stats-linux-amd64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats + GOOS=linux GOARCH=arm64 go build -o dist/gh-oss-stats-linux-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats + GOOS=darwin GOARCH=arm64 go build -o dist/gh-oss-stats-darwin-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats - name: Trigger pkg.go.dev indexing run: | diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 10d2206..023e7ea 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -59,9 +59,9 @@ jobs: run: | echo "Building gh-oss-stats ..." mkdir -p dist - GOOS=linux GOARCH=amd64 go build -o dist/gh-oss-stats-linux-amd64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats - GOOS=linux GOARCH=arm64 go build -o dist/gh-oss-stats-linux-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats - GOOS=darwin GOARCH=arm64 go build -o dist/gh-oss-stats-darwin-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats + GOOS=linux GOARCH=amd64 go build -o dist/gh-oss-stats-linux-amd64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats + GOOS=linux GOARCH=arm64 go build -o dist/gh-oss-stats-linux-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats + GOOS=darwin GOARCH=arm64 go build -o dist/gh-oss-stats-darwin-arm64 -ldflags="-X github.com/mabd-dev/gh-oss-stats/internal/analytics.mixpanelToken=${{ secrets.MIXPANEL_TOKEN }}" ./cmd/gh-oss-stats - name: Verify binaries run: | From eee6e54095d7298a468d8cb1db6bb7cd6ccee11c Mon Sep 17 00:00:00 2001 From: MABD Date: Thu, 16 Apr 2026 09:29:24 +0300 Subject: [PATCH 21/25] set cache to false on setup-go. remove -race from test command --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 023e7ea..d937fec 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,7 +26,7 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - cache: true + cache: false - name: Download dependencies run: go mod download @@ -35,7 +35,7 @@ jobs: run: go mod verify - name: Run tests - run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./... + run: go test -v -coverprofile=coverage.out -covermode=atomic ./... - name: Display coverage run: go tool cover -func=coverage.out From df5de4228af8ddd27b704bf136c1d6d5918f92bd Mon Sep 17 00:00:00 2001 From: MABD Date: Fri, 17 Apr 2026 07:46:00 +0300 Subject: [PATCH 22/25] reset tests.yml --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d937fec..023e7ea 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,7 +26,7 @@ jobs: uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - cache: false + cache: true - name: Download dependencies run: go mod download @@ -35,7 +35,7 @@ jobs: run: go mod verify - name: Run tests - run: go test -v -coverprofile=coverage.out -covermode=atomic ./... + run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./... - name: Display coverage run: go tool cover -func=coverage.out From c6757ecd5ee7b5c62b92a8e540ce9e78f4e1fd9e Mon Sep 17 00:00:00 2001 From: MABD Date: Fri, 17 Apr 2026 07:47:50 +0300 Subject: [PATCH 23/25] docs: updated release notes --- docs/release-notes/v0.3.5.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/release-notes/v0.3.5.md b/docs/release-notes/v0.3.5.md index d6123c9..fc672ff 100644 --- a/docs/release-notes/v0.3.5.md +++ b/docs/release-notes/v0.3.5.md @@ -6,7 +6,8 @@ Codename: ## ✨ Features -- **NEW** add anonymous usage telemetry via Mixpanel +- **NEW**: add anonymous usage telemetry via Mixpanel + Collects anonymous usage data (os, version, ci) on each run. Disable with GH_OSS_STATS_TELEMETRY_DISABLED=1. See README for full details. From 239d945374a8a0b31db22e00ead83de15f6cebc1 Mon Sep 17 00:00:00 2001 From: MABD Date: Fri, 17 Apr 2026 07:55:15 +0300 Subject: [PATCH 24/25] test update: keep only go veresion 1.25 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 023e7ea..158038e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: - go-version: ['1.23', '1.24', '1.25'] + go-version: ['1.25'] steps: - name: Checkout code From 6640afd6379f8ca326ab2d30a11e9540aa90f7b9 Mon Sep 17 00:00:00 2001 From: MABD Date: Fri, 17 Apr 2026 08:02:14 +0300 Subject: [PATCH 25/25] remove go version matrix --- .github/workflows/tests.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 158038e..473b231 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,10 +14,6 @@ jobs: name: Run Tests runs-on: ubuntu-latest - strategy: - matrix: - go-version: ['1.25'] - steps: - name: Checkout code uses: actions/checkout@v4 @@ -25,7 +21,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: ${{ matrix.go-version }} + go-version: 1.25 cache: true - name: Download dependencies