Skip to content

Commit 4c35007

Browse files
authored
add checks for dependencies (#23)
2 parents 770e128 + b49d4a9 commit 4c35007

3 files changed

Lines changed: 223 additions & 0 deletions

File tree

internal/check/deps.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package check
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os/exec"
7+
"path/filepath"
8+
)
9+
10+
// goModDownload is a variable so tests can stub it.
11+
var goModDownload = func(dir string) error {
12+
cmd := exec.Command("go", "mod", "download", "-json", "all")
13+
cmd.Dir = dir
14+
return cmd.Run()
15+
}
16+
17+
type DepsCheck struct {
18+
Dir string
19+
Stack string // "node", "python", or "go"
20+
goCheck func(dir string) error
21+
}
22+
23+
func (c *DepsCheck) Name() string {
24+
switch c.Stack {
25+
case "node":
26+
return "Node dependencies installed"
27+
case "python":
28+
return "Python dependencies installed"
29+
case "go":
30+
return "Go dependencies installed"
31+
default:
32+
return "Project dependencies installed"
33+
}
34+
}
35+
36+
func (c *DepsCheck) Run(_ context.Context) Result {
37+
switch c.Stack {
38+
case "node":
39+
return c.runNode()
40+
case "python":
41+
return c.runPython()
42+
case "go":
43+
return c.runGo()
44+
default:
45+
return Result{
46+
Name: c.Name(),
47+
Status: StatusSkipped,
48+
Message: "unknown stack type for dependency check",
49+
}
50+
}
51+
}
52+
53+
func (c *DepsCheck) runNode() Result {
54+
nodeModules := filepath.Join(c.Dir, "node_modules")
55+
if dirExists(nodeModules) {
56+
return Result{
57+
Name: c.Name(),
58+
Status: StatusPass,
59+
Message: "node_modules directory exists",
60+
}
61+
}
62+
63+
return Result{
64+
Name: c.Name(),
65+
Status: StatusFail,
66+
Message: "node_modules directory not found",
67+
Fix: "run `npm install` or `pnpm install` to install Node dependencies",
68+
}
69+
}
70+
71+
func (c *DepsCheck) runPython() Result {
72+
venv := filepath.Join(c.Dir, "venv")
73+
dotVenv := filepath.Join(c.Dir, ".venv")
74+
75+
if dirExists(venv) || dirExists(dotVenv) {
76+
return Result{
77+
Name: c.Name(),
78+
Status: StatusPass,
79+
Message: "Python virtual environment directory exists",
80+
}
81+
}
82+
83+
return Result{
84+
Name: c.Name(),
85+
Status: StatusFail,
86+
Message: "Python virtual environment directory not found",
87+
Fix: "create a virtual environment (e.g. `python -m venv .venv`) and install dependencies with `pip install -r requirements.txt` or equivalent",
88+
}
89+
}
90+
91+
func (c *DepsCheck) runGo() Result {
92+
vendorDir := filepath.Join(c.Dir, "vendor")
93+
if dirExists(vendorDir) {
94+
return Result{
95+
Name: c.Name(),
96+
Status: StatusPass,
97+
Message: "vendor directory exists; Go dependencies are vendored",
98+
}
99+
}
100+
101+
check := c.goCheck
102+
if check == nil {
103+
check = goModDownload
104+
}
105+
106+
if err := check(c.Dir); err != nil {
107+
return Result{
108+
Name: c.Name(),
109+
Status: StatusFail,
110+
Message: fmt.Sprintf("Go module cache not populated: %v", err),
111+
Fix: "run `go mod download` to download Go module dependencies",
112+
}
113+
}
114+
115+
return Result{
116+
Name: c.Name(),
117+
Status: StatusPass,
118+
Message: "Go module cache is populated",
119+
}
120+
}
121+

