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
25 changes: 20 additions & 5 deletions src/ruby/versions/ruby.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,24 @@ func (v *Versions) GetBundlerVersion() (string, error) {
return "", err
}

re := regexp.MustCompile(`Bundler version (\d+\.\d+\.\d+) .*`)
match := re.FindStringSubmatch(stdout.String())
return parseBundlerVersion(stdout.String())
}

// parseBundlerVersion extracts the bundler version number from the output of
// `bundle version`.
//
// Historically this command printed output prefixed with "Bundler version "
// (e.g. "Bundler version 2.7.2 (2025-09-09 commit b463ced1459)"), but as of
// RubyGems/Bundler 4.x the prefix has been dropped and the command just
// prints the bare version number instead (e.g.
// "4.0.22 (2026-09-30 commit ff2bd50)"). This parses both formats by making
// the "Bundler version " prefix optional.
func parseBundlerVersion(output string) (string, error) {
re := regexp.MustCompile(`(?:Bundler version )?(\d+\.\d+\.\d+)`)
match := re.FindStringSubmatch(output)

if len(match) != 2 {
return "", fmt.Errorf("failed to determine bundler version from output: %s", stdout)
return "", fmt.Errorf("failed to determine bundler version from output: %s", output)
}

return match[1], nil
Expand Down Expand Up @@ -191,9 +204,11 @@ func (v *Versions) GemMajorVersion(gem string) (int, error) {
}
}

//Should return true if either:
// Should return true if either:
// (1) the only platform in the Gemfile.lock is windows (mingw/mswin)
// -or-
//
// -or-
//
// (2) the Gemfile.lock line endings are /r/n, rather than just /n
func (v *Versions) HasWindowsGemfileLock() (bool, error) {
gemfileLockPath := v.Gemfile() + ".lock"
Expand Down
62 changes: 62 additions & 0 deletions src/ruby/versions/ruby_internal_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package versions

import "testing"

// TestParseBundlerVersion is a regression test for a real CI failure caused
// by a rubygems dependency bump (3.4.19 -> 4.0.22): `bundle version`'s
// output format changed, dropping the "Bundler version " prefix, which broke
// the previous regex entirely (zero matches, hard failure during staging).
func TestParseBundlerVersion(t *testing.T) {
cases := []struct {
name string
output string
want string
wantErr bool
}{
{
name: "legacy format with 'Bundler version ' prefix",
output: "Bundler version 2.7.2 (2025-09-09 commit b463ced1459)\n",
want: "2.7.2",
},
{
name: "new format without prefix (RubyGems/Bundler 4.x)",
output: "4.0.22 (2026-09-30 commit ff2bd50)\n",
want: "4.0.22",
},
{
name: "bare version number with no trailing metadata",
output: "1.17.2\n",
want: "1.17.2",
},
{
name: "unrecognized output",
output: "error: something went wrong\n",
wantErr: true,
},
{
name: "empty output",
output: "",
wantErr: true,
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := parseBundlerVersion(tc.output)

if tc.wantErr {
if err == nil {
t.Fatalf("expected an error, got version %q", got)
}
return
}

if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != tc.want {
t.Fatalf("got %q, want %q", got, tc.want)
}
})
}
}
Loading