Skip to content

Commit eeded58

Browse files
File naming fixes + unification
1 parent 30148d0 commit eeded58

10 files changed

Lines changed: 110 additions & 88 deletions

File tree

.dir2md.json

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
{
22
"selectedFiles": [
3-
"lib/utils.js",
3+
".dir2md.json",
4+
".github/workflows/build-release.yml",
5+
".gitignore",
6+
".npmignore",
7+
"README.md",
8+
"build.sh",
9+
"index.js",
10+
"jsconfig.json",
411
"lib/config.js",
12+
"lib/installer.js",
13+
"lib/installers.js",
514
"lib/search.js",
615
"lib/sources.js",
716
"lib/updater.js",
8-
"lib/installer.js",
9-
"lib/installers.js",
10-
"index.js"
17+
"lib/utils.js",
18+
"package.json"
1119
],
12-
"prompt": "Why is dmg installation not working - additionally when downloading a file from github or somewhere else log the full path to the downloaded file in its temp dir.",
13-
"timestamp": "2025-08-14T21:11:52.711Z",
14-
"cwd": "/Users/tjs/Documents/.coding/repos/justinstall"
20+
"timestamp": "2025-09-09T00:06:22.210Z",
21+
"cwd": "/Users/tjs/Documents/.coding/repos/justinstall",
22+
"prompt": "Find bugs comprehensively across this project then make a plan to fix them, finally write out the fixed code. Adhere to clean code principles"
1523
}

.github/workflows/build-release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ jobs:
8888
for platform in "${!EXPECTED_BINARIES[@]}"; do
8989
binary="${EXPECTED_BINARIES[$platform]}"
9090
if [[ -f "$BUILD_DIR/$binary" ]]; then
91-
size=$(stat -f%z "$BUILD_DIR/$binary" 2>/dev/null || stat -c%s "$BUILD_DIR/$binary" 2>/dev/null)
91+
size=$(stat -c%s "$BUILD_DIR/$binary" 2>/dev/null)
9292
echo "✅ $platform: $binary (${size} bytes)"
9393
else
9494
echo "❌ Missing $platform binary: $binary"
@@ -157,4 +157,4 @@ jobs:
157157
fail_on_unmatched_files: true
158158
generate_release_notes: false
159159
env:
160-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
160+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