internal/check/deps_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package check
2+
3+
import (
4+
"context"
5+
"errors"
6+
"os"
7+
"path/filepath"
8+
"testing"
9+
)
10+
11+
func TestDepsCheck_Node_PassAndFail(t *testing.T) {
12+
dir := t.TempDir()
13+
check := &DepsCheck{Dir: dir, Stack: "node"}
14+
15+
// Pass when node_modules exists
16+
if err := os.Mkdir(filepath.Join(dir, "node_modules"), 0o755); err != nil {
17+
t.Fatalf("failed to create node_modules: %v", err)
18+
}
19+
result := check.Run(context.Background())
20+
if result.Status != StatusPass {
21+
t.Errorf("expected pass when node_modules exists, got %v: %s", result.Status, result.Message)
22+
}
23+
24+
// Fail when node_modules is missing
25+
if err := os.RemoveAll(filepath.Join(dir, "node_modules")); err != nil {
26+
t.Fatalf("failed to remove node_modules: %v", err)
27+
}
28+
result = check.Run(context.Background())
29+
if result.Status != StatusFail {
30+
t.Errorf("expected fail when node_modules missing, got %v: %s", result.Status, result.Message)
31+
}
32+
}
33+
34+
func TestDepsCheck_Python_PassAndFail(t *testing.T) {
35+
dir := t.TempDir()
36+
check := &DepsCheck{Dir: dir, Stack: "python"}
37+
38+
// Pass when .venv exists
39+
if err := os.Mkdir(filepath.Join(dir, ".venv"), 0o755); err != nil {
40+
t.Fatalf("failed to create .venv: %v", err)
41+
}
42+
result := check.Run(context.Background())
43+
if result.Status != StatusPass {
44+
t.Errorf("expected pass when .venv exists, got %v: %s", result.Status, result.Message)
45+
}
46+
47+
// Fail when no venv directories exist
48+
if err := os.RemoveAll(filepath.Join(dir, ".venv")); err != nil {
49+
t.Fatalf("failed to remove .venv: %v", err)
50+
}
51+
result = check.Run(context.Background())
52+
if result.Status != StatusFail {
53+
t.Errorf("expected fail when no venv directories, got %v: %s", result.Status, result.Message)
54+
}
55+
}
56+
57+
func TestDepsCheck_Go_PassAndFail(t *testing.T) {
58+
dir := t.TempDir()
59+
60+
// Pass when vendor directory exists
61+
check := &DepsCheck{Dir: dir, Stack: "go"}
62+
if err := os.Mkdir(filepath.Join(dir, "vendor"), 0o755); err != nil {
63+
t.Fatalf("failed to create vendor: %v", err)
64+
}
65+
result := check.Run(context.Background())
66+
if result.Status != StatusPass {
67+
t.Errorf("expected pass when vendor exists, got %v: %s", result.Status, result.Message)
68+
}
69+
70+
// When vendor is missing, pass if goCheck succeeds
71+
if err := os.RemoveAll(filepath.Join(dir, "vendor")); err != nil {
72+
t.Fatalf("failed to remove vendor: %v", err)
73+
}
74+
check = &DepsCheck{
75+
Dir: dir,
76+
Stack: "go",
77+
goCheck: func(string) error {
78+
return nil
79+
},
80+
}
81+
result = check.Run(context.Background())
82+
if result.Status != StatusPass {
83+
t.Errorf("expected pass when goCheck succeeds, got %v: %s", result.Status, result.Message)
84+
}
85+
86+
// Fail when goCheck reports an error
87+
check = &DepsCheck{
88+
Dir: dir,
89+
Stack: "go",
90+
goCheck: func(string) error {
91+
return errors.New("download failed")
92+
},
93+
}
94+
result = check.Run(context.Background())
95+
if result.Status != StatusFail {
96+
t.Errorf("expected fail when goCheck fails, got %v: %s", result.Status, result.Message)
97+
}
98+
}
99+

internal/check/registry.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,19 @@ func Build(stack detector.DetectedStack) []Check {
1212
if stack.Go {
1313
cs = append(cs, &BinaryCheck{Binary: "go"})
1414
cs = append(cs, &GoVersionCheck{Dir: "."})
15+
cs = append(cs, &DepsCheck{Dir: ".", Stack: "go"})
1516
}
1617
if stack.Node {
1718
cs = append(cs, &BinaryCheck{Binary: "node"})
1819
cs = append(cs, &BinaryCheck{Binary: "npm"})
1920
cs = append(cs, &NodeVersionCheck{Dir: "."})
21+
cs = append(cs, &DepsCheck{Dir: ".", Stack: "node"})
2022
cs = append(cs, &GitHooksCheck{Dir: ".", Stack: "node"})
2123
}
2224
if stack.Python {
2325
cs = append(cs, &BinaryCheck{Binary: "python3"})
2426
cs = append(cs, &BinaryCheck{Binary: "pip"})
27+
cs = append(cs, &DepsCheck{Dir: ".", Stack: "python"})
2528
cs = append(cs, &GitHooksCheck{Dir: ".", Stack: "python"})
2629
}
2730
if stack.Java {

0 commit comments

Comments
 (0)