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.
This commit is contained in:
Mohamed Boudra
2026-07-16 12:04:45 +02:00
committed by GitHub
parent d7ca1b5a03
commit 47532952f3
5 changed files with 383 additions and 153 deletions

View File

@@ -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:

View File

@@ -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:

View File

@@ -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`

View File

@@ -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() || "<empty>"}`,
`App stderr:\n${stderr.join("").trim() || "<empty>"}`,
@@ -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(", ") || "<none>"}`,
);
}
}
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);
}
}

View File

@@ -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<boolean> {
return true;
}
async function runDesktopSmokeIfRequested(): Promise<boolean> {
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<void> {
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<void> {
if (!setupSingleInstanceLock()) {
return;
@@ -912,9 +863,6 @@ async function bootstrap(): Promise<void> {
},
});
ensureNotificationCenterRegistration();
if (await runDesktopSmokeIfRequested()) {
return;
}
registerDaemonManager();
registerWindowManager();
registerDialogHandlers();