Skip to content

Commit 33cc8be

Browse files
Install within zip
1 parent 57760ee commit 33cc8be

4 files changed

Lines changed: 266 additions & 20 deletions

File tree

index.js

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

2222
const HELP = `justinstall <github-url|website-url|file-url|local-file> [options]
23-
\tv1.1.0 - Just install anything. Supports .tar.gz, .zip, .dmg, .app, .pkg, and .deb files.
23+
\tv1.2.0 - Just install anything. Supports .tar.gz, .zip, .dmg, .app, .pkg, and .deb files.
24+
\tZIP files containing DMG or PKG packages are automatically detected and installed.
2425
\tBinaries will be installed to ~/.local/bin.
2526
2627
\tOptions:

lib/installer.js

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const {
1717
selectBestAsset,
1818
extractArchive,
1919
getBinaries,
20+
processExtractedPackages,
2021
installApp,
2122
installPkg,
2223
mountDMG,
@@ -150,8 +151,7 @@ const performInstallation = async (args, isUpdate = false) => {
150151
}
151152

152153
log.log(
153-
`Successfully ${
154-
isUpdate ? "updated" : "installed"
154+
`Successfully ${isUpdate ? "updated" : "installed"
155155
}: ${installationResult.binaries.join(", ")}`
156156
);
157157
} finally {
@@ -236,8 +236,7 @@ const handleSmartUrlSource = async (
236236
log.debug("Assets found:");
237237
result.assets.forEach((asset, index) => {
238238
log.debug(
239-
` ${index + 1}. ${asset.name} (ext: ${
240-
asset.extension || "none"
239+
` ${index + 1}. ${asset.name} (ext: ${asset.extension || "none"
241240
}, url: ${asset.browser_download_url})`
242241
);
243242
});
@@ -251,15 +250,14 @@ const handleSmartUrlSource = async (
251250
log.log("No compatible assets found. Available downloads:");
252251
result.assets.forEach((asset, index) => {
253252
log.log(
254-
` ${index + 1}. ${asset.name} (${
255-
asset.extension || "no extension"
253+
` ${index + 1}. ${asset.name} (${asset.extension || "no extension"
256254
}) - ${asset.browser_download_url}`
257255
);
258256
});
259257

260258
throw new Error(
261259
`Couldn't find a compatible download for ${platformInfo.platform}/${platformInfo.arch}. ` +
262-
`Check manually: ${source.url}`
260+
`Check manually: ${source.url}`
263261
);
264262
}
265263

@@ -285,8 +283,7 @@ const handleWebsiteSource = async (source, platformInfo, capabilities, log) => {
285283
log.debug("Assets found:");
286284
assets.forEach((asset, index) => {
287285
log.debug(
288-
` ${index + 1}. ${asset.name} (ext: ${
289-
asset.extension || "none"
286+
` ${index + 1}. ${asset.name} (ext: ${asset.extension || "none"
290287
}, url: ${asset.browser_download_url})`
291288
);
292289
});
@@ -300,15 +297,14 @@ const handleWebsiteSource = async (source, platformInfo, capabilities, log) => {
300297
log.log("No compatible assets found. Available downloads:");
301298
assets.forEach((asset, index) => {
302299
log.log(
303-
` ${index + 1}. ${asset.name} (${
304-
asset.extension || "no extension"
300+
` ${index + 1}. ${asset.name} (${asset.extension || "no extension"
305301
}) - ${asset.browser_download_url}`
306302
);
307303
});
308304

309305
throw new Error(
310306
`Couldn't find a compatible download for ${platformInfo.platform}/${platformInfo.arch}. ` +
311-
`Check manually: ${source.url}`
307+
`Check manually: ${source.url}`
312308
);
313309
}
314310

@@ -324,10 +320,10 @@ const displayScriptPreview = (script, log) => {
324320
const { code, source, score } = script;
325321
log.debug(
326322
`Found installer code in ${source} (score: ${score}):\n\n\t` +
327-
colors.fg.green +
328-
code +
329-
colors.reset +
330-
"\n"
323+
colors.fg.green +
324+
code +
325+
colors.reset +
326+
"\n"
331327
);
332328
};
333329

@@ -498,7 +494,7 @@ const handleGitHubSource = async (source, platformInfo, capabilities, log) => {
498494
if (!selected) {
499495
throw new Error(
500496
`Couldn't find a compatible binary for ${platformInfo.platform}/${platformInfo.arch}. ` +
501-
`Check manually: https://github.com/${source.owner}/${source.repo}/releases`
497+
`Check manually: https://github.com/${source.owner}/${source.repo}/releases`
502498
);
503499
}
504500

@@ -627,14 +623,32 @@ const installSelected = async (selected, downloadPath, log) => {
627623
}
628624

629625
log.debug(`Found binaries: ${extractedBinaries.join(", ")}`);
630-
destinations = await installBinaries(
626+
627+
// Check if any of the extracted files are DMG or PKG packages
628+
const packageResult = await processExtractedPackages(
631629
extractedBinaries,
632630
outputDir,
633631
selected.name,
634632
checkPath,
635633
log
636634
);
637-
binariesList = extractedBinaries.map((bin) => path.basename(bin));
635+
636+
if (packageResult) {
637+
// Use the package installation result
638+
installationMethod = packageResult.method;
639+
destinations = packageResult.destinations;
640+
binariesList = packageResult.binaries;
641+
} else {
642+
// Fall back to regular binary installation
643+
destinations = await installBinaries(
644+
extractedBinaries,
645+
outputDir,
646+
selected.name,
647+
checkPath,
648+
log
649+
);
650+
binariesList = extractedBinaries.map((bin) => path.basename(bin));
651+
}
638652
break;
639653
}
640654

lib/installers.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,91 @@ const getBinaries = (dir) => {
237237
return [...new Set(binaries)]; // Remove duplicates
238238
};
239239

240+
/**
241+
* Process extracted packages (DMG/PKG) found within ZIP archives
242+
* @param {string[]} binaries - List of files found by getBinaries
243+
* @param {string} outputDir - Directory containing extracted files
244+
* @param {string} selectedName - Name of the original selected file
245+
* @param {Function} checkPathFn - Function to check destination paths
246+
* @param {Object} logger - Logger instance
247+
* @returns {Object|null} Installation result or null if no packages found
248+
*/
249+
const processExtractedPackages = async (
250+
binaries,
251+
outputDir,
252+
selectedName,
253+
checkPathFn,
254+
logger
255+
) => {
256+
// Look for DMG or PKG files in the extracted binaries
257+
const dmgFile = binaries.find((f) => f.toLowerCase().endsWith(".dmg"));
258+
const pkgFile = binaries.find((f) => f.toLowerCase().endsWith(".pkg"));
259+
260+
if (dmgFile) {
261+
logger.log(`Found DMG file in ZIP: ${dmgFile}`);
262+
const dmgPath = path.join(outputDir, dmgFile);
263+
264+
try {
265+
mountDMG(dmgPath, outputDir, logger);
266+
const dmgBinaries = getBinaries(outputDir);
267+
logger.debug(
268+
`Found ${dmgBinaries.length} items in DMG: ${dmgBinaries.join(", ")}`
269+
);
270+
271+
const appFile = dmgBinaries.find((f) => f.endsWith(".app"));
272+
const nestedPkgFile = dmgBinaries.find((f) => f.endsWith(".pkg"));
273+
274+
if (appFile) {
275+
logger.log(`Installing .app bundle from DMG: ${appFile}`);
276+
const destinations = await installApp(appFile, outputDir, checkPathFn, logger);
277+
return {
278+
method: "dmg_app",
279+
destinations,
280+
binaries: [appFile],
281+
};
282+
} else if (nestedPkgFile) {
283+
logger.log(`Installing .pkg file from DMG: ${nestedPkgFile}`);
284+
const destinations = installPkg(path.join(outputDir, nestedPkgFile));
285+
return {
286+
method: "dmg_pkg",
287+
destinations,
288+
binaries: [nestedPkgFile],
289+
};
290+
} else if (dmgBinaries.length > 0) {
291+
logger.log("No .app or .pkg found in DMG, trying to install executables");
292+
const destinations = await installBinaries(
293+
dmgBinaries,
294+
outputDir,
295+
selectedName,
296+
checkPathFn,
297+
logger,
298+
true // isMountedVolume = true
299+
);
300+
return {
301+
method: "dmg_binaries",
302+
destinations,
303+
binaries: dmgBinaries.map((bin) => path.basename(bin)),
304+
};
305+
} else {
306+
throw new Error("No installable files found in DMG");
307+
}
308+
} finally {
309+
ejectDMG(outputDir, logger);
310+
}
311+
} else if (pkgFile) {
312+
logger.log(`Found PKG file in ZIP: ${pkgFile}`);
313+
const pkgPath = path.join(outputDir, pkgFile);
314+
const destinations = installPkg(pkgPath);
315+
return {
316+
method: "pkg",
317+
destinations,
318+
binaries: [pkgFile],
319+
};
320+
}
321+
322+
return null; // No packages found
323+
};
324+
240325
const installApp = async (appPath, outputDir, checkPathFn, logger = null) => {
241326
const dest = path.join("/Applications", path.basename(appPath));
242327
await checkPathFn(dest);
@@ -395,6 +480,7 @@ module.exports = {
395480
selectBestAsset,
396481
extractArchive,
397482
getBinaries,
483+
processExtractedPackages,
398484
installApp,
399485
installPkg,
400486
mountDMG,

package-lock.json

Lines changed: 145 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)