Skip to content

Instantly share code, notes, and snippets.

@jiahut
Last active July 30, 2025 04:56
Show Gist options
  • Save jiahut/4ac72d5df5dc7d003c9fa711b52d48dc to your computer and use it in GitHub Desktop.
Save jiahut/4ac72d5df5dc7d003c9fa711b52d48dc to your computer and use it in GitHub Desktop.
#!/usr/bin/env bun
// @bun
class K{defaultArch=["x64","amd64","arm64","aarch64","x86_64","universal"];defaultFileTypes=[".zip",".tar.gz",".exe",".dmg",".deb",".rpm",".AppImage"];async downloadLatest(k,q={},j=process.cwd()){try{let v=this.convertToApiUrl(k);console.log(`\uD83D\uDD0D \u83B7\u53D6\u6700\u65B0\u7248\u672C\u4FE1\u606F: ${v}`);let C=await this.fetchLatestRelease(v);if(console.log(`\uD83D\uDCE6 \u627E\u5230\u7248\u672C: ${C.tag_name} - ${C.name}`),C.assets.length===0){console.log("\u274C \u8BE5\u7248\u672C\u6CA1\u6709\u53EF\u4E0B\u8F7D\u7684\u8D44\u6E90\u6587\u4EF6");return}let E=this.filterAssets(C.assets,q);if(E.length===0){console.log("\u274C \u6CA1\u6709\u627E\u5230\u5339\u914D\u7684\u6587\u4EF6"),this.listAvailableAssets(C.assets);return}if(E.length===1)await this.downloadAsset(E[0],j);else{let F=await this.selectAsset(E);if(F)await this.downloadAsset(F,j);else console.log("\u274C \u672A\u9009\u62E9\u4EFB\u4F55\u6587\u4EF6\uFF0C\u53D6\u6D88\u4E0B\u8F7D")}}catch(v){console.error("\u274C \u4E0B\u8F7D\u5931\u8D25:",v instanceof Error?v.message:String(v)),process.exit(1)}}convertToApiUrl(k){let q=/github\.com\/([^\/]+)\/([^\/]+)/,j=k.match(q);if(!j)throw new Error("\u65E0\u6548\u7684 GitHub \u4ED3\u5E93 URL");let[,v,C]=j;return`https://api.github.com/repos/${v}/${C}/releases/latest`}async fetchLatestRelease(k){let q=await fetch(k,{headers:{Accept:"application/vnd.github.v3+json","User-Agent":"GitHub-Release-Downloader"}});if(!q.ok)throw new Error(`GitHub API \u8BF7\u6C42\u5931\u8D25: ${q.status} ${q.statusText}`);return await q.json()}filterAssets(k,q){let{filters:j=[]}=q;if(j.length===0)return k.filter((v)=>{let C=v.name.toLowerCase(),E=this.defaultFileTypes.some((J)=>C.endsWith(J.toLowerCase())),N=!this.defaultArch.some((J)=>C.includes(J.toLowerCase()))||this.defaultArch.some((J)=>C.includes(J.toLowerCase()));return E&&N});return k.filter((v)=>{let C=v.name.toLowerCase();return j.every((E)=>C.includes(E.toLowerCase()))})}async downloadAsset(k,q){console.log(`\u2B07\uFE0F \u5F00\u59CB\u4E0B\u8F7D: ${k.name} (${this.formatSize(k.size)})`);let j=await fetch(k.browser_download_url);if(!j.ok)throw new Error(`\u4E0B\u8F7D\u5931\u8D25: ${j.status} ${j.statusText}`);let v=`${q}/${k.name}`,C=await j.arrayBuffer();await Bun.write(v,C),console.log(`\u2705 \u4E0B\u8F7D\u5B8C\u6210: ${v}`)}formatSize(k){let q=["B","KB","MB","GB"],j=k,v=0;while(j>=1024&&v<q.length-1)j/=1024,v++;return`${j.toFixed(1)} ${q[v]}`}async selectAsset(k){console.log(`\uD83C\uDFAF \u627E\u5230 ${k.length} \u4E2A\u5339\u914D\u6587\u4EF6\uFF0C\u4F7F\u7528 fzf \u9009\u62E9:`);let q=k.map((j)=>`${j.name} (${this.formatSize(j.size)})`);try{let j=Bun.spawn(["fzf","--prompt=\u9009\u62E9\u6587\u4EF6: ","--height=40%","--reverse","--border"],{stdin:"pipe",stdout:"pipe",stderr:"inherit"});if(j.stdin.write(q.join(`
`)),j.stdin.end(),await j.exited,j.exitCode!==0)return console.log("\u274C \u672A\u9009\u62E9\u4EFB\u4F55\u6587\u4EF6\uFF0C\u53D6\u6D88\u4E0B\u8F7D"),null;let C=(await new Response(j.stdout).text()).trim();if(!C)return null;let E=q.findIndex((F)=>F===C);return E>=0?k[E]:null}catch(j){return console.error("\u274C fzf \u9009\u62E9\u5931\u8D25:",j),console.log("\uD83D\uDCA1 \u8BF7\u786E\u4FDD\u7CFB\u7EDF\u5DF2\u5B89\u88C5 fzf (brew install fzf \u6216 apt install fzf)"),null}}listAvailableAssets(k){console.log(`
\uD83D\uDCCB \u53EF\u7528\u7684\u6587\u4EF6\u5217\u8868:`),k.forEach((q,j)=>{console.log(` ${j+1}. ${q.name} (${this.formatSize(q.size)})`)})}}function O(){let k=process.argv.slice(2);if(k.length===0)console.log(`
\u4F7F\u7528\u65B9\u6CD5:
bun download-latest.ts <github-repo-url> [\u7B5B\u9009\u6761\u4EF6...]
\u793A\u4F8B:
bun download-latest.ts https://github.com/microsoft/vscode
bun download-latest.ts https://github.com/denoland/deno arm64 .zip
bun download-latest.ts https://github.com/user/repo linux x64
bun download-latest.ts github.com/user/repo windows .exe
\u8BF4\u660E:
- \u7B2C\u4E00\u4E2A\u53C2\u6570\u5FC5\u987B\u662F GitHub \u4ED3\u5E93\u5730\u5740
- \u540E\u7EED\u53C2\u6570\u90FD\u4F5C\u4E3A\u7B5B\u9009\u6761\u4EF6\uFF0C\u6587\u4EF6\u540D\u5FC5\u987B\u5305\u542B\u6240\u6709\u7B5B\u9009\u6761\u4EF6
- \u5982\u679C\u4E0D\u63D0\u4F9B\u7B5B\u9009\u6761\u4EF6\uFF0C\u4F1A\u4F7F\u7528\u9ED8\u8BA4\u89C4\u5219\u8FC7\u6EE4\u5E38\u89C1\u7684\u53EF\u6267\u884C\u6587\u4EF6
`),process.exit(0);let q=k[0],v={filters:k.slice(1)},C=process.cwd();return{repoUrl:q,options:v,outputDir:C}}async function P(){let{repoUrl:k,options:q,outputDir:j}=O();await new K().downloadLatest(k,q,j)}if(import.meta.main)P();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment