Files
fluxer-desktop-patched/fluxer_desktop/electron-builder.config.cjs
T
brenden 682afacd30 Add native self-hosted instance connection to fluxer_desktop
Trimmed monorepo checkout (fluxer_desktop + packages/voice_engine_v2 +
tools/ci) with a "Connect to a Different Server" menu item and popout
that lets the desktop app switch to any self-hosted Fluxer instance,
plus fixes for well-known discovery on single-domain self-hosted
deployments and a false-positive ERR_ABORTED on same-origin client
redirects during the switch. Defaults to chat.fluxr.chat and uses an
isolated userData directory from the official build.
2026-07-01 18:22:43 -04:00

1310 lines
46 KiB
JavaScript

// SPDX-License-Identifier: AGPL-3.0-or-later
const isCanary = process.env.BUILD_CHANNEL === 'canary';
const {execFile} = require('node:child_process');
const fs = require('node:fs/promises');
const os = require('node:os');
const path = require('node:path');
const {promisify} = require('node:util');
const execFileAsync = promisify(execFile);
const productName = isCanary ? 'Fluxer Canary' : 'Fluxer';
const appId = isCanary ? 'app.fluxer.canary' : 'app.fluxer';
const iconDir = isCanary ? 'icons-canary' : 'icons-stable';
const packageName = isCanary ? 'fluxer_desktop_canary' : 'fluxer_desktop';
const linuxPackageName = isCanary ? 'fluxer-canary' : 'fluxer';
const desktopBuildVariant = process.env.FLUXER_DESKTOP_BUILD_VARIANT || process.env.DESKTOP_VARIANT || 'default';
const windowsGameCaptureModuleEnabled =
desktopBuildVariant === 'windows-game-capture' || process.env.FLUXER_WINDOWS_GAME_CAPTURE_MODULE_ENABLED === 'true';
const linuxDesktopActionIds = ['open-settings', 'new-dm'];
const linuxDesktopActionList = `${linuxDesktopActionIds.join(';')};`;
const rpmBuildIdFilePrefix = '/usr/lib/.build-id';
const rpmBuildIdLinkFpmArgs = [
'--rpm-rpmbuild-define',
'_build_id_links none',
'--rpm-rpmbuild-define',
'_missing_build_ids_terminate_build 0',
];
const macOSMinimumSystemVersion = '12.0';
const isLinuxBuild = process.argv.includes('--linux');
const isMacBuild = process.argv.includes('--mac');
const isWindowsBuild = process.argv.includes('--win');
const targetPlatform = isLinuxBuild ? 'linux' : isMacBuild ? 'darwin' : isWindowsBuild ? 'win32' : process.platform;
const metadataName = isLinuxBuild ? linuxPackageName : packageName;
const provisioningProfile = isCanary
? 'build_resources/profiles/Fluxer_Canary.provisionprofile'
: 'build_resources/profiles/Fluxer.provisionprofile';
const supportedTargetArchs = ['x64', 'arm64'];
const electronArch = process.env.ELECTRON_ARCH;
const cliTargetArch = supportedTargetArchs.find((arch) => process.argv.includes(`--${arch}`)) || null;
const targetNativeArch = electronArch || cliTargetArch;
if (electronArch && !supportedTargetArchs.includes(electronArch)) {
throw new Error(`Unsupported ELECTRON_ARCH: ${electronArch}`);
}
const targetArchs = electronArch ? [electronArch] : supportedTargetArchs;
const winTargets = [
{
target: 'dir',
arch: targetArchs,
},
];
const fluxerNativePackages = [
'@fluxer/mac-app-audio',
'@fluxer/mac-clipboard',
'@fluxer/mac-screen-capture',
'@fluxer/mac-sysctl',
'@fluxer/mac-tcc',
'@fluxer/macos-input-hook',
'@fluxer/win-process-loopback',
...(windowsGameCaptureModuleEnabled ? ['@fluxer/win-game-capture'] : []),
'@fluxer/win-clipboard',
'@fluxer/win-shell',
'@fluxer/win-toast',
'@fluxer/windows-input-hook',
'@fluxer/linux-audio-capture',
'@fluxer/linux-portals',
'@fluxer/linux-screen-capture',
'@fluxer/linux-notifications',
'@fluxer/linux-evdev',
'@fluxer/linux-input-hook',
'@fluxer/system-hunspell',
'@fluxer/platform-info',
'@fluxer/webauthn',
'@fluxer/webrtc-sender',
];
const fluxerNativePackagesByPlatform = {
darwin: [
'@fluxer/mac-app-audio',
'@fluxer/mac-clipboard',
'@fluxer/mac-screen-capture',
'@fluxer/mac-sysctl',
'@fluxer/mac-tcc',
'@fluxer/macos-input-hook',
'@fluxer/platform-info',
'@fluxer/webauthn',
'@fluxer/webrtc-sender',
],
win32: [
'@fluxer/win-process-loopback',
...(windowsGameCaptureModuleEnabled ? ['@fluxer/win-game-capture'] : []),
'@fluxer/win-clipboard',
'@fluxer/win-shell',
'@fluxer/win-toast',
'@fluxer/windows-input-hook',
'@fluxer/platform-info',
'@fluxer/webauthn',
'@fluxer/webrtc-sender',
],
linux: [
'@fluxer/linux-audio-capture',
'@fluxer/linux-portals',
'@fluxer/linux-screen-capture',
'@fluxer/linux-notifications',
'@fluxer/linux-evdev',
'@fluxer/linux-input-hook',
'@fluxer/system-hunspell',
'@fluxer/platform-info',
'@fluxer/webauthn',
'@fluxer/webrtc-sender',
],
};
const velopackNativeFiles = [
'velopack_nodeffi_linux_arm64_gnu.node',
'velopack_nodeffi_linux_x64_gnu.node',
'velopack_nodeffi_osx.node',
'velopack_nodeffi_win_arm64_msvc.node',
'velopack_nodeffi_win_x64_msvc.node',
'velopack_nodeffi_win_x86_msvc.node',
];
const nativeRuntimeFilePatterns = [
'node_modules/@fluxer/mac-app-audio/package.json',
'node_modules/@fluxer/mac-app-audio/index.js',
'node_modules/@fluxer/mac-app-audio/loader-diagnostics.cjs',
'node_modules/@fluxer/mac-app-audio/*.node',
'node_modules/@fluxer/mac-screen-capture/package.json',
'node_modules/@fluxer/mac-screen-capture/index.js',
'node_modules/@fluxer/mac-screen-capture/loader-diagnostics.cjs',
'node_modules/@fluxer/mac-screen-capture/*.node',
'node_modules/@fluxer/mac-clipboard/package.json',
'node_modules/@fluxer/mac-clipboard/index.js',
'node_modules/@fluxer/mac-clipboard/loader-diagnostics.cjs',
'node_modules/@fluxer/mac-clipboard/*.node',
'node_modules/@fluxer/mac-sysctl/package.json',
'node_modules/@fluxer/mac-sysctl/index.js',
'node_modules/@fluxer/mac-sysctl/loader-diagnostics.cjs',
'node_modules/@fluxer/mac-sysctl/*.node',
'node_modules/@fluxer/mac-tcc/package.json',
'node_modules/@fluxer/mac-tcc/index.js',
'node_modules/@fluxer/mac-tcc/loader-diagnostics.cjs',
'node_modules/@fluxer/mac-tcc/*.node',
'node_modules/@fluxer/win-process-loopback/package.json',
'node_modules/@fluxer/win-process-loopback/index.js',
'node_modules/@fluxer/win-process-loopback/binding.js',
'node_modules/@fluxer/win-process-loopback/loader-diagnostics.cjs',
'node_modules/@fluxer/win-process-loopback/*.node',
...(windowsGameCaptureModuleEnabled
? [
'node_modules/@fluxer/win-game-capture/package.json',
'node_modules/@fluxer/win-game-capture/index.js',
'node_modules/@fluxer/win-game-capture/loader-diagnostics.cjs',
'node_modules/@fluxer/win-game-capture/*.node',
'node_modules/@fluxer/win-game-capture/*.dll',
'node_modules/@fluxer/win-game-capture/*.exe',
'node_modules/@fluxer/win-game-capture/compatibility.json',
'node_modules/@fluxer/win-game-capture/fluxer-vulkan-layer.*.json',
]
: []),
'node_modules/@fluxer/win-clipboard/package.json',
'node_modules/@fluxer/win-clipboard/index.js',
'node_modules/@fluxer/win-clipboard/loader-diagnostics.cjs',
'node_modules/@fluxer/win-clipboard/*.node',
'node_modules/@fluxer/win-shell/package.json',
'node_modules/@fluxer/win-shell/index.js',
'node_modules/@fluxer/win-shell/loader-diagnostics.cjs',
'node_modules/@fluxer/win-shell/*.node',
'node_modules/@fluxer/win-toast/package.json',
'node_modules/@fluxer/win-toast/index.js',
'node_modules/@fluxer/win-toast/loader-diagnostics.cjs',
'node_modules/@fluxer/win-toast/*.node',
'node_modules/@fluxer/linux-audio-capture/package.json',
'node_modules/@fluxer/linux-audio-capture/index.js',
'node_modules/@fluxer/linux-audio-capture/loader-diagnostics.cjs',
'node_modules/@fluxer/linux-audio-capture/*.node',
'node_modules/@fluxer/linux-portals/package.json',
'node_modules/@fluxer/linux-portals/index.js',
'node_modules/@fluxer/linux-portals/loader-diagnostics.cjs',
'node_modules/@fluxer/linux-portals/*.node',
'node_modules/@fluxer/linux-screen-capture/package.json',
'node_modules/@fluxer/linux-screen-capture/index.js',
'node_modules/@fluxer/linux-screen-capture/loader-diagnostics.cjs',
'node_modules/@fluxer/linux-screen-capture/*.node',
'node_modules/@fluxer/linux-screen-capture/THIRD_PARTY_OBS_VKCAPTURE.md',
'node_modules/@fluxer/linux-screen-capture/obs-vkcapture/**/*',
'node_modules/@fluxer/linux-notifications/package.json',
'node_modules/@fluxer/linux-notifications/index.js',
'node_modules/@fluxer/linux-notifications/loader-diagnostics.cjs',
'node_modules/@fluxer/linux-notifications/*.node',
'node_modules/@fluxer/linux-evdev/package.json',
'node_modules/@fluxer/linux-evdev/index.js',
'node_modules/@fluxer/linux-evdev/loader-diagnostics.cjs',
'node_modules/@fluxer/linux-evdev/*.node',
'node_modules/@fluxer/system-hunspell/package.json',
'node_modules/@fluxer/system-hunspell/index.js',
'node_modules/@fluxer/system-hunspell/loader-diagnostics.cjs',
'node_modules/@fluxer/system-hunspell/*.node',
'node_modules/@fluxer/macos-input-hook/package.json',
'node_modules/@fluxer/macos-input-hook/index.js',
'node_modules/@fluxer/macos-input-hook/loader-diagnostics.cjs',
'node_modules/@fluxer/macos-input-hook/*.node',
'node_modules/@fluxer/windows-input-hook/package.json',
'node_modules/@fluxer/windows-input-hook/index.js',
'node_modules/@fluxer/windows-input-hook/loader-diagnostics.cjs',
'node_modules/@fluxer/windows-input-hook/*.node',
'node_modules/@fluxer/linux-input-hook/package.json',
'node_modules/@fluxer/linux-input-hook/index.js',
'node_modules/@fluxer/linux-input-hook/loader-diagnostics.cjs',
'node_modules/@fluxer/linux-input-hook/*.node',
'node_modules/@fluxer/platform-info/package.json',
'node_modules/@fluxer/platform-info/index.js',
'node_modules/@fluxer/platform-info/loader-diagnostics.cjs',
'node_modules/@fluxer/platform-info/*.node',
'node_modules/@fluxer/webauthn/package.json',
'node_modules/@fluxer/webauthn/index.js',
'node_modules/@fluxer/webauthn/index.d.ts',
'node_modules/@fluxer/webauthn/loader-diagnostics.cjs',
'node_modules/@fluxer/webauthn/*.node',
'node_modules/@fluxer/webauthn/*.so*',
'node_modules/@fluxer/webrtc-sender/package.json',
'node_modules/@fluxer/webrtc-sender/index.js',
'node_modules/@fluxer/webrtc-sender/index.d.ts',
'node_modules/@fluxer/webrtc-sender/*.node',
'node_modules/.pnpm/@fluxer+*/node_modules/@fluxer/*/loader-diagnostics.cjs',
'node_modules/.pnpm/@fluxer+win-process-loopback@*/node_modules/@fluxer/win-process-loopback/*.node',
...(windowsGameCaptureModuleEnabled
? [
'node_modules/.pnpm/@fluxer+win-game-capture@*/node_modules/@fluxer/win-game-capture/*.node',
'node_modules/.pnpm/@fluxer+win-game-capture@*/node_modules/@fluxer/win-game-capture/*.dll',
'node_modules/.pnpm/@fluxer+win-game-capture@*/node_modules/@fluxer/win-game-capture/*.exe',
'node_modules/.pnpm/@fluxer+win-game-capture@*/node_modules/@fluxer/win-game-capture/compatibility.json',
'node_modules/.pnpm/@fluxer+win-game-capture@*/node_modules/@fluxer/win-game-capture/fluxer-vulkan-layer.*.json',
]
: []),
'node_modules/.pnpm/@fluxer+win-clipboard@*/node_modules/@fluxer/win-clipboard/*.node',
'node_modules/.pnpm/@fluxer+win-shell@*/node_modules/@fluxer/win-shell/*.node',
'node_modules/.pnpm/@fluxer+win-toast@*/node_modules/@fluxer/win-toast/*.node',
'node_modules/.pnpm/@fluxer+windows-input-hook@*/node_modules/@fluxer/windows-input-hook/*.node',
'node_modules/.pnpm/@fluxer+linux-audio-capture@*/node_modules/@fluxer/linux-audio-capture/*.node',
'node_modules/.pnpm/@fluxer+linux-portals@*/node_modules/@fluxer/linux-portals/*.node',
'node_modules/.pnpm/@fluxer+linux-screen-capture@*/node_modules/@fluxer/linux-screen-capture/*.node',
'node_modules/.pnpm/@fluxer+linux-screen-capture@*/node_modules/@fluxer/linux-screen-capture/THIRD_PARTY_OBS_VKCAPTURE.md',
'node_modules/.pnpm/@fluxer+linux-screen-capture@*/node_modules/@fluxer/linux-screen-capture/obs-vkcapture/**/*',
'node_modules/.pnpm/@fluxer+linux-notifications@*/node_modules/@fluxer/linux-notifications/*.node',
'node_modules/.pnpm/@fluxer+linux-evdev@*/node_modules/@fluxer/linux-evdev/*.node',
'node_modules/.pnpm/@fluxer+linux-input-hook@*/node_modules/@fluxer/linux-input-hook/*.node',
'node_modules/.pnpm/@fluxer+mac-app-audio@*/node_modules/@fluxer/mac-app-audio/*.node',
'node_modules/.pnpm/@fluxer+mac-screen-capture@*/node_modules/@fluxer/mac-screen-capture/*.node',
'node_modules/.pnpm/@fluxer+mac-clipboard@*/node_modules/@fluxer/mac-clipboard/*.node',
'node_modules/.pnpm/@fluxer+mac-sysctl@*/node_modules/@fluxer/mac-sysctl/*.node',
'node_modules/.pnpm/@fluxer+mac-tcc@*/node_modules/@fluxer/mac-tcc/*.node',
'node_modules/.pnpm/@fluxer+macos-input-hook@*/node_modules/@fluxer/macos-input-hook/*.node',
'node_modules/.pnpm/@fluxer+platform-info@*/node_modules/@fluxer/platform-info/*.node',
'node_modules/.pnpm/@fluxer+webauthn@*/node_modules/@fluxer/webauthn/*.node',
'node_modules/.pnpm/@fluxer+webauthn@*/node_modules/@fluxer/webauthn/*.so*',
'node_modules/.pnpm/@fluxer+webrtc-sender@*/node_modules/@fluxer/webrtc-sender/*.node',
];
const nativeBuildArtifactExcludes = [
'!node_modules/@fluxer/**/src/**/*',
'!node_modules/@fluxer/**/target/**/*',
'!node_modules/@fluxer/**/build/**/*',
'!node_modules/@fluxer/**/Cargo.toml',
'!node_modules/@fluxer/**/Cargo.lock',
'!node_modules/@fluxer/**/CMakeLists.txt',
'!node_modules/@fluxer/**/tsconfig.json',
'!node_modules/@fluxer/**/*.d.ts',
'!node_modules/@fluxer/**/*.d.ts.map',
'!node_modules/@fluxer/**/*.map',
'!node_modules/@fluxer/**/*.rs',
'!node_modules/@fluxer/**/*.swift',
];
const packagedRuntimeArtifactExcludes = [
'!dist/**/*.map',
'!node_modules/**/.cache/**/*',
'!node_modules/**/.github/**/*',
'!node_modules/**/.yarn/**/*',
'!node_modules/**/.yarnrc.yml',
'!node_modules/**/*.map',
'!node_modules/**/*.d.ts',
'!node_modules/**/*.d.ts.map',
'!node_modules/**/*.tsbuildinfo',
'!node_modules/**/tsconfig.json',
'!node_modules/**/tsconfig.*.json',
'!node_modules/**/README*',
'!node_modules/**/CHANGELOG*',
'!node_modules/hunspell-asm/src/**/*',
'!node_modules/hunspell-asm/dist/esm/**/*',
'!node_modules/hunspell-asm/dist/types/**/*',
'!node_modules/hunspell-asm/dist/cjs/lib/browser/**/*',
'!node_modules/emscripten-wasm-loader/src/**/*',
'!node_modules/emscripten-wasm-loader/dist/esm/**/*',
'!node_modules/emscripten-wasm-loader/dist/types/**/*',
];
const bundledDependencyExcludes = [
'!node_modules/@homebridge/dbus-native/**/*',
'!node_modules/@simplewebauthn/browser/**/*',
'!node_modules/duplexer/**/*',
'!node_modules/event-stream/**/*',
'!node_modules/from/**/*',
'!node_modules/hexy/**/*',
'!node_modules/long/**/*',
'!node_modules/map-stream/**/*',
'!node_modules/minimist/**/*',
'!node_modules/pause-stream/**/*',
'!node_modules/safe-buffer/**/*',
'!node_modules/sax/**/*',
'!node_modules/split/**/*',
'!node_modules/stream-combiner/**/*',
'!node_modules/through/**/*',
'!node_modules/xml2js/**/*',
'!node_modules/xmlbuilder/**/*',
];
const windowsGameCapturePackageExcludes = !windowsGameCaptureModuleEnabled
? ['!node_modules/@fluxer/win-game-capture/**/*', '!node_modules/.pnpm/@fluxer+win-game-capture@*/**/*']
: [];
const platformNativeRuntimeExcludes = platformNativeExcludes(targetPlatform, targetNativeArch);
const platformRuntimeDependencyExcludes =
targetPlatform === 'darwin'
? []
: ['!node_modules/github-url-to-object/**/*', '!node_modules/ms/**/*', '!node_modules/update-electron-app/**/*'];
const linuxDesktopEntry = {
Name: productName,
GenericName: 'Instant Messenger',
Comment: isCanary ? 'Canary build of Fluxer' : 'Instant messaging and VoIP',
Keywords: 'chat;im;messaging;messenger;voip;voice;video;call;',
Categories: 'Network;InstantMessaging;Chat;',
StartupWMClass: linuxPackageName,
StartupNotify: 'true',
SingleMainWindow: 'true',
MimeType: 'x-scheme-handler/fluxer;',
'X-GNOME-UsesNotifications': 'true',
};
const linuxDesktopEntryWithActions = {
...linuxDesktopEntry,
Actions: linuxDesktopActionList,
};
const linuxInstalledExecPath = quoteDesktopExecArg(path.posix.join('/opt', productName, linuxPackageName));
const linuxDesktopActions = {
'open-settings': {
Name: 'Open Settings',
Exec: buildLinuxDesktopTaskExec('open-settings'),
},
'new-dm': {
Name: 'New Direct Message',
Exec: buildLinuxDesktopTaskExec('new-dm'),
},
};
function velopackNativeFile(platform, arch) {
if (platform === 'darwin') return 'velopack_nodeffi_osx.node';
if (!arch) return null;
if (platform === 'win32') return `velopack_nodeffi_win_${arch}_msvc.node`;
if (platform === 'linux') return `velopack_nodeffi_linux_${arch}_gnu.node`;
return null;
}
function platformNativeExcludes(platform, arch) {
if (!arch) return [];
const keepFluxerPackages = new Set(fluxerNativePackagesByPlatform[platform] ?? []);
const keepVelopackNativeFile = velopackNativeFile(platform, arch);
return [
...fluxerNativePackages
.filter((packageName) => !keepFluxerPackages.has(packageName))
.map((packageName) => `!node_modules/${packageName}/**/*`),
...(platform === 'win32'
? velopackNativeFiles
.filter((fileName) => fileName !== keepVelopackNativeFile)
.map((fileName) => `!node_modules/velopack/lib/native/${fileName}`)
: ['!node_modules/velopack/**/*']),
];
}
function quoteDesktopExecArg(value) {
return `"${value.replace(/(["`$\\])/g, '\\$1')}"`;
}
function buildLinuxDesktopTaskExec(taskId) {
return `${linuxInstalledExecPath} --fluxer-task=${taskId} %U`;
}
function normalizeArch(arch) {
if (arch === 'x64' || arch === 'arm64') {
return arch;
}
if (arch === 1) return 'x64';
if (arch === 3) return 'arm64';
return electronArch || process.arch;
}
function platformTag(platform, arch) {
if (platform === 'darwin') return `darwin-${arch}`;
if (platform === 'win32') return `win32-${arch}-msvc`;
if (platform === 'linux') return `linux-${arch}-gnu`;
return null;
}
function addWindowsGameCaptureArtifacts(artifacts, tag, arch) {
if (!windowsGameCaptureModuleEnabled) return;
const add = (relativePath) => {
artifacts.push({
packageName: '@fluxer/win-game-capture',
relativePath,
});
};
add(`win-game-capture.${tag}.node`);
add(`fluxer-game-hook.${tag}.dll`);
add(`fluxer-inject-helper.${tag}.exe`);
add(`fluxer-vulkan-layer.${tag}.dll`);
add(`fluxer-vulkan-layer.${tag}.json`);
if (arch === 'x64') {
add('fluxer-game-hook.win32-ia32-msvc.dll');
add('fluxer-inject-helper.win32-ia32-msvc.exe');
}
}
function expectedNativeRuntimeArtifacts(platform, arch) {
const tag = platformTag(platform, arch);
if (!tag) return [];
const artifacts = [];
artifacts.push({
packageName: '@fluxer/webauthn',
relativePath: `webauthn.${tag}.node`,
});
artifacts.push({
packageName: '@fluxer/webrtc-sender',
relativePath: `webrtc-sender.${tag}.node`,
});
if (platform === 'darwin') {
artifacts.push({
packageName: '@fluxer/mac-app-audio',
relativePath: `mac-app-audio.darwin-${arch}.node`,
});
artifacts.push({
packageName: '@fluxer/mac-screen-capture',
relativePath: `mac-screen-capture.darwin-${arch}.node`,
});
artifacts.push({
packageName: '@fluxer/mac-clipboard',
relativePath: `mac-clipboard.darwin-${arch}.node`,
});
artifacts.push({
packageName: '@fluxer/mac-sysctl',
relativePath: `mac-sysctl.darwin-${arch}.node`,
});
artifacts.push({
packageName: '@fluxer/mac-tcc',
relativePath: `mac-tcc.darwin-${arch}.node`,
});
artifacts.push({
packageName: '@fluxer/macos-input-hook',
relativePath: `macos-input-hook.darwin-${arch}.node`,
});
artifacts.push({
packageName: '@fluxer/platform-info',
relativePath: `platform-info.${tag}.node`,
});
} else if (platform === 'win32') {
artifacts.push({
packageName: '@fluxer/win-process-loopback',
relativePath: `win-process-loopback.${tag}.node`,
});
addWindowsGameCaptureArtifacts(artifacts, tag, arch);
artifacts.push({
packageName: '@fluxer/win-clipboard',
relativePath: `win-clipboard.${tag}.node`,
});
artifacts.push({
packageName: '@fluxer/win-shell',
relativePath: `win-shell.${tag}.node`,
});
artifacts.push({
packageName: '@fluxer/win-toast',
relativePath: `win-toast.${tag}.node`,
});
artifacts.push({
packageName: '@fluxer/windows-input-hook',
relativePath: `windows-input-hook.${tag}.node`,
});
artifacts.push({
packageName: '@fluxer/platform-info',
relativePath: `platform-info.${tag}.node`,
});
} else if (platform === 'linux') {
artifacts.push({
packageName: '@fluxer/linux-audio-capture',
relativePath: `linux-audio-capture.${tag}.node`,
});
artifacts.push({
packageName: '@fluxer/linux-portals',
relativePath: `linux-portals.${tag}.node`,
});
artifacts.push({
packageName: '@fluxer/linux-screen-capture',
relativePath: `linux-screen-capture.${tag}.node`,
});
artifacts.push({
packageName: '@fluxer/linux-notifications',
relativePath: `linux-notifications.${tag}.node`,
});
artifacts.push({
packageName: '@fluxer/linux-evdev',
relativePath: `linux-evdev.${tag}.node`,
});
artifacts.push({
packageName: '@fluxer/system-hunspell',
relativePath: `system-hunspell.${tag}.node`,
});
artifacts.push({
packageName: '@fluxer/linux-input-hook',
relativePath: `linux-input-hook.${tag}.node`,
});
artifacts.push({
packageName: '@fluxer/platform-info',
relativePath: `platform-info.${tag}.node`,
});
}
return artifacts;
}
function isLinuxSharedLibraryArtifact(fileName) {
return /\.so(?:\.|$)/.test(fileName);
}
async function expectedNativeRuntimeArtifactsForAppDir(platform, arch, appDir) {
const artifacts = expectedNativeRuntimeArtifacts(platform, arch);
if (platform !== 'linux') return artifacts;
const webAuthnRoot = path.join(appDir, 'node_modules', '@fluxer', 'webauthn');
const linuxWebAuthnRuntimeLibraries = new Set(['libfido2.so.1']);
try {
for (const entry of await fs.readdir(webAuthnRoot, {withFileTypes: true})) {
if (entry.isFile() && isLinuxSharedLibraryArtifact(entry.name)) {
linuxWebAuthnRuntimeLibraries.add(entry.name);
}
}
} catch (error) {
if (!error || error.code !== 'ENOENT') throw error;
}
for (const libraryName of [...linuxWebAuthnRuntimeLibraries].sort()) {
artifacts.push({
packageName: '@fluxer/webauthn',
relativePath: libraryName,
});
}
return artifacts;
}
function packagePathParts(packageName) {
const parts = packageName.split('/');
if (parts.length === 2 && parts[0].startsWith('@')) {
return parts;
}
return [packageName];
}
function isNonEmptyString(value) {
return typeof value === 'string' && value.length > 0;
}
function resolveAppDir(context) {
const candidates = [
context.appDir,
context.packager?.info?.appDir,
context.packager?.appDir,
context.packager?.projectDir,
context.packager?.info?.projectDir,
process.cwd(),
];
const appDir = candidates.find(isNonEmptyString);
if (!appDir) {
throw new Error('Unable to resolve electron-builder app directory for native artifact verification.');
}
return appDir;
}
async function fileExists(filePath) {
return fs
.stat(filePath)
.then((stat) => stat.isFile())
.catch((error) => {
if (error && error.code === 'ENOENT') {
return false;
}
throw error;
});
}
function expectedDarwinMachOArch(arch) {
if (arch === 'x64') return 'x86_64';
if (arch === 'arm64') return 'arm64';
return null;
}
async function darwinMachOArchitectures(filePath) {
const {stdout} = await execFileAsync('lipo', ['-archs', filePath]);
return stdout.trim().split(/\s+/).filter(Boolean);
}
async function darwinMachOFileTypes(filePath) {
const {stdout} = await execFileAsync('otool', ['-hv', filePath]);
const knownFileTypes = new Set(['OBJECT', 'EXECUTE', 'FVMLIB', 'CORE', 'PRELOAD', 'DYLIB', 'DYLINKER', 'BUNDLE']);
return stdout
.split(/\r?\n/)
.flatMap((line) => line.trim().split(/\s+/))
.filter((token) => knownFileTypes.has(token));
}
async function darwinMachOLoadCommands(filePath) {
const {stdout} = await execFileAsync('otool', ['-l', filePath]);
return stdout
.split(/\r?\n/)
.map((line) => line.trim().match(/^cmd\s+(\S+)$/)?.[1])
.filter(Boolean);
}
async function verifyDarwinNativeArchitectures(platform, arch, entries, stage) {
if (platform !== 'darwin') return;
const expectedArch = expectedDarwinMachOArch(arch);
if (!expectedArch) return;
const mismatches = [];
for (const entry of entries) {
if (!(await fileExists(entry.path))) continue;
const archs = await darwinMachOArchitectures(entry.path);
const fileTypes = await darwinMachOFileTypes(entry.path);
if (!archs.includes(expectedArch)) {
mismatches.push(`${entry.label}: has ${archs.join(', ') || '<none>'}; expected ${expectedArch}`);
}
if (arch === 'x64' && archs.includes('x86_64h') && !archs.includes('x86_64')) {
mismatches.push(`${entry.label}: has x86_64h only; expected baseline x86_64 for Intel compatibility`);
}
if (fileTypes.length === 0 || fileTypes.some((fileType) => fileType !== 'BUNDLE' && fileType !== 'DYLIB')) {
mismatches.push(
`${entry.label}: Mach-O file type ${fileTypes.join(', ') || '<none>'}; expected DYLIB or BUNDLE for Node addon loading`,
);
}
if (fileTypes.includes('BUNDLE')) {
const loadCommands = await darwinMachOLoadCommands(entry.path);
if (loadCommands.includes('LC_ID_DYLIB')) {
mismatches.push(
`${entry.label}: Mach-O BUNDLE contains LC_ID_DYLIB; dyld rejects this combination when Electron loads the addon`,
);
}
}
}
if (mismatches.length > 0) {
throw new Error(
[
`Wrong native runtime architecture(s) ${stage} for ${platform}/${arch}:`,
...mismatches.map((entry) => ` - ${entry}`),
].join('\n'),
);
}
}
async function verifyNativePackageInputs(context) {
if (process.env.FLUXER_SKIP_NATIVE === 'true') return;
const platform = context.electronPlatformName;
const arch = normalizeArch(context.arch);
const appDir = resolveAppDir(context);
const missing = [];
const entries = [];
for (const artifact of await expectedNativeRuntimeArtifactsForAppDir(platform, arch, appDir)) {
const artifactPath = path.join(
appDir,
'node_modules',
...packagePathParts(artifact.packageName),
artifact.relativePath,
);
entries.push({
label: `${artifact.packageName}/${artifact.relativePath}`,
path: artifactPath,
});
if (!(await fileExists(artifactPath))) {
missing.push(`${artifact.packageName}/${artifact.relativePath}`);
}
}
if (missing.length > 0) {
throw new Error(
[
`Missing native runtime artifact(s) before packaging for ${platform}/${arch}:`,
...missing.map((entry) => ` - ${entry}`),
'Run `pnpm build` from fluxer_desktop so native build outputs are synced into node_modules before electron-builder runs.',
].join('\n'),
);
}
await verifyDarwinNativeArchitectures(platform, arch, entries, 'before packaging');
}
async function verifyPackagedNativeArtifacts(context) {
if (process.env.FLUXER_SKIP_NATIVE === 'true') return;
const platform = context.electronPlatformName;
const arch = normalizeArch(context.arch);
const appDir = resolveAppDir(context);
const missing = [];
const entries = [];
for (const artifact of await expectedNativeRuntimeArtifactsForAppDir(platform, arch, appDir)) {
const artifactPath = path.join(
context.appOutDir,
'resources',
'app.asar.unpacked',
'node_modules',
...packagePathParts(artifact.packageName),
artifact.relativePath,
);
entries.push({
label: `${artifact.packageName}/${artifact.relativePath}`,
path: artifactPath,
});
if (!(await fileExists(artifactPath))) {
missing.push(`${artifact.packageName}/${artifact.relativePath}`);
}
}
if (missing.length > 0) {
throw new Error(
[
`Missing unpacked native runtime artifact(s) after packaging for ${platform}/${arch}:`,
...missing.map((entry) => ` - ${entry}`),
'Check electron-builder asarUnpack patterns and native package artifact sync.',
].join('\n'),
);
}
await verifyDarwinNativeArchitectures(platform, arch, entries, 'after packaging');
}
async function copyMissingPackagedNativeArtifacts(context) {
const platform = context.electronPlatformName;
const arch = normalizeArch(context.arch);
const appDir = resolveAppDir(context);
for (const artifact of await expectedNativeRuntimeArtifactsForAppDir(platform, arch, appDir)) {
const packageParts = packagePathParts(artifact.packageName);
const sourcePath = path.join(appDir, 'node_modules', ...packageParts, artifact.relativePath);
const targetPath = path.join(
context.appOutDir,
'resources',
'app.asar.unpacked',
'node_modules',
...packageParts,
artifact.relativePath,
);
if ((await fileExists(targetPath)) || !(await fileExists(sourcePath))) {
continue;
}
await fs.mkdir(path.dirname(targetPath), {recursive: true});
await fs.copyFile(sourcePath, targetPath);
}
}
async function removeIfExists(targetPath) {
await fs.rm(targetPath, {force: true, recursive: true});
}
async function cleanupNativeBuildIntermediates(context) {
const unpackedNodeModules = path.join(context.appOutDir, 'resources', 'app.asar.unpacked', 'node_modules');
const entries = await fs.readdir(unpackedNodeModules, {withFileTypes: true}).catch((error) => {
if (error && error.code === 'ENOENT') {
return [];
}
throw error;
});
const packageDirs = [];
for (const entry of entries) {
if (!entry.isDirectory()) {
continue;
}
const entryPath = path.join(unpackedNodeModules, entry.name);
if (entry.name.startsWith('@')) {
const scopedEntries = await fs.readdir(entryPath, {withFileTypes: true});
for (const scopedEntry of scopedEntries) {
if (scopedEntry.isDirectory()) {
packageDirs.push(path.join(entryPath, scopedEntry.name));
}
}
} else {
packageDirs.push(entryPath);
}
}
await Promise.all(packageDirs.map((packageDir) => removeIfExists(path.join(packageDir, 'build', 'Release', 'obj'))));
}
async function addLinuxLegacyBinarySymlink(context) {
if (context.electronPlatformName !== 'linux') return;
const legacyName = packageName;
const currentName = linuxPackageName;
if (legacyName === currentName) return;
const linkPath = path.join(context.appOutDir, legacyName);
try {
await fs.symlink(currentName, linkPath);
} catch (error) {
if (!error || error.code !== 'EEXIST') throw error;
}
}
async function afterPack(context) {
await copyMissingPackagedNativeArtifacts(context);
await cleanupNativeBuildIntermediates(context);
await addLinuxLegacyBinarySymlink(context);
await verifyPackagedNativeArtifacts(context);
}
async function listRpmPackageFiles(artifactPath) {
try {
const {stdout} = await execFileAsync('rpm', ['-qpl', artifactPath], {
maxBuffer: 16 * 1024 * 1024,
});
return stdout
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean);
} catch (error) {
if (error && error.code === 'ENOENT') {
throw new Error(`Cannot inspect RPM artifact ${artifactPath}: rpm executable is not available.`);
}
const stderr = typeof error?.stderr === 'string' ? error.stderr.trim() : '';
throw new Error(`Cannot inspect RPM artifact ${artifactPath}: ${stderr || error?.message || String(error)}`);
}
}
async function listDebPackageFiles(artifactPath) {
try {
const {stdout} = await execFileAsync('dpkg-deb', ['--contents', artifactPath], {
maxBuffer: 16 * 1024 * 1024,
});
return stdout
.split(/\r?\n/)
.map((line) => line.match(/^\S+\s+\S+\s+\d+\s+\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}\s+(.+)$/)?.[1])
.filter(Boolean)
.map((filePath) => filePath.replace(/^\.\//, '/'));
} catch (error) {
if (error && error.code === 'ENOENT') {
throw new Error(`Cannot inspect DEB artifact ${artifactPath}: dpkg-deb executable is not available.`);
}
const stderr = typeof error?.stderr === 'string' ? error.stderr.trim() : '';
throw new Error(`Cannot inspect DEB artifact ${artifactPath}: ${stderr || error?.message || String(error)}`);
}
}
async function verifyRpmArtifactsDoNotOwnBuildIds(buildResult) {
const rpmArtifacts = (buildResult.artifactPaths ?? []).filter(
(artifactPath) => path.extname(artifactPath) === '.rpm',
);
const violations = [];
for (const artifactPath of rpmArtifacts) {
const packageFiles = await listRpmPackageFiles(artifactPath);
const buildIdFiles = packageFiles.filter(
(filePath) => filePath === rpmBuildIdFilePrefix || filePath.startsWith(`${rpmBuildIdFilePrefix}/`),
);
if (buildIdFiles.length > 0) {
violations.push({artifactPath, buildIdFiles});
}
}
if (violations.length === 0) return [];
const lines = [
`RPM artifact(s) must not own ${rpmBuildIdFilePrefix} entries.`,
'These global rpmbuild-generated links collide with other Electron RPMs that bundle the same upstream ELF binaries.',
];
for (const {artifactPath, buildIdFiles} of violations) {
lines.push(` - ${path.basename(artifactPath)}:`);
for (const filePath of buildIdFiles.slice(0, 12)) {
lines.push(` ${filePath}`);
}
if (buildIdFiles.length > 12) {
lines.push(` ... ${buildIdFiles.length - 12} more`);
}
}
throw new Error(lines.join('\n'));
}
function packageFilesContainAppArmorProfile(packageFiles) {
return packageFiles.some((filePath) => filePath.endsWith('/resources/apparmor-profile'));
}
async function verifyLinuxPackagesContainAppArmorProfile(buildResult) {
const packageArtifacts = (buildResult.artifactPaths ?? []).filter((artifactPath) =>
['.deb', '.rpm'].includes(path.extname(artifactPath)),
);
const violations = [];
for (const artifactPath of packageArtifacts) {
const extension = path.extname(artifactPath);
const packageFiles =
extension === '.deb' ? await listDebPackageFiles(artifactPath) : await listRpmPackageFiles(artifactPath);
if (!packageFilesContainAppArmorProfile(packageFiles)) {
violations.push({artifactPath});
}
}
if (violations.length === 0) return;
const lines = [
'Linux package artifact(s) must include the Electron AppArmor profile.',
'Ubuntu 24.04+ restricts unprivileged user namespaces; packaged Electron apps need this profile so the Chromium sandbox can run without forcing --no-sandbox.',
];
for (const {artifactPath} of violations) {
lines.push(` - ${path.basename(artifactPath)}`);
}
throw new Error(lines.join('\n'));
}
async function readElfNeededLibraries(artifactPath) {
try {
const {stdout} = await execFileAsync('readelf', ['-d', artifactPath], {
maxBuffer: 8 * 1024 * 1024,
});
return stdout
.split(/\r?\n/)
.map((line) => line.match(/\(NEEDED\)\s+Shared library: \[([^\]]+)\]/)?.[1])
.filter(Boolean);
} catch (error) {
if (error && error.code === 'ENOENT') {
throw new Error(`Cannot inspect AppImage artifact ${artifactPath}: readelf executable is not available.`);
}
const stderr = typeof error?.stderr === 'string' ? error.stderr.trim() : '';
throw new Error(`Cannot inspect AppImage artifact ${artifactPath}: ${stderr || error?.message || String(error)}`);
}
}
async function verifyAppImageArtifactsDoNotNeedFuse2(buildResult) {
const appImageArtifacts = (buildResult.artifactPaths ?? []).filter(
(artifactPath) => path.extname(artifactPath) === '.AppImage',
);
const violations = [];
for (const artifactPath of appImageArtifacts) {
const neededLibraries = await readElfNeededLibraries(artifactPath);
if (neededLibraries.includes('libfuse.so.2')) {
violations.push({artifactPath, neededLibraries});
}
}
if (violations.length === 0) return;
const lines = [
'AppImage artifact(s) must not depend on libfuse.so.2.',
'Use electron-builder toolsets.appimage 1.0.3 so the AppImage runtime is static and works on modern distributions without libfuse2.',
];
for (const {artifactPath, neededLibraries} of violations) {
lines.push(` - ${path.basename(artifactPath)} imports: ${neededLibraries.join(', ') || '<none>'}`);
}
throw new Error(lines.join('\n'));
}
function canExecuteAppImageArtifact(artifactPath) {
const artifactName = path.basename(artifactPath).toLowerCase();
const hostArch = process.arch;
if (artifactName.includes('arm64') || artifactName.includes('aarch64')) {
return hostArch === 'arm64';
}
if (artifactName.includes('x86_64') || artifactName.includes('amd64') || artifactName.includes('x64')) {
return hostArch === 'x64';
}
return true;
}
async function extractAppImagePattern(artifactPath, pattern, tempDir) {
try {
await execFileAsync(path.resolve(artifactPath), ['--appimage-extract', pattern], {
cwd: tempDir,
maxBuffer: 8 * 1024 * 1024,
});
} catch (error) {
const stderr = typeof error?.stderr === 'string' ? error.stderr.trim() : '';
throw new Error(
`Cannot extract ${pattern} from AppImage artifact ${artifactPath}: ${stderr || error?.message || String(error)}`,
);
}
}
async function findExtractedFiles(rootDir, predicate) {
const results = [];
async function visit(directory) {
const entries = await fs.readdir(directory, {withFileTypes: true});
for (const entry of entries) {
const entryPath = path.join(directory, entry.name);
if (entry.isDirectory()) {
await visit(entryPath);
} else if (entry.isFile() && predicate(entryPath)) {
results.push(entryPath);
}
}
}
await visit(rootDir);
return results;
}
async function inspectAppImageLauncher(artifactPath) {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fluxer-appimage-'));
try {
await extractAppImagePattern(artifactPath, 'AppRun', tempDir);
await extractAppImagePattern(artifactPath, '*.desktop', tempDir);
const squashfsRoot = path.join(tempDir, 'squashfs-root');
const appRun = await fs.readFile(path.join(squashfsRoot, 'AppRun'), 'utf8');
const desktopFiles = await findExtractedFiles(squashfsRoot, (filePath) => path.extname(filePath) === '.desktop');
const violations = [];
if (desktopFiles.length === 0) {
violations.push('does not contain a desktop entry');
}
const appRunUsesNamespaceProbe = /unshare\s+(?:-Ur|--user)\s+true/.test(appRun);
if (!appRunUsesNamespaceProbe || !appRun.includes('NO_SANDBOX=--no-sandbox')) {
violations.push('AppRun does not use the expected user-namespace probe before falling back to --no-sandbox');
}
for (const desktopFile of desktopFiles) {
const desktopEntry = await fs.readFile(desktopFile, 'utf8');
for (const line of desktopEntry.split(/\r?\n/)) {
if (line.startsWith('Exec=') && line.includes('--no-sandbox')) {
violations.push(`${path.basename(desktopFile)} has an unconditional --no-sandbox Exec line`);
}
}
}
return violations;
} finally {
await fs.rm(tempDir, {recursive: true, force: true});
}
}
async function verifyAppImageArtifactsUseSandboxAwareLauncher(buildResult) {
const appImageArtifacts = (buildResult.artifactPaths ?? []).filter(
(artifactPath) => path.extname(artifactPath) === '.AppImage',
);
const violations = [];
for (const artifactPath of appImageArtifacts) {
if (!canExecuteAppImageArtifact(artifactPath)) {
console.warn(
`Skipping AppImage launcher extraction for ${path.basename(artifactPath)} because it does not match host architecture ${process.arch}.`,
);
continue;
}
const artifactViolations = await inspectAppImageLauncher(artifactPath);
if (artifactViolations.length > 0) {
violations.push({artifactPath, artifactViolations});
}
}
if (violations.length === 0) return;
const lines = [
'AppImage artifact(s) must use the static-runtime, sandbox-aware launcher contract.',
'The desktop entry must not pass --no-sandbox unconditionally; AppRun may add it only after the user-namespace probe fails.',
];
for (const {artifactPath, artifactViolations} of violations) {
lines.push(` - ${path.basename(artifactPath)}:`);
for (const violation of artifactViolations) {
lines.push(` ${violation}`);
}
}
throw new Error(lines.join('\n'));
}
async function verifyLinuxArtifactContracts(buildResult) {
await verifyRpmArtifactsDoNotOwnBuildIds(buildResult);
await verifyLinuxPackagesContainAppArmorProfile(buildResult);
await verifyAppImageArtifactsDoNotNeedFuse2(buildResult);
await verifyAppImageArtifactsUseSandboxAwareLauncher(buildResult);
}
module.exports = {
appId,
productName,
copyright: 'Copyright © 2026 Fluxer Platform AB',
// biome-ignore lint/suspicious/noTemplateCurlyInString: electron-builder placeholders, not JS template literals.
artifactName: '${productName}-${version}-${os}-${arch}.${ext}',
directories: {
buildResources: 'build_resources',
output: 'dist-electron',
},
files: [
'dist/**/*',
'package.json',
...nativeRuntimeFilePatterns,
'node_modules/hunspell-asm/**/*',
...nativeBuildArtifactExcludes,
...packagedRuntimeArtifactExcludes,
...bundledDependencyExcludes,
...windowsGameCapturePackageExcludes,
...platformNativeRuntimeExcludes,
...platformRuntimeDependencyExcludes,
],
extraMetadata: {
main: 'dist/main/index.js',
name: metadataName,
...(Boolean(process.env.VERSION) ? {version: process.env.VERSION} : {}),
...(targetPlatform === 'linux' ? {desktopName: `${linuxPackageName}.desktop`} : {}),
},
extraResources: [
{
from: `build_resources/${iconDir}/`,
to: 'icons',
filter: [
'16x16.png',
'24x24.png',
'32x32.png',
'48x48.png',
'64x64.png',
'128x128.png',
'256x256.png',
'512x512.png',
'FluxerTrayTemplate.png',
'FluxerTrayTemplate@2x.png',
'icon.ico',
'icon.png',
],
},
{
from: `build_resources/${iconDir}/badges/`,
to: 'badges',
filter: ['**/*'],
},
],
asar: {
smartUnpack: false,
},
asarUnpack: [
'**/*.node',
'node_modules/@fluxer/win-process-loopback/*.node',
...(windowsGameCaptureModuleEnabled
? [
'node_modules/@fluxer/win-game-capture/*.node',
'node_modules/@fluxer/win-game-capture/*.dll',
'node_modules/@fluxer/win-game-capture/*.exe',
'node_modules/@fluxer/win-game-capture/*.json',
]
: []),
'node_modules/@fluxer/win-clipboard/*.node',
'node_modules/@fluxer/win-shell/*.node',
'node_modules/@fluxer/win-toast/*.node',
'node_modules/@fluxer/linux-audio-capture/*.node',
'node_modules/@fluxer/linux-portals/*.node',
'node_modules/@fluxer/linux-screen-capture/*.node',
'node_modules/@fluxer/linux-screen-capture/obs-vkcapture/**/*',
'node_modules/@fluxer/linux-notifications/*.node',
'node_modules/@fluxer/linux-evdev/*.node',
'node_modules/@fluxer/system-hunspell/*.node',
'node_modules/@fluxer/macos-input-hook/*.node',
'node_modules/@fluxer/mac-app-audio/*.node',
'node_modules/@fluxer/mac-screen-capture/*.node',
'node_modules/@fluxer/mac-clipboard/*.node',
'node_modules/@fluxer/mac-sysctl/*.node',
'node_modules/@fluxer/mac-tcc/*.node',
'node_modules/@fluxer/windows-input-hook/*.node',
'node_modules/@fluxer/linux-input-hook/*.node',
'node_modules/@fluxer/platform-info/*.node',
'node_modules/@fluxer/webauthn/*.node',
'node_modules/@fluxer/webauthn/*.so*',
'node_modules/.pnpm/@fluxer+win-process-loopback@*/node_modules/@fluxer/win-process-loopback/*.node',
...(windowsGameCaptureModuleEnabled
? [
'node_modules/.pnpm/@fluxer+win-game-capture@*/node_modules/@fluxer/win-game-capture/*.node',
'node_modules/.pnpm/@fluxer+win-game-capture@*/node_modules/@fluxer/win-game-capture/*.dll',
'node_modules/.pnpm/@fluxer+win-game-capture@*/node_modules/@fluxer/win-game-capture/*.exe',
'node_modules/.pnpm/@fluxer+win-game-capture@*/node_modules/@fluxer/win-game-capture/*.json',
]
: []),
'node_modules/.pnpm/@fluxer+win-clipboard@*/node_modules/@fluxer/win-clipboard/*.node',
'node_modules/.pnpm/@fluxer+win-shell@*/node_modules/@fluxer/win-shell/*.node',
'node_modules/.pnpm/@fluxer+win-toast@*/node_modules/@fluxer/win-toast/*.node',
'node_modules/.pnpm/@fluxer+windows-input-hook@*/node_modules/@fluxer/windows-input-hook/*.node',
'node_modules/.pnpm/@fluxer+linux-audio-capture@*/node_modules/@fluxer/linux-audio-capture/*.node',
'node_modules/.pnpm/@fluxer+linux-portals@*/node_modules/@fluxer/linux-portals/*.node',
'node_modules/.pnpm/@fluxer+linux-screen-capture@*/node_modules/@fluxer/linux-screen-capture/*.node',
'node_modules/.pnpm/@fluxer+linux-screen-capture@*/node_modules/@fluxer/linux-screen-capture/obs-vkcapture/**/*',
'node_modules/.pnpm/@fluxer+linux-notifications@*/node_modules/@fluxer/linux-notifications/*.node',
'node_modules/.pnpm/@fluxer+linux-evdev@*/node_modules/@fluxer/linux-evdev/*.node',
'node_modules/.pnpm/@fluxer+linux-input-hook@*/node_modules/@fluxer/linux-input-hook/*.node',
'node_modules/.pnpm/@fluxer+system-hunspell@*/node_modules/@fluxer/system-hunspell/*.node',
'node_modules/.pnpm/@fluxer+macos-input-hook@*/node_modules/@fluxer/macos-input-hook/*.node',
'node_modules/.pnpm/@fluxer+mac-app-audio@*/node_modules/@fluxer/mac-app-audio/*.node',
'node_modules/.pnpm/@fluxer+mac-screen-capture@*/node_modules/@fluxer/mac-screen-capture/*.node',
'node_modules/.pnpm/@fluxer+mac-clipboard@*/node_modules/@fluxer/mac-clipboard/*.node',
'node_modules/.pnpm/@fluxer+mac-sysctl@*/node_modules/@fluxer/mac-sysctl/*.node',
'node_modules/.pnpm/@fluxer+mac-tcc@*/node_modules/@fluxer/mac-tcc/*.node',
'node_modules/.pnpm/@fluxer+platform-info@*/node_modules/@fluxer/platform-info/*.node',
'node_modules/.pnpm/@fluxer+webauthn@*/node_modules/@fluxer/webauthn/*.node',
'node_modules/.pnpm/@fluxer+webauthn@*/node_modules/@fluxer/webauthn/*.so*',
],
compression: 'normal',
npmRebuild: false,
protocols: [
{
name: appId,
role: 'Viewer',
schemes: ['fluxer'],
},
],
beforePack: verifyNativePackageInputs,
afterPack,
afterAllArtifactBuild: verifyLinuxArtifactContracts,
toolsets: {
appimage: '1.0.3',
},
mac: {
category: 'public.app-category.social-networking',
minimumSystemVersion: macOSMinimumSystemVersion,
icon: `build_resources/${iconDir}/_compiled/AppIcon.icns`,
darkModeSupport: true,
hardenedRuntime: true,
gatekeeperAssess: false,
notarize: true,
provisioningProfile,
entitlements: isCanary
? 'build_resources/entitlements.mac.canary.plist'
: 'build_resources/entitlements.mac.stable.plist',
entitlementsInherit: 'build_resources/entitlements.mac.inherit.plist',
target: [
{
target: 'dmg',
arch: targetArchs,
},
{
target: 'zip',
arch: targetArchs,
},
],
extendInfo: {
NSMicrophoneUsageDescription: 'Fluxer needs access to your microphone to enable voice chat features.',
NSCameraUsageDescription: 'Fluxer needs access to your camera to enable video chat features.',
NSAppleEventsUsageDescription: 'Fluxer needs access to Apple Events for automation features.',
NSAudioCaptureUsageDescription: 'Fluxer captures audio from the screen or window you choose to share.',
NSScreenCaptureUsageDescription: 'Fluxer captures the screen or window you choose to share.',
},
},
dmg: {
contents: [
{
x: 130,
y: 220,
},
{
x: 410,
y: 220,
type: 'link',
path: '/Applications',
},
],
},
win: {
icon: `build_resources/${iconDir}/icon.ico`,
target: winTargets,
},
portable: {
// biome-ignore lint/suspicious/noTemplateCurlyInString: electron-builder expands these placeholders.
artifactName: '${productName}-${version}-portable-${os}-${arch}.${ext}',
},
linux: {
icon: `build_resources/${iconDir}/icon.png`,
category: 'Network;InstantMessaging;Chat;',
target: [
{
target: 'AppImage',
arch: targetArchs,
},
{
target: 'deb',
arch: targetArchs,
},
{
target: 'rpm',
arch: targetArchs,
},
{
target: 'tar.gz',
arch: targetArchs,
},
],
desktop: {
entry: linuxDesktopEntry,
},
},
deb: {
packageCategory: 'net',
desktop: {
entry: linuxDesktopEntryWithActions,
desktopActions: linuxDesktopActions,
},
depends: [
'libgtk-3-0',
'libnotify4',
'libnss3',
'libxss1',
'libxtst6',
'xdg-utils',
'libatspi2.0-0',
'libuuid1',
'libsecret-1-0',
'libpulse0',
'libpipewire-0.3-0',
'libstdc++6',
'libgcc-s1',
],
},
rpm: {
desktop: {
entry: linuxDesktopEntryWithActions,
desktopActions: linuxDesktopActions,
},
fpm: rpmBuildIdLinkFpmArgs,
depends: [
'gtk3',
'libnotify',
'nss',
'libXScrnSaver',
'libXtst',
'xdg-utils',
'at-spi2-core',
'libuuid',
'libsecret',
'pulseaudio-libs',
'pipewire-libs',
'libstdc++',
'libgcc',
],
},
publish: null,
};