buddymac 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/cli.mjs +33 -7
- package/package.json +1 -1
- package/release.json +3 -3
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ npx buddymac
|
|
|
6
6
|
|
|
7
7
|
Downloads BuddyMac to `~/Applications` and opens it. Requires an Apple Silicon Mac running macOS 26 or later and Node.js 20 or later. No Bun installation is needed.
|
|
8
8
|
|
|
9
|
-
The installer checks a pinned SHA-256 checksum
|
|
9
|
+
The installer shows download progress and checks a pinned SHA-256 checksum, Francesco Oddo's Apple Developer signature, and macOS Gatekeeper approval. It does not overwrite existing apps or change your app data. To update, quit BuddyMac and move the previous app out of the destination first.
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
12
|
npx buddymac --no-open
|
|
@@ -14,6 +14,6 @@ npx buddymac --destination /Applications
|
|
|
14
14
|
npx buddymac --help
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
The app is signed and notarized by Apple, with the notarization ticket included. The installer preserves macOS download protection.
|
|
18
18
|
|
|
19
19
|
Source and downloads: https://github.com/OxFrancesco/BuddyMac
|
package/cli.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
|
-
import {
|
|
3
|
+
import { createWriteStream } from 'node:fs';
|
|
4
4
|
import { access, mkdir, mkdtemp, readFile, rename, rm } from 'node:fs/promises';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import { join, resolve } from 'node:path';
|
|
7
7
|
import { spawnSync } from 'node:child_process';
|
|
8
|
-
import { Readable } from 'node:stream';
|
|
8
|
+
import { Readable, Transform } from 'node:stream';
|
|
9
9
|
import { pipeline } from 'node:stream/promises';
|
|
10
10
|
|
|
11
11
|
const release = JSON.parse(await readFile(new URL('./release.json', import.meta.url), 'utf8'));
|
|
@@ -15,8 +15,7 @@ const help = `Usage: npx buddymac [--destination <folder>] [--no-open]
|
|
|
15
15
|
Installs BuddyMac ${release.version} to ~/Applications and opens it.
|
|
16
16
|
Requires Apple Silicon, macOS 26+, and Node.js 20+.
|
|
17
17
|
Existing apps are never overwritten. Quit and move an old copy before updating.
|
|
18
|
-
|
|
19
|
-
in System Settings > Privacy & Security after the first launch attempt.
|
|
18
|
+
The download is signed and notarized by Apple. macOS download protection stays enabled.
|
|
20
19
|
|
|
21
20
|
--destination <folder> Install in a different folder
|
|
22
21
|
--no-open Install without opening the app
|
|
@@ -32,6 +31,7 @@ function run(command, parameters) {
|
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
async function install() {
|
|
34
|
+
const started = performance.now();
|
|
35
35
|
let destination = join(homedir(), 'Applications');
|
|
36
36
|
let open = true;
|
|
37
37
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -67,20 +67,46 @@ async function install() {
|
|
|
67
67
|
stage = await mkdtemp(join(destination, '.buddymac-'));
|
|
68
68
|
const archive = join(stage, 'BuddyMac.zip');
|
|
69
69
|
process.stdout.write(`Downloading BuddyMac ${release.version}…\n`);
|
|
70
|
+
const downloadStarted = performance.now();
|
|
70
71
|
const response = await fetch(release.url, { signal: AbortSignal.timeout(300_000) });
|
|
71
72
|
if (!response.ok || !response.body) throw new Error(`Download failed: HTTP ${response.status}. ${release.url}`);
|
|
72
|
-
|
|
73
|
+
const total = Number(response.headers.get('content-length'));
|
|
74
|
+
let received = 0;
|
|
73
75
|
const hash = createHash('sha256');
|
|
74
|
-
|
|
76
|
+
const meter = new Transform({ transform(chunk, encoding, done) {
|
|
77
|
+
received += chunk.length;
|
|
78
|
+
hash.update(chunk);
|
|
79
|
+
done(null, chunk);
|
|
80
|
+
} });
|
|
81
|
+
const progress = () => {
|
|
82
|
+
const seconds = (performance.now() - downloadStarted) / 1000;
|
|
83
|
+
const mb = (received / 1_000_000).toFixed(1);
|
|
84
|
+
const size = total > 0 ? ` / ${(total / 1_000_000).toFixed(1)} MB (${Math.min(100, Math.floor(received / total * 100))}%)` : ' MB';
|
|
85
|
+
const message = `${mb}${size} · ${(received / 1_000_000 / Math.max(seconds, 0.001)).toFixed(1)} MB/s · ${seconds.toFixed(0)}s`;
|
|
86
|
+
process.stdout.write(process.stdout.isTTY ? `\r\x1b[2K${message}` : `${message}\n`);
|
|
87
|
+
};
|
|
88
|
+
const timer = setInterval(progress, process.stdout.isTTY ? 250 : 5000);
|
|
89
|
+
try {
|
|
90
|
+
await pipeline(Readable.fromWeb(response.body), meter, createWriteStream(archive, { flags: 'wx' }));
|
|
91
|
+
} finally {
|
|
92
|
+
clearInterval(timer);
|
|
93
|
+
if (process.stdout.isTTY) process.stdout.write('\r\x1b[2K');
|
|
94
|
+
}
|
|
75
95
|
if (hash.digest('hex') !== release.sha256) throw new Error('Download checksum mismatch. No app was installed.');
|
|
96
|
+
process.stdout.write(`Download verified in ${((performance.now() - downloadStarted) / 1000).toFixed(1)}s. Unpacking…\n`);
|
|
97
|
+
const unpackStarted = performance.now();
|
|
76
98
|
run('/usr/bin/ditto', ['-x', '-k', archive, stage]);
|
|
77
99
|
const app = join(stage, 'BuddyMac.app');
|
|
100
|
+
process.stdout.write(`Unpacked in ${((performance.now() - unpackStarted) / 1000).toFixed(1)}s. Checking Apple signature and notarization…\n`);
|
|
101
|
+
const verificationStarted = performance.now();
|
|
78
102
|
run('/usr/bin/codesign', ['--verify', '--deep', '--strict', '-R', '=anchor apple generic and certificate leaf[subject.OU] = "G2442WAF29"', app]);
|
|
79
103
|
const identifier = run('/usr/libexec/PlistBuddy', ['-c', 'Print :CFBundleIdentifier', join(app, 'Contents/Info.plist')]);
|
|
80
104
|
if (identifier !== 'org.buddytools.BuddyMac') throw new Error('Unexpected application identifier.');
|
|
81
105
|
run('/usr/bin/xattr', ['-w', 'com.apple.quarantine', `0081;${Math.floor(Date.now() / 1000).toString(16)};BuddyMac Installer;`, app]);
|
|
106
|
+
run('/usr/sbin/spctl', ['--assess', '--type', 'execute', app]);
|
|
107
|
+
process.stdout.write(`Apple verification passed in ${((performance.now() - verificationStarted) / 1000).toFixed(1)}s.\n`);
|
|
82
108
|
await rename(app, target);
|
|
83
|
-
process.stdout.write(`Installed ${target}
|
|
109
|
+
process.stdout.write(`Installed ${target} in ${((performance.now() - started) / 1000).toFixed(1)}s.\n`);
|
|
84
110
|
if (open) run('/usr/bin/open', [target]);
|
|
85
111
|
} finally {
|
|
86
112
|
if (stage) await rm(stage, { recursive: true, force: true });
|
package/package.json
CHANGED
package/release.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.1.
|
|
3
|
-
"url": "https://github.com/OxFrancesco/BuddyMac/releases/download/v0.1.
|
|
4
|
-
"sha256": "
|
|
2
|
+
"version": "0.1.3",
|
|
3
|
+
"url": "https://github.com/OxFrancesco/BuddyMac/releases/download/v0.1.3/BuddyMac-darwin-arm64.zip",
|
|
4
|
+
"sha256": "4a4fcd984d46ca6644eed072aae50eecf1bdbe7bdb2710e9cd10e4c54a791165"
|
|
5
5
|
}
|