mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-26 03:03:04 +00:00
fix(desktop): allow concurrent worktree dev instances
Desktop terminals can inherit another instance's profile path and debugging flags. Scope the profile to the current dev root and select an available debugging port so instances do not share global runtime state.
This commit is contained in:
+11
-4
@@ -99,10 +99,17 @@ The iOS simulator shares the Mac's loopback, so `localhost:<port>` reaches the h
|
||||
|
||||
### Desktop renderer profiling
|
||||
|
||||
`npm run dev:desktop` starts Electron with Chromium remote debugging enabled on
|
||||
`http://127.0.0.1:9223` so renderer CPU profiles can be captured through CDP.
|
||||
It launches its own Electron-flavored Expo server and passes that URL to Electron.
|
||||
Override the CDP port with `PASEO_ELECTRON_REMOTE_DEBUGGING_PORT` when `9223` is busy.
|
||||
`npm run dev:desktop` starts Electron with Chromium remote debugging enabled so
|
||||
renderer CPU profiles can be captured through CDP. It prefers
|
||||
`http://127.0.0.1:9223` and automatically selects a free port when that port is
|
||||
already occupied. The startup banner prints the selected URL. It launches its own
|
||||
Electron-flavored Expo server and passes that URL to Electron. Set
|
||||
`PASEO_ELECTRON_REMOTE_DEBUGGING_PORT` when a QA workflow requires a fixed port.
|
||||
|
||||
Desktop dev also scopes Electron `userData` to the current dev root. This prevents
|
||||
desktop-only environment inherited by terminals opened inside Paseo from coupling
|
||||
a new worktree instance to the parent desktop instance's profile or single-instance
|
||||
lock.
|
||||
|
||||
With desktop dev running, verify the real BrowserWindow, titlebar clearance, fullscreen
|
||||
transition, and 751-pixel settings split with:
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env node
|
||||
import net from "node:net";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const DEFAULT_REMOTE_DEBUGGING_PORT = 9223;
|
||||
|
||||
export function resolveDevUserDataDir({ devRoot, inheritedUserDataDir, fallbackRoot }) {
|
||||
if (devRoot) {
|
||||
return path.join(devRoot, ".dev", "user-data");
|
||||
}
|
||||
if (inheritedUserDataDir) {
|
||||
return inheritedUserDataDir;
|
||||
}
|
||||
if (!fallbackRoot) {
|
||||
throw new Error("A dev root or fallback root is required");
|
||||
}
|
||||
return path.join(fallbackRoot, ".dev", "user-data");
|
||||
}
|
||||
|
||||
export function buildElectronFlags(inheritedFlags, remoteDebuggingPort) {
|
||||
const flags = (inheritedFlags ?? "")
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.filter((flag) => !flag.startsWith("--remote-debugging-port="));
|
||||
flags.push(`--remote-debugging-port=${remoteDebuggingPort}`);
|
||||
return flags.join(" ");
|
||||
}
|
||||
|
||||
function parsePort(value, name) {
|
||||
const port = Number(value);
|
||||
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
|
||||
throw new Error(`${name} must be an integer between 1 and 65535`);
|
||||
}
|
||||
return port;
|
||||
}
|
||||
|
||||
export function findAvailablePort(preferredPort, host = "127.0.0.1") {
|
||||
return new Promise((resolve, reject) => {
|
||||
const server = net.createServer();
|
||||
server.unref();
|
||||
server.once("error", (error) => {
|
||||
if (error.code === "EADDRINUSE" || error.code === "EACCES") {
|
||||
resolve(findAvailablePort(0, host));
|
||||
return;
|
||||
}
|
||||
reject(error);
|
||||
});
|
||||
server.listen(preferredPort, host, () => {
|
||||
const address = server.address();
|
||||
if (!address || typeof address === "string") {
|
||||
server.close();
|
||||
reject(new Error("Could not resolve an available CDP port"));
|
||||
return;
|
||||
}
|
||||
server.close((error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve(address.port);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function selectRemoteDebuggingPort({
|
||||
configuredPort,
|
||||
preferredPort = DEFAULT_REMOTE_DEBUGGING_PORT,
|
||||
findAvailablePort: findPort = findAvailablePort,
|
||||
} = {}) {
|
||||
if (configuredPort) {
|
||||
return parsePort(configuredPort, "PASEO_ELECTRON_REMOTE_DEBUGGING_PORT");
|
||||
}
|
||||
return findPort(preferredPort);
|
||||
}
|
||||
|
||||
export async function resolveDevRuntime(env = process.env) {
|
||||
const remoteDebuggingPort = await selectRemoteDebuggingPort({
|
||||
configuredPort: env.PASEO_ELECTRON_REMOTE_DEBUGGING_PORT,
|
||||
});
|
||||
return {
|
||||
electronFlags: buildElectronFlags(env.PASEO_ELECTRON_FLAGS, remoteDebuggingPort),
|
||||
remoteDebuggingPort,
|
||||
userDataDir: resolveDevUserDataDir({
|
||||
devRoot: env.PASEO_DEV_ROOT,
|
||||
inheritedUserDataDir: env.PASEO_ELECTRON_USER_DATA_DIR,
|
||||
fallbackRoot: env.PASEO_DEV_RUNTIME_FALLBACK_ROOT,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
||||
if (isMain) {
|
||||
try {
|
||||
process.stdout.write(JSON.stringify(await resolveDevRuntime()));
|
||||
} catch (error) {
|
||||
console.error(`[dev-runtime] ${error instanceof Error ? error.message : String(error)}`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import path from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import {
|
||||
buildElectronFlags,
|
||||
resolveDevUserDataDir,
|
||||
selectRemoteDebuggingPort,
|
||||
} from "./dev-runtime.mjs";
|
||||
|
||||
describe("desktop dev runtime isolation", () => {
|
||||
test("scopes inherited user data to the explicitly selected dev root", () => {
|
||||
expect(
|
||||
resolveDevUserDataDir({
|
||||
devRoot: "/worktrees/feature-a",
|
||||
inheritedUserDataDir: "/checkouts/paseo/.dev/user-data",
|
||||
}),
|
||||
).toBe(path.join("/worktrees/feature-a", ".dev", "user-data"));
|
||||
});
|
||||
|
||||
test("keeps an explicit user data override outside a managed dev root", () => {
|
||||
expect(
|
||||
resolveDevUserDataDir({
|
||||
inheritedUserDataDir: "/tmp/paseo-user-data",
|
||||
fallbackRoot: "/checkouts/paseo",
|
||||
}),
|
||||
).toBe("/tmp/paseo-user-data");
|
||||
});
|
||||
|
||||
test("replaces an inherited CDP flag with this instance's selected port", () => {
|
||||
expect(buildElectronFlags("--disable-gpu --remote-debugging-port=9223", 43127)).toBe(
|
||||
"--disable-gpu --remote-debugging-port=43127",
|
||||
);
|
||||
});
|
||||
|
||||
test("selects another CDP port when the default is occupied", async () => {
|
||||
const selectedPort = await selectRemoteDebuggingPort({
|
||||
preferredPort: 9223,
|
||||
findAvailablePort: async (preferredPort) => {
|
||||
expect(preferredPort).toBe(9223);
|
||||
return 43127;
|
||||
},
|
||||
});
|
||||
|
||||
expect(selectedPort).toBe(43127);
|
||||
});
|
||||
|
||||
test("honors an explicit CDP port without silently changing it", async () => {
|
||||
const selectedPort = await selectRemoteDebuggingPort({
|
||||
configuredPort: "9333",
|
||||
findAvailablePort: async () => {
|
||||
throw new Error("must not probe an explicit port");
|
||||
},
|
||||
});
|
||||
|
||||
expect(selectedPort).toBe(9333);
|
||||
});
|
||||
});
|
||||
@@ -33,17 +33,13 @@ try {
|
||||
# Set EXPO_DEV_URL in the environment so Electron inherits it
|
||||
$env:EXPO_DEV_URL = "http://localhost:$($env:EXPO_PORT)"
|
||||
|
||||
$RemoteDebuggingPort = if ($env:PASEO_ELECTRON_REMOTE_DEBUGGING_PORT) {
|
||||
$env:PASEO_ELECTRON_REMOTE_DEBUGGING_PORT
|
||||
} else {
|
||||
"9223"
|
||||
}
|
||||
$ExistingElectronFlags = if ($env:PASEO_ELECTRON_FLAGS) {
|
||||
"$($env:PASEO_ELECTRON_FLAGS) "
|
||||
} else {
|
||||
""
|
||||
}
|
||||
$env:PASEO_ELECTRON_FLAGS = "$($ExistingElectronFlags)--remote-debugging-port=$RemoteDebuggingPort"
|
||||
$env:PASEO_DEV_ROOT = $RootDir
|
||||
$env:PASEO_DEV_RUNTIME_FALLBACK_ROOT = $RootDir
|
||||
$DevRuntime = node "$ScriptDir\dev-runtime.mjs" | ConvertFrom-Json
|
||||
$RemoteDebuggingPort = $DevRuntime.remoteDebuggingPort
|
||||
$env:PASEO_ELECTRON_FLAGS = $DevRuntime.electronFlags
|
||||
$env:PASEO_ELECTRON_USER_DATA_DIR = $DevRuntime.userDataDir
|
||||
Remove-Item Env:\PASEO_DEV_RUNTIME_FALLBACK_ROOT -ErrorAction SilentlyContinue
|
||||
|
||||
# Allow any origin in dev so Electron on random ports works.
|
||||
# SECURITY: wildcard CORS is unsafe in production — only acceptable here because
|
||||
@@ -64,7 +60,6 @@ if (-not $env:PASEO_HOME) {
|
||||
} else {
|
||||
$PaseoHomeManaged = $false
|
||||
}
|
||||
if (-not $env:PASEO_ELECTRON_USER_DATA_DIR) { $env:PASEO_ELECTRON_USER_DATA_DIR = "$DevStateDir\user-data" }
|
||||
New-Item -ItemType Directory -Force -Path $env:PASEO_HOME, $env:PASEO_ELECTRON_USER_DATA_DIR | Out-Null
|
||||
|
||||
$DevDaemonPort = if ($env:PASEO_DEV_DAEMON_PORT) { $env:PASEO_DEV_DAEMON_PORT } else { "6788" }
|
||||
|
||||
@@ -12,7 +12,13 @@ export PASEO_LISTEN="${PASEO_LISTEN:-127.0.0.1:6768}"
|
||||
configure_dev_paseo_home
|
||||
|
||||
DEV_ROOT="${PASEO_DEV_ROOT:-$(default_dev_paseo_root)}"
|
||||
export PASEO_ELECTRON_USER_DATA_DIR="${PASEO_ELECTRON_USER_DATA_DIR:-$DEV_ROOT/.dev/user-data}"
|
||||
export PASEO_DEV_ROOT="$DEV_ROOT"
|
||||
export PASEO_DEV_RUNTIME_FALLBACK_ROOT="$DEV_ROOT"
|
||||
DEV_RUNTIME="$(node "$SCRIPT_DIR/dev-runtime.mjs")"
|
||||
REMOTE_DEBUGGING_PORT="$(node -e 'process.stdout.write(String(JSON.parse(process.argv[1]).remoteDebuggingPort))' "$DEV_RUNTIME")"
|
||||
export PASEO_ELECTRON_FLAGS="$(node -e 'process.stdout.write(JSON.parse(process.argv[1]).electronFlags)' "$DEV_RUNTIME")"
|
||||
export PASEO_ELECTRON_USER_DATA_DIR="$(node -e 'process.stdout.write(JSON.parse(process.argv[1]).userDataDir)' "$DEV_RUNTIME")"
|
||||
unset PASEO_DEV_RUNTIME_FALLBACK_ROOT
|
||||
mkdir -p "$PASEO_ELECTRON_USER_DATA_DIR"
|
||||
|
||||
if [ -z "${EXPO_PORT:-}" ]; then
|
||||
@@ -24,8 +30,6 @@ export EXPO_DEV_URL="http://localhost:${EXPO_PORT}"
|
||||
DAEMON_ENDPOINT="$(resolve_dev_daemon_endpoint)"
|
||||
export PASEO_DAEMON_ENDPOINT="$DAEMON_ENDPOINT"
|
||||
|
||||
REMOTE_DEBUGGING_PORT="${PASEO_ELECTRON_REMOTE_DEBUGGING_PORT:-9223}"
|
||||
export PASEO_ELECTRON_FLAGS="${PASEO_ELECTRON_FLAGS:+$PASEO_ELECTRON_FLAGS }--remote-debugging-port=$REMOTE_DEBUGGING_PORT"
|
||||
export PASEO_CORS_ORIGINS="${PASEO_CORS_ORIGINS:-*}"
|
||||
|
||||
npm run build:main
|
||||
|
||||
Reference in New Issue
Block a user