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
32 changes: 32 additions & 0 deletions internal/check/deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ func (c *DepsCheck) Name() string {
return "Python dependencies installed"
case "go":
return "Go dependencies installed"
case "ruby":
return "Ruby dependencies installed"
default:
return "Project dependencies installed"
}
Expand All @@ -57,6 +59,8 @@ func (c *DepsCheck) Run(_ context.Context) Result {
return c.runPython()
case "go":
return c.runGo()
case "ruby":
return c.runRuby()
default:
return Result{
Name: c.Name(),
Expand Down Expand Up @@ -248,6 +252,34 @@ func parseFreeze(output []byte) map[string]struct{} {
return installed
}

func (c *DepsCheck) runRuby() Result {
// vendor/bundle is the canonical signal that bundle install --path has been run.
// Gemfile.lock is the fallback: it exists once bundle install has succeeded at least once.
vendorBundle := filepath.Join(c.Dir, "vendor", "bundle")
gemfileLock := filepath.Join(c.Dir, "Gemfile.lock")

if dirExists(vendorBundle) {
return Result{
Name: c.Name(),
Status: StatusPass,
Message: "vendor/bundle directory exists; Ruby gems are installed",
}
}
if _, err := os.Stat(gemfileLock); err == nil {
return Result{
Name: c.Name(),
Status: StatusPass,
Message: "Gemfile.lock exists; Ruby gems have been installed",
}
}
return Result{
Name: c.Name(),
Status: StatusFail,
Message: "Gemfile.lock not found and vendor/bundle directory missing",
Fix: "run `bundle install` to install Ruby gems",
}
}

func (c *DepsCheck) runGo() Result {
vendorDir := filepath.Join(c.Dir, "vendor")
if dirExists(vendorDir) {
Expand Down
55 changes: 55 additions & 0 deletions internal/check/deps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,4 +245,59 @@ func setupPythonDir(t *testing.T, requirements string) (dir string, pipBin strin
t.Fatalf("write requirements.txt: %v", err)
}
return dir, pipBin
}

func TestDepsCheck_Ruby_Pass_VendorBundle(t *testing.T) {
dir := t.TempDir()
if err := os.MkdirAll(filepath.Join(dir, "vendor", "bundle"), 0o755); err != nil {
t.Fatalf("mkdir vendor/bundle: %v", err)
}
c := &DepsCheck{Dir: dir, Stack: "ruby"}
r := c.Run(context.Background())
if r.Status != StatusPass {
t.Errorf("expected pass when vendor/bundle exists, got %v: %s", r.Status, r.Message)
}
}

func TestDepsCheck_Ruby_Pass_GemfileLock(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "Gemfile.lock"), []byte("GEM\n"), 0o644); err != nil {
t.Fatalf("write Gemfile.lock: %v", err)
}
c := &DepsCheck{Dir: dir, Stack: "ruby"}
r := c.Run(context.Background())
if r.Status != StatusPass {
t.Errorf("expected pass when Gemfile.lock exists, got %v: %s", r.Status, r.Message)
}
}

func TestDepsCheck_Ruby_Fail_NeitherPresent(t *testing.T) {
dir := t.TempDir()
c := &DepsCheck{Dir: dir, Stack: "ruby"}
r := c.Run(context.Background())
if r.Status != StatusFail {
t.Errorf("expected fail when neither vendor/bundle nor Gemfile.lock exist, got %v", r.Status)
}
if !strings.Contains(r.Fix, "bundle install") {
t.Errorf("expected fix to mention 'bundle install', got: %s", r.Fix)
}
}

func TestDepsCheck_Ruby_VendorBundle_Takes_Priority(t *testing.T) {
// Both present — vendor/bundle should win (pass with that message).
dir := t.TempDir()
if err := os.MkdirAll(filepath.Join(dir, "vendor", "bundle"), 0o755); err != nil {
t.Fatalf("mkdir vendor/bundle: %v", err)
}
if err := os.WriteFile(filepath.Join(dir, "Gemfile.lock"), []byte("GEM\n"), 0o644); err != nil {
t.Fatalf("write Gemfile.lock: %v", err)
}
c := &DepsCheck{Dir: dir, Stack: "ruby"}
r := c.Run(context.Background())
if r.Status != StatusPass {
t.Errorf("expected pass, got %v: %s", r.Status, r.Message)
}
if !strings.Contains(r.Message, "vendor/bundle") {
t.Errorf("expected vendor/bundle message to take priority, got: %s", r.Message)
}
}
7 changes: 6 additions & 1 deletion internal/check/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ func Build(stack detector.DetectedStack) []Check {
cs = append(cs, &DepsCheck{Dir: ".", Stack: "python"})
cs = append(cs, &GitHooksCheck{Dir: ".", Stack: "python"})
}
if stack.Ruby {
cs = append(cs, &BinaryCheck{Binary: "ruby"})
cs = append(cs, &BinaryCheck{Binary: "bundler"})
cs = append(cs, &DepsCheck{Dir: ".", Stack: "ruby"})
}
if stack.Java {
cs = append(cs, &BinaryCheck{Binary: "java"})
if stack.Maven {
Expand Down Expand Up @@ -122,4 +127,4 @@ func Build(stack detector.DetectedStack) []Check {
func fileExists(path string) bool {
_, err := os.Stat(path)
return err == nil
}
}
2 changes: 2 additions & 0 deletions internal/detector/detector.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type DetectedStack struct {
// Possible values: "npm", "pnpm", "yarn". Empty string when Node is false.
PackageManager string
Python bool
Ruby bool
Java bool
Maven bool
Gradle bool
Expand Down Expand Up @@ -44,6 +45,7 @@ func Detect(dir string) DetectedStack {
}
stack.Python = fileExists(filepath.Join(dir, "requirements.txt")) ||
fileExists(filepath.Join(dir, "pyproject.toml"))
stack.Ruby = fileExists(filepath.Join(dir, "Gemfile"))
stack.Maven = fileExists(filepath.Join(dir, "pom.xml"))
stack.Gradle = fileExists(filepath.Join(dir, "build.gradle"))
stack.Java = stack.Maven || stack.Gradle
Expand Down
17 changes: 17 additions & 0 deletions internal/detector/detector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,21 @@ func TestDetect_GoWork_false_when_absent(t *testing.T) {
if stack.GoWork {
t.Error("expected GoWork=false when go.work is absent")
}
}

func TestDetect_Ruby_true_when_gemfile_present(t *testing.T) {
dir := t.TempDir()
touch(t, filepath.Join(dir, "Gemfile"))
stack := Detect(dir)
if !stack.Ruby {
t.Error("expected Ruby=true when Gemfile exists")
}
}

func TestDetect_Ruby_false_when_absent(t *testing.T) {
dir := t.TempDir()
stack := Detect(dir)
if stack.Ruby {
t.Error("expected Ruby=false when no Gemfile present")
}
}
Loading