Skip to content

Commit eac2eac

Browse files
committed
Fix Windows Whisper detection and slim release to 5 installers
1 parent b467a6f commit eac2eac

2 files changed

Lines changed: 85 additions & 34 deletions

File tree

.github/workflows/release.yml

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,26 @@ jobs:
122122
- name: Flatten artifacts
123123
run: |
124124
mkdir -p release
125-
find artifacts -type f \( -name '*.dmg' -o -name '*.zip' -o -name '*.exe' -o -name '*.AppImage' -o -name '*.deb' \) -exec cp -v {} release/ \;
125+
# Pick only the primary installer per platform (max 2-3 per
126+
# platform). This intentionally drops:
127+
# - Windows portable EXE (we ship the NSIS installer)
128+
# - macOS .zip (the DMG is the canonical macOS installer)
129+
# - .blockmap files (electron-updater diff metadata)
130+
# - latest-*.yml (electron-updater channel metadata)
131+
# - Source archives (GitHub auto-generates these from the tag)
132+
#
133+
# The order below is the order files appear in the release page.
134+
find artifacts -type f -name 'OpenCluely-Setup-*.exe' \
135+
-exec cp -v {} release/ \;
136+
# macOS DMGs — electron-builder omits the arch suffix for the
137+
# default (x64) arch, so we keep every OpenCluely-*.dmg.
138+
find artifacts -type f -name 'OpenCluely-*.dmg' \
139+
-exec cp -v {} release/ \;
140+
find artifacts -type f -name 'opencluely_*_amd64.deb' \
141+
-exec cp -v {} release/ \;
142+
find artifacts -type f -name '*.AppImage' \
143+
-exec cp -v {} release/ \;
144+
echo "--- Files selected for release ---"
126145
ls -la release/
127146
128147
- name: Generate SHA256 checksums
@@ -146,8 +165,7 @@ jobs:
146165
147166
| Platform | File | Notes |
148167
|---|---|---|
149-
| **Windows** | `*.exe` | NSIS installer — installs app + adds to Start Menu |
150-
| **Windows** | `*-portable.exe` | Portable, no install required |
168+
| **Windows** | `OpenCluely-Setup-*.exe` | NSIS installer — installs app + adds to Start Menu |
151169
| **macOS (Apple Silicon)** | `*-arm64.dmg` | M1/M2/M3 Macs |
152170
| **macOS (Intel)** | `*-x64.dmg` | Older Intel Macs |
153171
| **Linux** | `*.deb` | Debian/Ubuntu — auto-pulls system deps (Python, ffmpeg, GTK) |

src/core/whisper-installer.js

