From 98addcd6b776b859107cb263db0e0296ee21f5a4 Mon Sep 17 00:00:00 2001 From: RW Date: Sat, 23 Aug 2025 19:36:46 +0200 Subject: [PATCH 1/3] feat: allow migrating fiber using commit hash --- cmd/internal/migrations/common_test.go | 25 +++++++++++ cmd/migrate.go | 58 +++++++++++++++++++++++++- cmd/migrate_test.go | 36 ++++++++++++++++ 3 files changed, 118 insertions(+), 1 deletion(-) diff --git a/cmd/internal/migrations/common_test.go b/cmd/internal/migrations/common_test.go index 2e160ce..593e797 100644 --- a/cmd/internal/migrations/common_test.go +++ b/cmd/internal/migrations/common_test.go @@ -49,3 +49,28 @@ require github.com/gofiber/fiber/v2 v2.0.0` assert.Contains(t, mod, "github.com/gofiber/fiber/v3 v3.0.0") assert.Contains(t, buf.String(), "Migrating Go packages") } + +func Test_MigrateGoPkgs_WithHash(t *testing.T) { + dir, err := os.MkdirTemp("", "mgpkgs_hash") + require.NoError(t, err) + defer func() { require.NoError(t, os.RemoveAll(dir)) }() + + file := filepath.Join(dir, "main.go") + require.NoError(t, os.WriteFile(file, []byte("package main"), 0o600)) + + modContent := `module example + +go 1.22 + +require github.com/gofiber/fiber/v2 v2.0.0` + require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(modContent), 0o600)) + + var buf bytes.Buffer + cmd := newCmd(&buf) + target := semver.MustParse("3.0.1-0.20200102030405-abcdef123456") + require.NoError(t, migrations.MigrateGoPkgs(cmd, dir, nil, target)) + + mod := readFile(t, filepath.Join(dir, "go.mod")) + assert.Contains(t, mod, "github.com/gofiber/fiber/v3 v3.0.1-0.20200102030405-abcdef123456") + assert.Contains(t, buf.String(), "Migrating Go packages") +} diff --git a/cmd/migrate.go b/cmd/migrate.go index 3a96e56..2595f1b 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -1,19 +1,25 @@ package cmd import ( + "context" + "encoding/json" "fmt" + "net/http" "os" "strings" + "time" "github.com/Masterminds/semver/v3" "github.com/muesli/termenv" "github.com/spf13/cobra" + "golang.org/x/mod/module" "github.com/gofiber/cli/cmd/internal/migrations" ) func newMigrateCmd() *cobra.Command { var targetVersionS string + var targetHash string var force bool var skipGoMod bool var verbose bool @@ -27,11 +33,13 @@ func newMigrateCmd() *cobra.Command { cmd.Flags().BoolVarP(&force, "force", "f", false, "Force migration even if already on version") cmd.Flags().BoolVarP(&skipGoMod, "skip_go_mod", "s", false, "Skip running go mod tidy, download and vendor") cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose output") + cmd.Flags().StringVar(&targetHash, "hash", "", "Commit hash for Fiber version") cmd.RunE = func(cmd *cobra.Command, _ []string) error { return migrateRunE(cmd, MigrateOptions{ CurrentVersionFile: currentVersionFile, TargetVersionS: targetVersionS, + TargetHash: targetHash, Force: force, SkipGoMod: skipGoMod, Verbose: verbose, @@ -46,6 +54,7 @@ var migrateCmd = newMigrateCmd() type MigrateOptions struct { CurrentVersionFile string TargetVersionS string + TargetHash string Force bool SkipGoMod bool Verbose bool @@ -66,11 +75,24 @@ func migrateRunE(cmd *cobra.Command, opts MigrateOptions) error { } } opts.TargetVersionS = strings.TrimPrefix(opts.TargetVersionS, "v") - targetVersion, err := semver.NewVersion(opts.TargetVersionS) + baseVersion, err := semver.NewVersion(opts.TargetVersionS) if err != nil { return fmt.Errorf("invalid version for \"%s\": %w", opts.TargetVersionS, err) } + targetVersion := baseVersion + if opts.TargetHash != "" { + pv, err := pseudoVersionFromHash(baseVersion, opts.TargetHash) + if err != nil { + return fmt.Errorf("pseudo version: %w", err) + } + opts.TargetVersionS = pv + targetVersion, err = semver.NewVersion(pv) + if err != nil { + return fmt.Errorf("invalid pseudo version for \"%s\": %w", pv, err) + } + } + if !targetVersion.GreaterThan(currentVersion) && !(opts.Force && targetVersion.Equal(currentVersion)) { return fmt.Errorf("target version v%s is not greater than current version v%s", opts.TargetVersionS, currentVersionS) } @@ -102,3 +124,37 @@ func migrateRunE(cmd *cobra.Command, opts MigrateOptions) error { return nil } + +func pseudoVersionFromHash(base *semver.Version, hash string) (string, error) { + url := fmt.Sprintf("https://api.github.com/repos/gofiber/fiber/commits/%s", hash) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return "", fmt.Errorf("create http request: %w", err) + } + res, err := http.DefaultClient.Do(req) + if err != nil { + return "", fmt.Errorf("http request failed: %w", err) + } + defer res.Body.Close() + + var data struct { + Commit struct { + Committer struct { + Date time.Time `json:"date"` + } `json:"committer"` + } `json:"commit"` + } + if err := json.NewDecoder(res.Body).Decode(&data); err != nil { + return "", fmt.Errorf("decode response: %w", err) + } + + short := hash + if len(short) > 12 { + short = short[:12] + } + pv := module.PseudoVersion("v"+fmt.Sprint(base.Major()), "v"+base.String(), data.Commit.Committer.Date, short) + return strings.TrimPrefix(pv, "v"), nil +} diff --git a/cmd/migrate_test.go b/cmd/migrate_test.go index d58f8f2..58e4d86 100644 --- a/cmd/migrate_test.go +++ b/cmd/migrate_test.go @@ -1,6 +1,7 @@ package cmd import ( + "fmt" "net/http" "os" "os/exec" @@ -303,3 +304,38 @@ func main() {}` content := readFileTB(t, filepath.Join(dir, "main.go")) assert.NotContains(t, content, "github.com/gofiber/fiber/v2") } + +func Test_Migrate_WithHash(t *testing.T) { + dir, err := os.MkdirTemp("", "migrate_hash") + require.NoError(t, err) + defer func() { require.NoError(t, os.RemoveAll(dir)) }() + + gomod := `module example.com/demo + +go 1.20 + +require github.com/gofiber/fiber/v2 v2.0.6 +` + require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(gomod), 0o600)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main"), 0o600)) + + cwd, err := os.Getwd() + require.NoError(t, err) + require.NoError(t, os.Chdir(dir)) + defer func() { require.NoError(t, os.Chdir(cwd)) }() + + hash := "abcdef1234567890abcdef1234567890abcdef12" + httpmock.Activate() + defer httpmock.DeactivateAndReset() + commitURL := fmt.Sprintf("https://api.github.com/repos/gofiber/fiber/commits/%s", hash) + httpmock.RegisterResponder(http.MethodGet, commitURL, httpmock.NewBytesResponder(200, []byte(`{"commit":{"committer":{"date":"2020-01-02T03:04:05Z"}}}`))) + + cmd := newMigrateCmd() + setupCmd() + defer teardownCmd() + _, err = runCobraCmd(cmd, "-t=3.0.0", "--hash="+hash) + require.NoError(t, err) + + gm := readFileTB(t, filepath.Join(dir, "go.mod")) + assert.Contains(t, gm, "github.com/gofiber/fiber/v3 v3.0.1-0.20200102030405-abcdef123456") +} From cfe9a0d7fdbed4da65c100e31b9e394b9b33b2eb Mon Sep 17 00:00:00 2001 From: RW Date: Sat, 23 Aug 2025 19:48:49 +0200 Subject: [PATCH 2/3] chore: fix lint warnings --- cmd/migrate.go | 10 ++++++---- cmd/migrate_test.go | 34 +++++++++++----------------------- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/cmd/migrate.go b/cmd/migrate.go index 2595f1b..bdcdaeb 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "os" + "strconv" "strings" "time" @@ -126,7 +127,7 @@ func migrateRunE(cmd *cobra.Command, opts MigrateOptions) error { } func pseudoVersionFromHash(base *semver.Version, hash string) (string, error) { - url := fmt.Sprintf("https://api.github.com/repos/gofiber/fiber/commits/%s", hash) + url := "https://api.github.com/repos/gofiber/fiber/commits/" + hash ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() @@ -134,11 +135,12 @@ func pseudoVersionFromHash(base *semver.Version, hash string) (string, error) { if err != nil { return "", fmt.Errorf("create http request: %w", err) } - res, err := http.DefaultClient.Do(req) + client := http.Client{} + res, err := client.Do(req) if err != nil { return "", fmt.Errorf("http request failed: %w", err) } - defer res.Body.Close() + defer func() { _ = res.Body.Close() }() var data struct { Commit struct { @@ -155,6 +157,6 @@ func pseudoVersionFromHash(base *semver.Version, hash string) (string, error) { if len(short) > 12 { short = short[:12] } - pv := module.PseudoVersion("v"+fmt.Sprint(base.Major()), "v"+base.String(), data.Commit.Committer.Date, short) + pv := module.PseudoVersion("v"+strconv.FormatUint(base.Major(), 10), "v"+base.String(), data.Commit.Committer.Date, short) return strings.TrimPrefix(pv, "v"), nil } diff --git a/cmd/migrate_test.go b/cmd/migrate_test.go index 58e4d86..20509b6 100644 --- a/cmd/migrate_test.go +++ b/cmd/migrate_test.go @@ -1,7 +1,6 @@ package cmd import ( - "fmt" "net/http" "os" "os/exec" @@ -22,18 +21,19 @@ func readFileTB(tb testing.TB, path string) string { return string(b) } -func Test_Migrate_V2_to_V3(t *testing.T) { - dir, err := os.MkdirTemp("", "migrate_v2_v3") - require.NoError(t, err) - defer func() { require.NoError(t, os.RemoveAll(dir)) }() - - gomod := `module example.com/demo +const goModV2 = `module example.com/demo go 1.20 require github.com/gofiber/fiber/v2 v2.0.6 ` - require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(gomod), 0o600)) + +func Test_Migrate_V2_to_V3(t *testing.T) { + dir, err := os.MkdirTemp("", "migrate_v2_v3") + require.NoError(t, err) + defer func() { require.NoError(t, os.RemoveAll(dir)) }() + + require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(goModV2), 0o600)) main := `package main import ( @@ -119,13 +119,7 @@ func Test_Migrate_DefaultTarget(t *testing.T) { require.NoError(t, err) defer func() { require.NoError(t, os.RemoveAll(dir)) }() - gomod := `module example.com/demo - -go 1.20 - -require github.com/gofiber/fiber/v2 v2.0.6 -` - require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(gomod), 0o600)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(goModV2), 0o600)) main := `package main import "github.com/gofiber/fiber/v2" @@ -310,13 +304,7 @@ func Test_Migrate_WithHash(t *testing.T) { require.NoError(t, err) defer func() { require.NoError(t, os.RemoveAll(dir)) }() - gomod := `module example.com/demo - -go 1.20 - -require github.com/gofiber/fiber/v2 v2.0.6 -` - require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(gomod), 0o600)) + require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(goModV2), 0o600)) require.NoError(t, os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main"), 0o600)) cwd, err := os.Getwd() @@ -327,7 +315,7 @@ require github.com/gofiber/fiber/v2 v2.0.6 hash := "abcdef1234567890abcdef1234567890abcdef12" httpmock.Activate() defer httpmock.DeactivateAndReset() - commitURL := fmt.Sprintf("https://api.github.com/repos/gofiber/fiber/commits/%s", hash) + commitURL := "https://api.github.com/repos/gofiber/fiber/commits/" + hash httpmock.RegisterResponder(http.MethodGet, commitURL, httpmock.NewBytesResponder(200, []byte(`{"commit":{"committer":{"date":"2020-01-02T03:04:05Z"}}}`))) cmd := newMigrateCmd() From 4c1bd8a2725640ab6b0842c7f78a56bf337643b0 Mon Sep 17 00:00:00 2001 From: RW Date: Sat, 23 Aug 2025 19:59:42 +0200 Subject: [PATCH 3/3] fix: check response body close error --- cmd/migrate.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/migrate.go b/cmd/migrate.go index bdcdaeb..ba3a532 100644 --- a/cmd/migrate.go +++ b/cmd/migrate.go @@ -140,7 +140,11 @@ func pseudoVersionFromHash(base *semver.Version, hash string) (string, error) { if err != nil { return "", fmt.Errorf("http request failed: %w", err) } - defer func() { _ = res.Body.Close() }() + defer func() { + if err := res.Body.Close(); err != nil { + fmt.Fprintf(os.Stderr, "failed to close response body: %v\n", err) + } + }() var data struct { Commit struct {