基于 Go 实现 Deno upgrade

书接上篇,我在向 Deno 学习优秀的脚本管理中向大家介绍了 Deno 是如何管理它的安装包的——以 Github release 的形式发布、执行编写好的 shell 脚本安装程序以及基于 tags 的版本管理。有版本管理就会有更新的需求,本文就是在研读了 Deno 的 upgrade 命令后使用 Go 语言实现了自己的 upgrade 命令。

获取最新版本

我们先来看下 Deno 的源码:

1use deno_runtime::deno_fetch::reqwest::Client; 2const RELEASE_URL: &str = "https://github.com/denoland/deno/releases"; 3... 4async fn get_latest_release_version( 5 client: &Client, 6) -> Result<String, AnyError> { 7 println!("Looking up latest version"); 8 9 let res = client 10 .get(&format!("{}/latest", RELEASE_URL)) 11 .send() 12 .await?; 13 let version = res.url().path_segments().unwrap().last().unwrap(); 14 15 Ok(version.replace("v", "")) 16}

分析出以上代码做了以下几件事:

  1. 由于是异步函数,用 println 函数提示正在查找新版本
  2. 使用 GET 方式请求 https://github.com/denoland/deno/releases/latest
  3. 由于 https://github.com/denoland/deno/releases/latest 会重定向到最新的 release https://github.com/denoland/deno/releases/tag/v1.7.1,通过一顿字符串操作我们便取出了 v1.7.1
  4. 调用字符串的 replace 方法替换 v"" 得到版本号

让我们用 Go 来实现它:

代码看起来差不多,比如模板字符串写法、字符串处理

1package release 2 3import ( 4 "net/http" 5 "strings" 6) 7 8func getLatestVersion() string{ 9 releaseURL := fmt.Sprintf("https://github.com/%s/%s/releases/latest", user, repo) 10 resp, _ := http.Get(releaseURL) 11 defer resp.Body.Close() // 为了防止内存泄漏 12 13 pathArr := strings.Split(resp.Request.URL.Path, "/") 14 latest := strings.Replace(pathArr[len(pathArr)-1], "v", "", 1) 15 return latest 16}

版本检测

版本的处理少不了SemVer。我们使用 go-version 来处理版本相关工作,有了版本比较我们便可以实现一个版本检查的功能。以下代码是 我封装的 go-release 中的一段代码:

1type UpdateInfo struct { 2 IsUpdate bool 3 LatestVersion string 4} 5 6func CheckUpdate(user string, repo string, current string) (updateInfo *UpdateInfo, err error) { 7 releaseURL := fmt.Sprintf("https://github.com/%s/%s/releases/latest", user, repo) 8 resp, err := http.Get(releaseURL) 9 if err != nil { 10 return nil, err 11 } 12 defer resp.Body.Close() 13 14 current = strings.Replace(current, "v", "", 1) 15 pathArr := strings.Split(resp.Request.URL.Path, "/") 16 latest := strings.Replace(pathArr[len(pathArr)-1], "v", "", 1) 17 18 currentVersion, err := version.NewVersion(current) 19 if err != nil { 20 return nil, err 21 } 22 latestVersion, err := version.NewVersion(latest) 23 if err != nil { 24 return nil, err 25 } 26 updateInfo = &UpdateInfo{ 27 IsUpdate: currentVersion.LessThan(latestVersion), 28 LatestVersion: latest, 29 } 30 return updateInfo, nil 31}

cobra 实现 upgrade

下面的代码中,我们基于 go-release 实现了一个带 upgrade 命令的简易 CLI,核心逻辑便是比较本地版本是否是最新的,如果不是则安装最新的。

1const Version = "0.0.1" 2 3func checkUpgrade(current string) { 4 fmt.Println("Looking up latest version") 5 update, err := release.CheckUpdate("youngjuning", "tpc", current) 6 if err != nil { 7 panic(err) 8 } 9 if update.IsUpdate { 10 fmt.Printf("Found latest version %v \n", update.LatestVersion) 11 sh.Command("bash", "-c", "curl -fsSL https://raw.githubusercontent.com/youngjuning/tpc/main/install.sh | sh").Run() 12 } else { 13 fmt.Printf("Local version %v is the most recent release \n", current) 14 } 15} 16 17var rootCmd = &cobra.Command{ 18 Use: "app", 19 Version: Version, 20} 21 22var cmdUpgrade = &cobra.Command{ 23 Use: "upgrade", 24 Run: func(cmd *cobra.Command, args []string) { 25 checkUpgrade(Version, force) 26 }, 27} 28 29func main() { 30 rootCmd.AddCommand(cmdUpgrade) 31 rootCmd.Execute() 32}

强制更新方案

Node.js 命令行工具检查更新的正确姿势 对这个问题做了很深入的研究并实现了 Node 版的工具。核心逻辑就是每次执行命令时异步去执行检查更新。这句话翻译成 Go 就是用协程去执行检查更新的动作,这样执行命令还是会立马得到反馈,程序则会在后台执行检查更新,当检测到有新版本则强制更新。

完整代码在go-release/example,对 Cobra 不熟悉的同学可以看一下 Cobra 中文文档

执行 shell 命令推荐 go-sh,它对 exec 包做了封装,提供了更好地使用体验( PS:还不是因为我菜)

1// rootCmd 代表没有调用子命令时的基础命令 2var rootCmd = &cobra.Command{ 3 Use: "tpc", 4 Version: Version, 5 Run: func(cmd *cobra.Command, args []string) { 6 sh.Command("tpc", "-h").Run() 7 }, 8 // 每次执行完命令后去执行检查更新,Start 表示不阻塞原来的任务,还有一个 Run 方法则是会阻塞 9 PersistentPostRun: func(cmd *cobra.Command, args []string) { 10 sh.Command("bash", "-c", "tpc upgrade --force=false").Start() 11 }, 12}

go-release 的诞生

go-release 核心代码翻译自 Deno 的 upgrade,开源项目,点赞是我维护的动力,在此求个 Star。

go-release 的诞生源自我工作中要实现一个 CLI 工具的版本管理功能,由于是个菜鸟,中间遇到了各种各样的困难,幸得大佬无私地解答,才能很快实现了这么一个功能,在此鸣谢:

林小帅

漫画 Go 语言 纯手绘版 作者好家伙:

justjavac 大佬:

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

Android So动态加载 优雅实现与原理分析

背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我