Skip to content

fix: fiber migrate --to v3 resolves to latest matching release instead of 3.0.0#276

Open
Copilot wants to merge 3 commits intomasterfrom
copilot/fix-fiber-migrate-version
Open

fix: fiber migrate --to v3 resolves to latest matching release instead of 3.0.0#276
Copilot wants to merge 3 commits intomasterfrom
copilot/fix-fiber-migrate-version

Conversation

Copy link
Contributor

Copilot AI commented Feb 26, 2026

When passing a partial or wildcard version like --to v3, --to 3.1, or --to 3.*, the semver library coerces the input to its lowest patch (e.g. "3"3.0.0), so migrations always targeted the first release rather than the latest stable release matching the user's intent.

Explain the details for making this change. What existing problem does the pull request solve?

The fix introduces a partialVersionConstraint helper that detects partial/wildcard version strings and converts them into semver constraint ranges. These constraints are then used to query the GitHub releases list API and resolve the latest matching non-prerelease release. Full version strings (e.g. 3.0.0, 3.0.0-beta.1) continue to be used as-is.

Changes

  • cmd/version.go: Replaced LatestFiberVersionForMajor(major uint64) with LatestFiberVersionForConstraint(constraint *semver.Constraints) — queries the GitHub releases list API (sorted newest-first), evaluates each non-draft non-prerelease release against the semver constraint, and returns the first match
  • cmd/migrate.go: Added partialVersionConstraint(v string) (*semver.Constraints, error) helper that normalizes trailing wildcard suffixes (.*, .x, .*.*, .x.x), validates all remaining parts are numeric, and builds the appropriate constraint range. migrateRunE calls this before semver parsing and resolves via API if a constraint is returned
  • cmd/migrate_test.go: Expanded Test_Migrate_TargetVersionShort into a 9-case table test; added Test_PartialVersionConstraint with 11 subtests for the helper directly

Supported partial/wildcard formats

Input Resolves to
v3, 3, 3.*, 3.x, 3.*.*, 3.x.x latest 3.x.x
3.1, 3.1.*, 3.1.x latest 3.1.x
3.0.0, 3.0.0-beta.1 used directly, no API lookup

Example

# Before: always migrated to 3.0.0
fiber migrate --to v3
# > Migration from Fiber 2.52.11 to 3.0.0

# After: resolves to latest 3.x (e.g. 3.1.0)
fiber migrate --to v3
# > Migration from Fiber 2.52.11 to 3.1.0

# Also works for minor-pinned input
fiber migrate --to 3.1
# > Migration from Fiber 2.52.11 to 3.1.2

Behavior without --to (calls releases/latest) is unchanged.

Original prompt

This section details on the original issue you should resolve

<issue_title>🐛 fiber migrate --to v3 resolves to 3.0.0 instead of latest 3.x</issue_title>
<issue_description>## Issue description

I found an inconsistency between docs/user expectation and actual fiber migrate behavior.

The official docs mention this migration flow:
https://docs.gofiber.io/whats_new

As a user, when I run:

fiber migrate --to v3

I expect it to migrate to the latest stable 3.x release (best practice for security/patch updates), not to the first 3.0.0 release.

How to reproduce

go install github.com/gofiber/cli/fiber@latest
fiber migrate --to v3
  • Output: Migration from Fiber 2.52.11 to 3.0.0
  • go.mod is set to github.com/gofiber/fiber/v3 v3.0.0

When running without --to:

fiber migrate

it migrated to 3.1.0 (latest as of today), which is what I expected from major-target migration too.

Expected result

Either:

  1. --to v3 resolves to the latest 3.x,
  2. docs/help clearly state that major-only version input resolves to 3.0.0.</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Co-authored-by: ReneWerner87 <7063188+ReneWerner87@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix fiber migrate command to target latest 3.x version fix: fiber migrate --to v3 resolves to latest 3.x instead of 3.0.0 Feb 26, 2026
@ReneWerner87
Copy link
Member

@copilot Your solution focuses on the major number.
But what if, for example, “v3.1” or “v3..” or similar things are specified? Can't semver be used to determine what the user wants and then use GitHub APIs accordingly to resolve this version or search directly with semver specifications?

…traints

Co-authored-by: ReneWerner87 <7063188+ReneWerner87@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 26, 2026

