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
36 changes: 33 additions & 3 deletions cmd/symbols/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,19 @@ const (
// no sources.
sourcePathFlag = "source-path"

defaultPath = "."
defaultPath = "."

// defaultBackendUrl is the observability API for LaunchDarkly production, which
// is what defaultBackendURLFor derives for the default base URI.
defaultBackendUrl = "https://pri.observability.app.launchdarkly.com"

// Every LaunchDarkly instance publishes the observability API under its own
// host, named for the instance the app is served from: staging's app at
// ld-stg.launchdarkly.com has its API at pri.observability.ld-stg.launchdarkly.com.
// "pri" is the authenticated graph, which is the one that hands out upload URLs.
launchDarklyDomain = "launchdarkly.com"
observabilityAPIPrefix = "pri.observability."

// reactNativeSymbolsIDPrefix is the storage "version" segment for symbols-id
// addressed JS maps (Symbols Id Lane). Keys become _sym/js/id/<symbolsID>/<file>,
// matching what the symbolication backend derives from the reported symbols id.
Expand Down Expand Up @@ -218,7 +228,7 @@ func runE(client resources.Client) func(cmd *cobra.Command, args []string) error
skipExisting := !viper.GetBool(noSkipExistingFlag)

if backendUrl == "" {
backendUrl = defaultBackendUrl
backendUrl = defaultBackendURLFor(viper.GetString(cliflags.BaseURIFlag))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent backend default change

Medium Severity

When --backend-url is unset, uploads now derive the observability endpoint from --base-uri for LaunchDarkly hosts, changing the previous production default for non-prod instances with no transitional stderr notice. That violates the rule that user-facing CLI default changes need a warning for at least one release cycle.

Additional Locations (1)
Fix in Cursor Fix in Web

Triggered by learned rule: Breaking CLI default changes require transitional stderr warnings

Reviewed by Cursor Bugbot for commit 1dec099. Configure here.

}

// Apple dSYMs take a dedicated path: they are compiled to per-arch .dsymmap
Expand Down Expand Up @@ -518,6 +528,26 @@ func readSymbolsIDFile(filePath string) string {
return strings.TrimSpace(string(content))
}

// defaultBackendURLFor derives the observability API endpoint from the LaunchDarkly
// base URI, so aiming the CLI at another instance takes the one flag that names the
// instance rather than two flags that have to agree.
//
// Only LaunchDarkly's own hosts are derived from. A base URI pointing at a local or
// proxied stack says nothing about where its observability API listens, so those keep
// the production default and --backend-url stays the way to say otherwise.
func defaultBackendURLFor(baseURI string) string {
parsed, err := url.Parse(strings.TrimSpace(baseURI))
if err != nil {
return defaultBackendUrl
}

host := parsed.Hostname()
if host != launchDarklyDomain && !strings.HasSuffix(host, "."+launchDarklyDomain) {
return defaultBackendUrl
}
return "https://" + observabilityAPIPrefix + host
}

// getSymbolUploadUrls returns one upload URL per requested key, in order.
//
// With skipExisting, a key whose bytes the backend already stores comes back empty
Expand Down Expand Up @@ -712,7 +742,7 @@ func initFlags(cmd *cobra.Command) {
cmd.Flags().String(basePathFlag, "", "An optional base path for the uploaded symbol files")
_ = viper.BindPFlag(basePathFlag, cmd.Flags().Lookup(basePathFlag))

cmd.Flags().String(backendUrlFlag, defaultBackendUrl, "An optional backend url for self-hosted deployments")
cmd.Flags().String(backendUrlFlag, "", fmt.Sprintf("An optional backend url for self-hosted deployments. Defaults to the observability API of whichever instance --%s names (%s for the default)", cliflags.BaseURIFlag, defaultBackendUrl))
_ = viper.BindPFlag(backendUrlFlag, cmd.Flags().Lookup(backendUrlFlag))

cmd.Flags().Bool(includeSourcesFlag, false, fmt.Sprintf("Also upload your source files so the errors page can show source context around native frames (%s and %s). Your source is stored in LaunchDarkly", typeAppleDSYM, typeAndroid))
Expand Down
22 changes: 22 additions & 0 deletions cmd/symbols/upload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,28 @@ func TestNewUploadCmd(t *testing.T) {
assert.Equal(t, []string{"true"}, cmd.Flags().Lookup("project").Annotations["required"])
}

// Naming the instance once, with --base-uri, is what should aim an upload at it: the
// observability API of every LaunchDarkly instance is named for that instance, so
// asking for both is asking for two flags that can disagree.
func TestDefaultBackendURLFor(t *testing.T) {
for name, tc := range map[string]struct{ baseURI, want string }{
"production": {"https://app.launchdarkly.com", defaultBackendUrl},
"staging": {"https://ld-stg.launchdarkly.com", "https://pri.observability.ld-stg.launchdarkly.com"},
"trailing slash": {"https://ld-stg.launchdarkly.com/", "https://pri.observability.ld-stg.launchdarkly.com"},
"surrounding whitespace": {" https://ld-stg.launchdarkly.com ", "https://pri.observability.ld-stg.launchdarkly.com"},
"regional instance": {"https://app.eu.launchdarkly.com", "https://pri.observability.app.eu.launchdarkly.com"},

// Nothing about one of these says where an observability API listens, so the
// production default stands and --backend-url remains how to say otherwise.
"local stack": {"http://localhost:3000", defaultBackendUrl},
"host that merely ends in the domain name": {"https://notlaunchdarkly.com", defaultBackendUrl},
"unset": {"", defaultBackendUrl},
"garbage": {"://", defaultBackendUrl},
} {
assert.Equal(t, tc.want, defaultBackendURLFor(tc.baseURI), name)
}
}

func TestIsReactNativeUploadFile(t *testing.T) {
// React Native iOS bundle + map.
assert.True(t, isReactNativeUploadFile("main.jsbundle"))
Expand Down
Loading