@@ -17,7 +17,9 @@ import (
17
17
"os/exec"
18
18
"path"
19
19
"path/filepath"
20
+ "regexp"
20
21
"runtime"
22
+ "strconv"
21
23
"strings"
22
24
"time"
23
25
@@ -139,16 +141,26 @@ func (u *updater) Run(ctx context.Context, force bool, coderURLArg string, versi
139
141
currentVersion , err := semver .NewVersion (u .versionF ())
140
142
if err != nil {
141
143
clog .LogWarn ("failed to determine current version of coder-cli" , clog .Causef (err .Error ()))
142
- } else if currentVersion . Compare ( desiredVersion ) == 0 {
144
+ } else if compareVersions ( currentVersion , desiredVersion ) == 0 {
143
145
clog .LogInfo ("Up to date!" )
144
146
return nil
145
147
}
146
148
147
149
if ! force {
148
- label := fmt .Sprintf ("Do you want to download version %d.%d.%d instead" ,
150
+ prerelease := ""
151
+ if desiredVersion .Prerelease () != "" {
152
+ prerelease = "-" + desiredVersion .Prerelease ()
153
+ }
154
+ hotfix := ""
155
+ if hotfixVersion (desiredVersion ) != "" {
156
+ hotfix = hotfixVersion (desiredVersion )
157
+ }
158
+ label := fmt .Sprintf ("Do you want to download version %d.%d.%d%s%s instead" ,
149
159
desiredVersion .Major (),
150
160
desiredVersion .Minor (),
151
161
desiredVersion .Patch (),
162
+ prerelease ,
163
+ hotfix ,
152
164
)
153
165
if _ , err := u .confirmF (label ); err != nil {
154
166
return clog .Fatal ("user cancelled operation" , clog .Tipf (`use "--force" to update without confirmation` ))
@@ -218,7 +230,7 @@ func (u *updater) Run(ctx context.Context, force bool, coderURLArg string, versi
218
230
return clog .Fatal ("failed to update coder binary" , clog .Causef (err .Error ()))
219
231
}
220
232
221
- clog .LogSuccess ("Updated coder CLI to version " + desiredVersion . String () )
233
+ clog .LogSuccess ("Updated coder CLI" )
222
234
return nil
223
235
}
224
236
@@ -308,6 +320,7 @@ func queryGithubAssetURL(httpClient getter, version *semver.Version, ostype stri
308
320
fmt .Fprint (& b , "-" )
309
321
fmt .Fprint (& b , version .Prerelease ())
310
322
}
323
+ fmt .Fprintf (& b , "%s" , hotfixVersion (version )) // this will be empty if no hotfix
311
324
312
325
urlString := fmt .Sprintf ("https://api.github.com/repos/cdr/coder-cli/releases/tags/v%s" , b .String ())
313
326
clog .LogInfo ("query github releases" , fmt .Sprintf ("url: %q" , urlString ))
@@ -493,3 +506,76 @@ func HasFilePathPrefix(s, prefix string) bool {
493
506
func defaultExec (ctx context.Context , cmd string , args ... string ) ([]byte , error ) {
494
507
return exec .CommandContext (ctx , cmd , args ... ).CombinedOutput ()
495
508
}
509
+
510
+ // hotfixExpr matches the build metadata used for identifying CLI hotfixes.
511
+ var hotfixExpr = regexp .MustCompile (`(?i)^.*?cli\.(\d+).*?$` )
512
+
513
+ // hotfixVersion returns the hotfix build metadata tag if it is present in v
514
+ // and an empty string otherwise.
515
+ func hotfixVersion (v * semver.Version ) string {
516
+ match := hotfixExpr .FindStringSubmatch (v .Metadata ())
517
+ if len (match ) < 2 {
518
+ return ""
519
+ }
520
+
521
+ return fmt .Sprintf ("+cli.%s" , match [1 ])
522
+ }
523
+
524
+ // compareVersions performs a NON-SEMVER-COMPLIANT comparison of two versions.
525
+ // If the two versions differ as per SemVer, then that result is returned.
526
+ // Otherwise, the build metadata of the two versions are compared based on
527
+ // the `cli.N` hotfix metadata.
528
+ //
529
+ // Examples:
530
+ // compareVersions(semver.MustParse("v1.0.0"), semver.MustParse("v1.0.0"))
531
+ // 0
532
+ // compareVersions(semver.MustParse("v1.0.0"), semver.MustParse("v1.0.1"))
533
+ // 1
534
+ // compareVersions(semver.MustParse("v1.0.1"), semver.MustParse("v1.0.0"))
535
+ // -1
536
+ // compareVersions(semver.MustParse("v1.0.0+cli.0"), semver.MustParse("v1.0.0"))
537
+ // 1
538
+ // compareVersions(semver.MustParse("v1.0.0+cli.0"), semver.MustParse("v1.0.0+cli.0"))
539
+ // 0
540
+ // compareVersions(semver.MustParse("v1.0.0"), semver.MustParse("v1.0.0+cli.0"))
541
+ // -1
542
+ // compareVersions(semver.MustParse("v1.0.0+cli.1"), semver.MustParse("v1.0.0+cli.0"))
543
+ // 1
544
+ // compareVersions(semver.MustParse("v1.0.0+cli.0"), semver.MustParse("v1.0.0+cli.1"))
545
+ // -1
546
+ //
547
+ func compareVersions (a , b * semver.Version ) int {
548
+ semverComparison := a .Compare (b )
549
+ if semverComparison != 0 {
550
+ return semverComparison
551
+ }
552
+
553
+ matchA := hotfixExpr .FindStringSubmatch (a .Metadata ())
554
+ matchB := hotfixExpr .FindStringSubmatch (b .Metadata ())
555
+
556
+ hotfixA := - 1
557
+ hotfixB := - 1
558
+
559
+ // extract hotfix versions from the metadata of a and b
560
+ if len (matchA ) > 1 {
561
+ if n , err := strconv .Atoi (matchA [1 ]); err == nil {
562
+ hotfixA = n
563
+ }
564
+ }
565
+ if len (matchB ) > 1 {
566
+ if n , err := strconv .Atoi (matchB [1 ]); err == nil {
567
+ hotfixB = n
568
+ }
569
+ }
570
+
571
+ // compare hotfix versions
572
+ if hotfixA < hotfixB {
573
+ return - 1
574
+ }
575
+ if hotfixA > hotfixB {
576
+ return 1
577
+ }
578
+ // both versions are the same if their semver and hotfix
579
+ // metadata are the same.
580
+ return 0
581
+ }
0 commit comments