fix(binarymgr): sort packages by semver client-side
Summary
The GitLab packages API sorts order_by=version lexicographically, so 8.99.0 ranks above 8.100.0. fetchLatestPackage trusted the API's sort and returned packages[0], which made glab duo cli --update install 8.99.0 even though 8.100.0 was the latest release in the registry.
Reproducer (against the live registry):
$ glab api '/projects/46519181/packages?package_type=generic&package_name=duo-cli&order_by=version&sort=desc&per_page=1' \
| jq -r '.[].version'
8.99.08.100.0 is in the registry — it's just buried by the lex sort.
Fix
Switch fetchLatestPackage to order_by=created_at desc and pick the maximum semver client-side via hashicorp/go-version (already imported and used in CheckForUpdate a few lines up). Non-parseable version strings are skipped — the generic packages API permits any string.
With created_at desc the highest-semver release is always within the first page in practice (duo-cli releases at roughly weekly cadence; per_page=100 is ~2 years of headroom). The client-side scan is still required even with created_at desc — a hotfix on an older minor (e.g. 8.99.1 after 8.100.0) would otherwise mask the true latest.
Scope
binarymgr is shared infrastructure — this fix benefits every glab-managed binary whose minor version crosses 99 → 100 (Duo CLI today; future binaries automatically). No behavior change for binaries where lex and semver order happen to agree.
Deliberately not changed:
MaxCompatibleMajorgating: still errors out when the highest semver is past the major cap, same as today — just with the correct highest now.- Caching /
lastCheckTime: untouched. - Pagination: single page of 100 is sufficient; not adding pagination for a scenario that doesn't realistically exist.
Why not fix this upstream
The upstream feature for semver ordering on generic packages is gitlab-org/gitlab#372033 — opened 2022-08-28, still workflow::ready for design, no timeline. Even if/when it lands, self-managed instances on older GitLab versions wouldn't have it. Client-side is the right place regardless.
Test plan
New test TestManager_fetchLatestPackage (5 subtests) covers:
- Picks highest semver when lex and semver disagree (
8.99.0vs8.100.0) - Picks highest semver when the most recent upload is a hotfix on an older minor (
8.99.1after8.100.0) - Skips unparseable version strings (
latest,not-a-version) - Errors when registry is empty
- Errors when no version parses
The existing mock-based tests at manager_test.go:194,215 only handed back whatever the test wanted — they didn't exercise the real sort, which is why this slipped through. The new lex-vs-semver test locks the behavior in.
Verified locally:
-
go build ./... -
go vet ./... -
golangci-lint run ./internal/binarymgr/...— 0 issues -
go test ./internal/binarymgr/...— all passing including the 5 new cases - Pre-push hooks (check-generated, build, go-test 147 tests, go-lint) all green
Closes gitlab-org/editor-extensions/gitlab-lsp#2486 (closed)