Files
Qortal-Hub/electron/scripts/run-electron-package.mjs
2026-06-07 12:10:20 +03:00

196 lines
5.5 KiB
JavaScript

#!/usr/bin/env node
/**
* Build/package Qortal Hub with a platform-native frozen Reticulum binary.
*
* Why this wrapper exists:
* - `rnsd` must be frozen on the same OS/arch as the packaged app.
* - Cross-packaging a Windows/macOS app from Linux would otherwise embed the wrong
* Reticulum binary and break end-user startup.
*
* Use the matching host OS for each target, or use the GitHub Actions matrix workflow.
*/
import { spawnSync } from 'child_process';
const mode = process.argv[2];
const configs = {
pack: {
description: 'current host unpacked app',
allowed: [{ platform: 'linux' }, { platform: 'darwin' }, { platform: 'win32' }],
builderArgs: ['build', '--dir', '-c', './electron-builder.config.json'],
},
local: {
description: 'current host packaged app',
allowed: [{ platform: 'linux' }, { platform: 'darwin' }, { platform: 'win32' }],
builderArgs: ['build', '-c', './electron-builder.config.json', '--publish=never'],
},
lin: {
description: 'Linux x64 package',
allowed: [{ platform: 'linux', arch: 'x64' }],
builderArgs: ['build', '-c', './electron-builder.config.lin.json', '--publish=never', '-l'],
},
arm: {
description: 'Linux arm64 package',
allowed: [{ platform: 'linux', arch: 'arm64' }],
builderArgs: [
'build',
'-c',
'./electron-builder.config.arm.json',
'--publish=never',
'--linux',
'--arm64',
],
},
win: {
description: 'Windows package',
allowed: [{ platform: 'win32' }],
builderArgs: ['build', '-c', './electron-builder.config.win.json', '--publish=never', '-w'],
},
mac: {
description: 'macOS package',
allowed: [{ platform: 'darwin' }],
builderArgs: [
'build',
'-c',
'./electron-builder.config.mac.json',
'--publish=never',
'--mac',
'dmg',
'pkg',
'zip',
],
},
'mac-universal': {
description: 'macOS universal package',
allowed: [{ platform: 'darwin' }],
universal: true,
builderArgs: [
'build',
'-c',
'./electron-builder.config.mac.json',
'--publish=never',
'--mac',
'dmg',
'pkg',
'zip',
'--universal',
],
},
'mac-universal-unsigned': {
description: 'unsigned macOS universal package',
allowed: [{ platform: 'darwin' }],
universal: true,
builderEnv: {
CSC_IDENTITY_AUTO_DISCOVERY: 'false',
},
builderArgs: [
'build',
'-c',
'./electron-builder.config.mac-unsigned.json',
'--publish=never',
'--mac',
'dmg',
'pkg',
'zip',
'--universal',
],
},
};
function fail(message) {
console.error(message);
process.exit(1);
}
if (!mode || mode === 'all') {
fail(
[
'Cross-platform packaging is no longer safe through a single local command,',
'because Reticulum must be bundled as a native binary per OS/arch.',
'',
'Use one of:',
' npm run electron:make-lin # on Linux x64',
' npm run electron:make-arm # on Linux arm64',
' npm run electron:make-win # on Windows',
' npm run electron:make-mac # on macOS',
' npm run electron:make-mac-universal # on macOS, universal app',
' npm run electron:make-mac-universal-unsigned # on macOS, unsigned universal app',
'',
'Or use the GitHub Actions workflow to build the supported OS matrix.',
].join('\n')
);
}
const cfg = configs[mode];
if (!cfg) {
fail(`Unknown packaging mode: ${mode}`);
}
const supportedHere = cfg.allowed.some(
(rule) =>
process.platform === rule.platform &&
(rule.arch == null || process.arch === rule.arch)
);
if (!supportedHere) {
const here = `${process.platform}-${process.arch}`;
const expected = cfg.allowed
.map((rule) => `${rule.platform}${rule.arch ? `-${rule.arch}` : ''}`)
.join(' or ');
fail(
`Cannot build ${cfg.description} from ${here}. Build it on ${expected}, so the bundled Reticulum binary matches the packaged app.`
);
}
function run(cmd, args) {
// Windows: npm.cmd / electron-builder.cmd are cmd shims; spawn without a shell can
// fail with EINVAL. Running through the default shell matches how npm invokes scripts.
const res = spawnSync(cmd, args, {
stdio: 'inherit',
env: process.env,
windowsHide: true,
...(process.platform === 'win32' ? { shell: true } : {}),
});
if (res.error) {
console.error(`Failed to run ${cmd}:`, res.error.message);
process.exit(1);
}
if (res.status !== 0) {
process.exit(res.status ?? 1);
}
}
function runWithEnv(cmd, args, envExtra) {
const res = spawnSync(cmd, args, {
stdio: 'inherit',
env: { ...process.env, ...envExtra },
windowsHide: true,
...(process.platform === 'win32' ? { shell: true } : {}),
});
if (res.error) {
console.error(`Failed to run ${cmd}:`, res.error.message);
process.exit(1);
}
if (res.status !== 0) {
process.exit(res.status ?? 1);
}
}
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
run(npmCmd, ['run', 'build']);
if (cfg.universal) {
runWithEnv(npmCmd, ['run', 'bundle:reticulum'], {
QORTAL_RETICULUM_OUTPUT_DIR: 'resources/reticulum/darwin-arm64',
});
runWithEnv(npmCmd, ['run', 'bundle:reticulum'], {
QORTAL_RETICULUM_BUILD_ARCH: 'x64',
QORTAL_RETICULUM_OUTPUT_DIR: 'resources/reticulum/darwin-x64',
});
} else {
run(npmCmd, ['run', 'bundle:reticulum']);
}
const electronBuilderCmd = process.platform === 'win32' ? 'electron-builder.cmd' : 'electron-builder';
runWithEnv(electronBuilderCmd, cfg.builderArgs, cfg.builderEnv ?? {});