From 201eb6a6719415a18c224a3a8713d76645d50414 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Fri, 10 Apr 2026 10:11:55 +0700 Subject: [PATCH] Self-contained desktop dev script with worktree isolation dev:desktop now launches its own Metro on a random port + Electron, no dependency on a shared :8081. In worktrees, Electron gets a unique userData path and dock badge so multiple instances run side-by-side. --- packages/desktop/package.json | 2 +- packages/desktop/scripts/dev.sh | 28 ++++++++++++++++++++++++ packages/desktop/src/main.ts | 38 +++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100755 packages/desktop/scripts/dev.sh diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 20caca0ea..0704b3e01 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -7,7 +7,7 @@ "scripts": { "build": "npm --prefix ../.. run build:daemon && npm run build:main && electron-builder --config electron-builder.yml", "build:main": "tsc -p tsconfig.json", - "dev": "npm run build:main && wait-on tcp:8081 && electron .", + "dev": "./scripts/dev.sh", "test": "vitest run", "typecheck": "tsc --noEmit -p tsconfig.json" }, diff --git a/packages/desktop/scripts/dev.sh b/packages/desktop/scripts/dev.sh new file mode 100755 index 000000000..a4756af9a --- /dev/null +++ b/packages/desktop/scripts/dev.sh @@ -0,0 +1,28 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +DESKTOP_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +APP_DIR="$(cd "$DESKTOP_DIR/../app" && pwd)" +ROOT_DIR="$(cd "$DESKTOP_DIR/../.." && pwd)" + +# Build the Electron main process +npm run build:main + +# Get a random available port for Metro +EXPO_PORT=$("$ROOT_DIR/node_modules/.bin/get-port") +export EXPO_PORT + +echo "══════════════════════════════════════════════════════" +echo " Paseo Desktop Dev" +echo "══════════════════════════════════════════════════════" +echo " Metro: http://localhost:${EXPO_PORT}" +echo "══════════════════════════════════════════════════════" + +# Launch Metro + Electron together, kill both on exit +"$ROOT_DIR/node_modules/.bin/concurrently" \ + --kill-others \ + --names "metro,electron" \ + --prefix-colors "magenta,cyan" \ + "cd '$APP_DIR' && npx expo start --port $EXPO_PORT" \ + "$ROOT_DIR/node_modules/.bin/wait-on tcp:$EXPO_PORT && EXPO_DEV_URL=http://localhost:$EXPO_PORT electron '$DESKTOP_DIR'" diff --git a/packages/desktop/src/main.ts b/packages/desktop/src/main.ts index e581a3600..c57654955 100644 --- a/packages/desktop/src/main.ts +++ b/packages/desktop/src/main.ts @@ -8,6 +8,7 @@ inheritLoginShellEnv(); import path from "node:path"; import { pathToFileURL } from "node:url"; import { existsSync } from "node:fs"; +import { execFileSync } from "node:child_process"; import { app, BrowserWindow, ipcMain, nativeImage, net, protocol } from "electron"; import { registerDaemonManager } from "./daemon/daemon-manager.js"; import { @@ -38,6 +39,37 @@ const APP_SCHEME = "paseo"; const OPEN_PROJECT_EVENT = "paseo:event:open-project"; app.setName("Paseo"); +// In dev mode, detect git worktrees and isolate each instance so multiple +// Electron windows can run side-by-side (separate userData = separate lock). +let devWorktreeName: string | null = null; +if (!app.isPackaged) { + try { + const topLevel = execFileSync("git", ["rev-parse", "--show-toplevel"], { + encoding: "utf-8", + timeout: 3000, + }).trim(); + devWorktreeName = path.basename(topLevel); + // Main checkout (e.g. "paseo") gets default userData — only worktrees diverge. + const commonDir = path.resolve( + topLevel, + execFileSync("git", ["rev-parse", "--git-common-dir"], { + cwd: topLevel, + encoding: "utf-8", + timeout: 3000, + }).trim(), + ); + const isWorktree = path.resolve(topLevel, ".git") !== commonDir; + if (isWorktree) { + app.setPath("userData", path.join(app.getPath("appData"), `Paseo-${devWorktreeName}`)); + log.info("[worktree] isolated userData for worktree:", devWorktreeName); + } else { + devWorktreeName = null; + } + } catch { + devWorktreeName = null; + } +} + // Allow users to pass Chromium flags via PASEO_ELECTRON_FLAGS for debugging // rendering issues (e.g. "--disable-gpu --ozone-platform=x11"). // Must run before app.whenReady(). @@ -127,7 +159,9 @@ async function createMainWindow(): Promise { const iconPath = getWindowIconPath(); const systemTheme = resolveSystemWindowTheme(); + const title = devWorktreeName ? `Paseo (${devWorktreeName})` : "Paseo"; const mainWindow = new BrowserWindow({ + title, width: 1200, height: 800, show: false, @@ -144,6 +178,10 @@ async function createMainWindow(): Promise { }, }); + if (devWorktreeName) { + app.dock?.setBadge(devWorktreeName); + } + setupWindowResizeEvents(mainWindow); setupDefaultContextMenu(mainWindow); setupDragDropPrevention(mainWindow);