Skip to content
Open
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
1 change: 1 addition & 0 deletions cmd/internal/migrations/lists.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ var Migrations = []Migration{
v3migrations.MigrateCORSConfig,
v3migrations.MigrateCSRFConfig,
v3migrations.MigrateMonitorImport,
v3migrations.MigrateContribPackages,
v3migrations.MigrateUtilsImport,
v3migrations.MigrateHealthcheckConfig,
v3migrations.MigrateProxyTLSConfig,
Expand Down
110 changes: 110 additions & 0 deletions cmd/internal/migrations/v3/contrib_packages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
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
}

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), 0o600); err != nil {
return fmt.Errorf("write %s: %w", path, err)
}
Comment on lines +62 to +82
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

When updating the go.mod file, the original file permissions are not preserved because os.WriteFile is called with a hardcoded mode 0o600. This could cause unexpected permission changes in the user's project. It's better to read the original file's permissions and use them when writing the updated file.

                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()); 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] == '/'
}
123 changes: 123 additions & 0 deletions cmd/internal/migrations/v3/contrib_packages_test.go
Original file line number Diff line number Diff line change
@@ -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())
}
2 changes: 1 addition & 1 deletion cmd/internal/migrations/v3/monitor_import.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion cmd/internal/migrations/v3/monitor_import_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
2 changes: 1 addition & 1 deletion cmd/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
15 changes: 10 additions & 5 deletions cmd/third_party.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ var (

const vendorDir = "vendor"

const (
contribModulePrefix = "github.com/gofiber/contrib/v3/"
contribRepoPrefix = "gofiber/contrib/v3"
)
Comment on lines +30 to +33

Choose a reason for hiding this comment

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

P1 Badge Use correct repo path when generating contrib pseudo versions

The new contribRepoPrefix constant now includes /v3, but refreshContrib passes this value to pseudoVersionFromHash when a hash is provided. pseudoVersionFromHash constructs the GitHub API URL from that prefix, so the request targets https://api.github.com/repos/gofiber/contrib/v3/..., which does not exist—the repository is still gofiber/contrib. As a result, running the contrib refresh command with --hash will fail to fetch commit metadata and cannot compute pseudo versions. Keep the repository prefix as gofiber/contrib while using the /v3 suffix only for import paths.

Useful? React with 👍 / 👎.


func refreshContrib(cmd *cobra.Command, cwd, hash string) (bool, error) {
modules, err := findContribModules(cwd)
if err != nil {
Expand All @@ -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 {
Expand All @@ -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)
}
Expand All @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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 ""
Expand Down
8 changes: 4 additions & 4 deletions cmd/third_party_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand All @@ -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) {
Expand Down
Loading