Lines changed: 64 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,18 @@ class WhisperInstaller {
8787
// 1. Honor WHISPER_COMMAND env if user has it set already
8888
const fromEnv = (process.env.WHISPER_COMMAND || '').trim();
8989
if (fromEnv) {
90-
const probe = await this._probe(fromEnv);
91-
if (probe.ok) {
92-
return {
93-
found: true,
94-
command: fromEnv,
95-
version: probe.version,
96-
source: 'env',
97-
};
90+
const parsed = this._parseCommandString(fromEnv);
91+
if (parsed && parsed.length > 0) {
92+
// eslint-disable-next-line no-await-in-loop
93+
const probe = await this._probe(parsed);
94+
if (probe.ok) {
95+
return {
96+
found: true,
97+
command: fromEnv,
98+
version: probe.version,
99+
source: 'env',
100+
};
101+
}
98102
}
99103
}
100104

@@ -106,7 +110,9 @@ class WhisperInstaller {
106110
if (probe.ok) {
107111
return {
108112
found: true,
109-
command: candidate,
113+
// Join for display in the wizard UI; the array is preserved
114+
// separately if we ever need to re-execute.
115+
command: candidate.join(' '),
110116
version: probe.version,
111117
source: 'probe',
112118
};
@@ -206,41 +212,65 @@ class WhisperInstaller {
206212

207213
_candidateCommands() {
208214
if (this.platform === 'win32') {
209-
const localVenv = path.join(this.cwd, '.venv-whisper', 'Scripts', 'whisper.exe');
215+
const venvWhisper = path.join(this.cwd, '.venv-whisper', 'Scripts', 'whisper.exe');
216+
const venvPython = path.join(this.cwd, '.venv-whisper', 'Scripts', 'python.exe');
210217
return [
211-
'whisper',
212-
'whisper.exe',
213-
localVenv,
214-
path.join(this.cwd, '.venv-whisper', 'Scripts', 'python.exe') + ' -m whisper',
215-
'python -m whisper',
216-
'python3 -m whisper',
218+
// Plain `whisper` on PATH — respects PATHEXT, picks up `whisper.exe`
219+
['whisper'],
220+
['whisper.exe'],
221+
// Project-local venv direct path — works even when venv's Scripts
222+
// folder isn't on PATH. path.join keeps backslashes intact.
223+
[venvWhisper],
224+
// Run whisper as a Python module through the venv's python.exe.
225+
// Using array form (not string concat) so paths-with-spaces work.
226+
[venvPython, '-m', 'whisper'],
227+
// System Python via the launcher `py` (the canonical Windows
228+
// invocation), then bare `python`, then `python3`.
229+
['py', '-m', 'whisper'],
230+
['python', '-m', 'whisper'],
231+
['python3', '-m', 'whisper'],
217232
];
218233
}
219234
if (this.platform === 'darwin') {
220235
const homebrew = '/opt/homebrew/bin/whisper';
221236
const usrLocal = '/usr/local/bin/whisper';
237+
const venvWhisper = path.join(this.cwd, '.venv-whisper', 'bin', 'whisper');
222238
return [
223-
homebrew,
224-
usrLocal,
225-
'whisper',
226-
'python3 -m whisper',
227-
path.join(this.cwd, '.venv-whisper', 'bin', 'whisper'),
239+
[homebrew],
240+
[usrLocal],
241+
['whisper'],
242+
['python3', '-m', 'whisper'],
243+
[venvWhisper],
228244
];
229245
}
230246
const localVenvBin = path.join(this.cwd, '.venv-whisper', 'bin', 'whisper');
231247
return [
232-
'whisper',
233-
'/usr/local/bin/whisper',
234-
'/usr/bin/whisper',
235-
localVenvBin,
236-
'python3 -m whisper',
248+
['whisper'],
249+
['/usr/local/bin/whisper'],
250+
['/usr/bin/whisper'],
251+
[localVenvBin],
252+
['python3', '-m', 'whisper'],
237253
];
238254
}
239255

240-
async _probe(command) {
241-
const parts = command.match(/(?:[^\s"]+|"[^"]*")+/g) || [command];
242-
const cmd = parts[0].replace(/^"|"$/g, '');
243-
const args = [...parts.slice(1).map((a) => a.replace(/^"|"$/g, '')), '--help'];
256+
/**
257+
* Parse a user-supplied command string (e.g. from WHISPER_COMMAND env)
258+
* into a `[cmd, ...args]` tuple. Respects double-quoted segments so
259+
* paths-with-spaces survive intact.
260+
*/
261+
_parseCommandString(cmdString) {
262+
if (!cmdString) return null;
263+
const trimmed = String(cmdString).trim();
264+
if (!trimmed) return null;
265+
const parts = trimmed.match(/(?:[^\s"]+|"[^"]*")+/g) || [trimmed];
266+
return parts.map((p) => p.replace(/^"|"$/g, ''));
267+
}
268+
269+
async _probe(candidate) {
270+
// Candidate is now always `[cmd, ...args]`. We append `--help` to
271+
// confirm the binary actually runs without surfacing a usage error.
272+
const cmd = candidate[0];
273+
const args = [...candidate.slice(1), '--help'];
244274
const r = await this.runExec(cmd, args);
245275
if (!r.ok) return { ok: false };
246276
const version = this._extractVersion(r.stdout + r.stderr);
@@ -281,8 +311,11 @@ class WhisperInstaller {
281311
}
282312

283313
_detectPython() {
314+
// On Windows, `py` (the Python Launcher) is almost always on PATH
315+
// when Python is installed — `python` and `python3` are optional.
316+
// On macOS/Linux, prefer `python3` since `python` may point at Py2.
284317
const candidates = this.platform === 'win32'
285-
? ['python', 'py', 'python3']
318+
? ['py', 'python', 'python3']
286319
: ['python3', 'python'];
287320
// Sync probe — just check existence. We don't have an async probe
288321
// here without breaking the install flow; fall back to first candidate.

0 commit comments

Comments
 (0)