Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## Pending

### Fixed

- `scout setup <framework>` now links to a real docs page for every framework — the old `/docs/<framework>` 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
Expand Down
50 changes: 27 additions & 23 deletions cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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))
Expand All @@ -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,
Expand Down
59 changes: 59 additions & 0 deletions cmd/setup_test.go
Original file line number Diff line number Diff line change
@@ -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/<framework> 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)
}
Loading