Skip to content

Commit 8b476fd

Browse files
authored
Merge pull request #272 from gofiber/add_template_pkg_migrator
2 parents 53bba68 + 44946e6 commit 8b476fd

4 files changed

Lines changed: 281 additions & 2 deletions

File tree

cmd/internal/migrations/lists.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ var Migrations = []Migration{
7373
v3migrations.MigrateSessionExtractor,
7474
v3migrations.MigrateSessionStore,
7575
v3migrations.MigrateStorageVersions,
76+
v3migrations.MigrateTemplateVersions,
7677
v3migrations.MigrateSessionRelease,
7778
v3migrations.MigrateKeyAuthConfig,
7879
v3migrations.MigrateJWTExtractor,

cmd/internal/migrations/v3/swagger_packages.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ const (
2525
fiberSwaggerOld = "github.com/gofiber/swagger"
2626
fiberSwaggerNew = "github.com/gofiber/contrib/v3/swaggo"
2727
goModVersionPattern = `v[a-zA-Z0-9.+-]+`
28+
vendorDirName = "vendor"
29+
goModFileName = "go.mod"
2830
)
2931

3032
func MigrateSwaggerPackages(cmd *cobra.Command, cwd string, _, _ *semver.Version) error {
@@ -61,12 +63,12 @@ func migrateSwaggerModules(cwd string) (bool, error) {
6163
return walkErr
6264
}
6365
if d.IsDir() {
64-
if d.Name() == "vendor" {
66+
if d.Name() == vendorDirName {
6567
return filepath.SkipDir
6668
}
6769
return nil
6870
}
69-
if d.Name() != "go.mod" {
71+
if d.Name() != goModFileName {
7072
return nil
7173
}
7274

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package v3
2+
3+
import (
4+
"fmt"
5+
"io/fs"
6+
"os"
7+
"path/filepath"
8+
"regexp"
9+
"strings"
10+
11+
semver "github.com/Masterminds/semver/v3"
12+
"github.com/spf13/cobra"
13+
14+
"github.com/gofiber/cli/cmd/internal"
15+
)
16+
17+
var reTemplateImport = regexp.MustCompile(`"github\.com/gofiber/template/([a-zA-Z0-9_-]+)(?:/v(\d+))?([^\"]*)"`)
18+
19+
type templateTarget struct {
20+
modulePath string
21+
version string
22+
}
23+
24+
type templateGoModPatterns struct {
25+
require *regexp.Regexp
26+
replace *regexp.Regexp
27+
}
28+
29+
// templateTargets maps template packages to their minimum module path and version
30+
// required for Fiber v3 core compatibility. Source:
31+
// https://github.com/gofiber/template/pull/437
32+
var templateTargets = map[string]templateTarget{
33+
"ace": {modulePath: "github.com/gofiber/template/ace/v3", version: "v3.0.0"},
34+
"amber": {modulePath: "github.com/gofiber/template/amber/v3", version: "v3.0.0"},
35+
"django": {modulePath: "github.com/gofiber/template/django/v4", version: "v4.0.0"},
36+
"handlebars": {modulePath: "github.com/gofiber/template/handlebars/v3", version: "v3.0.0"},
37+
"html": {modulePath: "github.com/gofiber/template/html/v3", version: "v3.0.0"},
38+
"jet": {modulePath: "github.com/gofiber/template/jet/v3", version: "v3.0.0"},
39+
"mustache": {modulePath: "github.com/gofiber/template/mustache/v3", version: "v3.0.0"},
40+
"pug": {modulePath: "github.com/gofiber/template/pug/v3", version: "v3.0.0"},
41+
"slim": {modulePath: "github.com/gofiber/template/slim/v3", version: "v3.0.0"},
42+
}
43+
44+
// MigrateTemplateVersions updates template package imports and go.mod entries
45+
// to the minimum versions required for Fiber v3 core compatibility.
46+
func MigrateTemplateVersions(cmd *cobra.Command, cwd string, _, _ *semver.Version) error {
47+
changedImports, err := internal.ChangeFileContent(cwd, func(content string) string {
48+
return reTemplateImport.ReplaceAllStringFunc(content, func(match string) string {
49+
sub := reTemplateImport.FindStringSubmatch(match)
50+
if len(sub) != 4 {
51+
return match
52+
}
53+
54+
target, ok := templateTargets[sub[1]]
55+
if !ok {
56+
return match
57+
}
58+
59+
return fmt.Sprintf(`"%s%s"`, target.modulePath, sub[3])
60+
})
61+
})
62+
if err != nil {
63+
return fmt.Errorf("failed to migrate template imports: %w", err)
64+
}
65+
66+
modChanged, err := migrateTemplateModules(cwd)
67+
if err != nil {
68+
return err
69+
}
70+
71+
if !changedImports && !modChanged {
72+
return nil
73+
}
74+
75+
cmd.Println("Migrated template package versions")
76+
return nil
77+
}
78+
79+
func migrateTemplateModules(cwd string) (bool, error) {
80+
modChanged := false
81+
patterns := compileTemplateGoModPatterns()
82+
83+
walkErr := filepath.WalkDir(cwd, func(path string, d fs.DirEntry, walkErr error) error {
84+
if walkErr != nil {
85+
return walkErr
86+
}
87+
if d.IsDir() {
88+
if d.Name() == vendorDirName {
89+
return filepath.SkipDir
90+
}
91+
return nil
92+
}
93+
if d.Name() != goModFileName {
94+
return nil
95+
}
96+
97+
info, err := d.Info()
98+
if err != nil {
99+
return fmt.Errorf("stat %s: %w", path, err)
100+
}
101+
102+
b, err := os.ReadFile(path) // #nosec G304 -- reading module file
103+
if err != nil {
104+
return fmt.Errorf("read %s: %w", path, err)
105+
}
106+
content := string(b)
107+
updated := content
108+
109+
for pkg, target := range templateTargets {
110+
updated = updateTemplateGoModModule(updated, target.modulePath, target.version, patterns[pkg])
111+
}
112+
113+
if updated == content {
114+
return nil
115+
}
116+
117+
if err := os.WriteFile(path, []byte(updated), info.Mode().Perm()); err != nil {
118+
return fmt.Errorf("write %s: %w", path, err)
119+
}
120+
modChanged = true
121+
return nil
122+
})
123+
if walkErr != nil {
124+
return false, fmt.Errorf("failed to migrate template modules: %w", walkErr)
125+
}
126+
127+
return modChanged, nil
128+
}
129+
130+
func compileTemplateGoModPatterns() map[string]templateGoModPatterns {
131+
patterns := make(map[string]templateGoModPatterns, len(templateTargets))
132+
for pkg := range templateTargets {
133+
rePath := fmt.Sprintf(`github\.com/gofiber/template/%s(?:/v\d+)?`, regexp.QuoteMeta(pkg))
134+
patterns[pkg] = templateGoModPatterns{
135+
require: regexp.MustCompile(fmt.Sprintf(`(?m)^(\s*(?:require\s+)?)%s\s+%s`, rePath, goModVersionPattern)),
136+
replace: regexp.MustCompile(fmt.Sprintf(`(?m)^(\s*replace\s+)%s(\s+%s)?(\s+=>\s+)`, rePath, goModVersionPattern)),
137+
}
138+
}
139+
return patterns
140+
}
141+
142+
func updateTemplateGoModModule(content, newPath, version string, patterns templateGoModPatterns) string {
143+
content = patterns.require.ReplaceAllString(content, fmt.Sprintf(`${1}%s %s`, newPath, version))
144+
content = patterns.replace.ReplaceAllStringFunc(content, func(s string) string {
145+
sub := patterns.replace.FindStringSubmatch(s)
146+
if len(sub) != 4 {
147+
return s
148+
}
149+
150+
if strings.TrimSpace(sub[2]) == "" {
151+
return fmt.Sprintf("%s%s%s", sub[1], newPath, sub[3])
152+
}
153+
return fmt.Sprintf("%s%s %s%s", sub[1], newPath, version, sub[3])
154+
})
155+
156+
return content
157+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package v3_test
2+
3+
import (
4+
"bytes"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
12+
"github.com/gofiber/cli/cmd/internal/migrations/v3"
13+
)
14+
15+
func Test_MigrateTemplateVersions(t *testing.T) {
16+
dir := t.TempDir()
17+
18+
file := writeTempFile(t, dir, `package main
19+
import (
20+
amber "github.com/gofiber/template/amber"
21+
django "github.com/gofiber/template/django/v3"
22+
html "github.com/gofiber/template/html/v2"
23+
)
24+
25+
var (
26+
_ = html.New
27+
_ = django.New
28+
_ = amber.New
29+
)
30+
`)
31+
32+
modContent := `module example
33+
34+
go 1.22
35+
36+
require (
37+
github.com/gofiber/template/html/v2 v2.0.0
38+
github.com/gofiber/template/django/v3 v3.0.1
39+
github.com/gofiber/template/amber v1.8.3
40+
)
41+
42+
replace github.com/gofiber/template/html/v2 => ../html
43+
replace github.com/gofiber/template/django/v3 => ../django`
44+
require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(modContent), 0o600))
45+
46+
var buf bytes.Buffer
47+
cmd := newCmd(&buf)
48+
require.NoError(t, v3.MigrateTemplateVersions(cmd, dir, nil, nil))
49+
50+
content := readFile(t, file)
51+
assert.Contains(t, content, "github.com/gofiber/template/html/v3")
52+
assert.Contains(t, content, "github.com/gofiber/template/django/v4")
53+
assert.Contains(t, content, "github.com/gofiber/template/amber/v3")
54+
assert.NotContains(t, content, "github.com/gofiber/template/html/v2")
55+
assert.NotContains(t, content, "github.com/gofiber/template/django/v3")
56+
57+
mod := readFile(t, filepath.Join(dir, "go.mod"))
58+
assert.Contains(t, mod, "github.com/gofiber/template/html/v3 v3.0.0")
59+
assert.Contains(t, mod, "github.com/gofiber/template/django/v4 v4.0.0")
60+
assert.Contains(t, mod, "github.com/gofiber/template/amber/v3 v3.0.0")
61+
assert.Contains(t, mod, "replace github.com/gofiber/template/html/v3 => ../html")
62+
assert.Contains(t, mod, "replace github.com/gofiber/template/django/v4 => ../django")
63+
64+
assert.Contains(t, buf.String(), "Migrated template package versions")
65+
}
66+
67+
func Test_MigrateTemplateVersions_ReplaceWithVersion(t *testing.T) {
68+
dir := t.TempDir()
69+
70+
_ = writeTempFile(t, dir, `package main
71+
import _ "github.com/gofiber/template/html/v2"
72+
`)
73+
74+
modContent := `module example
75+
76+
go 1.22
77+
78+
require github.com/gofiber/template/html/v2 v2.0.0
79+
80+
replace github.com/gofiber/template/html/v2 v2.0.0 => ../html`
81+
require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(modContent), 0o600))
82+
83+
var buf bytes.Buffer
84+
cmd := newCmd(&buf)
85+
require.NoError(t, v3.MigrateTemplateVersions(cmd, dir, nil, nil))
86+
87+
mod := readFile(t, filepath.Join(dir, "go.mod"))
88+
assert.Contains(t, mod, "replace github.com/gofiber/template/html/v3 v3.0.0 => ../html")
89+
assert.NotContains(t, mod, "replace github.com/gofiber/template/html/v3 v2.0.0 => ../html")
90+
}
91+
92+
func Test_MigrateTemplateVersions_Idempotent(t *testing.T) {
93+
dir := t.TempDir()
94+
95+
file := writeTempFile(t, dir, `package main
96+
import "github.com/gofiber/template/html/v3"
97+
`)
98+
99+
modContent := `module example
100+
101+
go 1.22
102+
103+
require github.com/gofiber/template/html/v3 v3.0.0`
104+
require.NoError(t, os.WriteFile(filepath.Join(dir, "go.mod"), []byte(modContent), 0o600))
105+
106+
var buf bytes.Buffer
107+
cmd := newCmd(&buf)
108+
require.NoError(t, v3.MigrateTemplateVersions(cmd, dir, nil, nil))
109+
firstContent := readFile(t, file)
110+
firstMod := readFile(t, filepath.Join(dir, "go.mod"))
111+
112+
require.NoError(t, v3.MigrateTemplateVersions(cmd, dir, nil, nil))
113+
secondContent := readFile(t, file)
114+
secondMod := readFile(t, filepath.Join(dir, "go.mod"))
115+
116+
assert.Equal(t, firstContent, secondContent)
117+
assert.Equal(t, firstMod, secondMod)
118+
assert.Empty(t, buf.String())
119+
}

0 commit comments

Comments
 (0)