Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 41 additions & 7 deletions cmd/internal/migrations/dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ import (
var ExecCommand = exec.Command

// MigrateDependencies ensures that dependencies shared with Fiber are at least
// the versions required by the target Fiber release.
// the versions required by the target Fiber release, and preserves higher
// versions already declared by the project.
//
// It updates go.mod files that already require a dependency also required by
// Fiber, bumping the version when it is lower than Fiber's requirement. No
// changes are made if the existing version is equal or higher.
func MigrateDependencies(cmd *cobra.Command, cwd string, _, target *semver.Version) error {
// The current map contains the dependency versions present before any
// migrations ran, keyed by module directory.
func MigrateDependencies(cmd *cobra.Command, cwd string, current map[string]map[string]*semver.Version, target *semver.Version) error {
fiberModule := fmt.Sprintf("github.com/gofiber/fiber/v%d@v%s", target.Major(), target.String())

c := ExecCommand("go", "mod", "download", "-json", fiberModule)
Expand Down Expand Up @@ -80,17 +80,22 @@ func MigrateDependencies(cmd *cobra.Command, cwd string, _, target *semver.Versi
}

changed := false
orig := current[dir]
for _, r := range mf.Require {
targetVer, ok := deps[r.Mod.Path]
if !ok {
continue
}
maxVer := targetVer
if v, ok := orig[r.Mod.Path]; ok && v.GreaterThan(maxVer) {
maxVer = v
}
currVer, err := semver.NewVersion(strings.TrimPrefix(r.Mod.Version, "v"))
if err != nil {
return fmt.Errorf("parse %s version in %s: %w", r.Mod.Path, modFile, err)
}
if currVer.LessThan(targetVer) {
r.Mod.Version = "v" + targetVer.String()
if currVer.LessThan(maxVer) {
r.Mod.Version = "v" + maxVer.String()
changed = true
}
}
Expand All @@ -113,3 +118,32 @@ func MigrateDependencies(cmd *cobra.Command, cwd string, _, target *semver.Versi
}
return nil
}

func dependencyVersions(root string) (map[string]map[string]*semver.Version, error) {
dirs, err := fiberModuleDirs(root)
if err != nil {
return nil, fmt.Errorf("find modules: %w", err)
}
deps := make(map[string]map[string]*semver.Version, len(dirs))
for _, dir := range dirs {
modFile := filepath.Join(dir, "go.mod")
b, err := os.ReadFile(modFile) // #nosec G304
if err != nil {
return nil, fmt.Errorf("read %s: %w", modFile, err)
}
mf, err := modfile.Parse(modFile, b, nil)
if err != nil {
return nil, fmt.Errorf("parse %s: %w", modFile, err)
}
m := make(map[string]*semver.Version, len(mf.Require))
for _, r := range mf.Require {
v, err := semver.NewVersion(strings.TrimPrefix(r.Mod.Version, "v"))
if err != nil {
return nil, fmt.Errorf("parse %s version in %s: %w", r.Mod.Path, modFile, err)
}
m[r.Mod.Path] = v
}
deps[dir] = m
}
return deps, nil
}
16 changes: 14 additions & 2 deletions cmd/internal/migrations/dependencies_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,13 @@ require (
var buf bytes.Buffer
cmd := newCmd(&buf)
target := semver.MustParse("3.0.0")
require.NoError(t, migrations.MigrateDependencies(cmd, dir, nil, target))
curr := map[string]map[string]*semver.Version{
dir: {
"github.com/valyala/fasthttp": semver.MustParse("1.0.0"),
"github.com/andybalholm/brotli": semver.MustParse("1.2.0"),
},
}
require.NoError(t, migrations.MigrateDependencies(cmd, dir, curr, target))

content := readFile(t, filepath.Join(dir, "go.mod"))
assert.Contains(t, content, "github.com/valyala/fasthttp v1.10.0")
Expand Down Expand Up @@ -87,7 +93,13 @@ require (
var buf bytes.Buffer
cmd := newCmd(&buf)
target := semver.MustParse("3.0.0")
require.NoError(t, migrations.MigrateDependencies(cmd, dir, nil, target))
curr := map[string]map[string]*semver.Version{
dir: {
"github.com/valyala/fasthttp": semver.MustParse("1.10.0"),
"github.com/andybalholm/brotli": semver.MustParse("1.2.0"),
},
}
require.NoError(t, migrations.MigrateDependencies(cmd, dir, curr, target))

content := readFile(t, filepath.Join(dir, "go.mod"))
assert.Contains(t, content, "github.com/valyala/fasthttp v1.10.0")
Expand Down
16 changes: 15 additions & 1 deletion cmd/internal/migrations/lists.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type Migration struct {
// Example structure:
// {"from": ">=2.0.0", "to": "<=3.*.*", "fn": [MigrateFN, MigrateFN]}
var Migrations = []Migration{
{From: ">=1.0.0-0", To: ">=0.0.0-0", Functions: []MigrationFn{MigrateGoPkgs, MigrateDependencies}},
{From: ">=1.0.0-0", To: ">=0.0.0-0", Functions: []MigrationFn{MigrateGoPkgs}},
{
From: ">=2.0.0-0",
To: "<4.0.0-0",
Expand Down Expand Up @@ -94,6 +94,14 @@ func migrationName(fn MigrationFn) string {
// It will run all migrations that match the current and target version
func DoMigration(cmd *cobra.Command, cwd string, curr, target *semver.Version, skipGoMod, verbose bool) error {
var errs []error
var origDeps map[string]map[string]*semver.Version
if !skipGoMod {
var err error
origDeps, err = dependencyVersions(cwd)
if err != nil {
return fmt.Errorf("record dependencies: %w", err)
}
}
for _, m := range Migrations {
toC, err := semver.NewConstraint(m.To)
if err != nil {
Expand Down Expand Up @@ -136,6 +144,12 @@ func DoMigration(cmd *cobra.Command, cwd string, curr, target *semver.Version, s
if err := internal.RunGoMod(cwd); err != nil {
errs = append(errs, fmt.Errorf("go mod: %w", err))
}
if err := MigrateDependencies(cmd, cwd, origDeps, target); err != nil {
errs = append(errs, fmt.Errorf("migrate dependencies: %w", err))
}
if err := internal.RunGoMod(cwd); err != nil {
errs = append(errs, fmt.Errorf("go mod: %w", err))
}
}

if len(errs) > 0 {
Expand Down
30 changes: 28 additions & 2 deletions cmd/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package cmd
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"strconv"
Expand Down Expand Up @@ -136,6 +138,8 @@ func pseudoVersionFromHash(base *semver.Version, hash string) (string, error) {
if err != nil {
return "", fmt.Errorf("create http request: %w", err)
}
req.Header.Set("User-Agent", "fiber-cli")

client := http.Client{}
res, err := client.Do(req)
if err != nil {
Expand All @@ -146,26 +150,48 @@ func pseudoVersionFromHash(base *semver.Version, hash string) (string, error) {
fmt.Fprintf(os.Stderr, "failed to close response body: %v\n", err)
}
}()
if res.StatusCode != http.StatusOK {
msg, err := io.ReadAll(res.Body)
if err != nil || len(msg) == 0 {
msg = []byte(res.Status)
}
Comment on lines +154 to +157
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The error returned by io.ReadAll is being swallowed. If reading the response body fails, the error is ignored. This can mask underlying issues like network problems. It's better to handle this error explicitly and return it.

msg, err := io.ReadAll(res.Body)
if err != nil {
    return "", fmt.Errorf("http request failed with status %s, and could not read response body: %w", res.Status, err)
}
if len(msg) == 0 {
    msg = []byte(res.Status)
}

return "", fmt.Errorf("http request failed: %s", strings.TrimSpace(string(msg)))
}

var data struct {
Commit struct {
Committer struct {
Date time.Time `json:"date"`
Date *time.Time `json:"date"`
} `json:"committer"`
Author struct {
Date *time.Time `json:"date"`
} `json:"author"`
} `json:"commit"`
SHA string `json:"sha"`
}
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
return "", fmt.Errorf("decode response: %w", err)
}

var commitTime time.Time
switch {
case data.Commit.Committer.Date != nil && !data.Commit.Committer.Date.IsZero():
commitTime = *data.Commit.Committer.Date
case data.Commit.Author.Date != nil && !data.Commit.Author.Date.IsZero():
commitTime = *data.Commit.Author.Date
default:
return "", errors.New("commit date not found")
}

commitTime = commitTime.UTC().Truncate(time.Second)

short := data.SHA
if short == "" {
short = hash
}
if len(short) > 12 {
short = short[:12]
}
pv := module.PseudoVersion("v"+strconv.FormatUint(base.Major(), 10), "v"+base.String(), data.Commit.Committer.Date, short)
pv := module.PseudoVersion("v"+strconv.FormatUint(base.Major(), 10), "v"+base.String(), commitTime, short)
return strings.TrimPrefix(pv, "v"), nil
}
2 changes: 1 addition & 1 deletion cmd/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ require github.com/gofiber/fiber/v3 v3.0.0
require.NoError(t, err)
assert.Contains(t, out, "Migration from Fiber 2.0.0 to 3.0.0")
assert.NotContains(t, out, "Migrating Go packages")
assert.Len(t, cmds, 3)
assert.Len(t, cmds, 6)
})

t.Run("force skip go mod", func(t *testing.T) {
Expand Down
Loading