diff --git a/cmd/internal/migrations/lists.go b/cmd/internal/migrations/lists.go index b104d63..864a77e 100644 --- a/cmd/internal/migrations/lists.go +++ b/cmd/internal/migrations/lists.go @@ -55,6 +55,7 @@ var Migrations = []Migration{ v3migrations.MigrateCORSConfig, v3migrations.MigrateCSRFConfig, v3migrations.MigrateMonitorImport, + v3migrations.MigrateContribPackages, v3migrations.MigrateUtilsImport, v3migrations.MigrateHealthcheckConfig, v3migrations.MigrateProxyTLSConfig, diff --git a/cmd/internal/migrations/v3/contrib_packages.go b/cmd/internal/migrations/v3/contrib_packages.go new file mode 100644 index 0000000..17d32f5 --- /dev/null +++ b/cmd/internal/migrations/v3/contrib_packages.go @@ -0,0 +1,114 @@ +package v3 + +import ( + "fmt" + "io/fs" + "os" + "path/filepath" + "regexp" + "strings" + + semver "github.com/Masterminds/semver/v3" + "github.com/spf13/cobra" + + "github.com/gofiber/cli/cmd/internal" +) + +var ( + contribImportRe = regexp.MustCompile(`(["\x60])github\.com/gofiber/contrib/([^"\x60\s]+)`) + contribModRe = regexp.MustCompile(`github\.com/gofiber/contrib/[^\s]+`) +) + +const ( + contribPrefix = "github.com/gofiber/contrib/" + contribV3Prefix = "github.com/gofiber/contrib/v3/" +) + +// MigrateContribPackages updates imports and module requirements that reference +// github.com/gofiber/contrib to use the v3 module path. +func MigrateContribPackages(cmd *cobra.Command, cwd string, _, _ *semver.Version) error { + changedImports, err := internal.ChangeFileContent(cwd, func(content string) string { + return contribImportRe.ReplaceAllStringFunc(content, func(match string) string { + sub := contribImportRe.FindStringSubmatch(match) + if len(sub) != 3 { + return match + } + rest := sub[2] + if hasVersionPrefix(rest) { + return match + } + return sub[1] + contribV3Prefix + rest + }) + }) + if err != nil { + return fmt.Errorf("failed to migrate contrib imports: %w", err) + } + + modChanged := false + walkErr := filepath.WalkDir(cwd, func(path string, d fs.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + if d.IsDir() { + if d.Name() == "vendor" { + return filepath.SkipDir + } + return nil + } + if d.Name() != "go.mod" { + return nil + } + info, err := d.Info() + if err != nil { + return fmt.Errorf("stat %s: %w", path, err) + } + + b, err := os.ReadFile(path) // #nosec G304 -- reading module file + if err != nil { + return fmt.Errorf("read %s: %w", path, err) + } + content := string(b) + if !contribModRe.MatchString(content) { + return nil + } + updated := contribModRe.ReplaceAllStringFunc(content, func(match string) string { + rest := strings.TrimPrefix(match, contribPrefix) + if rest == match || hasVersionPrefix(rest) { + return match + } + return contribV3Prefix + rest + }) + if updated == content { + return nil + } + if err := os.WriteFile(path, []byte(updated), info.Mode().Perm()); err != nil { + return fmt.Errorf("write %s: %w", path, err) + } + modChanged = true + return nil + }) + if walkErr != nil { + return fmt.Errorf("failed to migrate contrib modules: %w", walkErr) + } + + if !changedImports && !modChanged { + return nil + } + + cmd.Println("Migrating contrib packages") + return nil +} + +func hasVersionPrefix(rest string) bool { + if len(rest) < 2 || rest[0] != 'v' || rest[1] < '0' || rest[1] > '9' { + return false + } + i := 2 + for i < len(rest) && rest[i] >= '0' && rest[i] <= '9' { + i++ + } + if i == len(rest) { + return true + } + return rest[i] == '/' +} diff --git a/cmd/internal/migrations/v3/contrib_packages_test.go b/cmd/internal/migrations/v3/contrib_packages_test.go new file mode 100644 index 0000000..dd3c9b0 --- /dev/null +++ b/cmd/internal/migrations/v3/contrib_packages_test.go @@ -0,0 +1,123 @@ +package v3_test + +import ( + "bytes" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/gofiber/cli/cmd/internal/migrations/v3" +) + +func Test_MigrateContribPackages(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + + file := writeTempFile(t, dir, `package main +import ( + session "github.com/gofiber/contrib/session" +) + +func main() { + _ = session.NewStore +}`) + + modContent := `module example + +go 1.22 + +require ( + github.com/gofiber/fiber/v2 v2.0.0 + github.com/gofiber/contrib/session v1.2.3 +)` + require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(modContent), 0o600)) + + var buf bytes.Buffer + cmd := newCmd(&buf) + require.NoError(t, v3.MigrateContribPackages(cmd, dir, nil, nil)) + + content := readFile(t, file) + assert.Contains(t, content, "github.com/gofiber/contrib/v3/session") + assert.NotContains(t, content, "github.com/gofiber/contrib/session") + + mod := readFile(t, filepath.Join(dir, "go.mod")) + assert.Contains(t, mod, "github.com/gofiber/contrib/v3/session v1.2.3") + assert.NotContains(t, mod, "github.com/gofiber/contrib/session v1.2.3") + + assert.Contains(t, buf.String(), "Migrating contrib packages") +} + +func Test_MigrateContribPackages_Replace(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + + file := writeTempFile(t, dir, `package main +import ( + websocket "github.com/gofiber/contrib/websocket" +) + +var _ = websocket.New`) // keep import + + modContent := `module example + +go 1.22 + +require github.com/gofiber/contrib/websocket v1.0.0 + +replace github.com/gofiber/contrib/websocket => ../local` + require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(modContent), 0o600)) + + var buf bytes.Buffer + cmd := newCmd(&buf) + require.NoError(t, v3.MigrateContribPackages(cmd, dir, nil, nil)) + + content := readFile(t, file) + assert.Contains(t, content, "github.com/gofiber/contrib/v3/websocket") + + mod := readFile(t, filepath.Join(dir, "go.mod")) + assert.Contains(t, mod, "github.com/gofiber/contrib/v3/websocket v1.0.0") + assert.Contains(t, mod, "replace github.com/gofiber/contrib/v3/websocket => ../local") + + assert.Contains(t, buf.String(), "Migrating contrib packages") +} + +func Test_MigrateContribPackages_Idempotent(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + + file := writeTempFile(t, dir, `package main +import ( + "github.com/gofiber/contrib/v3/session" +) + +func main() { + _ = session.NewStore +}`) + + modContent := `module example + +go 1.22 + +require github.com/gofiber/contrib/v3/session v1.2.3` + require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(modContent), 0o600)) + + var buf bytes.Buffer + cmd := newCmd(&buf) + require.NoError(t, v3.MigrateContribPackages(cmd, dir, nil, nil)) + first := readFile(t, file) + firstMod := readFile(t, filepath.Join(dir, "go.mod")) + + require.NoError(t, v3.MigrateContribPackages(cmd, dir, nil, nil)) + second := readFile(t, file) + secondMod := readFile(t, filepath.Join(dir, "go.mod")) + + assert.Equal(t, first, second) + assert.Equal(t, firstMod, secondMod) + assert.Equal(t, "", buf.String()) +} diff --git a/cmd/internal/migrations/v3/monitor_import.go b/cmd/internal/migrations/v3/monitor_import.go index 26b3729..5fbeb7b 100644 --- a/cmd/internal/migrations/v3/monitor_import.go +++ b/cmd/internal/migrations/v3/monitor_import.go @@ -13,7 +13,7 @@ import ( func MigrateMonitorImport(cmd *cobra.Command, cwd string, _, _ *semver.Version) error { re := regexp.MustCompile(`github\.com/gofiber/fiber/([^/]+)/middleware/monitor`) changed, err := internal.ChangeFileContent(cwd, func(content string) string { - return re.ReplaceAllString(content, "github.com/gofiber/contrib/monitor") + return re.ReplaceAllString(content, "github.com/gofiber/contrib/v3/monitor") }) if err != nil { return fmt.Errorf("failed to migrate monitor import: %w", err) diff --git a/cmd/internal/migrations/v3/monitor_import_test.go b/cmd/internal/migrations/v3/monitor_import_test.go index b152cd5..0761ccf 100644 --- a/cmd/internal/migrations/v3/monitor_import_test.go +++ b/cmd/internal/migrations/v3/monitor_import_test.go @@ -27,7 +27,7 @@ var _ = monitor.New()`) require.NoError(t, v3.MigrateMonitorImport(cmd, dir, nil, nil)) content := readFile(t, file) - assert.Contains(t, content, "github.com/gofiber/contrib/monitor") + assert.Contains(t, content, "github.com/gofiber/contrib/v3/monitor") assert.NotContains(t, content, "fiber/v2/middleware/monitor") assert.Contains(t, buf.String(), "Migrating monitor middleware import") } diff --git a/cmd/migrate_test.go b/cmd/migrate_test.go index df62cdd..7a38a90 100644 --- a/cmd/migrate_test.go +++ b/cmd/migrate_test.go @@ -129,7 +129,7 @@ func main() { content := readFileTB(t, filepath.Join(dir, "main.go")) at := assert.New(t) at.Contains(content, "github.com/gofiber/fiber/v3") - at.Contains(content, "github.com/gofiber/contrib/monitor") + at.Contains(content, "github.com/gofiber/contrib/v3/monitor") at.Contains(content, "github.com/gofiber/fiber/v3/middleware/keyauth") at.NotContains(content, "*fiber.Ctx") at.Contains(content, "fiber.Ctx") diff --git a/cmd/third_party.go b/cmd/third_party.go index 8e28a03..1903e68 100644 --- a/cmd/third_party.go +++ b/cmd/third_party.go @@ -27,6 +27,11 @@ var ( const vendorDir = "vendor" +const ( + contribModulePrefix = "github.com/gofiber/contrib/v3/" + contribRepoPrefix = "gofiber/contrib/v3" +) + func refreshContrib(cmd *cobra.Command, cwd, hash string) (bool, error) { modules, err := findContribModules(cwd) if err != nil { @@ -41,7 +46,7 @@ func refreshContrib(cmd *cobra.Command, cwd, hash string) (bool, error) { reader := bufio.NewReader(cmd.InOrStdin()) for _, m := range modules { latest := latestContribVersionFn(m) - prompt := fmt.Sprintf("Version for github.com/gofiber/contrib/%s (default %s): ", m, latest) + prompt := fmt.Sprintf("Version for %s%s (default %s): ", contribModulePrefix, m, latest) cmd.Print(prompt) line, err := reader.ReadString('\n') if err != nil && err != io.EOF { @@ -65,7 +70,7 @@ func refreshContrib(cmd *cobra.Command, cwd, hash string) (bool, error) { if err != nil { return false, fmt.Errorf("parse version: %w", err) } - pv, err := pseudoVersionFromHash("gofiber/contrib", base, hash) + pv, err := pseudoVersionFromHash(contribRepoPrefix, base, hash) if err != nil { return false, fmt.Errorf("pseudo version: %w", err) } @@ -87,7 +92,7 @@ func refreshContrib(cmd *cobra.Command, cwd, hash string) (bool, error) { return s } major := majorFromVersion(ver) - return fmt.Sprintf("\"github.com/gofiber/contrib/%s%s%s\"", mod, majorPath(major), rest) + return fmt.Sprintf("\"%s%s%s\"", contribModulePrefix+mod, majorPath(major), rest) }) }) if err != nil { @@ -105,7 +110,7 @@ func refreshContrib(cmd *cobra.Command, cwd, hash string) (bool, error) { for mod, ver := range versions { major := majorFromVersion(ver) re := regexp.MustCompile(fmt.Sprintf(`(?m)^(\s*(?:require\s+)?)github.com/gofiber/contrib/(?:v\d+/)?%s(?:/v\d+)?\s+v[\w\.-]+`, regexp.QuoteMeta(mod))) - newLine := fmt.Sprintf(`${1}github.com/gofiber/contrib/%s%s %s`, mod, majorPath(major), ver) + newLine := fmt.Sprintf(`${1}%s%s %s`, contribModulePrefix+mod, majorPath(major), ver) replaced := re.ReplaceAllString(content, newLine) if replaced != content { content = replaced @@ -165,7 +170,7 @@ func findContribModules(cwd string) ([]string, error) { func latestContribVersion(module string) string { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - url := fmt.Sprintf("https://proxy.golang.org/github.com/gofiber/contrib/%s/@latest", module) + url := fmt.Sprintf("https://proxy.golang.org/%s%s/@latest", contribModulePrefix, module) b, status, err := cachedGET(ctx, url, nil) if err != nil || status != 200 { return "" diff --git a/cmd/third_party_test.go b/cmd/third_party_test.go index e67a391..90f4f21 100644 --- a/cmd/third_party_test.go +++ b/cmd/third_party_test.go @@ -15,13 +15,13 @@ func Test_refreshContrib(t *testing.T) { dir := t.TempDir() mainSrc := `package main -import _ "github.com/gofiber/contrib/monitor" +import _ "github.com/gofiber/contrib/v3/monitor" func main(){}` require.NoError(t, os.WriteFile(filepath.Join(dir, "main.go"), []byte(mainSrc), 0o600)) modSrc := `module test -require github.com/gofiber/contrib/monitor v1.0.0 +require github.com/gofiber/contrib/v3/monitor v1.0.0 ` require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(modSrc), 0o600)) @@ -40,11 +40,11 @@ require github.com/gofiber/contrib/monitor v1.0.0 content, err := os.ReadFile(filepath.Join(dir, "main.go")) // #nosec G304 require.NoError(t, err) - assert.Contains(t, string(content), "github.com/gofiber/contrib/monitor") + assert.Contains(t, string(content), "github.com/gofiber/contrib/v3/monitor") gm, err := os.ReadFile(filepath.Join(dir, "go.mod")) // #nosec G304 require.NoError(t, err) - assert.Contains(t, string(gm), "github.com/gofiber/contrib/monitor v1.2.3") + assert.Contains(t, string(gm), "github.com/gofiber/contrib/v3/monitor v1.2.3") } func Test_refreshStorage(t *testing.T) {