README.md

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -38,39 +38,3 @@ You can find the latest pre-releases in the [releases page](https://github.com/E
3838
(Add installation instructions here, e.g., how to download and set up the tool)
3939

4040
## Usage
41-
```
42-
justinstall <github-url|file-url|local-file>
43-
v1.0.0 - Just install anything. Supports .tar.gz, .zip, .dmg, .app, .pkg, and .deb files. Binaries will be installed to ~/.local/bin.
44-
45-
Example:
46-
justinstall atuinsh/atuin
47-
justinstall https://github.com/junegunn/fzf/
48-
justinstall https://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg
49-
justinstall tailscale.pkg
50-
```
51-
52-
## Supported Installation Methods
53-
54-
1. GitHub Repositories: Automatically fetches the latest release and selects the most compatible asset. **or** finds snippets from release notes and README files using hueristics.
55-
2. Direct URLs: Downloads and installs files from direct links.
56-
3. Local Files: Installs software from files already present on the local system.
57-
58-
## Contributing
59-
60-
61-
62-
Contributions are welcome! Please feel free to submit a Pull Request.
63-
64-
## Acknowledgements
65-
66-
Created by [Explosion-Scratch](https://github.com/explosion-scratch)
67-
68-
## Disclaimer
69-
Don't use on windows. I don't have windows so I haven't tested it on windows. It would probably still work well for binaries though.
70-
71-
This tool attempts to install software as safely as possible, but I'm not responsible if you install malware of if this breaks your system or harms anything in any way directly or indirectly. Always verify the source and contents of packages before installation.
72-
73-
Less scary note: I've included confirmations for many operations as a failsafe.
74-
75-
### Why's does the recording have errors:
76-
<small>Ignore the mise errors, I uninstalled it before recording to install it again. Also the regex thought it detected an install script but it didn't.</small>

build.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
#!/usr/bin/env bash
2+
set -e
3+
24
rm -rf build
3-
mkdir build
5+
mkdir -p build
46

57
VERSION=$(cat package.json | jq -r '.version')
68
bun build --compile --target=bun-linux-x64 index.js --outfile build/justinstall-$VERSION-linux-x64
79
bun build --compile --target=bun-linux-arm64 index.js --outfile build/justinstall-$VERSION-linux-arm64
8-
bun build --compile --target=bun-windows-x64 index.js --outfile build/justinstall-$VERSION-windows-x64
10+
bun build --compile --target=bun-windows-x64 index.js --outfile build/justinstall-$VERSION-windows-x64.exe
911
bun build --compile --target=bun-darwin-x64 index.js --outfile build/justinstall-$VERSION-darwin-x64
1012
bun build --compile --target=bun-darwin-arm64 index.js --outfile build/justinstall-$VERSION-darwin-arm64

index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ const HELP = `justinstall <github-url|website-url|file-url|local-file> [options]
4141
\tExamples:
4242
\t justinstall atuinsh/atuin
4343
\t justinstall https://github.com/junegunn/fzf/
44-
\t justinstall https://github.com/Explosion-Scratch/whisper-mac/releases/tag/v1.0.0-pre-41e7098-20250906-154430
44+
\t justinstall https://github.com/Explosion-Scratch/whisper-mac
4545
\t justinstall https://example.com/downloads/
4646
\t justinstall https://dl.google.com/chrome/mac/universal/stable/GGRO/googlechrome.dmg
4747
\t justinstall tailscale.pkg

lib/config.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,16 @@ const extractName = (selected) => {
103103
return selected.name
104104
.replace(/\.(tar\.gz|zip|dmg|pkg|deb|app)$/i, "")
105105
.replace(/v?[0-9]+\.[0-9]+\.[0-9]+/i, "")
106-
.replace(
107-
/(?:darwin|linux|windows|mac|osx|x64|arm64|aarch64|universal)/gi,
108-
""
109-
)
110-
.replace(/[ _\-\.]+$/, "")
111-
.replace(/^[ _\-\.]+/, "");
106+
.replace(/[-_]+(?:darwin|linux|windows|mac|osx|apple|x64|arm64|aarch64|universal|amd64)[-_]*/gi, "")
107+
.replace(/(?:darwin|linux|windows|mac|osx|apple|x64|arm64|aarch64|universal|amd64)[-_]*/gi, "")
108+
.replace(/[-_]+(?:darwin|linux|windows|mac|osx|apple|x64|arm64|aarch64|universal|amd64)$/gi, "")
109+
.replace(/^(?:darwin|linux|windows|mac|osx|apple|x64|arm64|aarch64|universal|amd64)[-_]+/gi, "")
110+
.replace(/[0-9]+\.[0-9]+\.[0-9]+/i, "")
111+
.replace(/[-_]+$/, "")
112+
.replace(/^[-_]+/, "")
113+
.replace(/[-_]{2,}/g, "-")
114+
.replace(/^-+|-+$/g, "")
115+
.replace(/os$/, "");
112116
};
113117

114118
module.exports = {

lib/installer.js

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const fs = require("fs");
22
const path = require("path");
33
const { execSync } = require("child_process");
4+
const os = require("os");
45

56
const {
67
parseSource,
@@ -62,7 +63,6 @@ const performInstallation = async (args, isUpdate = false) => {
6263
const customFilePath = args[1]; // For update with custom file path
6364

6465
tmpdir = execSync("mktemp -d").toString().trim();
65-
process.chdir(tmpdir);
6666

6767
const platformInfo = getPlatformInfo();
6868
const capabilities = getInstallCapabilities();
@@ -177,10 +177,11 @@ const handleFileSource = async (source, customFilePath, log) => {
177177
log.debug(`Installing from local file: ${filePath}`);
178178

179179
const stats = fs.statSync(filePath);
180+
const basename = path.basename(filePath);
180181
return {
181-
name: path.basename(filePath),
182+
name: extractName({ name: basename }),
182183
size: stats.size,
183-
extension: require("./sources").getExtension(path.basename(filePath)),
184+
extension: require("./sources").getExtension(basename),
184185
localPath: filePath,
185186
};
186187
};
@@ -220,10 +221,6 @@ const handleSmartUrlSource = async (
220221
// It was a direct download
221222
log.debug(`Direct download: ${result.filename} (${result.size} bytes)`);
222223

223-
// Write the buffer to a temporary file
224-
const tempFilename = path.join(tmpdir, result.filename);
225-
fs.writeFileSync(tempFilename, result.buffer);
226-
227224
const extension = require("./sources").getExtension(result.filename);
228225

229226
return {
@@ -232,7 +229,7 @@ const handleSmartUrlSource = async (
232229
size: result.size,
233230
extension: extension,
234231
browser_download_url: result.url,
235-
localPath: tempFilename,
232+
localPath: result.localPath,
236233
},
237234
releaseInfo: { body: `Direct download of ${result.filename}` },
238235
};
@@ -585,8 +582,10 @@ const installSelected = async (selected, downloadPath, log) => {
585582
installationMethod = "dmg";
586583

587584
try {
588-
mountDMG(downloadPath, outputDir, log);
589-
const dmgBinaries = getBinaries(outputDir);
585+
const mountDir = path.join(tmpdir, "dmg-mount");
586+
fs.mkdirSync(mountDir, { recursive: true });
587+
mountDMG(downloadPath, mountDir, log);
588+
const dmgBinaries = getBinaries(mountDir);
590589
log.debug(
591590
`Found ${dmgBinaries.length} items in DMG: ${dmgBinaries.join(", ")}`,
592591
);
@@ -596,7 +595,7 @@ const installSelected = async (selected, downloadPath, log) => {
596595

597596
if (appFile) {
598597
log.log(`Installing .app bundle: ${appFile}`);
599-
destinations = await installApp(appFile, outputDir, checkPath, log);
598+
destinations = await installApp(appFile, mountDir, checkPath, log);
600599
binariesList = [appFile];
601600

602601
// Ask to open app
@@ -605,7 +604,7 @@ const installSelected = async (selected, downloadPath, log) => {
605604
}
606605
} else if (pkgFile) {
607606
log.log(`Installing .pkg file: ${pkgFile}`);
608-
destinations = installPkg(path.join(outputDir, pkgFile));
607+
destinations = installPkg(path.join(mountDir, pkgFile));
609608
binariesList = [pkgFile];
610609
} else if (dmgBinaries.length > 0) {
611610
// Handle DMGs with executables but no .app or .pkg
@@ -614,7 +613,7 @@ const installSelected = async (selected, downloadPath, log) => {
614613
);
615614
destinations = await installBinaries(
616615
dmgBinaries,
617-
outputDir,
616+
mountDir,
618617
selected.name,
619618
checkPath,
620619
log,
@@ -639,7 +638,7 @@ const installSelected = async (selected, downloadPath, log) => {
639638
case "app":
640639
installationMethod = "app";
641640
destinations = await installApp(
642-
selected.name,
641+
path.basename(selected.name),
643642
path.dirname(downloadPath),
644643
checkPath,
645644
log,

lib/installers.js

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ const getInstallCapabilities = () => {
6060
};
6161

6262
return {
63-
deb: process.platform === "linux" && ebool("which apt"),
63+
deb: process.platform === "linux" && ebool("which dpkg"),
6464
dmg: process.platform === "darwin",
6565
pkg: process.platform === "darwin",
6666
app: process.platform === "darwin",
67-
rpm: ebool("which dnf"),
68-
"tar.zst": ebool("which pacman"),
67+
rpm: process.platform === "linux" && ebool("which rpm"),
68+
"tar.zst": ebool("which unzstd") || ebool("which zstd"),
6969
};
7070
};
7171

@@ -178,7 +178,7 @@ const extractArchive = (filePath, outputDir, extension) => {
178178
break;
179179
case "tar.zst":
180180
execSync(
181-
`tar -xf ${JSON.stringify(filePath)} --directory ${JSON.stringify(
181+
`unzstd < ${JSON.stringify(filePath)} | tar -xf - --directory ${JSON.stringify(
182182
outputDir
183183
)}`
184184
);
@@ -288,10 +288,12 @@ const processExtractedPackages = async (
288288
if (dmgFile) {
289289
logger.log(`Found DMG file in ZIP: ${dmgFile}`);
290290
const dmgPath = path.join(outputDir, dmgFile);
291+
const mountDir = path.join(outputDir, "dmg-mount");
292+
fs.mkdirSync(mountDir, { recursive: true });
291293

292294
try {
293-
mountDMG(dmgPath, outputDir, logger);
294-
const dmgBinaries = getBinaries(outputDir);
295+
mountDMG(dmgPath, mountDir, logger);
296+
const dmgBinaries = getBinaries(mountDir);
295297
logger.debug(
296298
`Found ${dmgBinaries.length} items in DMG: ${dmgBinaries.join(", ")}`
297299
);
@@ -301,15 +303,15 @@ const processExtractedPackages = async (
301303

302304
if (appFile) {
303305
logger.log(`Installing .app bundle from DMG: ${appFile}`);
304-
const destinations = await installApp(appFile, outputDir, checkPathFn, logger);
306+
const destinations = await installApp(appFile, mountDir, checkPathFn, logger);
305307
return {
306308
method: "dmg_app",
307309
destinations,
308310
binaries: [appFile],
309311
};
310312
} else if (nestedPkgFile) {
311313
logger.log(`Installing .pkg file from DMG: ${nestedPkgFile}`);
312-
const destinations = installPkg(path.join(outputDir, nestedPkgFile));
314+
const destinations = installPkg(path.join(mountDir, nestedPkgFile));
313315
return {
314316
method: "dmg_pkg",
315317
destinations,
@@ -319,7 +321,7 @@ const processExtractedPackages = async (
319321
logger.log("No .app or .pkg found in DMG, trying to install executables");
320322
const destinations = await installBinaries(
321323
dmgBinaries,
322-
outputDir,
324+
mountDir,
323325
selectedName,
324326
checkPathFn,
325327
logger,
@@ -334,7 +336,7 @@ const processExtractedPackages = async (
334336
throw new Error("No installable files found in DMG");
335337
}
336338
} finally {
337-
ejectDMG(outputDir, logger);
339+
ejectDMG(mountDir, logger);
338340
}
339341
} else if (pkgFile) {
340342
logger.log(`Found PKG file in ZIP: ${pkgFile}`);
@@ -397,6 +399,12 @@ const installPkg = (pkgPath) => {
397399

398400
const mountDMG = (dmgPath, mountPoint, logger = null) => {
399401
try {
402+
// Ensure mount point is empty
403+
if (fs.existsSync(mountPoint)) {
404+
fs.rmSync(mountPoint, { recursive: true, force: true });
405+
}
406+
fs.mkdirSync(mountPoint, { recursive: true });
407+
400408
execSync(
401409
`hdiutil attach ${JSON.stringify(
402410
dmgPath
@@ -489,7 +497,7 @@ const installBinaries = async (
489497
};
490498

491499
const installDeb = (debPath) => {
492-
execSync(`dpkg -i ${JSON.stringify(debPath)}`);
500+
execSync(`sudo dpkg -i ${JSON.stringify(debPath)}`);
493501
return ["System-wide deb installation"];
494502
};
495503

lib/search.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ const searchRepositories = async (query, options = {}) => {
1313
url.searchParams.set("order", order);
1414
url.searchParams.set("per_page", per_page);
1515

16-
const response = await fetch(url.toString());
16+
const response = await fetch(url.toString(), {
17+
headers: {
18+
"User-Agent": "justinstall/1.2.0",
19+
},
20+
});
1721

1822
if (!response.ok) {
1923
throw new Error(`GitHub API error: ${response.statusText}`);

0 commit comments

Comments
 (0)