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
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
resourcecmd "github.com/launchdarkly/ldcli/cmd/resources"
signupcmd "github.com/launchdarkly/ldcli/cmd/signup"
sourcemapscmd "github.com/launchdarkly/ldcli/cmd/sourcemaps"
symbolscmd "github.com/launchdarkly/ldcli/cmd/symbols"
whoamicmd "github.com/launchdarkly/ldcli/cmd/whoami"
"github.com/launchdarkly/ldcli/internal/analytics"
"github.com/launchdarkly/ldcli/internal/config"
Expand Down Expand Up @@ -259,6 +260,7 @@ func NewRootCommand(
cmd.AddCommand(resourcecmd.NewResourcesCmd())
cmd.AddCommand(devcmd.NewDevServerCmd(clients.ResourcesClient, analyticsTrackerFn, clients.DevClient))
cmd.AddCommand(sourcemapscmd.NewSourcemapsCmd(clients.ResourcesClient, analyticsTrackerFn))
cmd.AddCommand(symbolscmd.NewSymbolsCmd(clients.ResourcesClient, analyticsTrackerFn))
cmd.AddCommand(whoamicmd.NewWhoAmICmd(clients.ResourcesClient))
resourcecmd.AddAllResourceCmds(cmd, clients.ResourcesClient, analyticsTrackerFn)

Expand Down
42 changes: 25 additions & 17 deletions cmd/sourcemaps/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,21 +168,6 @@ func runE(client resources.Client) func(cmd *cobra.Command, args []string) error
}
}

var sourceMapUploadSuffixes = []string{
".js.map", ".js",
".jsbundle.map", ".jsbundle",
".bundle.map", ".bundle",
}

func isSourceMapUploadFile(name string) bool {
for _, suffix := range sourceMapUploadSuffixes {
if strings.HasSuffix(name, suffix) {
return true
}
}
return false
}

