From 47532952f325912094593889b65c98ea5aa49912 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Thu, 16 Jul 2026 12:04:45 +0200 Subject: [PATCH] Catch broken desktop packages before release (#2114) * test(desktop): exercise packaged app startup The previous smoke exited before IPC, window creation, and preload execution, so a broken packaged preload could still pass. Launch the normal app and observe its renderer bridge and managed daemon through CDP instead. * test(desktop): preserve Linux sandbox in smoke Configure the unpacked artifact's chrome-sandbox helper with the permissions Electron requires, while keeping sandboxing enabled so preload failures remain observable. Bound startup probes and include process logs in daemon timeout failures. * ci(desktop): keep packaged smoke on releases Remove the temporary pull-request artifact build after it validated the Linux launch path. Keep the real packaged smoke in the native desktop release matrix. * ci(desktop): gate packaged smoke by changes Run the Linux real-artifact smoke in pull-request CI when packages/desktop changes, while leaving the regular desktop unit-test matrix unconditional. --- .github/workflows/ci.yml | 34 ++ .github/workflows/desktop-release.yml | 32 +- docs/testing.md | 21 + .../scripts/smoke-packaged-desktop-app.js | 395 +++++++++++++----- packages/desktop/src/main.ts | 54 +-- 5 files changed, 383 insertions(+), 153 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d55621774..234374fcd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,9 +131,22 @@ jobs: matrix: os: [ubuntu-latest, windows-latest] runs-on: ${{ matrix.os }} + timeout-minutes: 30 + permissions: + contents: read + pull-requests: read steps: - uses: actions/checkout@v4 + - name: Detect desktop changes + if: matrix.os == 'ubuntu-latest' + id: desktop_changes + uses: dorny/paths-filter@v3 + with: + filters: | + desktop: + - 'packages/desktop/**' + - uses: actions/setup-node@v4 with: node-version: "22" @@ -147,6 +160,27 @@ jobs: - name: Run desktop tests run: npm run test --workspace=@getpaseo/desktop + - name: Install virtual display + if: matrix.os == 'ubuntu-latest' && steps.desktop_changes.outputs.desktop == 'true' + run: sudo apt-get update && sudo apt-get install -y xvfb xauth + + - name: Build and smoke unpacked desktop app + if: matrix.os == 'ubuntu-latest' && steps.desktop_changes.outputs.desktop == 'true' + run: npm run build:desktop -- --publish never --linux --x64 --dir + env: + EP_GH_IGNORE_TIME: true + PASEO_DESKTOP_SMOKE: "1" + PASEO_DESKTOP_SMOKE_ARTIFACT_DIR: ${{ runner.temp }}/desktop-smoke + + - name: Upload packaged smoke diagnostics + if: failure() && matrix.os == 'ubuntu-latest' && steps.desktop_changes.outputs.desktop == 'true' + uses: actions/upload-artifact@v4 + with: + name: desktop-packaged-smoke-linux-x64 + path: ${{ runner.temp }}/desktop-smoke + if-no-files-found: ignore + retention-days: 7 + app-tests: runs-on: ubuntu-latest env: diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml index b181f8df9..8f5526336 100644 --- a/.github/workflows/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -158,6 +158,7 @@ jobs: APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} PASEO_DESKTOP_SMOKE: "1" + PASEO_DESKTOP_SMOKE_ARTIFACT_DIR: ${{ runner.temp }}/desktop-smoke run: | set -euo pipefail build_args=(-- --publish never --mac --${{ matrix.electron_arch }}) @@ -165,6 +166,15 @@ jobs: build_args+=("-c.publish.channel=$RELEASE_CHANNEL") npm run build:desktop "${build_args[@]}" + - name: Upload packaged smoke diagnostics + if: failure() + uses: actions/upload-artifact@v4 + with: + name: desktop-smoke-macos-${{ matrix.electron_arch }} + path: ${{ runner.temp }}/desktop-smoke + if-no-files-found: ignore + retention-days: 7 + - name: Upload desktop artifacts to release if: env.SHOULD_PUBLISH == 'true' && env.IS_SMOKE_TAG != 'true' env: @@ -248,7 +258,7 @@ jobs: NODE - name: Install Linux smoke display - run: sudo apt-get update && sudo apt-get install -y xvfb + run: sudo apt-get update && sudo apt-get install -y xvfb xauth - name: Build desktop release shell: bash @@ -256,6 +266,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} EP_GH_IGNORE_TIME: true PASEO_DESKTOP_SMOKE: "1" + PASEO_DESKTOP_SMOKE_ARTIFACT_DIR: ${{ runner.temp }}/desktop-smoke run: | set -euo pipefail build_args=(-- --publish never --linux --x64) @@ -263,6 +274,15 @@ jobs: build_args+=("-c.publish.channel=$RELEASE_CHANNEL") npm run build:desktop "${build_args[@]}" + - name: Upload packaged smoke diagnostics + if: failure() + uses: actions/upload-artifact@v4 + with: + name: desktop-smoke-linux-x64 + path: ${{ runner.temp }}/desktop-smoke + if-no-files-found: ignore + retention-days: 7 + - name: Upload desktop artifacts to release if: env.SHOULD_PUBLISH == 'true' && env.IS_SMOKE_TAG != 'true' env: @@ -351,6 +371,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} EP_GH_IGNORE_TIME: true PASEO_DESKTOP_SMOKE: "1" + PASEO_DESKTOP_SMOKE_ARTIFACT_DIR: ${{ runner.temp }}/desktop-smoke run: | set -euo pipefail build_args=(-- --publish never --win --x64 --arm64) @@ -358,6 +379,15 @@ jobs: build_args+=("-c.publish.channel=$RELEASE_CHANNEL") npm run build:desktop "${build_args[@]}" + - name: Upload packaged smoke diagnostics + if: failure() + uses: actions/upload-artifact@v4 + with: + name: desktop-smoke-windows-x64 + path: ${{ runner.temp }}/desktop-smoke + if-no-files-found: ignore + retention-days: 7 + - name: Upload desktop artifacts to release if: env.SHOULD_PUBLISH == 'true' && env.IS_SMOKE_TAG != 'true' env: diff --git a/docs/testing.md b/docs/testing.md index 7bb773482..bea99d889 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -91,6 +91,27 @@ function createTestEmailSender() { When a test is labeled end-to-end, it calls the real service. No environment variable gates, no conditional skipping, no mocking the external dependency. +### Packaged desktop smoke + +The packaged desktop smoke is an external observer of the production launch path. It must not add a smoke-only branch to Electron main or start the daemon itself. + +The harness launches the unpacked packaged app with isolated user data and daemon state, connects to the real renderer over Chromium's debugging protocol, and requires all of these outcomes: + +- the `paseo://app/` renderer mounts into `#root`; +- the sandboxed preload exposes the desktop bridge; +- the renderer starts a fresh desktop-managed daemon through the normal startup bootstrap; +- the bundled CLI can query that daemon and run a terminal command. + +Pull-request CI runs the Linux x64 smoke under Xvfb when the cumulative PR diff changes `packages/desktop/**`. The desktop release matrix runs the harness against each host-native packaged app before publishing. All smoke jobs upload renderer, desktop, and daemon diagnostics on failure. + +To exercise the smoke locally on Linux: + +```bash +PASEO_DESKTOP_SMOKE=1 \ +PASEO_DESKTOP_SMOKE_ARTIFACT_DIR=/tmp/paseo-desktop-smoke \ +npm run build:desktop -- --publish never --linux --x64 --dir +``` + ## Test organization - Collocate tests with implementation: `thing.ts` + `thing.test.ts` diff --git a/packages/desktop/scripts/smoke-packaged-desktop-app.js b/packages/desktop/scripts/smoke-packaged-desktop-app.js index c7c983871..f86deec7c 100644 --- a/packages/desktop/scripts/smoke-packaged-desktop-app.js +++ b/packages/desktop/scripts/smoke-packaged-desktop-app.js @@ -4,12 +4,27 @@ const net = require("node:net"); const os = require("node:os"); const path = require("node:path"); const { setTimeout: delay } = require("node:timers/promises"); +const { chromium } = require("playwright"); const EXECUTABLE_NAME = "Paseo"; const SMOKE_TIMEOUT_MS = 60_000; const EXIT_TIMEOUT_MS = 10_000; const TERMINAL_CAPTURE_ATTEMPTS = 20; const TERMINAL_CAPTURE_INTERVAL_MS = 500; +const REQUIRED_DESKTOP_BRIDGE_KEYS = [ + "platform", + "invoke", + "getPendingOpenProject", + "events", + "window", + "dialog", + "notification", + "opener", + "editor", + "webUtils", + "menu", + "browser", +]; function createTempDir(prefix) { return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); @@ -52,6 +67,41 @@ function getMacMainExecutablePath(appPath) { return path.join(appPath, "Contents", "MacOS", EXECUTABLE_NAME); } +function ensureLinuxSandboxPermissions(appPath) { + if (process.platform !== "linux") { + return; + } + + const sandboxPath = path.join(appPath, "chrome-sandbox"); + if (!fs.existsSync(sandboxPath)) { + throw new Error(`Chromium sandbox helper does not exist: ${sandboxPath}`); + } + + const hasRequiredPermissions = () => { + const stat = fs.statSync(sandboxPath); + return stat.uid === 0 && (stat.mode & 0o7777) === 0o4755; + }; + if (hasRequiredPermissions()) { + return; + } + + const chown = spawnSync("sudo", ["-n", "chown", "root:root", sandboxPath], { + encoding: "utf8", + }); + const chmod = + chown.status === 0 + ? spawnSync("sudo", ["-n", "chmod", "4755", sandboxPath], { encoding: "utf8" }) + : null; + if (chown.error || chown.status !== 0 || chmod?.error || chmod?.status !== 0) { + throw new Error( + `Failed to configure Chromium sandbox helper ${sandboxPath}. Run: sudo chown root:root ${sandboxPath} && sudo chmod 4755 ${sandboxPath}.\n${chown.stderr?.trim() || chmod?.stderr?.trim() || chown.error || chmod?.error || "Permissions remained incorrect."}`, + ); + } + if (!hasRequiredPermissions()) { + throw new Error(`Chromium sandbox helper permissions remained incorrect: ${sandboxPath}`); + } +} + function getLaunchCommand(executablePath) { if (process.platform !== "linux") { return { @@ -62,7 +112,7 @@ function getLaunchCommand(executablePath) { return { command: "xvfb-run", - args: ["-a", executablePath], + args: ["-a", "--server-args=-screen 0 1280x800x24", executablePath], }; } @@ -103,6 +153,34 @@ function createDefaultDaemonEnv(extraEnv) { return env; } +function createIsolatedDesktopEnv({ home, listen, userData, cdpPort }) { + return { + ...process.env, + PASEO_HOME: home, + PASEO_LISTEN: listen, + PASEO_ELECTRON_USER_DATA_DIR: userData, + PASEO_ELECTRON_FLAGS: `--remote-debugging-address=127.0.0.1 --remote-debugging-port=${cdpPort}`, + }; +} + +function configureIsolatedDaemonHome(home, listen) { + fs.writeFileSync( + path.join(home, "config.json"), + `${JSON.stringify( + { + version: 1, + daemon: { + listen, + relay: { enabled: false }, + mcp: { enabled: false, injectIntoAgents: false }, + }, + }, + null, + 2, + )}\n`, + ); +} + function reserveLocalTcpPort() { return new Promise((resolve, reject) => { const server = net.createServer(); @@ -205,32 +283,13 @@ function assertDarwinProcessDoesNotUseMainAppExecutable({ appPath, pid, label }) } } -function parseSmokeLine(line) { - const prefix = "[paseo-smoke] "; - if (!line.startsWith(prefix)) { - return null; - } - return JSON.parse(line.slice(prefix.length)); -} - -function appendChunk(lines, chunk, onSmokeMessage) { - const text = chunk.toString(); - lines.push(text); - for (const line of text.split(/\r?\n/)) { - const parsed = parseSmokeLine(line.trim()); - if (parsed) { - onSmokeMessage(parsed); - } - } -} - function readIfExists(filePath) { return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : null; } -function formatLogs({ stdout, stderr, userData }) { +function formatLogs({ stdout, stderr, userData, daemonHome }) { const desktopLog = readIfExists(path.join(userData, "logs", "main.log")); - const daemonLog = readIfExists(path.join(os.homedir(), ".paseo", "daemon.log")); + const daemonLog = readIfExists(path.join(daemonHome, "daemon.log")); return [ `App stdout:\n${stdout.join("").trim() || ""}`, `App stderr:\n${stderr.join("").trim() || ""}`, @@ -239,6 +298,55 @@ function formatLogs({ stdout, stderr, userData }) { ].join("\n\n"); } +async function writeFailureArtifacts({ page, stdout, stderr, userData, daemonHome, error }) { + const artifactDir = process.env.PASEO_DESKTOP_SMOKE_ARTIFACT_DIR?.trim(); + if (!artifactDir) { + return; + } + + fs.mkdirSync(artifactDir, { recursive: true }); + fs.writeFileSync( + path.join(artifactDir, "failure.txt"), + `${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n\n${formatLogs({ + stdout, + stderr, + userData, + daemonHome, + })}\n`, + ); + + const desktopLog = readIfExists(path.join(userData, "logs", "main.log")); + if (desktopLog !== null) { + fs.writeFileSync(path.join(artifactDir, "desktop-main.log"), desktopLog); + } + const daemonLog = readIfExists(path.join(daemonHome, "daemon.log")); + if (daemonLog !== null) { + fs.writeFileSync(path.join(artifactDir, "daemon.log"), daemonLog); + } + + if (page) { + const renderer = await page + .evaluate(() => ({ + url: window.location.href, + title: document.title, + rootChildCount: document.querySelector("#root")?.childElementCount ?? 0, + rootText: document.querySelector("#root")?.textContent?.trim().slice(0, 2_000) ?? "", + bridgeKeys: + typeof window.paseoDesktop === "object" && window.paseoDesktop !== null + ? Object.keys(window.paseoDesktop) + : [], + })) + .catch((evaluationError) => ({ evaluationError: String(evaluationError) })); + fs.writeFileSync( + path.join(artifactDir, "renderer.json"), + `${JSON.stringify(renderer, null, 2)}\n`, + ); + await page + .screenshot({ path: path.join(artifactDir, "renderer.png"), fullPage: true }) + .catch(() => undefined); + } +} + function isRunning(child) { return child.exitCode === null && child.signalCode === null; } @@ -301,70 +409,119 @@ async function removeTempDir(tempDir) { } } -function waitForSmokeMessage({ child, stdout, stderr, userData, type, validate }) { - return new Promise((resolve, reject) => { - let settled = false; - - const finish = (callback, value) => { - if (settled) { - return; - } - settled = true; - clearTimeout(timer); - callback(value); - }; - - const timer = setTimeout(() => { - terminateChild(child); - finish( - reject, - new Error( - `Timed out waiting for packaged desktop smoke result.\n${formatLogs({ - stdout, - stderr, - userData, - })}`, - ), - ); - }, SMOKE_TIMEOUT_MS); - - const onSmokeMessage = (message) => { - if (message.type !== type) { - return; - } - if (validate(message)) { - finish(resolve, message); - return; - } - finish(reject, new Error(`Unexpected desktop smoke message: ${JSON.stringify(message)}`)); - }; - - child.stdout.on("data", (chunk) => appendChunk(stdout, chunk, onSmokeMessage)); - child.stderr.on("data", (chunk) => appendChunk(stderr, chunk, onSmokeMessage)); - child.once("error", (error) => finish(reject, error)); - child.once("exit", (code, signal) => { - if (settled) { - return; - } - finish( - reject, - new Error( - `Packaged app exited before reporting smoke success (code ${code}, signal ${ - signal ?? "none" - }).\n${formatLogs({ stdout, stderr, userData })}`, - ), - ); - }); - }); +function remainingTime(deadline) { + return Math.max(1, deadline - Date.now()); } -function assertRunningDesktopManagedDaemon(message) { - return ( - message.status?.status === "running" && - message.status?.desktopManaged === true && - typeof message.status?.pid === "number" && - typeof message.status?.listen === "string" && - message.status.listen.length > 0 +async function connectToPackagedApp({ + child, + cdpPort, + stdout, + stderr, + userData, + daemonHome, + deadline, +}) { + let lastError = null; + + while (Date.now() < deadline) { + if (!isRunning(child)) { + throw new Error( + `Packaged app exited before opening its debugging endpoint (code ${child.exitCode}, signal ${ + child.signalCode ?? "none" + }).\n${formatLogs({ stdout, stderr, userData, daemonHome })}`, + ); + } + + try { + return await chromium.connectOverCDP(`http://127.0.0.1:${cdpPort}`); + } catch (error) { + lastError = error; + await delay(250); + } + } + + throw new Error( + `Timed out connecting to the packaged app over CDP: ${lastError}.\n${formatLogs({ + stdout, + stderr, + userData, + daemonHome, + })}`, + ); +} + +async function waitForPackagedAppPage(browser, deadline) { + while (Date.now() < deadline) { + const page = browser + .contexts() + .flatMap((context) => context.pages()) + .find((candidate) => candidate.url().startsWith("paseo://app/")); + if (page) { + return page; + } + await delay(250); + } + throw new Error("Timed out waiting for the packaged paseo://app/ renderer"); +} + +async function assertPackagedRendererLoaded(page, deadline) { + await page.waitForFunction( + () => { + const root = document.querySelector("#root"); + return root instanceof HTMLElement && root.childElementCount > 0; + }, + undefined, + { timeout: remainingTime(deadline) }, + ); + + const bridgeKeys = await page.evaluate(() => + typeof window.paseoDesktop === "object" && window.paseoDesktop !== null + ? Object.keys(window.paseoDesktop) + : [], + ); + const missingBridgeKeys = REQUIRED_DESKTOP_BRIDGE_KEYS.filter((key) => !bridgeKeys.includes(key)); + if (missingBridgeKeys.length > 0) { + throw new Error( + `Packaged renderer is missing desktop preload bridge keys: ${missingBridgeKeys.join(", ")}. Present keys: ${bridgeKeys.join(", ") || ""}`, + ); + } +} + +async function waitForRendererStartedDaemon({ + page, + daemonHome, + listen, + stdout, + stderr, + userData, + deadline, +}) { + let lastStatus = null; + let lastError = null; + + while (Date.now() < deadline) { + try { + lastStatus = await page.evaluate(() => window.paseoDesktop.invoke("desktop_daemon_status")); + if ( + lastStatus?.status === "running" && + lastStatus.desktopManaged === true && + typeof lastStatus.pid === "number" && + typeof lastStatus.serverId === "string" && + lastStatus.serverId.length > 0 && + lastStatus.listen === listen && + path.resolve(lastStatus.home) === path.resolve(daemonHome) + ) { + return lastStatus; + } + } catch (error) { + lastError = error; + } + await delay(250); + } + + throw new Error( + `Packaged renderer did not start its desktop-managed daemon. Last status: ${JSON.stringify(lastStatus)}. Last error: ${lastError}.\n${formatLogs({ stdout, stderr, userData, daemonHome })}`, ); } @@ -634,12 +791,26 @@ async function stopCliDaemon({ appPath, env }) { async function smokePackagedDesktopApp({ appPath }) { const executablePath = getExecutablePath(appPath); assertExecutable(executablePath, "Packaged app executable"); + ensureLinuxSandboxPermissions(appPath); await smokeColdCliDaemonStart({ appPath }); const userData = createTempDir("paseo-smoke-user-data-"); - const env = createDefaultDaemonEnv({ - PASEO_DESKTOP_SMOKE: "1", - PASEO_ELECTRON_USER_DATA_DIR: userData, + const daemonHome = createTempDir("paseo-smoke-daemon-home-"); + const daemonPort = await reserveLocalTcpPort(); + let cdpPort = await reserveLocalTcpPort(); + for (let attempt = 0; cdpPort === daemonPort && attempt < 10; attempt += 1) { + cdpPort = await reserveLocalTcpPort(); + } + if (cdpPort === daemonPort) { + throw new Error("Failed to reserve distinct TCP ports for the daemon and CDP"); + } + const listen = `127.0.0.1:${daemonPort}`; + configureIsolatedDaemonHome(daemonHome, listen); + const env = createIsolatedDesktopEnv({ + home: daemonHome, + listen, + userData, + cdpPort, }); const stdout = []; @@ -651,7 +822,15 @@ async function smokePackagedDesktopApp({ appPath }) { env, stdio: ["pipe", "pipe", "pipe"], }); - let smokeStarted = false; + child.stdout.on("data", (chunk) => stdout.push(chunk.toString())); + child.stderr.on("data", (chunk) => stderr.push(chunk.toString())); + child.once("error", (error) => + stderr.push(`Packaged app launch error: ${error.stack ?? error}\n`), + ); + const deadline = Date.now() + SMOKE_TIMEOUT_MS; + + let browser = null; + let page = null; let daemonStopped = false; const stopDaemonForCleanup = async () => { @@ -659,36 +838,53 @@ async function smokePackagedDesktopApp({ appPath }) { return; } - await stopCliDaemon({ appPath, env: createDefaultDaemonEnv() }); + await stopCliDaemon({ appPath, env }); daemonStopped = true; }; try { - const message = await waitForSmokeMessage({ + browser = await connectToPackagedApp({ child, + cdpPort, stdout, stderr, userData, - type: "desktop-daemon-smoke-started", - validate: assertRunningDesktopManagedDaemon, + daemonHome, + deadline, }); - smokeStarted = true; - console.log("Packaged desktop smoke: desktop-managed daemon reported running"); - const cliEnv = createDefaultDaemonEnv(); - await smokeCliShim({ appPath, env: cliEnv }); - await smokeCliTerminal({ appPath, env: cliEnv }); + page = await waitForPackagedAppPage(browser, deadline); + await assertPackagedRendererLoaded(page, deadline); + console.log("Packaged desktop smoke: real app renderer and preload bridge loaded"); + const status = await waitForRendererStartedDaemon({ + page, + daemonHome, + listen, + stdout, + stderr, + userData, + deadline, + }); + console.log("Packaged desktop smoke: renderer-started desktop daemon reported running"); + await smokeCliShim({ appPath, env }); + await smokeCliTerminal({ appPath, env }); await stopDaemonForCleanup(); console.log( - `Packaged desktop smoke passed: desktop-managed daemon pid ${message.status.pid}, listen ${message.status.listen}; CLI shim daemon status and terminal smoke succeeded`, + `Packaged desktop smoke passed: real renderer and preload loaded; renderer-started desktop daemon pid ${status.pid}, listen ${status.listen}; CLI shim daemon status and terminal smoke succeeded`, ); } catch (error) { - if (smokeStarted && !daemonStopped) { + await writeFailureArtifacts({ page, stdout, stderr, userData, daemonHome, error }).catch( + (artifactError) => { + console.warn(`Packaged desktop smoke: failed to write failure artifacts: ${artifactError}`); + }, + ); + if (!daemonStopped) { try { await stopDaemonForCleanup(); } catch {} } throw error; } finally { + await browser?.close().catch(() => undefined); if (isRunning(child)) { terminateChild(child); if (!(await waitForChildExit(child))) { @@ -698,6 +894,7 @@ async function smokePackagedDesktopApp({ appPath }) { } releaseChildHandles(child); await removeTempDir(userData); + await removeTempDir(daemonHome); } } diff --git a/packages/desktop/src/main.ts b/packages/desktop/src/main.ts index 28bd4fbe9..ade45e013 100644 --- a/packages/desktop/src/main.ts +++ b/packages/desktop/src/main.ts @@ -23,7 +23,7 @@ import { session, webContents, } from "electron"; -import { createDaemonCommandHandlers, registerDaemonManager } from "./daemon/daemon-manager.js"; +import { registerDaemonManager } from "./daemon/daemon-manager.js"; import { parsePassthroughCliArgsFromArgv, runPassthroughCli } from "./daemon/cli/passthrough.js"; import { closeAllTransportSessions } from "./daemon/local-transport.js"; import { @@ -121,8 +121,6 @@ const FORWARDED_PASEO_SHORTCUT_KEYS = new Set([ "arrowup", "arrowdown", ]); -const DESKTOP_SMOKE_ENV = "PASEO_DESKTOP_SMOKE"; -const DESKTOP_SMOKE_STOP_REQUEST = "paseo-smoke-stop"; app.setName(APP_NAME); interface AttachedBrowserInput { @@ -822,53 +820,6 @@ async function runCliPassthroughIfRequested(): Promise { return true; } -async function runDesktopSmokeIfRequested(): Promise { - if (process.env[DESKTOP_SMOKE_ENV] !== "1") { - return false; - } - - const handlers = createDaemonCommandHandlers(); - const startStatus = await handlers.start_desktop_daemon(); - process.stdout.write( - `[paseo-smoke] ${JSON.stringify({ - type: "desktop-daemon-smoke-started", - status: startStatus, - })}\n`, - ); - - await waitForDesktopSmokeStopRequest(); - - const stopStatus = await handlers.stop_desktop_daemon(); - process.stdout.write( - `[paseo-smoke] ${JSON.stringify({ - type: "desktop-daemon-smoke-stopped", - stopStatus, - })}\n`, - ); - - app.exit(0); - return true; -} - -function waitForDesktopSmokeStopRequest(): Promise { - return new Promise((resolve) => { - let buffer = ""; - const stop = () => { - process.stdin.off("data", onData); - resolve(); - }; - const onData = (chunk: Buffer | string) => { - buffer += chunk.toString(); - if (buffer.includes(DESKTOP_SMOKE_STOP_REQUEST)) { - stop(); - } - }; - - process.stdin.on("data", onData); - process.stdin.resume(); - }); -} - async function bootstrap(): Promise { if (!setupSingleInstanceLock()) { return; @@ -912,9 +863,6 @@ async function bootstrap(): Promise { }, }); ensureNotificationCenterRegistration(); - if (await runDesktopSmokeIfRequested()) { - return; - } registerDaemonManager(); registerWindowManager(); registerDialogHandlers();