- Move cmd/core/cmd/* to cmd/* (flatten directory structure) - Update module path from github.com/host-uk/core/cmd/core to github.com/host-uk/core - Remove go.mod files from pkg/* (single module now) - Simplify pkg/mcp to file operations only (no GUI deps) - GUI features (display, webview, process) stay in core-gui/pkg/mcp - Fix import aliases (sdkpkg) for package name conflicts - Remove old backup directory (cmdbk) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
633 lines
17 KiB
Go
633 lines
17 KiB
Go
// Package gocmd provides Go development commands.
|
|
//
|
|
// Note: Package named gocmd because 'go' is a reserved keyword.
|
|
package gocmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
"github.com/host-uk/core/cmd/shared"
|
|
"github.com/leaanthony/clir"
|
|
)
|
|
|
|
// Style aliases for shared styles
|
|
var (
|
|
successStyle = shared.SuccessStyle
|
|
errorStyle = shared.ErrorStyle
|
|
dimStyle = shared.DimStyle
|
|
)
|
|
|
|
// AddGoCommands adds Go development commands.
|
|
func AddGoCommands(parent *clir.Cli) {
|
|
goCmd := parent.NewSubCommand("go", "Go development tools")
|
|
goCmd.LongDescription("Go development tools with enhanced output and environment setup.\n\n" +
|
|
"Commands:\n" +
|
|
" test Run tests\n" +
|
|
" cov Run tests with coverage report\n" +
|
|
" fmt Format Go code\n" +
|
|
" lint Run golangci-lint\n" +
|
|
" install Install Go binary\n" +
|
|
" mod Module management (tidy, download, verify)\n" +
|
|
" work Workspace management")
|
|
|
|
addGoTestCommand(goCmd)
|
|
addGoCovCommand(goCmd)
|
|
addGoFmtCommand(goCmd)
|
|
addGoLintCommand(goCmd)
|
|
addGoInstallCommand(goCmd)
|
|
addGoModCommand(goCmd)
|
|
addGoWorkCommand(goCmd)
|
|
}
|
|
|
|
func addGoTestCommand(parent *clir.Command) {
|
|
var (
|
|
coverage bool
|
|
pkg string
|
|
run string
|
|
short bool
|
|
race bool
|
|
json bool
|
|
verbose bool
|
|
)
|
|
|
|
testCmd := parent.NewSubCommand("test", "Run tests with coverage")
|
|
testCmd.LongDescription("Run Go tests with coverage reporting.\n\n" +
|
|
"Sets MACOSX_DEPLOYMENT_TARGET=26.0 to suppress linker warnings.\n" +
|
|
"Filters noisy output and provides colour-coded coverage.\n\n" +
|
|
"Examples:\n" +
|
|
" core go test\n" +
|
|
" core go test --coverage\n" +
|
|
" core go test --pkg ./pkg/crypt\n" +
|
|
" core go test --run TestHash")
|
|
|
|
testCmd.BoolFlag("coverage", "Show detailed per-package coverage", &coverage)
|
|
testCmd.StringFlag("pkg", "Package to test (default: ./...)", &pkg)
|
|
testCmd.StringFlag("run", "Run only tests matching regexp", &run)
|
|
testCmd.BoolFlag("short", "Run only short tests", &short)
|
|
testCmd.BoolFlag("race", "Enable race detector", &race)
|
|
testCmd.BoolFlag("json", "Output JSON results", &json)
|
|
testCmd.BoolFlag("v", "Verbose output", &verbose)
|
|
|
|
testCmd.Action(func() error {
|
|
return runGoTest(coverage, pkg, run, short, race, json, verbose)
|
|
})
|
|
}
|
|
|
|
func runGoTest(coverage bool, pkg, run string, short, race, jsonOut, verbose bool) error {
|
|
if pkg == "" {
|
|
pkg = "./..."
|
|
}
|
|
|
|
args := []string{"test"}
|
|
|
|
if coverage {
|
|
args = append(args, "-cover")
|
|
} else {
|
|
args = append(args, "-cover")
|
|
}
|
|
|
|
if run != "" {
|
|
args = append(args, "-run", run)
|
|
}
|
|
if short {
|
|
args = append(args, "-short")
|
|
}
|
|
if race {
|
|
args = append(args, "-race")
|
|
}
|
|
if verbose {
|
|
args = append(args, "-v")
|
|
}
|
|
|
|
args = append(args, pkg)
|
|
|
|
if !jsonOut {
|
|
fmt.Printf("%s Running tests\n", dimStyle.Render("Test:"))
|
|
fmt.Printf(" %s %s\n", dimStyle.Render("Package:"), pkg)
|
|
fmt.Println()
|
|
}
|
|
|
|
cmd := exec.Command("go", args...)
|
|
cmd.Env = append(os.Environ(), "MACOSX_DEPLOYMENT_TARGET=26.0", "CGO_ENABLED=0")
|
|
cmd.Dir, _ = os.Getwd()
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
outputStr := string(output)
|
|
|
|
// Filter linker warnings
|
|
lines := strings.Split(outputStr, "\n")
|
|
var filtered []string
|
|
for _, line := range lines {
|
|
if !strings.Contains(line, "ld: warning:") {
|
|
filtered = append(filtered, line)
|
|
}
|
|
}
|
|
outputStr = strings.Join(filtered, "\n")
|
|
|
|
// Parse results
|
|
passed, failed, skipped := parseTestResults(outputStr)
|
|
cov := parseOverallCoverage(outputStr)
|
|
|
|
if jsonOut {
|
|
fmt.Printf(`{"passed":%d,"failed":%d,"skipped":%d,"coverage":%.1f,"exit_code":%d}`,
|
|
passed, failed, skipped, cov, cmd.ProcessState.ExitCode())
|
|
fmt.Println()
|
|
return err
|
|
}
|
|
|
|
// Print filtered output if verbose or failed
|
|
if verbose || err != nil {
|
|
fmt.Println(outputStr)
|
|
}
|
|
|
|
// Summary
|
|
if err == nil {
|
|
fmt.Printf(" %s %d passed\n", successStyle.Render("✓"), passed)
|
|
} else {
|
|
fmt.Printf(" %s %d passed, %d failed\n", errorStyle.Render("✗"), passed, failed)
|
|
}
|
|
|
|
if cov > 0 {
|
|
covStyle := successStyle
|
|
if cov < 50 {
|
|
covStyle = errorStyle
|
|
} else if cov < 80 {
|
|
covStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#f59e0b"))
|
|
}
|
|
fmt.Printf("\n %s %s\n", dimStyle.Render("Coverage:"), covStyle.Render(fmt.Sprintf("%.1f%%", cov)))
|
|
}
|
|
|
|
if err == nil {
|
|
fmt.Printf("\n%s\n", successStyle.Render("PASS All tests passed"))
|
|
} else {
|
|
fmt.Printf("\n%s\n", errorStyle.Render("FAIL Some tests failed"))
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func parseTestResults(output string) (passed, failed, skipped int) {
|
|
passRe := regexp.MustCompile(`(?m)^ok\s+`)
|
|
failRe := regexp.MustCompile(`(?m)^FAIL\s+`)
|
|
skipRe := regexp.MustCompile(`(?m)^\?\s+`)
|
|
|
|
passed = len(passRe.FindAllString(output, -1))
|
|
failed = len(failRe.FindAllString(output, -1))
|
|
skipped = len(skipRe.FindAllString(output, -1))
|
|
return
|
|
}
|
|
|
|
func parseOverallCoverage(output string) float64 {
|
|
re := regexp.MustCompile(`coverage:\s+([\d.]+)%`)
|
|
matches := re.FindAllStringSubmatch(output, -1)
|
|
if len(matches) == 0 {
|
|
return 0
|
|
}
|
|
|
|
var total float64
|
|
for _, m := range matches {
|
|
var cov float64
|
|
fmt.Sscanf(m[1], "%f", &cov)
|
|
total += cov
|
|
}
|
|
return total / float64(len(matches))
|
|
}
|
|
|
|
func addGoCovCommand(parent *clir.Command) {
|
|
var (
|
|
pkg string
|
|
html bool
|
|
open bool
|
|
threshold float64
|
|
)
|
|
|
|
covCmd := parent.NewSubCommand("cov", "Run tests with coverage report")
|
|
covCmd.LongDescription("Run tests and generate coverage report.\n\n" +
|
|
"Examples:\n" +
|
|
" core go cov # Run with coverage summary\n" +
|
|
" core go cov --html # Generate HTML report\n" +
|
|
" core go cov --open # Generate and open HTML report\n" +
|
|
" core go cov --threshold 80 # Fail if coverage < 80%")
|
|
|
|
covCmd.StringFlag("pkg", "Package to test (default: ./...)", &pkg)
|
|
covCmd.BoolFlag("html", "Generate HTML coverage report", &html)
|
|
covCmd.BoolFlag("open", "Generate and open HTML report in browser", &open)
|
|
covCmd.Float64Flag("threshold", "Minimum coverage percentage (exit 1 if below)", &threshold)
|
|
|
|
covCmd.Action(func() error {
|
|
if pkg == "" {
|
|
// Auto-discover packages with tests
|
|
pkgs, err := findTestPackages(".")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to discover test packages: %w", err)
|
|
}
|
|
if len(pkgs) == 0 {
|
|
return fmt.Errorf("no test packages found")
|
|
}
|
|
pkg = strings.Join(pkgs, " ")
|
|
}
|
|
|
|
// Create temp file for coverage data
|
|
covFile, err := os.CreateTemp("", "coverage-*.out")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create coverage file: %w", err)
|
|
}
|
|
covPath := covFile.Name()
|
|
covFile.Close()
|
|
defer os.Remove(covPath)
|
|
|
|
fmt.Printf("%s Running tests with coverage\n", dimStyle.Render("Coverage:"))
|
|
// Truncate package list if too long for display
|
|
displayPkg := pkg
|
|
if len(displayPkg) > 60 {
|
|
displayPkg = displayPkg[:57] + "..."
|
|
}
|
|
fmt.Printf(" %s %s\n", dimStyle.Render("Package:"), displayPkg)
|
|
fmt.Println()
|
|
|
|
// Run tests with coverage
|
|
// We need to split pkg into individual arguments if it contains spaces
|
|
pkgArgs := strings.Fields(pkg)
|
|
args := append([]string{"test", "-coverprofile=" + covPath, "-covermode=atomic"}, pkgArgs...)
|
|
|
|
cmd := exec.Command("go", args...)
|
|
cmd.Env = append(os.Environ(), "MACOSX_DEPLOYMENT_TARGET=26.0")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
testErr := cmd.Run()
|
|
|
|
// Get coverage percentage
|
|
covCmd := exec.Command("go", "tool", "cover", "-func="+covPath)
|
|
covOutput, err := covCmd.Output()
|
|
if err != nil {
|
|
if testErr != nil {
|
|
return testErr
|
|
}
|
|
return fmt.Errorf("failed to get coverage: %w", err)
|
|
}
|
|
|
|
// Parse total coverage from last line
|
|
lines := strings.Split(strings.TrimSpace(string(covOutput)), "\n")
|
|
var totalCov float64
|
|
if len(lines) > 0 {
|
|
lastLine := lines[len(lines)-1]
|
|
// Format: "total: (statements) XX.X%"
|
|
if strings.Contains(lastLine, "total:") {
|
|
parts := strings.Fields(lastLine)
|
|
if len(parts) >= 3 {
|
|
covStr := strings.TrimSuffix(parts[len(parts)-1], "%")
|
|
fmt.Sscanf(covStr, "%f", &totalCov)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Print coverage summary
|
|
fmt.Println()
|
|
covStyle := successStyle
|
|
if totalCov < 50 {
|
|
covStyle = errorStyle
|
|
} else if totalCov < 80 {
|
|
covStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#f59e0b"))
|
|
}
|
|
fmt.Printf(" %s %s\n", dimStyle.Render("Total:"), covStyle.Render(fmt.Sprintf("%.1f%%", totalCov)))
|
|
|
|
// Generate HTML if requested
|
|
if html || open {
|
|
htmlPath := "coverage.html"
|
|
htmlCmd := exec.Command("go", "tool", "cover", "-html="+covPath, "-o="+htmlPath)
|
|
if err := htmlCmd.Run(); err != nil {
|
|
return fmt.Errorf("failed to generate HTML: %w", err)
|
|
}
|
|
fmt.Printf(" %s %s\n", dimStyle.Render("HTML:"), htmlPath)
|
|
|
|
if open {
|
|
// Open in browser
|
|
var openCmd *exec.Cmd
|
|
switch {
|
|
case exec.Command("which", "open").Run() == nil:
|
|
openCmd = exec.Command("open", htmlPath)
|
|
case exec.Command("which", "xdg-open").Run() == nil:
|
|
openCmd = exec.Command("xdg-open", htmlPath)
|
|
default:
|
|
fmt.Printf(" %s\n", dimStyle.Render("(open manually)"))
|
|
}
|
|
if openCmd != nil {
|
|
openCmd.Run()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check threshold
|
|
if threshold > 0 && totalCov < threshold {
|
|
fmt.Printf("\n%s Coverage %.1f%% is below threshold %.1f%%\n",
|
|
errorStyle.Render("FAIL"), totalCov, threshold)
|
|
return fmt.Errorf("coverage below threshold")
|
|
}
|
|
|
|
if testErr != nil {
|
|
return testErr
|
|
}
|
|
|
|
fmt.Printf("\n%s\n", successStyle.Render("OK"))
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func findTestPackages(root string) ([]string, error) {
|
|
pkgMap := make(map[string]bool)
|
|
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if !info.IsDir() && strings.HasSuffix(info.Name(), "_test.go") {
|
|
dir := filepath.Dir(path)
|
|
if !strings.HasPrefix(dir, ".") {
|
|
dir = "./" + dir
|
|
}
|
|
pkgMap[dir] = true
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var pkgs []string
|
|
for pkg := range pkgMap {
|
|
pkgs = append(pkgs, pkg)
|
|
}
|
|
return pkgs, nil
|
|
}
|
|
|
|
func addGoFmtCommand(parent *clir.Command) {
|
|
var (
|
|
fix bool
|
|
diff bool
|
|
check bool
|
|
)
|
|
|
|
fmtCmd := parent.NewSubCommand("fmt", "Format Go code")
|
|
fmtCmd.LongDescription("Format Go code using gofmt or goimports.\n\n" +
|
|
"Examples:\n" +
|
|
" core go fmt # Check formatting\n" +
|
|
" core go fmt --fix # Fix formatting\n" +
|
|
" core go fmt --diff # Show diff")
|
|
|
|
fmtCmd.BoolFlag("fix", "Fix formatting in place", &fix)
|
|
fmtCmd.BoolFlag("diff", "Show diff of changes", &diff)
|
|
fmtCmd.BoolFlag("check", "Check only, exit 1 if not formatted", &check)
|
|
|
|
fmtCmd.Action(func() error {
|
|
args := []string{}
|
|
if fix {
|
|
args = append(args, "-w")
|
|
}
|
|
if diff {
|
|
args = append(args, "-d")
|
|
}
|
|
if !fix && !diff {
|
|
args = append(args, "-l")
|
|
}
|
|
args = append(args, ".")
|
|
|
|
// Try goimports first, fall back to gofmt
|
|
var cmd *exec.Cmd
|
|
if _, err := exec.LookPath("goimports"); err == nil {
|
|
cmd = exec.Command("goimports", args...)
|
|
} else {
|
|
cmd = exec.Command("gofmt", args...)
|
|
}
|
|
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
return cmd.Run()
|
|
})
|
|
}
|
|
|
|
func addGoLintCommand(parent *clir.Command) {
|
|
var fix bool
|
|
|
|
lintCmd := parent.NewSubCommand("lint", "Run golangci-lint")
|
|
lintCmd.LongDescription("Run golangci-lint on the codebase.\n\n" +
|
|
"Examples:\n" +
|
|
" core go lint\n" +
|
|
" core go lint --fix")
|
|
|
|
lintCmd.BoolFlag("fix", "Fix issues automatically", &fix)
|
|
|
|
lintCmd.Action(func() error {
|
|
args := []string{"run"}
|
|
if fix {
|
|
args = append(args, "--fix")
|
|
}
|
|
|
|
cmd := exec.Command("golangci-lint", args...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
return cmd.Run()
|
|
})
|
|
}
|
|
|
|
func addGoInstallCommand(parent *clir.Command) {
|
|
var verbose bool
|
|
var noCgo bool
|
|
|
|
installCmd := parent.NewSubCommand("install", "Install Go binary")
|
|
installCmd.LongDescription("Install Go binary to $GOPATH/bin.\n\n" +
|
|
"Examples:\n" +
|
|
" core go install # Install current module\n" +
|
|
" core go install ./cmd/core # Install specific path\n" +
|
|
" core go install --no-cgo # Pure Go (no C dependencies)\n" +
|
|
" core go install -v # Verbose output")
|
|
|
|
installCmd.BoolFlag("v", "Verbose output", &verbose)
|
|
installCmd.BoolFlag("no-cgo", "Disable CGO (CGO_ENABLED=0)", &noCgo)
|
|
|
|
installCmd.Action(func() error {
|
|
// Get install path from args or default to current dir
|
|
args := installCmd.OtherArgs()
|
|
installPath := "./..."
|
|
if len(args) > 0 {
|
|
installPath = args[0]
|
|
}
|
|
|
|
// Detect if we're in a module with cmd/ subdirectories or a root main.go
|
|
if installPath == "./..." {
|
|
if _, err := os.Stat("core.go"); err == nil {
|
|
installPath = "."
|
|
} else if entries, err := os.ReadDir("cmd"); err == nil && len(entries) > 0 {
|
|
installPath = "./cmd/..."
|
|
} else if _, err := os.Stat("main.go"); err == nil {
|
|
installPath = "."
|
|
}
|
|
}
|
|
|
|
fmt.Printf("%s Installing\n", dimStyle.Render("Install:"))
|
|
fmt.Printf(" %s %s\n", dimStyle.Render("Path:"), installPath)
|
|
if noCgo {
|
|
fmt.Printf(" %s %s\n", dimStyle.Render("CGO:"), "disabled")
|
|
}
|
|
|
|
cmdArgs := []string{"install"}
|
|
if verbose {
|
|
cmdArgs = append(cmdArgs, "-v")
|
|
}
|
|
cmdArgs = append(cmdArgs, installPath)
|
|
|
|
cmd := exec.Command("go", cmdArgs...)
|
|
if noCgo {
|
|
cmd.Env = append(os.Environ(), "CGO_ENABLED=0")
|
|
}
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
fmt.Printf("\n%s\n", errorStyle.Render("FAIL Install failed"))
|
|
return err
|
|
}
|
|
|
|
// Show where it was installed
|
|
gopath := os.Getenv("GOPATH")
|
|
if gopath == "" {
|
|
home, _ := os.UserHomeDir()
|
|
gopath = filepath.Join(home, "go")
|
|
}
|
|
binDir := filepath.Join(gopath, "bin")
|
|
|
|
fmt.Printf("\n%s Installed to %s\n", successStyle.Render("OK"), binDir)
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func addGoModCommand(parent *clir.Command) {
|
|
modCmd := parent.NewSubCommand("mod", "Module management")
|
|
modCmd.LongDescription("Go module management commands.\n\n" +
|
|
"Commands:\n" +
|
|
" tidy Add missing and remove unused modules\n" +
|
|
" download Download modules to local cache\n" +
|
|
" verify Verify dependencies\n" +
|
|
" graph Print module dependency graph")
|
|
|
|
// tidy
|
|
tidyCmd := modCmd.NewSubCommand("tidy", "Tidy go.mod")
|
|
tidyCmd.Action(func() error {
|
|
cmd := exec.Command("go", "mod", "tidy")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
return cmd.Run()
|
|
})
|
|
|
|
// download
|
|
downloadCmd := modCmd.NewSubCommand("download", "Download modules")
|
|
downloadCmd.Action(func() error {
|
|
cmd := exec.Command("go", "mod", "download")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
return cmd.Run()
|
|
})
|
|
|
|
// verify
|
|
verifyCmd := modCmd.NewSubCommand("verify", "Verify dependencies")
|
|
verifyCmd.Action(func() error {
|
|
cmd := exec.Command("go", "mod", "verify")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
return cmd.Run()
|
|
})
|
|
|
|
// graph
|
|
graphCmd := modCmd.NewSubCommand("graph", "Print dependency graph")
|
|
graphCmd.Action(func() error {
|
|
cmd := exec.Command("go", "mod", "graph")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
return cmd.Run()
|
|
})
|
|
}
|
|
|
|
func addGoWorkCommand(parent *clir.Command) {
|
|
workCmd := parent.NewSubCommand("work", "Workspace management")
|
|
workCmd.LongDescription("Go workspace management commands.\n\n" +
|
|
"Commands:\n" +
|
|
" sync Sync go.work with modules\n" +
|
|
" init Initialize go.work\n" +
|
|
" use Add module to workspace")
|
|
|
|
// sync
|
|
syncCmd := workCmd.NewSubCommand("sync", "Sync workspace")
|
|
syncCmd.Action(func() error {
|
|
cmd := exec.Command("go", "work", "sync")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
return cmd.Run()
|
|
})
|
|
|
|
// init
|
|
initCmd := workCmd.NewSubCommand("init", "Initialize workspace")
|
|
initCmd.Action(func() error {
|
|
cmd := exec.Command("go", "work", "init")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
// Auto-add current module if go.mod exists
|
|
if _, err := os.Stat("go.mod"); err == nil {
|
|
cmd = exec.Command("go", "work", "use", ".")
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
return cmd.Run()
|
|
}
|
|
return nil
|
|
})
|
|
|
|
// use
|
|
useCmd := workCmd.NewSubCommand("use", "Add module to workspace")
|
|
useCmd.Action(func() error {
|
|
args := useCmd.OtherArgs()
|
|
if len(args) == 0 {
|
|
// Auto-detect modules
|
|
modules := findGoModules(".")
|
|
if len(modules) == 0 {
|
|
return fmt.Errorf("no go.mod files found")
|
|
}
|
|
for _, mod := range modules {
|
|
cmd := exec.Command("go", "work", "use", mod)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
if err := cmd.Run(); err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("Added %s\n", mod)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
cmdArgs := append([]string{"work", "use"}, args...)
|
|
cmd := exec.Command("go", cmdArgs...)
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
return cmd.Run()
|
|
})
|
|
}
|
|
|
|
func findGoModules(root string) []string {
|
|
var modules []string
|
|
filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if info.Name() == "go.mod" && path != "go.mod" {
|
|
modules = append(modules, filepath.Dir(path))
|
|
}
|
|
return nil
|
|
})
|
|
return modules
|
|
}
|