From 6d0f3750fe6975e95af2e9f8b92a0b31dd7d9350 Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 5 Mar 2026 10:52:59 +0000 Subject: [PATCH 01/15] workout what path we're using for the upload file path --- pkg/server/request.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/server/request.go b/pkg/server/request.go index 5276c053..1662c59d 100644 --- a/pkg/server/request.go +++ b/pkg/server/request.go @@ -121,6 +121,12 @@ func buildFileRequest(url string, fieldData map[string]string, fileFieldData map // - error: An error if any step of the file processing fails. Nil if the process is successful. func ProcessFileRequest(apiKey string, endpointPath string, uploadOptions map[string]string, fileFieldData map[string]FileField, fileName string, options options.CLI, logger log.Logger) error { + // log the uploadoptions filefielddata and filename for debugging purposes + fmt.Printf("fileFieldData: %v\n", fileFieldData) + for key, value := range fileFieldData { + fmt.Printf("fileFieldData key: %s, value: %v\n", key, value) + } + if apiKey != "" { uploadOptions["apiKey"] = apiKey } else { From 2cb4639834c1c28198ff0e545c0b33d8ad0c65f6 Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 5 Mar 2026 11:32:26 +0000 Subject: [PATCH 02/15] add exclude flag for the upload cli --- pkg/options/options.go | 7 ++++--- pkg/server/request.go | 10 ++++++---- pkg/utils/files.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/pkg/options/options.go b/pkg/options/options.go index 81b300bb..e01e3e27 100644 --- a/pkg/options/options.go +++ b/pkg/options/options.go @@ -117,9 +117,10 @@ type Breakpad struct { type Upload struct { // shared options - Retries int `help:"The number of retry attempts before failing an upload request" default:"0"` - Timeout int `help:"The number of seconds to wait before failing an upload request" default:"300"` - UploadAPIRootUrl string `help:"The upload server hostname, optionally containing port number"` + Retries int `help:"The number of retry attempts before failing an upload request" default:"0"` + Timeout int `help:"The number of seconds to wait before failing an upload request" default:"300"` + UploadAPIRootUrl string `help:"The upload server hostname, optionally containing port number"` + Exclude []string `help:"Exclude files matching these patterns (supports wildcards like *.map, path/to/*, etc.)"` // required options All DiscoverAndUploadAny `cmd:"" help:"Upload any symbol/mapping files"` diff --git a/pkg/server/request.go b/pkg/server/request.go index 1662c59d..87c8d40e 100644 --- a/pkg/server/request.go +++ b/pkg/server/request.go @@ -121,10 +121,12 @@ func buildFileRequest(url string, fieldData map[string]string, fileFieldData map // - error: An error if any step of the file processing fails. Nil if the process is successful. func ProcessFileRequest(apiKey string, endpointPath string, uploadOptions map[string]string, fileFieldData map[string]FileField, fileName string, options options.CLI, logger log.Logger) error { - // log the uploadoptions filefielddata and filename for debugging purposes - fmt.Printf("fileFieldData: %v\n", fileFieldData) - for key, value := range fileFieldData { - fmt.Printf("fileFieldData key: %s, value: %v\n", key, value) + // Check if the fileName itself should be excluded based on exclude patterns + if len(options.Upload.Exclude) > 0 { + if utils.IsFileExcluded(fileName, options.Upload.Exclude) { + logger.Info(fmt.Sprintf("Skipping the upload of: %s (matches exclude pattern)", fileName)) + return nil + } } if apiKey != "" { diff --git a/pkg/utils/files.go b/pkg/utils/files.go index d47928bf..aad02bf8 100644 --- a/pkg/utils/files.go +++ b/pkg/utils/files.go @@ -51,6 +51,37 @@ func IsDir(path string) bool { return err == nil && pathInfo.IsDir() } +// IsFileExcluded checks if a file path matches any of the exclude patterns. +// It supports wildcards like *.map, path/to/*, etc. +// +// Parameters: +// - filePath: The file path to check. +// - excludePatterns: A list of patterns to match against. +// +// Returns: +// - bool: True if the file matches any exclude pattern, false otherwise. +func IsFileExcluded(filePath string, excludePatterns []string) bool { + for _, pattern := range excludePatterns { + // Try matching the pattern against the full path + matched, err := filepath.Match(pattern, filePath) + if err == nil && matched { + return true + } + + // Try matching against the base name + matched, err = filepath.Match(pattern, filepath.Base(filePath)) + if err == nil && matched { + return true + } + + // Check if the pattern contains path separators and matches as substring + if strings.Contains(filePath, pattern) { + return true + } + } + return false +} + // BuildFileList compiles a list of files from the provided paths. // // Parameters: From a0b1fab7cb715c3c375a9a2dc45dbc8fc8a76702 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 6 Mar 2026 09:43:36 +0000 Subject: [PATCH 03/15] update tests for the exclude option --- features/cli/exclude-option.feature | 27 +++++++++ test/utils/files_test.go | 89 +++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 features/cli/exclude-option.feature diff --git a/features/cli/exclude-option.feature b/features/cli/exclude-option.feature new file mode 100644 index 00000000..f5d96234 --- /dev/null +++ b/features/cli/exclude-option.feature @@ -0,0 +1,27 @@ +Feature: Exclude option tests + + Scenario: Exclude files with wildcard extension pattern + When I run bugsnag-cli with upload js --upload-api-root-url=http://localhost:$MAZE_RUNNER_PORT --api-key=1234567890ABCDEF1234567890ABCDEF --overwrite --base-url=example.com --exclude=*.map --project-root=features/js/fixtures/js-multiple-maps features/js/fixtures/js-multiple-maps/dist/ + And I wait for 2 seconds + Then I should receive no sourcemaps + + Scenario: Exclude files matching specific filename pattern + When I run bugsnag-cli with upload js --upload-api-root-url=http://localhost:$MAZE_RUNNER_PORT --api-key=1234567890ABCDEF1234567890ABCDEF --overwrite --base-url=example.com --exclude=main.js.map --project-root=features/js/fixtures/js-multiple-maps features/js/fixtures/js-multiple-maps/dist/ + And I wait to receive 1 sourcemap + Then the sourcemaps are valid for the API + And the sourcemap payload field "minifiedUrl" equals "example.com/other.js" + + Scenario: Exclude files with multiple patterns + When I run bugsnag-cli with upload js --upload-api-root-url=http://localhost:$MAZE_RUNNER_PORT --api-key=1234567890ABCDEF1234567890ABCDEF --overwrite --base-url=example.com --exclude=main.js.map --exclude=other.js.map --project-root=features/js/fixtures/js-multiple-maps features/js/fixtures/js-multiple-maps/dist/ + And I wait for 2 seconds + Then I should receive no sourcemaps + + Scenario: Exclude with path pattern + When I run bugsnag-cli with upload js --upload-api-root-url=http://localhost:$MAZE_RUNNER_PORT --api-key=1234567890ABCDEF1234567890ABCDEF --overwrite --base-url=example.com --exclude=/dist/ --project-root=features/js/fixtures/js-multiple-maps features/js/fixtures/js-multiple-maps/dist/ + And I wait for 2 seconds + Then I should receive no sourcemaps + + Scenario: Upload succeeds when exclude pattern doesn't match + When I run bugsnag-cli with upload js --upload-api-root-url=http://localhost:$MAZE_RUNNER_PORT --api-key=1234567890ABCDEF1234567890ABCDEF --overwrite --base-url=example.com --exclude=*.log --project-root=features/js/fixtures/js-multiple-maps features/js/fixtures/js-multiple-maps/dist/ + And I wait to receive 2 sourcemaps + Then the sourcemaps are valid for the API diff --git a/test/utils/files_test.go b/test/utils/files_test.go index b0d5ed93..79b63070 100644 --- a/test/utils/files_test.go +++ b/test/utils/files_test.go @@ -50,3 +50,92 @@ func TestFilePathWalkDir(t *testing.T) { } assert.Equal(t, results, []string{"../testdata/android/variants/debug/.gitkeep", "../testdata/android/variants/release/.gitkeep"}, "This should return a file") } + +// TestIsFileExcluded - Tests the IsFileExcluded function +func TestIsFileExcluded(t *testing.T) { + t.Run("Matches wildcard extension pattern", func(t *testing.T) { + excluded := utils.IsFileExcluded("path/to/file.map", []string{"*.map"}) + assert.True(t, excluded, "Should exclude files with .map extension") + + excluded = utils.IsFileExcluded("file.js.map", []string{"*.map"}) + assert.True(t, excluded, "Should exclude files ending with .map") + }) + + t.Run("Matches wildcard path pattern with basename", func(t *testing.T) { + // filepath.Match works on basenames, so node_modules/* will match files in node_modules + // but the actual match happens via substring matching in the implementation + excluded := utils.IsFileExcluded("node_modules/package/file.js", []string{"node_modules"}) + assert.True(t, excluded, "Should exclude files with node_modules in path via substring") + + excluded = utils.IsFileExcluded("src/temp/file.js", []string{"temp"}) + assert.True(t, excluded, "Should exclude files with temp in path via substring") + }) + + t.Run("Matches exact filename", func(t *testing.T) { + excluded := utils.IsFileExcluded("path/to/test.map", []string{"test.map"}) + assert.True(t, excluded, "Should exclude exact filename match") + + excluded = utils.IsFileExcluded("test.map", []string{"test.map"}) + assert.True(t, excluded, "Should exclude exact filename in current dir") + }) + + t.Run("Matches substring path", func(t *testing.T) { + excluded := utils.IsFileExcluded("src/node_modules/lib/file.js", []string{"node_modules"}) + assert.True(t, excluded, "Should exclude files with path containing substring") + + excluded = utils.IsFileExcluded("dist/vendor/bundle.js", []string{"vendor"}) + assert.True(t, excluded, "Should exclude files with vendor in path") + }) + + t.Run("Does not match when pattern doesn't apply", func(t *testing.T) { + excluded := utils.IsFileExcluded("src/main.js", []string{"*.map"}) + assert.False(t, excluded, "Should not exclude .js file with .map pattern") + + excluded = utils.IsFileExcluded("src/components/file.js", []string{"node_modules"}) + assert.False(t, excluded, "Should not exclude files without matching substring") + }) + + t.Run("Handles multiple patterns", func(t *testing.T) { + patterns := []string{"*.map", "*.log", "node_modules"} + + excluded := utils.IsFileExcluded("file.map", patterns) + assert.True(t, excluded, "Should match first pattern") + + excluded = utils.IsFileExcluded("debug.log", patterns) + assert.True(t, excluded, "Should match second pattern") + + excluded = utils.IsFileExcluded("node_modules/lib/file.js", patterns) + assert.True(t, excluded, "Should match third pattern") + + excluded = utils.IsFileExcluded("src/main.js", patterns) + assert.False(t, excluded, "Should not match any pattern") + }) + + t.Run("Handles empty patterns", func(t *testing.T) { + excluded := utils.IsFileExcluded("any/file.js", []string{}) + assert.False(t, excluded, "Should not exclude with no patterns") + + excluded = utils.IsFileExcluded("any/file.js", nil) + assert.False(t, excluded, "Should not exclude with nil patterns") + }) + + t.Run("Handles complex wildcard patterns", func(t *testing.T) { + excluded := utils.IsFileExcluded("test.js.map", []string{"*.js.map"}) + assert.True(t, excluded, "Should match .js.map extension") + + excluded = utils.IsFileExcluded("bundle-v1.2.3.js", []string{"bundle-*.js"}) + assert.True(t, excluded, "Should match bundle with version pattern") + }) + + t.Run("Handles directory path patterns", func(t *testing.T) { + // Substring matching for directory paths + excluded := utils.IsFileExcluded("build/dist/main.js", []string{"build"}) + assert.True(t, excluded, "Should match files with build in path") + + excluded = utils.IsFileExcluded("src/build/main.js", []string{"build"}) + assert.True(t, excluded, "Should match build as substring in path") + + excluded = utils.IsFileExcluded("src/main.js", []string{"build"}) + assert.False(t, excluded, "Should not match when build is not in path") + }) +} From f7df5c847fc9a8eed1985f1f81dd762949865be4 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 6 Mar 2026 09:45:51 +0000 Subject: [PATCH 04/15] add changelog entry for the --exclude flag --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 426b3bb4..30676224 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## unreleased + +### Added + +- Add `--exclude` option to all upload commands to exclude files matching specific patterns. Supports wildcards like `*.map` for file extensions, exact filenames, and substring matching for paths (e.g., `node_modules`, `/dist/`). [#269](https://github.com/bugsnag/bugsnag-cli/pull/269) + ## [3.8.0] - 2026-03-03 ### Changed From 02d266653c923d8d6d2883bc5e2aa6b9d893377c Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 6 Mar 2026 10:23:38 +0000 Subject: [PATCH 05/15] configure http/1.1 support for the CLI --- pkg/server/request.go | 9 ++++- test/server/request_test.go | 72 +++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 test/server/request_test.go diff --git a/pkg/server/request.go b/pkg/server/request.go index 87c8d40e..e6458aa0 100644 --- a/pkg/server/request.go +++ b/pkg/server/request.go @@ -2,6 +2,7 @@ package server import ( "bytes" + "crypto/tls" "fmt" "github.com/bugsnag/bugsnag-cli/pkg/endpoints" "io" @@ -257,8 +258,14 @@ func processRequest(request *http.Request, timeout int, retryCount int, logger l // Returns: // - error: An error if any step of the request processing fails. Nil if the process is successful. func sendRequest(request *http.Request, timeout int, logger log.Logger) error { + // Configure transport to use HTTP/1.1 only + transport := &http.Transport{ + TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper), + } + client := &http.Client{ - Timeout: time.Duration(timeout) * time.Second, + Timeout: time.Duration(timeout) * time.Second, + Transport: transport, } response, err := client.Do(request) diff --git a/test/server/request_test.go b/test/server/request_test.go new file mode 100644 index 00000000..ccf8da2c --- /dev/null +++ b/test/server/request_test.go @@ -0,0 +1,72 @@ +package server_testing + +import ( + "crypto/tls" + "net/http" + "net/http/httptest" + "testing" + "time" +) + +func TestHTTPTransportUsesHTTP1Only(t *testing.T) { + // Create a test server that captures the protocol version used + var receivedProto string + testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receivedProto = r.Proto + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"success": true}`)) + })) + defer testServer.Close() + + // Create HTTP client with the same transport configuration as in sendRequest + transport := &http.Transport{ + TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper), + } + + client := &http.Client{ + Timeout: time.Duration(10) * time.Second, + Transport: transport, + } + + // Make a request + req, err := http.NewRequest("GET", testServer.URL, nil) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + resp, err := client.Do(req) + if err != nil { + t.Fatalf("Request failed: %v", err) + } + defer resp.Body.Close() + + // Verify HTTP/1.1 was used (not HTTP/2) + if receivedProto != "HTTP/1.1" { + t.Errorf("Expected HTTP/1.1, but got %s. HTTP/2 should be disabled.", receivedProto) + } + + if resp.ProtoMajor == 2 { + t.Error("Response indicates HTTP/2 was used, but HTTP/1.1 was expected") + } + + t.Logf("✓ Successfully verified HTTP/1.1 is being used (received: %s)", receivedProto) +} + +func TestHTTPTransportDisablesHTTP2(t *testing.T) { + // Verify that an empty TLSNextProto map disables HTTP/2 + transport := &http.Transport{ + TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper), + } + + // The presence of an empty (non-nil) TLSNextProto map should disable HTTP/2 + if transport.TLSNextProto == nil { + t.Error("TLSNextProto should not be nil to disable HTTP/2") + } + + if len(transport.TLSNextProto) != 0 { + t.Error("TLSNextProto should be empty to disable HTTP/2") + } + + t.Log("✓ Transport configuration correctly disables HTTP/2") +} From ac848a70038405ee82c2afec8071cb31c9a66242 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 6 Mar 2026 10:28:58 +0000 Subject: [PATCH 06/15] configure http/1.1 support for the CLI --- test/server/request_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/server/request_test.go b/test/server/request_test.go index ccf8da2c..96b18414 100644 --- a/test/server/request_test.go +++ b/test/server/request_test.go @@ -15,7 +15,7 @@ func TestHTTPTransportUsesHTTP1Only(t *testing.T) { receivedProto = r.Proto w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"success": true}`)) + _, _ = w.Write([]byte(`{"success": true}`)) })) defer testServer.Close() From 27d2e6ab0564526a24e4f7a0485ec307b07adb8c Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 6 Mar 2026 12:37:46 +0000 Subject: [PATCH 07/15] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30676224..234c92b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - Add `--exclude` option to all upload commands to exclude files matching specific patterns. Supports wildcards like `*.map` for file extensions, exact filenames, and substring matching for paths (e.g., `node_modules`, `/dist/`). [#269](https://github.com/bugsnag/bugsnag-cli/pull/269) +### Changed + +- Configure HTTP client to use HTTP/1.1 instead of HTTP/2 for all upload and build API requests. + ## [3.8.0] - 2026-03-03 ### Changed From 48f79d0fa8e0d74e7599ba6210a3aa702ce1158d Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 6 Mar 2026 12:40:16 +0000 Subject: [PATCH 08/15] update changelog --- CHANGELOG.md | 2 +- test/server/request_test.go | 72 ------------------------------------- 2 files changed, 1 insertion(+), 73 deletions(-) delete mode 100644 test/server/request_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 234c92b4..81fa10ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ ### Changed -- Configure HTTP client to use HTTP/1.1 instead of HTTP/2 for all upload and build API requests. +- Configure HTTP client to use HTTP/1.1 instead of HTTP/2 for all upload and build API requests. [#270](https://github.com/bugsnag/bugsnag-cli/pull/270) ## [3.8.0] - 2026-03-03 diff --git a/test/server/request_test.go b/test/server/request_test.go deleted file mode 100644 index 96b18414..00000000 --- a/test/server/request_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package server_testing - -import ( - "crypto/tls" - "net/http" - "net/http/httptest" - "testing" - "time" -) - -func TestHTTPTransportUsesHTTP1Only(t *testing.T) { - // Create a test server that captures the protocol version used - var receivedProto string - testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - receivedProto = r.Proto - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"success": true}`)) - })) - defer testServer.Close() - - // Create HTTP client with the same transport configuration as in sendRequest - transport := &http.Transport{ - TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper), - } - - client := &http.Client{ - Timeout: time.Duration(10) * time.Second, - Transport: transport, - } - - // Make a request - req, err := http.NewRequest("GET", testServer.URL, nil) - if err != nil { - t.Fatalf("Failed to create request: %v", err) - } - - resp, err := client.Do(req) - if err != nil { - t.Fatalf("Request failed: %v", err) - } - defer resp.Body.Close() - - // Verify HTTP/1.1 was used (not HTTP/2) - if receivedProto != "HTTP/1.1" { - t.Errorf("Expected HTTP/1.1, but got %s. HTTP/2 should be disabled.", receivedProto) - } - - if resp.ProtoMajor == 2 { - t.Error("Response indicates HTTP/2 was used, but HTTP/1.1 was expected") - } - - t.Logf("✓ Successfully verified HTTP/1.1 is being used (received: %s)", receivedProto) -} - -func TestHTTPTransportDisablesHTTP2(t *testing.T) { - // Verify that an empty TLSNextProto map disables HTTP/2 - transport := &http.Transport{ - TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper), - } - - // The presence of an empty (non-nil) TLSNextProto map should disable HTTP/2 - if transport.TLSNextProto == nil { - t.Error("TLSNextProto should not be nil to disable HTTP/2") - } - - if len(transport.TLSNextProto) != 0 { - t.Error("TLSNextProto should be empty to disable HTTP/2") - } - - t.Log("✓ Transport configuration correctly disables HTTP/2") -} From a59cdc4ae9f4ef78229e330e4658ac88cb43ed7e Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 6 Mar 2026 19:13:13 +0000 Subject: [PATCH 09/15] bump go version to allow use of http.Protocols --- go.mod | 4 ++-- go.sum | 2 -- pkg/server/request.go | 7 +++++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 01d18078..d1efb5a2 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/bugsnag/bugsnag-cli -go 1.23.1 +go 1.25.4 require ( github.com/alecthomas/kong v0.7.1 @@ -11,6 +11,7 @@ require ( github.com/stretchr/testify v1.8.4 golang.org/x/text v0.14.0 google.golang.org/protobuf v1.34.2 + howett.net/plist v1.0.1 ) require ( @@ -21,5 +22,4 @@ require ( golang.org/x/sys v0.19.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - howett.net/plist v1.0.1 // indirect ) diff --git a/go.sum b/go.sum index fec81fbf..19c7c934 100644 --- a/go.sum +++ b/go.sum @@ -19,8 +19,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= diff --git a/pkg/server/request.go b/pkg/server/request.go index e6458aa0..c05fc1d8 100644 --- a/pkg/server/request.go +++ b/pkg/server/request.go @@ -2,7 +2,6 @@ package server import ( "bytes" - "crypto/tls" "fmt" "github.com/bugsnag/bugsnag-cli/pkg/endpoints" "io" @@ -259,8 +258,12 @@ func processRequest(request *http.Request, timeout int, retryCount int, logger l // - error: An error if any step of the request processing fails. Nil if the process is successful. func sendRequest(request *http.Request, timeout int, logger log.Logger) error { // Configure transport to use HTTP/1.1 only + var protocols http.Protocols + protocols.SetHTTP1(true) + protocols.SetHTTP2(false) + transport := &http.Transport{ - TLSNextProto: make(map[string]func(authority string, c *tls.Conn) http.RoundTripper), + Protocols: &protocols, } client := &http.Client{ From a2de11c7753ec57d25969794dea3e9d57a853f1f Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 6 Mar 2026 19:24:56 +0000 Subject: [PATCH 10/15] update go version --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index d1efb5a2..784312d4 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/bugsnag/bugsnag-cli -go 1.25.4 +go 1.26.1 require ( github.com/alecthomas/kong v0.7.1 From 3bef045a22e7f26a20f3082b20336a533b9ec368 Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 9 Mar 2026 14:56:37 +0000 Subject: [PATCH 11/15] add support for ** globbing on the exclude flag --- go.mod | 1 + go.sum | 2 + pkg/options/options.go | 3 +- pkg/utils/files.go | 17 +++---- test/utils/files_test.go | 105 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 784312d4..972dae4c 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.26.1 require ( github.com/alecthomas/kong v0.7.1 + github.com/bmatcuk/doublestar/v4 v4.10.0 github.com/mattn/go-isatty v0.0.20 github.com/mitchellh/mapstructure v1.5.0 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 19c7c934..b73af61b 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/alecthomas/kong v0.7.1 h1:azoTh0IOfwlAX3qN9sHWTxACE2oV8Bg2gAwBsMwDQY4 github.com/alecthomas/kong v0.7.1/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U= github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE= github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= +github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= +github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/pkg/options/options.go b/pkg/options/options.go index e01e3e27..f1cd64c7 100644 --- a/pkg/options/options.go +++ b/pkg/options/options.go @@ -120,8 +120,7 @@ type Upload struct { Retries int `help:"The number of retry attempts before failing an upload request" default:"0"` Timeout int `help:"The number of seconds to wait before failing an upload request" default:"300"` UploadAPIRootUrl string `help:"The upload server hostname, optionally containing port number"` - Exclude []string `help:"Exclude files matching these patterns (supports wildcards like *.map, path/to/*, etc.)"` - + Exclude []string `help:"Exclude files matching these patterns. Patterns are matched against paths relative to the command execution directory. Supports wildcards (*.map), exact filenames (file.js.map), and path components (node_modules, /dist/)"` // required options All DiscoverAndUploadAny `cmd:"" help:"Upload any symbol/mapping files"` AndroidAab AndroidAabMapping `cmd:"" help:"Process and upload application bundle files for Android"` diff --git a/pkg/utils/files.go b/pkg/utils/files.go index aad02bf8..d84885cf 100644 --- a/pkg/utils/files.go +++ b/pkg/utils/files.go @@ -9,6 +9,8 @@ import ( "path/filepath" "strings" "time" + + "github.com/bmatcuk/doublestar/v4" ) const ( @@ -52,32 +54,27 @@ func IsDir(path string) bool { } // IsFileExcluded checks if a file path matches any of the exclude patterns. -// It supports wildcards like *.map, path/to/*, etc. +// It supports wildcards including *.map, path/to/*, and recursive patterns like node_modules/**. // // Parameters: // - filePath: The file path to check. -// - excludePatterns: A list of patterns to match against. +// - excludePatterns: A list of patterns to match against (supports ** for recursive matching). // // Returns: // - bool: True if the file matches any exclude pattern, false otherwise. func IsFileExcluded(filePath string, excludePatterns []string) bool { for _, pattern := range excludePatterns { - // Try matching the pattern against the full path - matched, err := filepath.Match(pattern, filePath) + // Try matching the pattern against the full path using doublestar (supports **) + matched, err := doublestar.Match(pattern, filePath) if err == nil && matched { return true } // Try matching against the base name - matched, err = filepath.Match(pattern, filepath.Base(filePath)) + matched, err = doublestar.Match(pattern, filepath.Base(filePath)) if err == nil && matched { return true } - - // Check if the pattern contains path separators and matches as substring - if strings.Contains(filePath, pattern) { - return true - } } return false } diff --git a/test/utils/files_test.go b/test/utils/files_test.go index 79b63070..32c5e641 100644 --- a/test/utils/files_test.go +++ b/test/utils/files_test.go @@ -138,4 +138,109 @@ func TestIsFileExcluded(t *testing.T) { excluded = utils.IsFileExcluded("src/main.js", []string{"build"}) assert.False(t, excluded, "Should not match when build is not in path") }) + + t.Run("Supports ** recursive globbing for directories", func(t *testing.T) { + // node_modules/** should match all files under node_modules at the root level + excluded := utils.IsFileExcluded("node_modules/package/file.js", []string{"node_modules/**"}) + assert.True(t, excluded, "Should exclude files in node_modules with ** pattern") + + excluded = utils.IsFileExcluded("node_modules/package/lib/deep/file.js", []string{"node_modules/**"}) + assert.True(t, excluded, "Should exclude deeply nested files in node_modules") + + // node_modules/** only matches if node_modules is at the start of the path + excluded = utils.IsFileExcluded("src/node_modules/package/file.js", []string{"node_modules/**"}) + assert.False(t, excluded, "node_modules/** pattern only matches at path start") + + excluded = utils.IsFileExcluded("src/components/file.js", []string{"node_modules/**"}) + assert.False(t, excluded, "Should not exclude files outside node_modules") + + // To match node_modules at any level, use **/node_modules/** + excluded = utils.IsFileExcluded("src/node_modules/package/file.js", []string{"**/node_modules/**"}) + assert.True(t, excluded, "Should exclude node_modules at any path level with **/node_modules/**") + + excluded = utils.IsFileExcluded("vendor/libs/node_modules/pkg/index.js", []string{"**/node_modules/**"}) + assert.True(t, excluded, "Should exclude node_modules deeply nested with ** pattern") + }) + + t.Run("Supports ** recursive globbing with wildcards", func(t *testing.T) { + // **/*.map should match all .map files anywhere in the tree + excluded := utils.IsFileExcluded("app.js.map", []string{"**/*.map"}) + assert.True(t, excluded, "Should match .map files in root") + + excluded = utils.IsFileExcluded("src/components/app.js.map", []string{"**/*.map"}) + assert.True(t, excluded, "Should match .map files in nested directories") + + excluded = utils.IsFileExcluded("build/dist/vendor/bundle.min.js.map", []string{"**/*.map"}) + assert.True(t, excluded, "Should match .map files deeply nested") + + excluded = utils.IsFileExcluded("src/app.js", []string{"**/*.map"}) + assert.False(t, excluded, "Should not match non-.map files") + }) + + t.Run("Supports ** in middle of path pattern", func(t *testing.T) { + // **/temp/** should match any files in temp directories at any level + excluded := utils.IsFileExcluded("temp/file.js", []string{"**/temp/**"}) + assert.True(t, excluded, "Should match files in root temp directory") + + excluded = utils.IsFileExcluded("src/temp/cache/file.js", []string{"**/temp/**"}) + assert.True(t, excluded, "Should match files in nested temp directory") + + excluded = utils.IsFileExcluded("build/output/temp/intermediate/file.js", []string{"**/temp/**"}) + assert.True(t, excluded, "Should match files in deeply nested temp directories") + + excluded = utils.IsFileExcluded("src/templates/file.js", []string{"**/temp/**"}) + assert.False(t, excluded, "Should not match files outside temp directories") + }) + + t.Run("Supports specific path with ** globbing", func(t *testing.T) { + // src/**/*.test.js should match test files in src and subdirectories + excluded := utils.IsFileExcluded("src/app.test.js", []string{"src/**/*.test.js"}) + assert.True(t, excluded, "Should match test files in src") + + excluded = utils.IsFileExcluded("src/components/button.test.js", []string{"src/**/*.test.js"}) + assert.True(t, excluded, "Should match test files in src subdirectories") + + excluded = utils.IsFileExcluded("src/utils/helpers/format.test.js", []string{"src/**/*.test.js"}) + assert.True(t, excluded, "Should match test files deeply nested in src") + + excluded = utils.IsFileExcluded("test/unit/app.test.js", []string{"src/**/*.test.js"}) + assert.False(t, excluded, "Should not match test files outside src") + + excluded = utils.IsFileExcluded("src/app.js", []string{"src/**/*.test.js"}) + assert.False(t, excluded, "Should not match non-test files in src") + }) + + t.Run("Combines ** patterns with other patterns", func(t *testing.T) { + patterns := []string{"**/*.map", "node_modules/**", "**/dist/**", "*.log"} + + excluded := utils.IsFileExcluded("src/app.js.map", patterns) + assert.True(t, excluded, "Should match .map pattern") + + excluded = utils.IsFileExcluded("node_modules/lib/index.js", patterns) + assert.True(t, excluded, "Should match node_modules pattern") + + excluded = utils.IsFileExcluded("build/dist/bundle.js", patterns) + assert.True(t, excluded, "Should match dist pattern") + + excluded = utils.IsFileExcluded("debug.log", patterns) + assert.True(t, excluded, "Should match .log pattern") + + excluded = utils.IsFileExcluded("src/components/app.js", patterns) + assert.False(t, excluded, "Should not match any pattern") + }) + + t.Run("Handles edge cases with ** patterns", func(t *testing.T) { + // Test various edge cases + excluded := utils.IsFileExcluded("file.js", []string{"**"}) + assert.True(t, excluded, "** should match everything") + + excluded = utils.IsFileExcluded("src/deep/path/file.js", []string{"**"}) + assert.True(t, excluded, "** should match any depth") + + excluded = utils.IsFileExcluded("a/b/c/d/file.js", []string{"**/b/**"}) + assert.True(t, excluded, "Should match with b directory in path") + + excluded = utils.IsFileExcluded("vendor/node_modules/pkg/index.js", []string{"**/node_modules/**"}) + assert.True(t, excluded, "Should match node_modules at any level") + }) } From 4366ae00c3269213ea9a482558f1079c4edeffb3 Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 9 Mar 2026 15:38:25 +0000 Subject: [PATCH 12/15] update exclude flag help text --- features/cli/exclude-option.feature | 9 +++++---- pkg/options/options.go | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/features/cli/exclude-option.feature b/features/cli/exclude-option.feature index f5d96234..b1434056 100644 --- a/features/cli/exclude-option.feature +++ b/features/cli/exclude-option.feature @@ -2,7 +2,6 @@ Feature: Exclude option tests Scenario: Exclude files with wildcard extension pattern When I run bugsnag-cli with upload js --upload-api-root-url=http://localhost:$MAZE_RUNNER_PORT --api-key=1234567890ABCDEF1234567890ABCDEF --overwrite --base-url=example.com --exclude=*.map --project-root=features/js/fixtures/js-multiple-maps features/js/fixtures/js-multiple-maps/dist/ - And I wait for 2 seconds Then I should receive no sourcemaps Scenario: Exclude files matching specific filename pattern @@ -13,12 +12,14 @@ Feature: Exclude option tests Scenario: Exclude files with multiple patterns When I run bugsnag-cli with upload js --upload-api-root-url=http://localhost:$MAZE_RUNNER_PORT --api-key=1234567890ABCDEF1234567890ABCDEF --overwrite --base-url=example.com --exclude=main.js.map --exclude=other.js.map --project-root=features/js/fixtures/js-multiple-maps features/js/fixtures/js-multiple-maps/dist/ - And I wait for 2 seconds Then I should receive no sourcemaps Scenario: Exclude with path pattern - When I run bugsnag-cli with upload js --upload-api-root-url=http://localhost:$MAZE_RUNNER_PORT --api-key=1234567890ABCDEF1234567890ABCDEF --overwrite --base-url=example.com --exclude=/dist/ --project-root=features/js/fixtures/js-multiple-maps features/js/fixtures/js-multiple-maps/dist/ - And I wait for 2 seconds + When I run bugsnag-cli with upload js --upload-api-root-url=http://localhost:$MAZE_RUNNER_PORT --api-key=1234567890ABCDEF1234567890ABCDEF --overwrite --base-url=example.com --exclude=features/js/fixtures/js-multiple-maps/dist/** --project-root=features/js/fixtures/js-multiple-maps features/js/fixtures/js-multiple-maps/dist/ + Then I should receive no sourcemaps + + Scenario: Exclude with path glob pattern + When I run bugsnag-cli with upload js --upload-api-root-url=http://localhost:$MAZE_RUNNER_PORT --api-key=1234567890ABCDEF1234567890ABCDEF --overwrite --base-url=example.com --exclude=**/dist/** --project-root=features/js/fixtures/js-multiple-maps features/js/fixtures/js-multiple-maps/dist/ Then I should receive no sourcemaps Scenario: Upload succeeds when exclude pattern doesn't match diff --git a/pkg/options/options.go b/pkg/options/options.go index f1cd64c7..41424618 100644 --- a/pkg/options/options.go +++ b/pkg/options/options.go @@ -120,7 +120,7 @@ type Upload struct { Retries int `help:"The number of retry attempts before failing an upload request" default:"0"` Timeout int `help:"The number of seconds to wait before failing an upload request" default:"300"` UploadAPIRootUrl string `help:"The upload server hostname, optionally containing port number"` - Exclude []string `help:"Exclude files matching these patterns. Patterns are matched against paths relative to the command execution directory. Supports wildcards (*.map), exact filenames (file.js.map), and path components (node_modules, /dist/)"` + Exclude []string `help:"Exclude files matching these patterns. Supports wildcards (*.map), recursive globs (node_modules/**, **/*.test.js) and exact filenames (file.js.map)."` // required options All DiscoverAndUploadAny `cmd:"" help:"Upload any symbol/mapping files"` AndroidAab AndroidAabMapping `cmd:"" help:"Process and upload application bundle files for Android"` From 516ef5649959274fce160b7369da205ec8aa12e1 Mon Sep 17 00:00:00 2001 From: Josh Date: Mon, 9 Mar 2026 15:48:20 +0000 Subject: [PATCH 13/15] add support for ** globbing on the exclude falg --- pkg/utils/files.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/utils/files.go b/pkg/utils/files.go index d84885cf..07ad16ba 100644 --- a/pkg/utils/files.go +++ b/pkg/utils/files.go @@ -70,6 +70,15 @@ func IsFileExcluded(filePath string, excludePatterns []string) bool { return true } + // If pattern doesn't start with **, also try matching with **/ prepended + // This allows patterns like "node_modules/**" to match anywhere in the path + if !strings.HasPrefix(pattern, "**/") { + matched, err = doublestar.Match("**/"+pattern, filePath) + if err == nil && matched { + return true + } + } + // Try matching against the base name matched, err = doublestar.Match(pattern, filepath.Base(filePath)) if err == nil && matched { From 356b82dad1d96864df46a5c835178b0c7cd16e9a Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 10 Mar 2026 11:24:40 +0000 Subject: [PATCH 14/15] ensure that we are using the full path for the name of the file uploaded --- pkg/android/process-uploads.go | 2 +- pkg/ios/process-dsym.go | 20 ++++++++++++++++++-- pkg/server/request.go | 2 +- pkg/upload/all.go | 10 +++++++++- pkg/upload/android-proguard.go | 20 ++++++++++++++++++-- pkg/upload/breakpad.go | 10 +++++++++- pkg/upload/dart.go | 20 ++++++++++++++++++-- pkg/upload/js.go | 10 +++++++++- pkg/upload/linux.go | 2 +- 9 files changed, 84 insertions(+), 12 deletions(-) diff --git a/pkg/android/process-uploads.go b/pkg/android/process-uploads.go index 1fd7d99d..961015bb 100644 --- a/pkg/android/process-uploads.go +++ b/pkg/android/process-uploads.go @@ -92,7 +92,7 @@ func UploadAndroidNdk( "/ndk-symbol", params, fileField, - filepath.Base(originalFile), + originalFile, opts, logger, ) diff --git a/pkg/ios/process-dsym.go b/pkg/ios/process-dsym.go index a65a88d6..beb3ef05 100644 --- a/pkg/ios/process-dsym.go +++ b/pkg/ios/process-dsym.go @@ -60,12 +60,28 @@ func ProcessDsymUpload(plistPath string, projectRoot string, options options.CLI } // Attempt to upload the dSYM file. - err = server.ProcessFileRequest(options.ApiKey, "/dsym", uploadOptions, fileFieldData, dsym.UUID, options, logger) + err = server.ProcessFileRequest( + options.ApiKey, + "/dsym", + uploadOptions, + fileFieldData, + dsym.UUID, + options, + logger, + ) if err != nil { // Retry with the base endpoint if a 404 error occurs. if strings.Contains(err.Error(), "404 Not Found") { logger.Debug(fmt.Sprintf("Retrying upload for dSYM %s at base endpoint", dsymInfo)) - err = server.ProcessFileRequest(options.ApiKey, "", uploadOptions, fileFieldData, dsym.UUID, options, logger) + err = server.ProcessFileRequest( + options.ApiKey, + "", + uploadOptions, + fileFieldData, + dsym.UUID, + options, + logger, + ) } if err != nil { return fmt.Errorf("failed to upload dSYM %s: %w", dsymInfo, err) diff --git a/pkg/server/request.go b/pkg/server/request.go index c05fc1d8..be4fe800 100644 --- a/pkg/server/request.go +++ b/pkg/server/request.go @@ -3,7 +3,6 @@ package server import ( "bytes" "fmt" - "github.com/bugsnag/bugsnag-cli/pkg/endpoints" "io" "mime/multipart" "net/http" @@ -14,6 +13,7 @@ import ( "github.com/pkg/errors" + "github.com/bugsnag/bugsnag-cli/pkg/endpoints" "github.com/bugsnag/bugsnag-cli/pkg/log" "github.com/bugsnag/bugsnag-cli/pkg/options" "github.com/bugsnag/bugsnag-cli/pkg/utils" diff --git a/pkg/upload/all.go b/pkg/upload/all.go index a155b1db..d7812fcf 100644 --- a/pkg/upload/all.go +++ b/pkg/upload/all.go @@ -48,7 +48,15 @@ func All(options options.CLI, logger log.Logger) error { fileFieldData["file"] = server.LocalFile(file) } - err := server.ProcessFileRequest(options.ApiKey, "", uploadOptions, fileFieldData, file, options, logger) + err := server.ProcessFileRequest( + options.ApiKey, + "", + uploadOptions, + fileFieldData, + file, + options, + logger, + ) if err != nil { return err } diff --git a/pkg/upload/android-proguard.go b/pkg/upload/android-proguard.go index 44610bd3..63565fc0 100644 --- a/pkg/upload/android-proguard.go +++ b/pkg/upload/android-proguard.go @@ -168,12 +168,28 @@ func ProcessAndroidProguard(options options.CLI, logger log.Logger) error { fileFieldData["proguard"] = server.LocalFile(outputFile) // Attempt upload to Bugsnag API - err = server.ProcessFileRequest(options.ApiKey, "/proguard", uploadOptions, fileFieldData, outputFile, options, logger) + err = server.ProcessFileRequest( + options.ApiKey, + "/proguard", + uploadOptions, + fileFieldData, + outputFile, + options, + logger, + ) // Retry at base endpoint if 404 received if err != nil && strings.Contains(err.Error(), "404 Not Found") { logger.Debug("Retrying upload for proguard at base endpoint") - err = server.ProcessFileRequest(options.ApiKey, "", uploadOptions, fileFieldData, outputFile, options, logger) + err = server.ProcessFileRequest( + options.ApiKey, + "", + uploadOptions, + fileFieldData, + outputFile, + options, + logger, + ) } if err != nil { diff --git a/pkg/upload/breakpad.go b/pkg/upload/breakpad.go index 01ca207c..d16db60e 100644 --- a/pkg/upload/breakpad.go +++ b/pkg/upload/breakpad.go @@ -71,7 +71,15 @@ func ProcessBreakpad(globalOptions options.CLI, logger log.Logger) error { ) // Send the file upload request to the Breakpad symbol endpoint - err = server.ProcessFileRequest(apiKey, "/breakpad-symbol"+queryParams, formFields, fileFieldData, file, globalOptions, logger) + err = server.ProcessFileRequest( + apiKey, + "/breakpad-symbol"+queryParams, + formFields, + fileFieldData, + file, + globalOptions, + logger, + ) if err != nil { return err } diff --git a/pkg/upload/dart.go b/pkg/upload/dart.go index e1683074..4b32ad5b 100644 --- a/pkg/upload/dart.go +++ b/pkg/upload/dart.go @@ -48,7 +48,15 @@ func Dart(options options.CLI, logger log.Logger) error { fileFieldData := make(map[string]server.FileField) fileFieldData["symbolFile"] = server.LocalFile(file) - err := server.ProcessFileRequest(options.ApiKey, "/dart-symbol", uploadOptions, fileFieldData, file, options, logger) + err := server.ProcessFileRequest( + options.ApiKey, + "/dart-symbol", + uploadOptions, + fileFieldData, + file, + options, + logger, + ) if err != nil { @@ -91,7 +99,15 @@ func Dart(options options.CLI, logger log.Logger) error { if options.DryRun { err = nil } else { - err = server.ProcessFileRequest(options.ApiKey, "/dart-symbol", uploadOptions, fileFieldData, file, options, logger) + err = server.ProcessFileRequest( + options.ApiKey, + "/dart-symbol", + uploadOptions, + fileFieldData, + file, + options, + logger, + ) } if err != nil { diff --git a/pkg/upload/js.go b/pkg/upload/js.go index a9747d8b..0a36099d 100644 --- a/pkg/upload/js.go +++ b/pkg/upload/js.go @@ -365,7 +365,15 @@ func uploadSingleSourceMap(sourceMapPath string, bundlePath string, bundleUrl st fileFieldData["sourceMap"] = sourceMapFile fileFieldData["minifiedFile"] = server.LocalFile(bundlePath) - err = server.ProcessFileRequest(options.ApiKey, "/sourcemap", uploadOptions, fileFieldData, sourceMapPath, options, logger) + err = server.ProcessFileRequest( + options.ApiKey, + "/sourcemap", + uploadOptions, + fileFieldData, + sourceMapPath, + options, + logger, + ) if err != nil { return fmt.Errorf("encountered error when uploading js sourcemap: %s", err.Error()) diff --git a/pkg/upload/linux.go b/pkg/upload/linux.go index ba2b2475..2d0fce5a 100644 --- a/pkg/upload/linux.go +++ b/pkg/upload/linux.go @@ -49,7 +49,7 @@ func uploadSymbolFile(symbolFile string, linuxOpts options.LinuxOptions, opts op "/linux", uploadOpts, fileField, - filepath.Base(symbolFile), + symbolFile, opts, logger, ); err != nil { From 9594d9e9db59de4c421dd640fb7616e533399cf4 Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 10 Mar 2026 14:29:16 +0000 Subject: [PATCH 15/15] add test for excluding an abs path --- features/cli/exclude-option.feature | 7 +++++++ features/steps/steps.rb | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/features/cli/exclude-option.feature b/features/cli/exclude-option.feature index b1434056..7eed15f8 100644 --- a/features/cli/exclude-option.feature +++ b/features/cli/exclude-option.feature @@ -18,6 +18,13 @@ Feature: Exclude option tests When I run bugsnag-cli with upload js --upload-api-root-url=http://localhost:$MAZE_RUNNER_PORT --api-key=1234567890ABCDEF1234567890ABCDEF --overwrite --base-url=example.com --exclude=features/js/fixtures/js-multiple-maps/dist/** --project-root=features/js/fixtures/js-multiple-maps features/js/fixtures/js-multiple-maps/dist/ Then I should receive no sourcemaps + Scenario: Exclude with absolute path + Given I get the current working directory + When I run bugsnag-cli with upload js --upload-api-root-url=http://localhost:$MAZE_RUNNER_PORT --api-key=1234567890ABCDEF1234567890ABCDEF --overwrite --base-url=example.com --exclude=$ABS_PATH/features/js/fixtures/js-multiple-maps/dist/other.js.map --project-root=features/js/fixtures/js-multiple-maps features/js/fixtures/js-multiple-maps/dist/ + And I wait to receive 1 sourcemap + Then the sourcemaps are valid for the API + And the sourcemap payload field "minifiedUrl" equals "example.com/main.js" + Scenario: Exclude with path glob pattern When I run bugsnag-cli with upload js --upload-api-root-url=http://localhost:$MAZE_RUNNER_PORT --api-key=1234567890ABCDEF1234567890ABCDEF --overwrite --base-url=example.com --exclude=**/dist/** --project-root=features/js/fixtures/js-multiple-maps features/js/fixtures/js-multiple-maps/dist/ Then I should receive no sourcemaps diff --git a/features/steps/steps.rb b/features/steps/steps.rb index b54a8676..e2f2c64e 100644 --- a/features/steps/steps.rb +++ b/features/steps/steps.rb @@ -489,3 +489,8 @@ def clean_and_build(scheme, project_path, build_target) Dir.chdir(base_dir) Maze.check.include(`ls #{@fixture_dir}/dist`, 'index.js.map') end + +Given('I get the current working directory') do + @current_dir = Dir.pwd + ENV['ABS_PATH'] = @current_dir +end