@copilot Your solution focuses on the major number.
But what if, for example, “v3.1” or “v3..” or similar things are specified? Can't semver be used to determine what the user wants and the...

Good point. Updated in adab364 to use semver constraints for the full range of partial/wildcard inputs:

  • --to v3 / --to 3 / --to 3.* / --to 3.x → latest 3.x.x (>= 3.0.0, < 4.0.0)
  • --to 3.1 / --to 3.1.* / --to 3.1.x → latest 3.1.x (>= 3.1.0, < 3.2.0)
  • Full versions like --to 3.0.0 or --to 3.0.0-beta.1 continue to be used directly

The core piece is a partialVersionConstraint(v string) (*semver.Constraints, error) helper in migrate.go that normalizes trailing wildcard suffixes (.*, .x, .*.*, .x.x), validates all remaining parts are numeric, then builds the appropriate semver constraint range. LatestFiberVersionForMajor in version.go was replaced by LatestFiberVersionForConstraint(*semver.Constraints) which evaluates each GitHub release against the constraint.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/repos/gofiber/cli/releases/latest
    • Triggering command: /tmp/go-build1023968210/b340/cmd.test /tmp/go-build1023968210/b340/cmd.test -test.paniconexit0 -test.v=test2json -test.timeout=10m0s -test.count=1 -test.shuffle=on ools/gotestsum@v1.13.0/cmd/govergo/format ux-amd64/pkg/tool/linux_amd64/vet pkg/mod/golang.o/home/REDACTED/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.25.0.lin-importcfg 5206234/b182/_x0-atomic m ux-amd64/pkg/too-buildtags rg/t�� rg/toolchain@v0.-errorsas m ux-amd64/pkg/tool/linux_amd64/vet . estsum/cmd/tool/-o m ux-amd64/pkg/too-trimpath (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title fix: fiber migrate --to v3 resolves to latest 3.x instead of 3.0.0 fix: fiber migrate --to v3 resolves to latest matching release instead of 3.0.0 Feb 26, 2026
@ReneWerner87
Copy link
Member

Cool thx

@ReneWerner87 ReneWerner87 marked this pull request as ready for review February 26, 2026 20:01
@ReneWerner87 ReneWerner87 requested a review from a team as a code owner February 26, 2026 20:01
@ReneWerner87 ReneWerner87 requested review from ReneWerner87, Copilot, efectn, gaby and sixcolors and removed request for a team February 26, 2026 20:01
@ReneWerner87 ReneWerner87 added the ☢️ Bug Something isn't working label Feb 26, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes fiber migrate --to handling for partial/wildcard versions (e.g. v3, 3.1, 3.*) so that migrations target the latest stable matching release rather than being coerced to the lowest patch (e.g. 3.0.0).

Changes:

  • Add semver constraint-based resolution for partial/wildcard --to inputs and resolve the latest matching non-prerelease Fiber release via the GitHub releases list API.
  • Introduce partialVersionConstraint to convert partial inputs into semver constraint ranges.
  • Expand migrate tests to cover multiple short/wildcard version forms and add direct tests for partialVersionConstraint.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
cmd/version.go Adds LatestFiberVersionForConstraint to scan GitHub releases and return the latest tag matching a semver constraint.
cmd/migrate.go Adds partialVersionConstraint and wires partial version resolution into migrateRunE before semver parsing.
cmd/migrate_test.go Extends migrate short-version coverage via table tests and adds unit tests for partialVersionConstraint.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +91 to +123
url := "https://api.github.com/repos/gofiber/fiber/releases?per_page=100"
b, status, err := cachedGET(ctx, url, nil)
if err != nil {
return "", fmt.Errorf("http request failed: %w", err)
}
if status != http.StatusOK {
msg := strings.TrimSpace(string(b))
if msg == "" {
msg = http.StatusText(status)
}
return "", fmt.Errorf("http request failed: %s", msg)
}

var releases []struct {
TagName string `json:"tag_name"`
Prerelease bool `json:"prerelease"`
Draft bool `json:"draft"`
}
if err := json.Unmarshal(b, &releases); err != nil {
return "", fmt.Errorf("decode response: %w", err)
}

for _, r := range releases {
if r.Draft || r.Prerelease {
continue
}
v, parseErr := semver.NewVersion(r.TagName)
if parseErr != nil {
continue
}
if constraint.Check(v) {
return strings.TrimPrefix(r.TagName, "v"), nil
}
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

LatestFiberVersionForConstraint only fetches the first page of releases (per_page=100) and does not follow pagination. For constraints that match older releases (e.g., targeting older majors/minors), this can incorrectly return "no matching release found" once the matching releases fall beyond the first 100 results. Consider iterating pages (using page= and/or Link headers) until a match is found or results are exhausted.

Suggested change
url := "https://api.github.com/repos/gofiber/fiber/releases?per_page=100"
b, status, err := cachedGET(ctx, url, nil)
if err != nil {
return "", fmt.Errorf("http request failed: %w", err)
}
if status != http.StatusOK {
msg := strings.TrimSpace(string(b))
if msg == "" {
msg = http.StatusText(status)
}
return "", fmt.Errorf("http request failed: %s", msg)
}
var releases []struct {
TagName string `json:"tag_name"`
Prerelease bool `json:"prerelease"`
Draft bool `json:"draft"`
}
if err := json.Unmarshal(b, &releases); err != nil {
return "", fmt.Errorf("decode response: %w", err)
}
for _, r := range releases {
if r.Draft || r.Prerelease {
continue
}
v, parseErr := semver.NewVersion(r.TagName)
if parseErr != nil {
continue
}
if constraint.Check(v) {
return strings.TrimPrefix(r.TagName, "v"), nil
}
type release struct {
TagName string `json:"tag_name"`
Prerelease bool `json:"prerelease"`
Draft bool `json:"draft"`
}
for page := 1; ; page++ {
url := fmt.Sprintf("https://api.github.com/repos/gofiber/fiber/releases?per_page=100&page=%d", page)
b, status, err := cachedGET(ctx, url, nil)
if err != nil {
return "", fmt.Errorf("http request failed: %w", err)
}
if status != http.StatusOK {
msg := strings.TrimSpace(string(b))
if msg == "" {
msg = http.StatusText(status)
}
return "", fmt.Errorf("http request failed: %s", msg)
}
var releases []release
if err := json.Unmarshal(b, &releases); err != nil {
return "", fmt.Errorf("decode response: %w", err)
}
// No more releases; we've exhausted all pages.
if len(releases) == 0 {
break
}
for _, r := range releases {
if r.Draft || r.Prerelease {
continue
}
v, parseErr := semver.NewVersion(r.TagName)
if parseErr != nil {
continue
}
if constraint.Check(v) {
return strings.TrimPrefix(r.TagName, "v"), nil
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +111 to +117
// latest matching release from GitHub.
if constraint, parseErr := partialVersionConstraint(opts.TargetVersionS); parseErr == nil {
resolved, resolveErr := LatestFiberVersionForConstraint(constraint)
if resolveErr != nil {
return fmt.Errorf("failed to resolve version %q: %w", opts.TargetVersionS, resolveErr)
}
opts.TargetVersionS = resolved
Copy link

Copilot AI Feb 26, 2026

Choose a reason for hiding this comment

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

Partial/wildcard resolution happens before the --hash pseudo-version logic. If a user combines --to 3 (or 3.1, 3.*, etc.) with --hash, the base version used to construct the pseudo-version will be the resolved latest release, which can produce an incorrect pseudo-version (it should generally be based on the version the user specified, or resolution should be skipped when --hash is provided). Consider bypassing constraint resolution when opts.TargetHash is set, or applying resolution only when no hash is requested.

Suggested change
// latest matching release from GitHub.
if constraint, parseErr := partialVersionConstraint(opts.TargetVersionS); parseErr == nil {
resolved, resolveErr := LatestFiberVersionForConstraint(constraint)
if resolveErr != nil {
return fmt.Errorf("failed to resolve version %q: %w", opts.TargetVersionS, resolveErr)
}
opts.TargetVersionS = resolved
// latest matching release from GitHub. When a target hash is provided, skip
// this resolution so that the pseudo-version base is derived directly from
// the user-specified version.
if opts.TargetHash == "" {
if constraint, parseErr := partialVersionConstraint(opts.TargetVersionS); parseErr == nil {
resolved, resolveErr := LatestFiberVersionForConstraint(constraint)
if resolveErr != nil {
return fmt.Errorf("failed to resolve version %q: %w", opts.TargetVersionS, resolveErr)
}
opts.TargetVersionS = resolved
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

☢️ Bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🐛 fiber migrate --to v3 resolves to 3.0.0 instead of latest 3.x

3 participants