Skip to content

Commit 45a5c6e

Browse files
Binary detection
1 parent 6bbcf31 commit 45a5c6e

2 files changed

Lines changed: 132 additions & 11 deletions

File tree

lib/installer.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const {
1818
selectBestAsset,
1919
extractArchive,
2020
getBinaries,
21+
selectBinaries,
2122
processExtractedPackages,
2223
installApp,
2324
installPkg,
@@ -641,7 +642,7 @@ const installSelected = async (selected, downloadPath, log) => {
641642
const mountDir = path.join(tmpdir, "dmg-mount");
642643
fs.mkdirSync(mountDir, { recursive: true });
643644
mountDMG(downloadPath, mountDir, log);
644-
const dmgBinaries = getBinaries(mountDir);
645+
let dmgBinaries = getBinaries(mountDir);
645646
log.debug(
646647
`Found ${dmgBinaries.length} items in DMG: ${dmgBinaries.join(", ")}`,
647648
);
@@ -667,15 +668,16 @@ const installSelected = async (selected, downloadPath, log) => {
667668
log.log(
668669
"No .app or .pkg found in DMG, trying to install executables",
669670
);
671+
const selectedBinaries = await selectBinaries(dmgBinaries, selected.name, log);
670672
destinations = await installBinaries(
671-
dmgBinaries,
673+
selectedBinaries,
672674
mountDir,
673675
selected.name,
674676
checkPath,
675677
log,
676678
true, // isMountedVolume = true
677679
);
678-
binariesList = dmgBinaries.map((bin) => path.basename(bin));
680+
binariesList = selectedBinaries.map((bin) => path.basename(bin));
679681
} else {
680682
throw new Error("No installable files found in DMG");
681683
}
@@ -750,14 +752,15 @@ const installSelected = async (selected, downloadPath, log) => {
750752
}
751753
} else {
752754
// Fall back to regular binary installation
755+
const selectedBinaries = await selectBinaries(extractedBinaries, selected.name, log);
753756
destinations = await installBinaries(
754-
extractedBinaries,
757+
selectedBinaries,
755758
outputDir,
756759
selected.name,
757760
checkPath,
758761
log,
759762
);
760-
binariesList = extractedBinaries.map((bin) => path.basename(bin));
763+
binariesList = selectedBinaries.map((bin) => path.basename(bin));
761764
}
762765
break;
763766
}

lib/installers.js

Lines changed: 124 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -245,19 +245,135 @@ const getBinaries = (dir) => {
245245
if (appDirectories.some((app) => f.startsWith(app + "/"))) {
246246
return false;
247247
}
248-
return execSync(`file ${JSON.stringify(path.resolve(dir, f))}`).includes(
249-
"executable"
250-
);
248+
249+
const filePath = path.resolve(dir, f);
250+
const fileOutput = execSync(`file ${JSON.stringify(filePath)}`).toString();
251+
252+
// Check if it's an executable binary or script
253+
return fileOutput.includes("executable") ||
254+
fileOutput.includes("ELF") ||
255+
fileOutput.includes("Mach-O") ||
256+
fileOutput.includes("script");
251257
} catch {
252258
return false;
253259
}
254260
};
255261

256-
binaries.push(...allFiles.filter(isExecutable));
262+
// Also check for files that look like binaries based on naming patterns
263+
const looksLikeBinary = (f) => {
264+
const filename = path.basename(f).toLowerCase();
265+
const dirname = path.dirname(f).toLowerCase();
266+
267+
// Skip files in common non-binary directories
268+
if (dirname.includes("doc") || dirname.includes("docs") || dirname.includes("man") ||
269+
dirname.includes("runtime") || dirname.includes("lib") || dirname.includes("share")) {
270+
return false;
271+
}
272+
273+
// Skip obvious non-binary files
274+
if (filename.includes("readme") || filename.includes("license") ||
275+
filename.includes("changelog") || filename.includes("copying") ||
276+
filename.includes("install") || filename.includes("makefile") ||
277+
filename.includes(".md") || filename.includes(".txt") ||
278+
filename.includes(".1") || filename.includes(".json") ||
279+
filename.includes(".yaml") || filename.includes(".yml") ||
280+
filename.includes(".toml") || filename.includes(".cfg") ||
281+
filename.includes(".conf") || filename.includes(".ini")) {
282+
return false;
283+
}
284+
285+
// Common binary naming patterns
286+
const binaryPatterns = [
287+
/^[a-z0-9_-]+$/, // Simple name like hx, git, node
288+
/^[a-z0-9_-]+\.(exe|bin|run)$/, // Extensions
289+
];
290+
291+
return binaryPatterns.some(pattern => pattern.test(filename));
292+
};
293+
294+
const executableFiles = allFiles.filter(isExecutable);
295+
const potentialBinaries = allFiles.filter(looksLikeBinary);
296+
297+
// Combine and remove duplicates
298+
binaries.push(...executableFiles);
299+
binaries.push(...potentialBinaries.filter(f => !executableFiles.includes(f)));
257300

