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.
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm --prefix ../.. run build:daemon && npm run build:main && electron-builder --config electron-builder.yml",
|
"build": "npm --prefix ../.. run build:daemon && npm run build:main && electron-builder --config electron-builder.yml",
|
||||||
"build:main": "tsc -p tsconfig.json",
|
"build:main": "tsc -p tsconfig.json",
|
||||||
"dev": "npm run build:main && wait-on tcp:8081 && electron .",
|
"dev": "./scripts/dev.sh",
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"typecheck": "tsc --noEmit -p tsconfig.json"
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
||||||
},
|
},
|
||||||
|
|||||||
Executable
+28
@@ -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'"
|
||||||
@@ -8,6 +8,7 @@ inheritLoginShellEnv();
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { pathToFileURL } from "node:url";
|
import { pathToFileURL } from "node:url";
|
||||||
import { existsSync } from "node:fs";
|
import { existsSync } from "node:fs";
|
||||||
|
import { execFileSync } from "node:child_process";
|
||||||
import { app, BrowserWindow, ipcMain, nativeImage, net, protocol } from "electron";
|
import { app, BrowserWindow, ipcMain, nativeImage, net, protocol } from "electron";
|
||||||
import { registerDaemonManager } from "./daemon/daemon-manager.js";
|
import { registerDaemonManager } from "./daemon/daemon-manager.js";
|
||||||
import {
|
import {
|
||||||
@@ -38,6 +39,37 @@ const APP_SCHEME = "paseo";
|
|||||||
const OPEN_PROJECT_EVENT = "paseo:event:open-project";
|
const OPEN_PROJECT_EVENT = "paseo:event:open-project";
|
||||||
app.setName("Paseo");
|
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
|
// Allow users to pass Chromium flags via PASEO_ELECTRON_FLAGS for debugging
|
||||||
// rendering issues (e.g. "--disable-gpu --ozone-platform=x11").
|
// rendering issues (e.g. "--disable-gpu --ozone-platform=x11").
|
||||||
// Must run before app.whenReady().
|
// Must run before app.whenReady().
|
||||||
@@ -127,7 +159,9 @@ async function createMainWindow(): Promise<void> {
|
|||||||
const iconPath = getWindowIconPath();
|
const iconPath = getWindowIconPath();
|
||||||
const systemTheme = resolveSystemWindowTheme();
|
const systemTheme = resolveSystemWindowTheme();
|
||||||
|
|
||||||
|
const title = devWorktreeName ? `Paseo (${devWorktreeName})` : "Paseo";
|
||||||
const mainWindow = new BrowserWindow({
|
const mainWindow = new BrowserWindow({
|
||||||
|
title,
|
||||||
width: 1200,
|
width: 1200,
|
||||||
height: 800,
|
height: 800,
|
||||||
show: false,
|
show: false,
|
||||||
@@ -144,6 +178,10 @@ async function createMainWindow(): Promise<void> {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (devWorktreeName) {
|
||||||
|
app.dock?.setBadge(devWorktreeName);
|
||||||
|
}
|
||||||
|
|
||||||
setupWindowResizeEvents(mainWindow);
|
setupWindowResizeEvents(mainWindow);
|
||||||
setupDefaultContextMenu(mainWindow);
|
setupDefaultContextMenu(mainWindow);
|
||||||
setupDragDropPrevention(mainWindow);
|
setupDragDropPrevention(mainWindow);
|
||||||
|
|||||||
Reference in New Issue
Block a user