-
Notifications
You must be signed in to change notification settings - Fork 21
Implement post-migration go module cleanup #133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2354077
2f5aaff
75786cd
310710f
44d9343
20a8c85
66b61b3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| package cmd | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "io/fs" | ||
| "os" | ||
| "path/filepath" | ||
| "strings" | ||
|
|
||
| "golang.org/x/mod/modfile" | ||
| ) | ||
|
|
||
| // runGoMod executes `go mod tidy`, `go mod download` and `go mod vendor` | ||
| // inside every directory under root that contains a go.mod file referencing | ||
| // github.com/gofiber/fiber. Directories named `vendor` are skipped. | ||
| func runGoMod(root string) error { | ||
| dirs, err := fiberModuleDirs(root) | ||
| if err != nil { | ||
| return fmt.Errorf("find modules: %w", err) | ||
| } | ||
| commands := [][]string{ | ||
| {"go", "mod", "tidy"}, | ||
| {"go", "mod", "download"}, | ||
| {"go", "mod", "vendor"}, | ||
| } | ||
| for _, dir := range dirs { | ||
| for _, args := range commands { | ||
| cmd := execCommand(args[0], args[1:]...) // #nosec G204 -- commands are controlled | ||
| cmd.Dir = dir | ||
| if err := runCmd(cmd); err != nil { | ||
| return fmt.Errorf("in %s: %w", dir, err) | ||
| } | ||
| } | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // fiberModuleDirs returns directories under root containing a go.mod file that | ||
| // requires github.com/gofiber/fiber. vendor directories are skipped. | ||
| func fiberModuleDirs(root string) ([]string, error) { | ||
| var dirs []string | ||
| walkErr := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if d.IsDir() && d.Name() == "vendor" { | ||
| return filepath.SkipDir | ||
| } | ||
| if !d.IsDir() && d.Name() == "go.mod" { | ||
| b, err := os.ReadFile(path) // #nosec G304 -- reading module file | ||
| if err != nil { | ||
| return fmt.Errorf("read %s: %w", path, err) | ||
| } | ||
| mf, err := modfile.Parse(path, b, nil) | ||
| if err != nil { | ||
| return fmt.Errorf("parse %s: %w", path, err) | ||
| } | ||
| for _, r := range mf.Require { | ||
| if strings.HasPrefix(r.Mod.Path, "github.com/gofiber/fiber") { | ||
| dirs = append(dirs, filepath.Dir(path)) | ||
| break | ||
| } | ||
| } | ||
| } | ||
| return nil | ||
| }) | ||
| if walkErr != nil { | ||
| return nil, fmt.Errorf("walk %s: %w", root, walkErr) | ||
| } | ||
| return dirs, nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| package migrations | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "io/fs" | ||
| "os" | ||
| "path/filepath" | ||
| "strings" | ||
|
|
||
| "golang.org/x/mod/modfile" | ||
|
|
||
| semver "github.com/Masterminds/semver/v3" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| // MigrateGoVersion ensures that all go.mod files referencing Fiber declare at | ||
| // least the provided Go version. Vendor directories are skipped. | ||
| func MigrateGoVersion(minVersion string) func(*cobra.Command, string, *semver.Version, *semver.Version) error { | ||
| minVer := semver.MustParse(minVersion) | ||
|
Comment on lines
+18
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Replace semver.MustParse with semver.NewVersion to avoid potential panics. Using func MigrateGoVersion(minVersion string) func(*cobra.Command, string, *semver.Version, *semver.Version) error {
- minVer := semver.MustParse(minVersion)
+ minVer, err := semver.NewVersion(minVersion)
+ if err != nil {
+ panic(fmt.Sprintf("invalid minimum version %q: %v", minVersion, err))
+ }
return func(cmd *cobra.Command, cwd string, _, _ *semver.Version) error {Alternatively, for even better error handling, validate the version when the migration function is called: func MigrateGoVersion(minVersion string) func(*cobra.Command, string, *semver.Version, *semver.Version) error {
return func(cmd *cobra.Command, cwd string, _, _ *semver.Version) error {
+ minVer, err := semver.NewVersion(minVersion)
+ if err != nil {
+ return fmt.Errorf("invalid minimum version %q: %w", minVersion, err)
+ }
- minVer := semver.MustParse(minVersion)
🤖 Prompt for AI Agents |
||
| return func(cmd *cobra.Command, cwd string, _, _ *semver.Version) error { | ||
| dirs, err := fiberModuleDirs(cwd) | ||
| if err != nil { | ||
| return fmt.Errorf("find modules: %w", err) | ||
| } | ||
| for _, dir := range dirs { | ||
| modFile := filepath.Join(dir, "go.mod") | ||
| b, err := os.ReadFile(modFile) // #nosec G304 | ||
| if err != nil { | ||
| return fmt.Errorf("read %s: %w", modFile, err) | ||
| } | ||
| lines := strings.Split(string(b), "\n") | ||
| changed := false | ||
| for i, line := range lines { | ||
| line = strings.TrimSpace(line) | ||
| if strings.HasPrefix(line, "go ") { | ||
| currVer, err := semver.NewVersion(strings.TrimSpace(strings.TrimPrefix(line, "go"))) | ||
| if err != nil { | ||
| return fmt.Errorf("parse go version in %s: %w", modFile, err) | ||
| } | ||
| if currVer.LessThan(minVer) { | ||
| lines[i] = "go " + minVer.String() | ||
| changed = true | ||
| } | ||
| break | ||
| } | ||
| } | ||
| if changed { | ||
| if err := os.WriteFile(modFile, []byte(strings.Join(lines, "\n")), 0o600); err != nil { | ||
| return fmt.Errorf("write %s: %w", modFile, err) | ||
| } | ||
| } | ||
| } | ||
| cmd.Printf("Ensuring go version >= %s\n", minVer.String()) | ||
| return nil | ||
| } | ||
| } | ||
|
|
||
| // fiberModuleDirs returns directories under root containing a go.mod file that | ||
| // requires github.com/gofiber/fiber. vendor directories are skipped. | ||
| func fiberModuleDirs(root string) ([]string, error) { | ||
| var dirs []string | ||
| walkErr := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if d.IsDir() && d.Name() == "vendor" { | ||
| return filepath.SkipDir | ||
| } | ||
| if !d.IsDir() && d.Name() == "go.mod" { | ||
| b, err := os.ReadFile(path) // #nosec G304 -- reading module file | ||
| if err != nil { | ||
| return fmt.Errorf("read %s: %w", path, err) | ||
| } | ||
| mf, err := modfile.Parse(path, b, nil) | ||
| if err != nil { | ||
| return fmt.Errorf("parse %s: %w", path, err) | ||
| } | ||
| for _, r := range mf.Require { | ||
| if strings.HasPrefix(r.Mod.Path, "github.com/gofiber/fiber") { | ||
| dirs = append(dirs, filepath.Dir(path)) | ||
| break | ||
| } | ||
| } | ||
| } | ||
| return nil | ||
| }) | ||
| if walkErr != nil { | ||
| return nil, fmt.Errorf("walk %s: %w", root, walkErr) | ||
| } | ||
| return dirs, nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package migrations_test | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "os" | ||
| "path/filepath" | ||
| "testing" | ||
|
|
||
| "github.com/gofiber/cli/cmd/internal/migrations" | ||
| "github.com/spf13/cobra" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func readFile(t *testing.T, path string) string { | ||
| t.Helper() | ||
| b, err := os.ReadFile(path) // #nosec G304 | ||
| require.NoError(t, err) | ||
| return string(b) | ||
| } | ||
|
|
||
| func newCmd(buf *bytes.Buffer) *cobra.Command { | ||
| cmd := &cobra.Command{} | ||
| cmd.SetOut(buf) | ||
| cmd.SetErr(buf) | ||
| return cmd | ||
| } | ||
|
|
||
| func Test_MigrateGoVersion(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| dir, err := os.MkdirTemp("", "mgover") | ||
| require.NoError(t, err) | ||
| defer func() { require.NoError(t, os.RemoveAll(dir)) }() | ||
|
|
||
| mod := `module example | ||
|
|
||
| go 1.21 | ||
|
|
||
| require github.com/gofiber/fiber/v2 v2.0.0` | ||
| require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(mod), 0o600)) | ||
|
|
||
| vendor := filepath.Join(dir, "vendor") | ||
| require.NoError(t, os.Mkdir(vendor, 0o750)) | ||
| require.NoError(t, os.WriteFile(filepath.Join(vendor, "go.mod"), []byte("module vendor\n\ngo 1.10"), 0o600)) | ||
|
|
||
| var buf bytes.Buffer | ||
| cmd := newCmd(&buf) | ||
| fn := migrations.MigrateGoVersion("1.23") | ||
| require.NoError(t, fn(cmd, dir, nil, nil)) | ||
|
|
||
| content := readFile(t, filepath.Join(dir, "go.mod")) | ||
| assert.Contains(t, content, "go 1.23") | ||
| assert.Contains(t, buf.String(), "1.23") | ||
|
|
||
| vendorContent := readFile(t, filepath.Join(vendor, "go.mod")) | ||
| assert.Contains(t, vendorContent, "go 1.10") | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.