fix: ensure macOS mpv window helper is built and bundled correctly (#93)

This commit is contained in:
2026-05-27 13:34:51 -07:00
committed by GitHub
parent 29cb8c7fb4
commit f033f87329
7 changed files with 145 additions and 5 deletions
+55
View File
@@ -1,7 +1,24 @@
const fs = require('node:fs/promises');
const fsSync = require('node:fs');
const path = require('node:path');
const LINUX_FFMPEG_LIBRARY = 'libffmpeg.so';
const MACOS_WINDOW_HELPER = 'get-mpv-window-macos';
const DEFAULT_MACOS_APP_BUNDLE = 'SubMiner.app';
function resolveMacOSAppBundlePath(context) {
if (context.appOutDir.endsWith('.app')) {
return context.appOutDir;
}
const productFilename = context.packager?.appInfo?.productFilename;
const bundleName =
typeof productFilename === 'string' && productFilename.trim()
? `${productFilename.trim()}.app`
: DEFAULT_MACOS_APP_BUNDLE;
return path.join(context.appOutDir, bundleName);
}
async function stageLinuxAppImageSharedLibrary(
context,
@@ -35,12 +52,50 @@ async function stageLinuxAppImageSharedLibrary(
return true;
}
async function verifyMacOSWindowHelper(
context,
deps = {
access: (filePath, mode) => fs.access(filePath, mode),
},
) {
if (context.electronPlatformName !== 'darwin') return false;
const appBundlePath = resolveMacOSAppBundlePath(context);
const helperPath = path.join(
appBundlePath,
'Contents',
'Resources',
'scripts',
MACOS_WINDOW_HELPER,
);
try {
await deps.access(helperPath, fsSync.constants.X_OK);
} catch (error) {
if (error && typeof error === 'object' && error.code === 'ENOENT') {
throw new Error(
`macOS packaging requires ${MACOS_WINDOW_HELPER} at ${helperPath}. Run bun run build:assets on macOS with swiftc available before packaging.`,
);
}
if (error && typeof error === 'object' && error.code === 'EACCES') {
throw new Error(`macOS window helper is not executable: ${helperPath}`);
}
throw error;
}
return true;
}
async function afterPack(context) {
await stageLinuxAppImageSharedLibrary(context);
await verifyMacOSWindowHelper(context);
}
module.exports = {
LINUX_FFMPEG_LIBRARY,
MACOS_WINDOW_HELPER,
resolveMacOSAppBundlePath,
stageLinuxAppImageSharedLibrary,
verifyMacOSWindowHelper,
default: afterPack,
};
@@ -6,15 +6,22 @@ import test from 'node:test';
const {
LINUX_FFMPEG_LIBRARY,
MACOS_WINDOW_HELPER,
default: afterPack,
stageLinuxAppImageSharedLibrary,
verifyMacOSWindowHelper,
} = require('./electron-builder-after-pack.cjs') as {
LINUX_FFMPEG_LIBRARY: string;
MACOS_WINDOW_HELPER: string;
default: (context: { appOutDir: string; electronPlatformName: string }) => Promise<void>;
stageLinuxAppImageSharedLibrary: (context: {
appOutDir: string;
electronPlatformName: string;
}) => Promise<boolean>;
verifyMacOSWindowHelper: (context: {
appOutDir: string;
electronPlatformName: string;
}) => Promise<boolean>;
};
function createWorkspace(name: string): string {
@@ -65,6 +72,53 @@ test('stageLinuxAppImageSharedLibrary skips non-Linux packaging contexts', async
}
});
test('verifyMacOSWindowHelper accepts packaged macOS helper binary', async () => {
const workspace = createWorkspace('subminer-after-pack-macos-helper');
const appOutDir = path.join(workspace, 'SubMiner-darwin-arm64');
const helperPath = path.join(
appOutDir,
'SubMiner.app',
'Contents',
'Resources',
'scripts',
MACOS_WINDOW_HELPER,
);
fs.mkdirSync(path.dirname(helperPath), { recursive: true });
fs.writeFileSync(helperPath, 'compiled helper', 'utf8');
fs.chmodSync(helperPath, 0o755);
try {
const verified = await verifyMacOSWindowHelper({
appOutDir,
electronPlatformName: 'darwin',
});
assert.equal(verified, true);
} finally {
fs.rmSync(workspace, { recursive: true, force: true });
}
});
test('verifyMacOSWindowHelper throws when macOS helper binary is missing', async () => {
const workspace = createWorkspace('subminer-after-pack-macos-helper-missing');
const appOutDir = path.join(workspace, 'SubMiner-darwin-arm64');
fs.mkdirSync(path.join(appOutDir, 'SubMiner.app', 'Contents', 'Resources'), { recursive: true });
try {
await assert.rejects(
verifyMacOSWindowHelper({
appOutDir,
electronPlatformName: 'darwin',
}),
/macOS packaging requires get-mpv-window-macos/,
);
} finally {
fs.rmSync(workspace, { recursive: true, force: true });
}
});
test('stageLinuxAppImageSharedLibrary throws when Linux packaging is missing libffmpeg.so', async () => {
const workspace = createWorkspace('subminer-after-pack-missing-library');
const appOutDir = path.join(workspace, 'SubMiner-linux-x64');
+2
View File
@@ -63,6 +63,8 @@ function buildMacosHelper() {
return;
}
ensureDir(scriptsOutputDir);
try {
execFileSync('swiftc', ['-O', macosHelperSourcePath, '-o', macosHelperBinaryPath], {
stdio: 'inherit',
+20
View File
@@ -0,0 +1,20 @@
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
const source = readFileSync('scripts/prepare-build-assets.mjs', 'utf8');
test('macOS helper build creates dist scripts directory before swiftc output', () => {
const buildFunctionIndex = source.indexOf('function buildMacosHelper()');
assert.notEqual(buildFunctionIndex, -1);
const swiftcIndex = source.indexOf("execFileSync('swiftc'", buildFunctionIndex);
assert.notEqual(swiftcIndex, -1);
const ensureDirIndex = source.lastIndexOf('ensureDir(scriptsOutputDir)', swiftcIndex);
assert.ok(
ensureDirIndex > buildFunctionIndex,
'buildMacosHelper must create dist/scripts before swiftc writes the helper binary',
);
});