Skip to content

Commit 75c989e

Browse files
committed
fix: bundle sherpa-onnx native packages in packaged app
sherpa-onnx-node native addon was not included in the dist build because it lives in pnpm's virtual store as an optionalDependency. This caused local TTS (Kitten) and STT (Parakeet) to fail in the packaged app. - Add sherpa-onnx platform packages as extraResources in electron-builder - Update getSherpaLibraryPath() in kitten-tts.ts and parakeet-stt.ts to check process.resourcesPath first when app is packaged - Wrap builder config sherpa logic in try-catch for electron-vite compat
1 parent 3b0f228 commit 75c989e

File tree

3 files changed

+103
-6
lines changed

3 files changed

+103
-6
lines changed

apps/desktop/electron-builder.config.cjs

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,87 @@
11
// @ts-check
22

3+
// Resolve sherpa-onnx native package paths for bundling into the packaged app.
4+
// sherpa-onnx is an optionalDependency that pnpm hoists to the root .pnpm store,
5+
// so electron-builder doesn't include it automatically. We find and bundle it via extraResources.
6+
//
7+
// This block is wrapped in try-catch because electron-vite also loads this config file
8+
// (to read appId/productName) and converts require() calls to ESM imports that fail
9+
// for Node built-ins. When that happens, we just return empty arrays (no sherpa bundling needed
10+
// during the vite build step — only during electron-builder packaging).
11+
let _sherpaResources = {};
12+
try {
13+
const _path = require('path');
14+
const _fs = require('fs');
15+
16+
/**
17+
* @param {string} platform
18+
* @param {string} arch
19+
* @returns {string | null}
20+
*/
21+
function _findSherpaPackagePath(platform, arch) {
22+
const platformPackage = `sherpa-onnx-${platform}-${arch}`;
23+
24+
// Check pnpm virtual store (monorepo root)
25+
const rootPnpmBase = _path.join(__dirname, '..', '..', 'node_modules', '.pnpm');
26+
if (_fs.existsSync(rootPnpmBase)) {
27+
try {
28+
const dirs = _fs.readdirSync(rootPnpmBase);
29+
const platformDir = dirs.find(d => d.startsWith(`${platformPackage}@`));
30+
if (platformDir) {
31+
const libPath = _path.join(rootPnpmBase, platformDir, 'node_modules', platformPackage);
32+
if (_fs.existsSync(libPath)) return libPath;
33+
}
34+
} catch { /* ignore */ }
35+
}
36+
37+
// Check local pnpm store
38+
const localPnpmBase = _path.join(__dirname, 'node_modules', '.pnpm');
39+
if (_fs.existsSync(localPnpmBase)) {
40+
try {
41+
const dirs = _fs.readdirSync(localPnpmBase);
42+
const platformDir = dirs.find(d => d.startsWith(`${platformPackage}@`));
43+
if (platformDir) {
44+
const libPath = _path.join(localPnpmBase, platformDir, 'node_modules', platformPackage);
45+
if (_fs.existsSync(libPath)) return libPath;
46+
}
47+
} catch { /* ignore */ }
48+
}
49+
50+
// Check standard node_modules
51+
const standardPath = _path.join(__dirname, 'node_modules', platformPackage);
52+
if (_fs.existsSync(standardPath)) return standardPath;
53+
54+
const rootStandardPath = _path.join(__dirname, '..', '..', 'node_modules', platformPackage);
55+
if (_fs.existsSync(rootStandardPath)) return rootStandardPath;
56+
57+
console.warn(`[electron-builder] Could not find ${platformPackage} - local TTS/STT may not work in packaged app`);
58+
return null;
59+
}
60+
61+
/**
62+
* @param {string} platform
63+
* @param {string} arch
64+
* @returns {Array<{from: string, to: string, filter: string[]}>}
65+
*/
66+
function _sherpaExtraResources(platform, arch) {
67+
const sherpaPath = _findSherpaPackagePath(platform, arch);
68+
if (!sherpaPath) return [];
69+
console.log(`[electron-builder] Bundling sherpa-onnx from: ${sherpaPath}`);
70+
return [{ from: sherpaPath, to: `sherpa-onnx-${platform}-${arch}`, filter: ['**/*'] }];
71+
}
72+
73+
_sherpaResources = {
74+
macArm64: _sherpaExtraResources('darwin', 'arm64'),
75+
macX64: _sherpaExtraResources('darwin', 'x64'),
76+
winX64: _sherpaExtraResources('win', 'x64'),
77+
linuxX64: _sherpaExtraResources('linux', 'x64'),
78+
linuxArm64: _sherpaExtraResources('linux', 'arm64'),
79+
};
80+
} catch {
81+
// Running inside electron-vite ESM context — sherpa bundling not needed here
82+
_sherpaResources = { macArm64: [], macX64: [], winX64: [], linuxX64: [], linuxArm64: [] };
83+
}
84+
385
/** @type {import('electron-builder').Configuration} */
486
module.exports = {
587
appId: "app.speakmcp",
@@ -59,7 +141,9 @@ module.exports = {
59141
from: "resources/bundled-skills",
60142
to: "bundled-skills",
61143
filter: ["**/*"]
62-
}
144+
},
145+
// sherpa-onnx native libraries for local TTS/STT
146+
..._sherpaResources.winX64,
63147
]
64148
},
65149
nsis: {
@@ -85,7 +169,10 @@ module.exports = {
85169
from: "resources/bundled-skills",
86170
to: "bundled-skills",
87171
filter: ["**/*"]
88-
}
172+
},
173+
// sherpa-onnx native libraries for local TTS/STT
174+
..._sherpaResources.macArm64,
175+
..._sherpaResources.macX64,
89176
],
90177
artifactName: "${productName}-${version}-${arch}.${ext}",
91178
entitlementsInherit: "build/entitlements.mac.plist",
@@ -244,7 +331,10 @@ module.exports = {
244331
from: "resources/bundled-skills",
245332
to: "bundled-skills",
246333
filter: ["**/*"]
247-
}
334+
},
335+
// sherpa-onnx native libraries for local TTS/STT
336+
..._sherpaResources.linuxX64,
337+
..._sherpaResources.linuxArm64,
248338
]
249339
},
250340
deb: {

apps/desktop/src/main/kitten-tts.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,12 @@ function getSherpaLibraryPath(): string | null {
138138

139139
const possiblePaths: string[] = []
140140

141-
// For packaged app, check resources directory
141+
// For packaged app, check extraResources directory first (bundled by electron-builder)
142142
if (app.isPackaged) {
143+
possiblePaths.push(
144+
path.join(process.resourcesPath, platformPackage)
145+
)
146+
// Legacy: also check node_modules in case it was bundled there
143147
possiblePaths.push(
144148
path.join(process.resourcesPath, "app", "node_modules", platformPackage)
145149
)

apps/desktop/src/main/parakeet-stt.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,12 @@ function getSherpaLibraryPath(): string | null {
126126

127127
const possiblePaths: string[] = []
128128

129-
// For packaged app, check resources directory
129+
// For packaged app, check extraResources directory first (bundled by electron-builder)
130130
if (app.isPackaged) {
131-
// In packaged app, node_modules is in resources/app/node_modules
131+
possiblePaths.push(
132+
path.join(process.resourcesPath, platformPackage)
133+
)
134+
// Legacy: also check node_modules in case it was bundled there
132135
possiblePaths.push(
133136
path.join(process.resourcesPath, "app", "node_modules", platformPackage)
134137
)

0 commit comments

Comments
 (0)