func getAllSourceMapFiles(path string) ([]SourceMapFile, error) {
var files []SourceMapFile
routeGroupPattern := regexp.MustCompile(`\(.+?\)/`)
Expand All @@ -200,6 +185,7 @@ func getAllSourceMapFiles(path string) ([]SourceMapFile, error) {
return files, nil
}

skippedReactNative := false
err = filepath.WalkDir(path, func(filePath string, d fs.DirEntry, err error) error {
if err != nil {
return err
Expand All @@ -209,7 +195,7 @@ func getAllSourceMapFiles(path string) ([]SourceMapFile, error) {
return filepath.SkipDir
}

if !d.IsDir() && isSourceMapUploadFile(filePath) {
if !d.IsDir() && (strings.HasSuffix(filePath, ".js.map") || strings.HasSuffix(filePath, ".js")) {
Comment thread
cursor[bot] marked this conversation as resolved.
relPath, err := filepath.Rel(path, filePath)
if err != nil {
return err
Expand All @@ -227,6 +213,8 @@ func getAllSourceMapFiles(path string) ([]SourceMapFile, error) {
Name: routeGroupRemovedPath,
})
}
} else if !d.IsDir() && isReactNativeArtifact(filePath) {
skippedReactNative = true
}

return nil
Expand All @@ -236,13 +224,33 @@ func getAllSourceMapFiles(path string) ([]SourceMapFile, error) {
return nil, err
}

// Transitional notice: `sourcemaps upload` used to also collect React Native
// bundles, but they now go through the dedicated `symbols upload` flow. Warn
// (instead of silently skipping) so anyone relying on the old behavior knows
// to switch, rather than shipping a build with no usable RN symbols.
if skippedReactNative {
fmt.Fprintln(os.Stderr, "warning: skipped React Native bundle(s) (*.jsbundle / *.bundle). `sourcemaps upload` only handles web sourcemaps (*.js / *.js.map); upload React Native symbols with `ldcli symbols upload --type react-native` instead.")
}

if len(files) == 0 {
return nil, fmt.Errorf("no sourcemap files found (looked for *.js.map, *.jsbundle.map, *.bundle.map and their minified files). Please double check that you have generated sourcemaps for your app")
return nil, fmt.Errorf("no .js.map files found. Please double check that you have generated sourcemaps for your app")
}

return files, nil
}

// isReactNativeArtifact reports whether name looks like a React Native bundle or
// its sourcemap. Used only to warn that these are skipped by `sourcemaps upload`
// (they belong to `symbols upload`), never to select files for upload here.
func isReactNativeArtifact(name string) bool {
for _, suffix := range []string{".jsbundle.map", ".jsbundle", ".bundle.map", ".bundle"} {
if strings.HasSuffix(name, suffix) {
return true
}
}
return false
}

func getS3Key(version, basePath, fileName string) string {
if version == "" {
version = "unversioned"
Expand Down
51 changes: 1 addition & 50 deletions cmd/sourcemaps/upload_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,56 +198,7 @@ func TestGetAllSourceMapFiles(t *testing.T) {
defer os.RemoveAll(emptyDir)
_, err = getAllSourceMapFiles(emptyDir)
assert.Error(t, err)
assert.Contains(t, err.Error(), "no sourcemap files found")
}

func TestIsSourceMapUploadFile(t *testing.T) {
// Web bundles + maps.
assert.True(t, isSourceMapUploadFile("app.js"))
assert.True(t, isSourceMapUploadFile("app.js.map"))
// React Native iOS bundle + map.
assert.True(t, isSourceMapUploadFile("main.jsbundle"))
assert.True(t, isSourceMapUploadFile("main.jsbundle.map"))
// React Native Android bundle + map.
assert.True(t, isSourceMapUploadFile("index.android.bundle"))
assert.True(t, isSourceMapUploadFile("index.android.bundle.map"))
// Unrelated files are ignored.
assert.False(t, isSourceMapUploadFile("styles.css"))
assert.False(t, isSourceMapUploadFile("styles.css.map"))
assert.False(t, isSourceMapUploadFile("README.md"))
}

func TestGetAllSourceMapFilesReactNative(t *testing.T) {
tempDir, err := os.MkdirTemp("", "sourcemap-rn-test")
assert.NoError(t, err)
defer os.RemoveAll(tempDir)

// The names React Native's `react-native bundle` produces.
rnFiles := []string{
"main.jsbundle",
"main.jsbundle.map",
"index.android.bundle",
"index.android.bundle.map",
}
for _, name := range rnFiles {
err = os.WriteFile(filepath.Join(tempDir, name), []byte("{}"), 0644)
assert.NoError(t, err)
}
// A non-sourcemap file that must be skipped.
err = os.WriteFile(filepath.Join(tempDir, "assets.png"), []byte("x"), 0644)
assert.NoError(t, err)

files, err := getAllSourceMapFiles(tempDir)
assert.NoError(t, err)

found := make(map[string]bool)
for _, f := range files {
found[f.Name] = true
}
for _, name := range rnFiles {
assert.True(t, found[name], "expected %s to be discovered for upload", name)
}
assert.False(t, found["assets.png"], "non-sourcemap files must be skipped")
assert.Contains(t, err.Error(), "no .js.map files found")
}

func TestGetSourceMapUploadUrlsErrors(t *testing.T) {
Expand Down
23 changes: 23 additions & 0 deletions cmd/symbols/symbols.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package symbols

import (
"github.com/spf13/cobra"

resourcescmd "github.com/launchdarkly/ldcli/cmd/resources"
"github.com/launchdarkly/ldcli/internal/analytics"
"github.com/launchdarkly/ldcli/internal/resources"
)

func NewSymbolsCmd(client resources.Client, analyticsTrackerFn analytics.TrackerFn) *cobra.Command {
cmd := &cobra.Command{
Use: "symbols",
Short: "Manage symbol files",
Long: "Manage symbol files (for example, React Native sourcemaps) for LaunchDarkly error monitoring",
Args: cobra.MinimumNArgs(1),
}

cmd.AddCommand(NewUploadCmd(client, analyticsTrackerFn))
cmd.SetUsageTemplate(resourcescmd.SubcommandUsageTemplate())

return cmd
}
Loading
Loading