Skip to content

Commit 57760ee

Browse files
Progress and website downloads
1 parent 581983f commit 57760ee

4 files changed

Lines changed: 442 additions & 26 deletions

File tree

index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const {
1919
confirm,
2020
} = require("./lib/utils");
2121

22-
const HELP = `justinstall <github-url|file-url|local-file> [options]
22+
const HELP = `justinstall <github-url|website-url|file-url|local-file> [options]
2323
\tv1.1.0 - Just install anything. Supports .tar.gz, .zip, .dmg, .app, .pkg, and .deb files.
2424
\tBinaries will be installed to ~/.local/bin.
2525
@@ -33,6 +33,7 @@ const HELP = `justinstall <github-url|file-url|local-file> [options]
3333
\tExamples:
3434
\t justinstall atuinsh/atuin
3535
\t justinstall https://github.com/junegunn/fzf/
36+
\t justinstall https://example.com/downloads/
3637
\t justinstall https://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg
3738
\t justinstall tailscale.pkg
3839
\t justinstall --search

lib/installer.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ const { execSync } = require("child_process");
55
const {
66
parseSource,
77
getGitHubAssets,
8+
getWebsiteAssets,
9+
trySmartDownload,
810
downloadFromUrl,
911
findInstallScripts,
1012
hasHighPriorityInstallScript,
@@ -78,6 +80,26 @@ const performInstallation = async (args, isUpdate = false) => {
7880
case "url":
7981
selected = await handleUrlSource(source, log);
8082
break;
83+
case "smart_url":
84+
const smartResult = await handleSmartUrlSource(
85+
source,
86+
platformInfo,
87+
capabilities,
88+
log
89+
);
90+
selected = smartResult.selected;
91+
releaseInfo = smartResult.releaseInfo;
92+
break;
93+
case "website":
94+
const websiteResult = await handleWebsiteSource(
95+
source,
96+
platformInfo,
97+
capabilities,
98+
log
99+
);
100+
selected = websiteResult.selected;
101+
releaseInfo = websiteResult.releaseInfo;
102+
break;
81103
case "github":
82104
const result = await handleGitHubSource(
83105
source,
@@ -176,6 +198,128 @@ const handleUrlSource = async (source, log) => {
176198
};
177199
};
178200

201+
const handleSmartUrlSource = async (
202+
source,
203+
platformInfo,
204+
capabilities,
205+
log
206+
) => {
207+
log.debug(`Attempting smart download from: ${source.url}`);
208+
209+
const result = await trySmartDownload(source.url, log);
210+
211+
if (result.type === "direct_download") {
212+
// It was a direct download
213+
log.debug(`Direct download: ${result.filename} (${result.size} bytes)`);
214+
215+
// Write the buffer to a temporary file
216+
const tempFilename = path.join(tmpdir, result.filename);
217+
fs.writeFileSync(tempFilename, result.buffer);
218+
219+
const extension = require("./sources").getExtension(result.filename);
220+
221+
return {
222+
selected: {
223+
name: result.filename,
224+
size: result.size,
225+
extension: extension,
226+
browser_download_url: result.url,
227+
localPath: tempFilename,
228+
},
229+
releaseInfo: { body: `Direct download of ${result.filename}` },
230+
};
231+
} else if (result.type === "scraped_assets") {
232+
// It was HTML, so we scraped it for assets
233+
log.debug(`Found ${result.assets.length} potential assets from HTML`);
234+
235+
if (result.assets.length > 0) {
236+
log.debug("Assets found:");
237+
result.assets.forEach((asset, index) => {
238+
log.debug(
239+
` ${index + 1}. ${asset.name} (ext: ${
240+
asset.extension || "none"
241+
}, url: ${asset.browser_download_url})`
242+
);
243+
});
244+
}
245+
246+
// Try to find a suitable asset using the same logic as GitHub
247+
const selected = selectBestAsset(result.assets, platformInfo, capabilities);
248+
249+
if (!selected) {
250+
// List available assets to help user
251+
log.log("No compatible assets found. Available downloads:");
252+
result.assets.forEach((asset, index) => {
253+
log.log(
254+
` ${index + 1}. ${asset.name} (${
255+
asset.extension || "no extension"
256+
}) - ${asset.browser_download_url}`
257+
);
258+
});
259+
260+
throw new Error(
261+
`Couldn't find a compatible download for ${platformInfo.platform}/${platformInfo.arch}. ` +
262+
`Check manually: ${source.url}`
263+
);
264+
}
265+
266+
log.debug(`Selected asset: ${selected.name} (${selected.extension})`);
267+
268+
return {
269+
selected,
270+
releaseInfo: { body: result.body },
271+
};
272+
}
273+
274+
throw new Error("Unexpected result from smart download");
275+
};
276+
277+
const handleWebsiteSource = async (source, platformInfo, capabilities, log) => {
278+
log.debug(`Scraping website for downloadable assets: ${source.url}`);
279+
280+
const { assets, body } = await getWebsiteAssets(source.url);
281+
282+
log.debug(`Found ${assets.length} potential assets`);
283+
284+
if (assets.length > 0) {
285+
log.debug("Assets found:");
286+
assets.forEach((asset, index) => {
287+
log.debug(
288+
` ${index + 1}. ${asset.name} (ext: ${
289+
asset.extension || "none"
290+
}, url: ${asset.browser_download_url})`
291+
);
292+
});
293+
}
294+
295+
// Try to find a suitable asset using the same logic as GitHub
296+
const selected = selectBestAsset(assets, platformInfo, capabilities);
297+
298+
if (!selected) {
299+
// List available assets to help user
300+
log.log("No compatible assets found. Available downloads:");
301+
assets.forEach((asset, index) => {
302+
log.log(
303+
` ${index + 1}. ${asset.name} (${
304+
asset.extension || "no extension"
305+
}) - ${asset.browser_download_url}`
306+
);
307+
});
308+
309+
throw new Error(
310+
`Couldn't find a compatible download for ${platformInfo.platform}/${platformInfo.arch}. ` +
311+
`Check manually: ${source.url}`
312+
);
313+
}
314+
315+
log.debug(`Selected asset: ${selected.name} (${selected.extension})`);
316+
317+
return {
318+
selected,
319+
releaseInfo: { body },
320+
};
321+
};
322+
179323
const displayScriptPreview = (script, log) => {
180324
const { code, source, score } = script;
181325
log.debug(

0 commit comments

Comments
 (0)