Package the Darwin desktop with electron-builder

This commit is contained in:
Ivan Malison
2026-07-31 23:38:46 +00:00
committed by Mohamed Boudra
parent b151dfb88d
commit 6e74c924e6
+74 -113
View File
@@ -16,57 +16,16 @@
# the upstream hash via `paseo.override { npmDepsHash = "..."; }`. # the upstream hash via `paseo.override { npmDepsHash = "..."; }`.
paseo, paseo,
}: }:
let
version = (builtins.fromJSON (builtins.readFile ../package.json)).version;
darwinInfoPlist = builtins.toFile "Paseo-Info.plist" (
lib.generators.toPlist { escape = true; } {
CFBundleDisplayName = "Paseo";
CFBundleExecutable = "Paseo";
CFBundleIconFile = "Paseo.icns";
CFBundleIdentifier = "sh.paseo.desktop";
CFBundleInfoDictionaryVersion = "6.0";
CFBundleName = "Paseo";
CFBundlePackageType = "APPL";
CFBundleShortVersionString = version;
CFBundleURLTypes = [
{
CFBundleURLName = "Paseo agent link";
CFBundleURLSchemes = [ "paseo" ];
}
];
CFBundleVersion = version;
LSApplicationCategoryType = "public.app-category.developer-tools";
LSEnvironment.MallocNanoZone = "0";
LSMinimumSystemVersion = "12.0";
NSAppTransportSecurity.NSAllowsArbitraryLoads = true;
NSAudioCaptureUsageDescription = "Paseo needs access to audio capture for voice input.";
NSBluetoothAlwaysUsageDescription = "Paseo needs access to Bluetooth audio devices.";
NSBluetoothPeripheralUsageDescription = "Paseo needs access to Bluetooth audio devices.";
NSCameraUsageDescription = "Paseo needs access to the camera for image attachments.";
NSHighResolutionCapable = true;
NSMainNibFile = "MainMenu";
NSMicrophoneUsageDescription = "Paseo needs access to the microphone for voice input.";
NSPrefersDisplaySafeAreaCompatibilityMode = false;
NSPrincipalClass = "AtomApplication";
NSQuitAlwaysKeepsWindows = false;
NSRequiresAquaSystemAppearance = false;
NSSupportsAutomaticGraphicsSwitching = true;
}
);
in
buildNpmPackage { buildNpmPackage {
pname = "paseo-desktop"; pname = "paseo-desktop";
inherit version; version = (builtins.fromJSON (builtins.readFile ../package.json)).version;
src = lib.cleanSourceWith { src = lib.cleanSourceWith {
src = ./..; src = ./..;
filter = filter = path: type: let
path: type: baseName = builtins.baseNameOf path;
let relPath = lib.removePrefix (toString ./..) path;
baseName = builtins.baseNameOf path; in
relPath = lib.removePrefix (toString ./..) path;
in
# Exclude mobile-only platform code (we only need the web/electron build) # Exclude mobile-only platform code (we only need the web/electron build)
!(lib.hasPrefix "/packages/app/android" relPath) !(lib.hasPrefix "/packages/app/android" relPath)
&& !(lib.hasPrefix "/packages/app/ios" relPath) && !(lib.hasPrefix "/packages/app/ios" relPath)
@@ -102,15 +61,15 @@ buildNpmPackage {
# Prevent onnxruntime-node's install script from running during automatic # Prevent onnxruntime-node's install script from running during automatic
# npm rebuild. We manually rebuild only node-pty in buildPhase. # npm rebuild. We manually rebuild only node-pty in buildPhase.
npmRebuildFlags = [ "--ignore-scripts" ]; npmRebuildFlags = ["--ignore-scripts"];
nativeBuildInputs = nativeBuildInputs =
[ [
python3 # for node-gyp (node-pty) python3 # for node-gyp (node-pty)
makeWrapper
] ]
++ lib.optionals stdenv.hostPlatform.isLinux [ ++ lib.optionals stdenv.hostPlatform.isLinux [
autoPatchelfHook autoPatchelfHook
makeWrapper
copyDesktopItems copyDesktopItems
]; ];
@@ -143,56 +102,75 @@ buildNpmPackage {
# Expo web export for the Electron renderer # Expo web export for the Electron renderer
( cd packages/app && PASEO_WEB_PLATFORM=electron npx expo export --platform web ) ( cd packages/app && PASEO_WEB_PLATFORM=electron npx expo export --platform web )
# Desktop main process (tsc only — NOT electron-builder) # Desktop main process
npm run build:main --workspace=@getpaseo/desktop npm run build:main --workspace=@getpaseo/desktop
${lib.optionalString stdenv.hostPlatform.isDarwin ''
# Let electron-builder create the native bundle layout (including helper
# app names and bundle identifiers), but source Electron from nixpkgs
# instead of downloading a release at build time.
(
cd packages/desktop
CSC_IDENTITY_AUTO_DISCOVERY=false \
../../node_modules/.bin/electron-builder \
--config electron-builder.yml \
--dir \
--mac \
--publish never \
--config.electronDist=${electron}/Applications \
--config.mac.notarize=false
)
''}
runHook postBuild runHook postBuild
''; '';
installPhase = '' installPhase = ''
runHook preInstall runHook preInstall
mkdir -p $out/share/paseo-desktop $out/bin mkdir -p $out/bin
# Materialize only the desktop and daemon runtime graphs. Copying the
# complete monorepo used to ship every build-time dependency (including
# Electron, Expo tooling, and cross-platform builder binaries), making the
# desktop output larger than 2 GiB.
PASEO_TRACE_DESKTOP=1 node scripts/trace-daemon.mjs > desktop-files.txt
while IFS= read -r path; do
[ -z "$path" ] && continue
mkdir -p "$out/share/paseo-desktop/$(dirname "$path")"
cp -a "$path" "$out/share/paseo-desktop/$path"
done < desktop-files.txt
# Keep the same unpackaged monorepo layout expected by main.js.
cp package.json $out/share/paseo-desktop/
mkdir -p $out/share/paseo-desktop/packages/app
cp -a packages/app/dist $out/share/paseo-desktop/packages/app/
for runtime_path in \
packages/desktop/dist/main.js \
packages/desktop/dist/preload.js \
packages/desktop/dist/features/browser-keyboard/guest-preload.js \
packages/desktop/package.json; do
if [ ! -e "$out/share/paseo-desktop/$runtime_path" ]; then
echo "desktop runtime trace omitted $runtime_path" >&2
exit 1
fi
done
if [ -e $out/share/paseo-desktop/node_modules/electron ]; then
echo "desktop runtime trace included npm Electron" >&2
exit 1
fi
# Skills directory referenced at runtime by some agents
if [ -d skills ]; then
cp -a skills $out/share/paseo-desktop/
fi
${lib.optionalString stdenv.hostPlatform.isLinux '' ${lib.optionalString stdenv.hostPlatform.isLinux ''
mkdir -p $out/share/paseo-desktop
# Materialize only the desktop and daemon runtime graphs. Copying the
# complete monorepo used to ship every build-time dependency (including
# Electron, Expo tooling, and cross-platform builder binaries), making the
# desktop output larger than 2 GiB.
PASEO_TRACE_DESKTOP=1 node scripts/trace-daemon.mjs > desktop-files.txt
while IFS= read -r path; do
[ -z "$path" ] && continue
mkdir -p "$out/share/paseo-desktop/$(dirname "$path")"
cp -a "$path" "$out/share/paseo-desktop/$path"
done < desktop-files.txt
# Keep the same unpackaged monorepo layout expected by main.js.
cp package.json $out/share/paseo-desktop/
mkdir -p $out/share/paseo-desktop/packages/app
cp -a packages/app/dist $out/share/paseo-desktop/packages/app/
for runtime_path in \
packages/desktop/dist/main.js \
packages/desktop/dist/preload.js \
packages/desktop/dist/features/browser-keyboard/guest-preload.js \
packages/desktop/package.json; do
if [ ! -e "$out/share/paseo-desktop/$runtime_path" ]; then
echo "desktop runtime trace omitted $runtime_path" >&2
exit 1
fi
done
if [ -e $out/share/paseo-desktop/node_modules/electron ]; then
echo "desktop runtime trace included npm Electron" >&2
exit 1
fi
# Skills directory referenced at runtime by some agents
if [ -d skills ]; then
cp -a skills $out/share/paseo-desktop/
fi
# Hicolor icon for desktop environments # Hicolor icon for desktop environments
install -Dm644 packages/desktop/assets/icon.png \ install -Dm644 packages/desktop/assets/icon.png \
$out/share/icons/hicolor/512x512/apps/paseo-desktop.png $out/share/icons/hicolor/512x512/apps/paseo-desktop.png
@@ -207,30 +185,13 @@ buildNpmPackage {
''} ''}
${lib.optionalString stdenv.hostPlatform.isDarwin '' ${lib.optionalString stdenv.hostPlatform.isDarwin ''
app="$out/Applications/Paseo.app" app="$(find packages/desktop/release -maxdepth 3 -type d -name Paseo.app -print -quit)"
mkdir -p "$app/Contents/MacOS" "$app/Contents/Resources" if [ -z "$app" ]; then
echo "electron-builder did not produce Paseo.app" >&2
# Reuse nixpkgs' Electron framework through store symlinks so the Paseo exit 1
# output stays small while still presenting a normal application bundle fi
# to LaunchServices. mkdir -p "$out/Applications"
ln -s ${electron}/Applications/Electron.app/Contents/Frameworks \ cp -R "$app" "$out/Applications/Paseo.app"
"$app/Contents/Frameworks"
ln -s ${electron}/Applications/Electron.app/Contents/MacOS/Electron \
"$app/Contents/MacOS/Electron"
for resource in ${electron}/Applications/Electron.app/Contents/Resources/*; do
ln -s "$resource" "$app/Contents/Resources/$(basename "$resource")"
done
cp ${darwinInfoPlist} "$app/Contents/Info.plist"
cp ${electron}/Applications/Electron.app/Contents/PkgInfo "$app/Contents/PkgInfo"
cp packages/desktop/assets/icon.icns "$app/Contents/Resources/Paseo.icns"
# Keep the same unpackaged runtime layout as the Linux Nix package. The
# outer .app supplies native macOS identity, icon, and URL handling while
# the wrapper points Electron at the immutable built main process.
makeWrapper "$app/Contents/MacOS/Electron" "$app/Contents/MacOS/Paseo" \
--add-flags "$out/share/paseo-desktop/packages/desktop/dist/main.js" \
--set EXPO_DEV_URL "paseo://app/"
ln -s ../Applications/Paseo.app/Contents/MacOS/Paseo "$out/bin/paseo-desktop" ln -s ../Applications/Paseo.app/Contents/MacOS/Paseo "$out/bin/paseo-desktop"
''} ''}
@@ -245,7 +206,7 @@ buildNpmPackage {
comment = "Self-hosted daemon for AI coding agents"; comment = "Self-hosted daemon for AI coding agents";
exec = "paseo-desktop"; exec = "paseo-desktop";
icon = "paseo-desktop"; icon = "paseo-desktop";
categories = [ "Development" ]; categories = ["Development"];
startupWMClass = "Paseo"; startupWMClass = "Paseo";
}) })
]; ];