Skip to content

Commit 08c2ee8

Browse files
authored
feat(gh-bin): download progress (#275)
1 parent dbced82 commit 08c2ee8

File tree

4 files changed

+51
-12
lines changed

4 files changed

+51
-12
lines changed

packages/gh-bin/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Changelog
2+
3+
## v0.1.2 (2025-05-27)
4+
5+
- feat(gh-bin): download progress

packages/gh-bin/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ $ npx gh-bin --help
1919

2020
```txt
2121
$ npx gh-bin --help
22-
gh-bin@0.1.1
22+
gh-bin@0.1.2
2323
2424
Usage:
2525
npx gh-bin https://github.com/<owner>/<repo>

packages/gh-bin/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "gh-bin",
3-
"version": "0.1.1",
3+
"version": "0.1.2",
44
"homepage": "https://github.com/hi-ogawa/js-utils/tree/main/packages/gh-bin",
55
"repository": {
66
"type": "git",

packages/gh-bin/src/cli.ts

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,36 +61,64 @@ async function main() {
6161

6262
// prompt which files to download from assets
6363
// TODO: reorder by matching arch/os/platform
64-
const selectedAsset = await prompts.select<string>({
64+
const selectedAssetMeta = await prompts.select<{
65+
name: string;
66+
browser_download_url: string;
67+
}>({
6568
message: "Select an asset to download",
6669
options: assets.map((asset: any) => ({
6770
label: asset.name,
68-
value: asset.browser_download_url,
71+
value: asset,
6972
})),
7073
});
71-
if (prompts.isCancel(selectedAsset)) {
74+
if (prompts.isCancel(selectedAssetMeta)) {
7275
return;
7376
}
7477

7578
// download a selected asset
79+
const selectedAsset = selectedAssetMeta.name;
80+
const selectedAssetUrl = selectedAssetMeta.browser_download_url;
7681
const downloadSpinner = prompts.spinner();
7782
downloadSpinner.start(`Downloading ${selectedAsset}`);
7883
let tmpAssetPath = path.join(
7984
os.tmpdir(),
8085
`gh-bin-asset-${owner}-${repo}${path.extname(selectedAsset)}`
8186
);
8287
try {
83-
const res = await fetch(selectedAsset);
88+
const res = await fetch(selectedAssetUrl);
8489
if (!res.ok || !res.body) {
85-
console.error(`[ERROR] Failed to download '${selectedAsset}'\n`);
90+
console.error(`[ERROR] Failed to download '${selectedAssetUrl}'\n`);
8691
process.exit(1);
8792
}
88-
await fs.promises.writeFile(
89-
tmpAssetPath,
90-
Readable.fromWeb(res.body as any)
93+
const contentLength = res.headers.get("content-length");
94+
const total = contentLength ? Number(contentLength) : null;
95+
let current = 0;
96+
const stream = res.body.pipeThrough(
97+
new TransformStream({
98+
transform(chunk, controller) {
99+
controller.enqueue(chunk);
100+
current += chunk.byteLength;
101+
if (total) {
102+
downloadSpinner.message(
103+
`Downloading ${selectedAsset} (${prettyBytes(total)} - ${((100 * current) / total!).toFixed(2)}%)`
104+
);
105+
} else {
106+
downloadSpinner.message(
107+
`Downloading ${selectedAsset} (${prettyBytes(current)})`
108+
);
109+
}
110+
},
111+
flush() {
112+
downloadSpinner.stop(
113+
`Download success ${selectedAsset} (${prettyBytes(current)})`
114+
);
115+
},
116+
})
91117
);
92-
} finally {
93-
downloadSpinner.stop(`Downloaded ${selectedAsset}`);
118+
await fs.promises.writeFile(tmpAssetPath, Readable.fromWeb(stream as any));
119+
} catch (e) {
120+
downloadSpinner.stop(`Failed to download '${selectedAssetUrl}'`);
121+
throw e;
94122
}
95123

96124
let defaultBinName = repo;
@@ -166,6 +194,12 @@ async function main() {
166194
console.log(`Executable is installed in ${destPath}`);
167195
}
168196

197+
function prettyBytes(bytes: number): string {
198+
return bytes > 1024
199+
? `${(bytes / (1024 * 1024)).toFixed(2)} MB`
200+
: `${(bytes / 1024).toFixed(2)} KB`;
201+
}
202+
169203
async function fetchGhApi(url: string) {
170204
const res = await fetch(url, {
171205
headers: {

0 commit comments

Comments
 (0)