diff --git a/CHANGELOG.md b/CHANGELOG.md index b6bc03b..3e88c6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Pending + +### Fixed + +- `scout setup ` now links to a real docs page for every framework — the old `/docs/` URLs all 404'd because Scout's docs are organized by language (#27) + ## [0.5.0] - 2026-09-10 This release rounds out command coverage across the whole Scout API surface diff --git a/cmd/setup.go b/cmd/setup.go index c8d0111..8c6f37a 100644 --- a/cmd/setup.go +++ b/cmd/setup.go @@ -7,21 +7,25 @@ import ( "github.com/spf13/cobra" ) +// Scout's docs are organized by language, not by framework, and several +// frameworks have no dedicated page — so each entry carries its own URL +// rather than deriving one from the framework name. var frameworks = []struct { - name string - desc string + name string + desc string + docsURL string }{ - {"rails", "Ruby on Rails"}, - {"django", "Python Django"}, - {"flask", "Python Flask"}, - {"phoenix", "Elixir Phoenix"}, - {"express", "Node.js Express"}, - {"laravel", "PHP Laravel"}, - {"sinatra", "Ruby Sinatra"}, - {"fastapi", "Python FastAPI"}, - {"celery", "Python Celery"}, - {"dramatiq", "Python Dramatiq"}, - {"sidekiq", "Ruby Sidekiq"}, + {"rails", "Ruby on Rails", "https://scoutapm.com/docs/ruby"}, + {"django", "Python Django", "https://scoutapm.com/docs/python/django"}, + {"flask", "Python Flask", "https://scoutapm.com/docs/python/flask"}, + {"phoenix", "Elixir Phoenix", "https://scoutapm.com/docs/elixir"}, + {"express", "Node.js Express", "https://scoutapm.com/docs/node/express"}, + {"laravel", "PHP Laravel", "https://scoutapm.com/docs/php/laravel"}, + {"sinatra", "Ruby Sinatra", "https://scoutapm.com/docs/ruby/sinatra"}, + {"fastapi", "Python FastAPI", "https://scoutapm.com/docs/python/fastapi"}, + {"celery", "Python Celery", "https://scoutapm.com/docs/python/celery"}, + {"dramatiq", "Python Dramatiq", "https://scoutapm.com/docs/python/other-libraries#dramatiq"}, + {"sidekiq", "Ruby Sidekiq", "https://scoutapm.com/docs/ruby#instrumented-libraries"}, } var setupCmd = &cobra.Command{ @@ -35,6 +39,15 @@ func init() { rootCmd.AddCommand(setupCmd) } +func frameworkDocsURL(name string) (string, bool) { + for _, f := range frameworks { + if f.name == name { + return f.docsURL, true + } + } + return "", false +} + func runSetup(cmd *cobra.Command, args []string) { if len(args) == 0 { names := make([]string, len(frameworks)) @@ -59,20 +72,11 @@ func runSetup(cmd *cobra.Command, args []string) { } framework := args[0] - found := false - for _, f := range frameworks { - if f.name == framework { - found = true - break - } - } - + docsURL, found := frameworkDocsURL(framework) if !found { exitError(fmt.Sprintf("unknown framework: %s", framework)) } - docsURL := fmt.Sprintf("https://scoutapm.com/docs/%s", framework) - if structuredOutput(map[string]interface{}{ "framework": framework, "docs_url": docsURL, diff --git a/cmd/setup_test.go b/cmd/setup_test.go new file mode 100644 index 0000000..ad0843b --- /dev/null +++ b/cmd/setup_test.go @@ -0,0 +1,59 @@ +package cmd + +import ( + "net/url" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFrameworkTableIsWellFormed(t *testing.T) { + seen := make(map[string]bool, len(frameworks)) + + for _, f := range frameworks { + t.Run(f.name, func(t *testing.T) { + assert.NotEmpty(t, f.name) + assert.NotEmpty(t, f.desc) + assert.False(t, seen[f.name], "duplicate framework entry") + seen[f.name] = true + + u, err := url.Parse(f.docsURL) + require.NoError(t, err) + assert.Equal(t, "https", u.Scheme) + assert.Equal(t, "scoutapm.com", u.Host) + assert.True(t, strings.HasPrefix(u.Path, "/docs/"), "docs URL should live under /docs/: %s", f.docsURL) + + // Scout's docs are organized by language, so /docs/ 404s. + assert.NotEqual(t, "/docs/"+f.name, u.Path, "docs URL must not be derived from the framework name") + }) + } +} + +func TestFrameworkDocsURLLookup(t *testing.T) { + tests := []struct { + name string + want string + }{ + {"rails", "https://scoutapm.com/docs/ruby"}, + {"django", "https://scoutapm.com/docs/python/django"}, + {"sinatra", "https://scoutapm.com/docs/ruby/sinatra"}, + {"express", "https://scoutapm.com/docs/node/express"}, + {"sidekiq", "https://scoutapm.com/docs/ruby#instrumented-libraries"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, found := frameworkDocsURL(tt.name) + require.True(t, found) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestFrameworkDocsURLUnknown(t *testing.T) { + got, found := frameworkDocsURL("cobol") + assert.False(t, found) + assert.Empty(t, got) +}