Skip to content

Commit 6fe75c7

Browse files
authored
Merge pull request #185 from gofiber/codex/2025-08-24-06-41-42
2 parents f62b139 + 2682556 commit 6fe75c7

File tree

7 files changed

+312
-4
lines changed

7 files changed

+312
-4
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package migrations
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
"os/exec"
9+
"path/filepath"
10+
"strings"
11+
12+
semver "github.com/Masterminds/semver/v3"
13+
"github.com/spf13/cobra"
14+
"golang.org/x/mod/modfile"
15+
)
16+
17+
// ExecCommand is used to run external commands. It can be replaced in tests.
18+
var ExecCommand = exec.Command
19+
20+
// MigrateDependencies ensures that dependencies shared with Fiber are at least
21+
// the versions required by the target Fiber release.
22+
//
23+
// It updates go.mod files that already require a dependency also required by
24+
// Fiber, bumping the version when it is lower than Fiber's requirement. No
25+
// changes are made if the existing version is equal or higher.
26+
func MigrateDependencies(cmd *cobra.Command, cwd string, _, target *semver.Version) error {
27+
fiberModule := fmt.Sprintf("github.com/gofiber/fiber/v%d@v%s", target.Major(), target.String())
28+
29+
c := ExecCommand("go", "mod", "download", "-json", fiberModule)
30+
var stdout, stderr bytes.Buffer
31+
c.Stdout = &stdout
32+
c.Stderr = &stderr
33+
if err := c.Run(); err != nil {
34+
msg := strings.TrimSpace(stderr.String())
35+
if msg != "" {
36+
return fmt.Errorf("download fiber module: %s: %w", msg, err)
37+
}
38+
return fmt.Errorf("download fiber module: %w", err)
39+
}
40+
var info struct {
41+
GoMod string `json:"GoMod"` //nolint:tagliatelle // field name defined by go tool output
42+
}
43+
if err := json.Unmarshal(stdout.Bytes(), &info); err != nil {
44+
return fmt.Errorf("parse download info: %w", err)
45+
}
46+
47+
b, err := os.ReadFile(info.GoMod) // #nosec G304
48+
if err != nil {
49+
return fmt.Errorf("read fiber go.mod: %w", err)
50+
}
51+
mf, err := modfile.Parse(info.GoMod, b, nil)
52+
if err != nil {
53+
return fmt.Errorf("parse fiber go.mod: %w", err)
54+
}
55+
56+
deps := make(map[string]*semver.Version, len(mf.Require))
57+
for _, r := range mf.Require {
58+
v, err := semver.NewVersion(strings.TrimPrefix(r.Mod.Version, "v"))
59+
if err != nil {
60+
return fmt.Errorf("parse fiber dependency %s version %s: %w", r.Mod.Path, r.Mod.Version, err)
61+
}
62+
deps[r.Mod.Path] = v
63+
}
64+
65+
dirs, err := fiberModuleDirs(cwd)
66+
if err != nil {
67+
return fmt.Errorf("find modules: %w", err)
68+
}
69+
70+
anyChanged := false
71+
for _, dir := range dirs {
72+
modFile := filepath.Join(dir, "go.mod")
73+
b, err := os.ReadFile(modFile) // #nosec G304
74+
if err != nil {
75+
return fmt.Errorf("read %s: %w", modFile, err)
76+
}
77+
mf, err := modfile.Parse(modFile, b, nil)
78+
if err != nil {
79+
return fmt.Errorf("parse %s: %w", modFile, err)
80+
}
81+
82+
changed := false
83+
for _, r := range mf.Require {
84+
targetVer, ok := deps[r.Mod.Path]
85+
if !ok {
86+
continue
87+
}
88+
currVer, err := semver.NewVersion(strings.TrimPrefix(r.Mod.Version, "v"))
89+
if err != nil {
90+
return fmt.Errorf("parse %s version in %s: %w", r.Mod.Path, modFile, err)
91+
}
92+
if currVer.LessThan(targetVer) {
93+
r.Mod.Version = "v" + targetVer.String()
94+
changed = true
95+
}
96+
}
97+
98+
if changed {
99+
mf.SetRequire(mf.Require)
100+
formatted, err := mf.Format()
101+
if err != nil {
102+
return fmt.Errorf("format %s: %w", modFile, err)
103+
}
104+
if err := os.WriteFile(modFile, formatted, 0o600); err != nil {
105+
return fmt.Errorf("write %s: %w", modFile, err)
106+
}
107+
anyChanged = true
108+
}
109+
}
110+
111+
if anyChanged {
112+
cmd.Println("Updating dependency versions")
113+
}
114+
return nil
115+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package migrations_test
2+
3+
import (
4+
"bytes"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
semver "github.com/Masterminds/semver/v3"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
13+
"github.com/gofiber/cli/cmd/internal/migrations"
14+
)
15+
16+
func Test_MigrateDependencies(t *testing.T) {
17+
dir, err := os.MkdirTemp("", "mdeps")
18+
require.NoError(t, err)
19+
defer func() { require.NoError(t, os.RemoveAll(dir)) }()
20+
21+
mod := `module example
22+
23+
go 1.22
24+
25+
require (
26+
github.com/gofiber/fiber/v3 v3.0.0
27+
github.com/valyala/fasthttp v1.0.0
28+
github.com/andybalholm/brotli v1.2.0
29+
)`
30+
require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(mod), 0o600))
31+
32+
fiberMod := `module github.com/gofiber/fiber/v3
33+
34+
go 1.22
35+
36+
require (
37+
github.com/valyala/fasthttp v1.10.0
38+
github.com/andybalholm/brotli v1.0.0
39+
)`
40+
fiberGoMod := filepath.Join(dir, "fiber.mod")
41+
require.NoError(t, os.WriteFile(fiberGoMod, []byte(fiberMod), 0o600))
42+
43+
restore := stubFiberDownload(t, fiberGoMod)
44+
defer restore()
45+
46+
var buf bytes.Buffer
47+
cmd := newCmd(&buf)
48+
target := semver.MustParse("3.0.0")
49+
require.NoError(t, migrations.MigrateDependencies(cmd, dir, nil, target))
50+
51+
content := readFile(t, filepath.Join(dir, "go.mod"))
52+
assert.Contains(t, content, "github.com/valyala/fasthttp v1.10.0")
53+
assert.Contains(t, content, "github.com/andybalholm/brotli v1.2.0")
54+
assert.Contains(t, buf.String(), "Updating dependency versions")
55+
}
56+
57+
func Test_MigrateDependencies_NoChange(t *testing.T) {
58+
dir, err := os.MkdirTemp("", "mdeps_nc")
59+
require.NoError(t, err)
60+
defer func() { require.NoError(t, os.RemoveAll(dir)) }()
61+
62+
mod := `module example
63+
64+
go 1.22
65+
66+
require (
67+
github.com/gofiber/fiber/v3 v3.0.0
68+
github.com/valyala/fasthttp v1.10.0
69+
github.com/andybalholm/brotli v1.2.0
70+
)`
71+
require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(mod), 0o600))
72+
73+
fiberMod := `module github.com/gofiber/fiber/v3
74+
75+
go 1.22
76+
77+
require (
78+
github.com/valyala/fasthttp v1.10.0
79+
github.com/andybalholm/brotli v1.0.0
80+
)`
81+
fiberGoMod := filepath.Join(dir, "fiber.mod")
82+
require.NoError(t, os.WriteFile(fiberGoMod, []byte(fiberMod), 0o600))
83+
84+
restore := stubFiberDownload(t, fiberGoMod)
85+
defer restore()
86+
87+
var buf bytes.Buffer
88+
cmd := newCmd(&buf)
89+
target := semver.MustParse("3.0.0")
90+
require.NoError(t, migrations.MigrateDependencies(cmd, dir, nil, target))
91+
92+
content := readFile(t, filepath.Join(dir, "go.mod"))
93+
assert.Contains(t, content, "github.com/valyala/fasthttp v1.10.0")
94+
assert.Contains(t, content, "github.com/andybalholm/brotli v1.2.0")
95+
assert.Empty(t, buf.String())
96+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package migrations_test
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"path/filepath"
8+
"testing"
9+
10+
"github.com/gofiber/cli/cmd/internal/migrations"
11+
)
12+
13+
// stubFiberDownload replaces migrations.ExecCommand with a helper that
14+
// returns the provided Fiber go.mod path in JSON format. It returns a
15+
// function to restore the original ExecCommand.
16+
func stubFiberDownload(t *testing.T, fiberGoMod string) func() {
17+
t.Helper()
18+
orig := migrations.ExecCommand
19+
out := fmt.Sprintf(`{"GoMod":%q}`, filepath.ToSlash(fiberGoMod))
20+
migrations.ExecCommand = func(string, ...string) *exec.Cmd {
21+
cmd := exec.Command(os.Args[0], "-test.run=TestHelperProcess", "--") // #nosec G204 -- test helper
22+
cmd.Env = []string{
23+
"GO_WANT_HELPER_PROCESS=1",
24+
"GO_HELPER_STDOUT=" + out,
25+
}
26+
return cmd
27+
}
28+
return func() { migrations.ExecCommand = orig }
29+
}
30+
31+
func TestHelperProcess(t *testing.T) {
32+
t.Helper()
33+
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
34+
return
35+
}
36+
if out := os.Getenv("GO_HELPER_STDOUT"); out != "" {
37+
_, _ = fmt.Fprint(os.Stdout, out)
38+
}
39+
os.Exit(0) //nolint:revive // helper process exits intentionally
40+
}