258301
return [...new Set(binaries)]; // Remove duplicates
259302
};
260303

304+
/**
305+
* Prompt user to choose from multiple binaries found in a package
306+
* @param {string[]} binaries - List of binary files found
307+
* @param {string} packageName - Name of the package being installed
308+
* @param {Object} logger - Logger instance
309+
* @returns {Promise<string[]>} Selected binaries
310+
*/
311+
const selectBinaries = async (binaries, packageName, logger = null) => {
312+
if (binaries.length <= 1) {
313+
return binaries;
314+
}
315+
316+
// Filter out obvious non-binaries for cleaner selection
317+
const filteredBinaries = binaries.filter(f => {
318+
const filename = path.basename(f).toLowerCase();
319+
return !filename.includes('readme') &&
320+
!filename.includes('license') &&
321+
!filename.includes('changelog') &&
322+
!filename.includes('.md') &&
323+
!filename.includes('.txt') &&
324+
!filename.includes('.1') && // man pages
325+
!f.includes('doc/') &&
326+
!f.includes('docs/') &&
327+
!f.includes('man/');
328+
});
329+
330+
// If filtering leaves us with just one, use it
331+
if (filteredBinaries.length === 1) {
332+
if (logger) {
333+
logger.log(`Auto-selected binary: ${filteredBinaries[0]}`);
334+
}
335+
return filteredBinaries;
336+
}
337+
338+
// If filtering leaves none, fall back to original list
339+
const binariesToChoose = filteredBinaries.length > 0 ? filteredBinaries : binaries;
340+
341+
if (logger) {
342+
logger.log(`Multiple binaries found in ${packageName}. Please choose which to install:`);
343+
binariesToChoose.forEach((binary, index) => {
344+
logger.log(` ${index + 1}. ${binary}`);
345+
});
346+
}
347+
348+
const readline = require('readline');
349+
const rli = readline.createInterface({
350+
input: process.stdin,
351+
output: process.stdout,
352+
});
353+
354+
const choice = await new Promise((resolve) => {
355+
rli.question(`Enter binary number to install (1-${binariesToChoose.length}), or 'all' for all: `, (ans) => {
356+
rli.close();
357+
resolve(ans.trim().toLowerCase());
358+
});
359+
});
360+
361+
if (choice === 'all') {
362+
return binariesToChoose;
363+
}
364+
365+
const numericChoice = parseInt(choice);
366+
if (numericChoice >= 1 && numericChoice <= binariesToChoose.length) {
367+
return [binariesToChoose[numericChoice - 1]];
368+
}
369+
370+
// Default to first binary if invalid choice
371+
if (logger) {
372+
logger.log(`Invalid choice, installing first binary: ${binariesToChoose[0]}`);
373+
}
374+
return [binariesToChoose[0]];
375+
};
376+
261377
// Use extractName from config for consistent name normalization
262378

263379
/**
@@ -328,8 +444,9 @@ const processExtractedPackages = async (
328444
};
329445
} else if (dmgBinaries.length > 0) {
330446
logger.log("No .app or .pkg found in DMG, trying to install executables");
447+
const selectedBinaries = await selectBinaries(dmgBinaries, selectedName, logger);
331448
const destinations = await installBinaries(
332-
dmgBinaries,
449+
selectedBinaries,
333450
mountDir,
334451
selectedName,
335452
checkPathFn,
@@ -339,7 +456,7 @@ const processExtractedPackages = async (
339456
return {
340457
method: "dmg_binaries",
341458
destinations,
342-
binaries: dmgBinaries.map((bin) => path.basename(bin)),
459+
binaries: selectedBinaries.map((bin) => path.basename(bin)),
343460
};
344461
} else {
345462
throw new Error("No installable files found in DMG");
@@ -517,6 +634,7 @@ module.exports = {
517634
selectBestAsset,
518635
extractArchive,
519636
getBinaries,
637+
selectBinaries,
520638
processExtractedPackages,
521639
installApp,
522640
installPkg,

0 commit comments

Comments
 (0)