Last active
July 30, 2025 04:56
-
-
Save jiahut/4ac72d5df5dc7d003c9fa711b52d48dc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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