cmd/internal/migrations/lists.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ type Migration struct {
3030
// Example structure:
3131
// {"from": ">=2.0.0", "to": "<=3.*.*", "fn": [MigrateFN, MigrateFN]}
3232
var Migrations = []Migration{
33-
{From: ">=1.0.0-0", To: ">=0.0.0-0", Functions: []MigrationFn{MigrateGoPkgs}},
33+
{From: ">=1.0.0-0", To: ">=0.0.0-0", Functions: []MigrationFn{MigrateGoPkgs, MigrateDependencies}},
3434
{
3535
From: ">=2.0.0-0",
3636
To: "<4.0.0-0",

cmd/internal/migrations/lists_test.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,22 @@ func Test_DoMigration_Verbose(t *testing.T) {
4444
}
4545

4646
func Test_DoMigration_Verbose_Run(t *testing.T) {
47-
t.Parallel()
4847
curr := semver.MustParse("1.0.0")
4948
target := semver.MustParse("2.0.0")
5049

5150
t.Run("no changes", func(t *testing.T) {
52-
t.Parallel()
51+
fiberMod := `module github.com/gofiber/fiber/v2
52+
53+
go 1.22
5354
55+
require github.com/valyala/fasthttp v1.0.0`
5456
dir := t.TempDir()
57+
fiberGoMod := filepath.Join(dir, "fiber.mod")
58+
require.NoError(t, os.WriteFile(fiberGoMod, []byte(fiberMod), 0o600))
59+
60+
restore := stubFiberDownload(t, fiberGoMod)
61+
t.Cleanup(restore)
62+
5563
require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module test\n\nrequire github.com/gofiber/fiber/v2 v2.0.0\n"), 0o600))
5664
var buf bytes.Buffer
5765
cmd := &cobra.Command{}
@@ -63,9 +71,18 @@ func Test_DoMigration_Verbose_Run(t *testing.T) {
6371
})
6472

6573
t.Run("changes", func(t *testing.T) {
66-
t.Parallel()
74+
fiberMod := `module github.com/gofiber/fiber/v1
75+
76+
go 1.22
6777
78+
require github.com/valyala/fasthttp v1.0.0`
6879
dir := t.TempDir()
80+
fiberGoMod := filepath.Join(dir, "fiber.mod")
81+
require.NoError(t, os.WriteFile(fiberGoMod, []byte(fiberMod), 0o600))
82+
83+
restore := stubFiberDownload(t, fiberGoMod)
84+
t.Cleanup(restore)
85+
6986
require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module test\n\nrequire github.com/gofiber/fiber/v1 v1.0.0\n"), 0o600))
7087
require.NoError(t, os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main\nimport \"github.com/gofiber/fiber/v1\"\n"), 0o600))
7188
var buf bytes.Buffer

cmd/migrate_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd
22

33
import (
4+
"fmt"
45
"net/http"
56
"os"
67
"os/exec"
@@ -12,6 +13,7 @@ import (
1213
"github.com/stretchr/testify/require"
1314

1415
cmdinternal "github.com/gofiber/cli/cmd/internal"
16+
"github.com/gofiber/cli/cmd/internal/migrations"
1517
)
1618

1719
func readFileTB(tb testing.TB, path string) string {
@@ -21,6 +23,41 @@ func readFileTB(tb testing.TB, path string) string {
2123
return string(b)
2224
}
2325

26+
func TestMain(m *testing.M) {
27+
orig := migrations.ExecCommand
28+
29+
tmpDir, err := os.MkdirTemp("", "fiber_mod")
30+
if err != nil {
31+
panic(err)
32+
}
33+
fiberMod := `module github.com/gofiber/fiber/v2
34+
35+
go 1.22
36+
37+
require github.com/valyala/fasthttp v1.0.0`
38+
fiberGoMod := filepath.Join(tmpDir, "go.mod")
39+
if err := os.WriteFile(fiberGoMod, []byte(fiberMod), 0o600); err != nil {
40+
panic(err)
41+
}
42+
43+
migrations.ExecCommand = func(string, ...string) *exec.Cmd {
44+
cmd := exec.Command(os.Args[0], "-test.run=TestHelperProcess", "--", "go", "mod", "download") // #nosec G204 -- test helper
45+
cmd.Env = []string{
46+
"GO_WANT_HELPER_PROCESS=1",
47+
"GO_HELPER_STDOUT=" + fmt.Sprintf(`{"GoMod":%q}`, filepath.ToSlash(fiberGoMod)),
48+
}
49+
return cmd
50+
}
51+
52+
code := m.Run()
53+
54+
migrations.ExecCommand = orig
55+
if err := os.RemoveAll(tmpDir); err != nil {
56+
panic(err)
57+
}
58+
os.Exit(code)
59+
}
60+
2461
const goModV2 = `module example.com/demo
2562
2663
go 1.20

cmd/tester_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ func TestHelperProcess(t *testing.T) {
5757
testExit(1)
5858
return
5959
}
60+
if out := os.Getenv("GO_HELPER_STDOUT"); out != "" {
61+
_, _ = fmt.Fprint(os.Stdout, out)
62+
}
6063

6164
testExit(0)
6265
}

0 commit comments

Comments
 (0)