internal/deps/version.go
91 lines · 2485 bytes
5 symbols in this file
1// Package deps reports dependencies that are behind their upstream
2// release. It reads manifests out of a repository's default branch and
3// asks the ecosystem's registry what the current version is; it never
4// executes a package manager, so the check needs no runner and no
5// per-repo configuration beyond opting in.
6package deps
7
8import (
9 "strconv"
10 "strings"
11)
12
13// Newer reports whether latest is a strictly greater release than current.
14// The four ecosystems agree on the part that matters here — dot-separated
15// numbers, optionally followed by a suffix — so one tolerant comparison
16// covers all of them. Anything it cannot read compares equal, which
17// reports nothing rather than reporting noise.
18func Newer(current, latest string) bool {
19 ca, cs := split(current)
20 la, ls := split(latest)
21 if len(ca) == 0 || len(la) == 0 {
22 return false
23 }
24 for i := 0; i < len(ca) || i < len(la); i++ {
25 c, l := at(ca, i), at(la, i)
26 if c != l {
27 return l > c
28 }
29 }
30 // Same release numbers: the suffix decides. A prerelease is behind the
31 // plain release, which is behind a post-release.
32 return rank(ls) > rank(cs)
33}
34
35// IsPrerelease reports whether v carries a prerelease marker. Registries
36// mostly hand back stable versions already; this keeps the exceptions from
37// being suggested.
38func IsPrerelease(v string) bool {
39 _, suffix := split(v)
40 return rank(suffix) < 0
41}
42
43// split separates the leading dot-separated numbers from whatever follows:
44// "v1.2.3-rc1" becomes ([1 2 3], "-rc1"), "2.0b1" becomes ([2 0], "b1").
45func split(v string) ([]int, string) {
46 v = strings.TrimSpace(v)
47 v = strings.TrimPrefix(v, "v")
48 var nums []int
49 i := 0
50 for i < len(v) {
51 j := i
52 for j < len(v) && v[j] >= '0' && v[j] <= '9' {
53 j++
54 }
55 if j == i {
56 break
57 }
58 n, err := strconv.Atoi(v[i:j])
59 if err != nil {
60 break
61 }
62 nums = append(nums, n)
63 if j < len(v) && v[j] == '.' && j+1 < len(v) && v[j+1] >= '0' && v[j+1] <= '9' {
64 i = j + 1
65 continue
66 }
67 i = j
68 break
69 }
70 return nums, v[i:]
71}
72
73func at(nums []int, i int) int {
74 if i < len(nums) {
75 return nums[i]
76 }
77 return 0
78}
79
80// rank orders the three kinds of suffix a release can carry: -1 for a
81// prerelease, 0 for none, 1 for a PEP 440 post-release. Two prereleases
82// rank equal, which reports nothing rather than guessing at rc1 vs beta2.
83func rank(suffix string) int {
84 switch {
85 case suffix == "":
86 return 0
87 case strings.HasPrefix(strings.TrimLeft(suffix, ".-_"), "post"):
88 return 1
89 }
90 return -1
91}