From 48a411690adc635df250820a91e81d943cd8a2a0 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Mon, 16 Mar 2026 22:22:19 -0500 Subject: [PATCH] chore(desktop): bump xterm betas and stabilize CI --- apps/desktop/package.json | 20 +++---- .../src/main/lib/host-service-manager.test.ts | 56 +++++++++++++------ .../src/main/lib/host-service-manager.ts | 5 +- .../window-state/bounds-validation.test.ts | 21 ++++--- .../lib/window-state/bounds-validation.ts | 38 +++++++++++-- bun.lock | 40 ++++++------- 6 files changed, 118 insertions(+), 62 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 29a085c597..14a5657c2c 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -134,16 +134,16 @@ "@types/express": "^5.0.5", "@types/pidusage": "^2.0.5", "@vercel/blob": "^2.0.0", - "@xterm/addon-clipboard": "0.3.0-beta.148", - "@xterm/addon-fit": "0.12.0-beta.148", - "@xterm/addon-image": "0.10.0-beta.148", - "@xterm/addon-ligatures": "0.11.0-beta.148", - "@xterm/addon-search": "0.17.0-beta.148", - "@xterm/addon-serialize": "0.15.0-beta.148", - "@xterm/addon-unicode11": "0.10.0-beta.148", - "@xterm/addon-webgl": "0.20.0-beta.147", - "@xterm/headless": "6.1.0-beta.148", - "@xterm/xterm": "6.1.0-beta.148", + "@xterm/addon-clipboard": "0.3.0-beta.195", + "@xterm/addon-fit": "0.12.0-beta.195", + "@xterm/addon-image": "0.10.0-beta.195", + "@xterm/addon-ligatures": "0.11.0-beta.195", + "@xterm/addon-search": "0.17.0-beta.195", + "@xterm/addon-serialize": "0.15.0-beta.195", + "@xterm/addon-unicode11": "0.10.0-beta.195", + "@xterm/addon-webgl": "0.20.0-beta.194", + "@xterm/headless": "6.1.0-beta.195", + "@xterm/xterm": "6.1.0-beta.195", "ai": "^6.0.0", "better-auth": "1.4.18", "better-sqlite3": "12.6.2", diff --git a/apps/desktop/src/main/lib/host-service-manager.test.ts b/apps/desktop/src/main/lib/host-service-manager.test.ts index ea9c26b0f6..5e19bd909f 100644 --- a/apps/desktop/src/main/lib/host-service-manager.test.ts +++ b/apps/desktop/src/main/lib/host-service-manager.test.ts @@ -1,4 +1,13 @@ -import { beforeEach, describe, expect, it, mock } from "bun:test"; +import { + afterAll, + beforeAll, + beforeEach, + describe, + expect, + it, + mock, + spyOn, +} from "bun:test"; import type { ChildProcess } from "node:child_process"; import { EventEmitter } from "node:events"; @@ -23,29 +32,42 @@ const getProcessEnvWithShellPathMock = mock( async (env: Record) => env, ); let lastChild: MockChildProcess | null = null; -const spawnMock = mock(() => { +const spawnMock = mock((..._args: unknown[]) => { lastChild = new MockChildProcess(); return lastChild as unknown as ChildProcess; }); +let HostServiceManager: typeof import("./host-service-manager").HostServiceManager; -mock.module("electron", () => ({ - app: { - isPackaged: false, - getAppPath: () => "/tmp/app", - }, -})); - -mock.module("../../lib/trpc/routers/workspaces/utils/shell-env", () => ({ - getProcessEnvWithShellPath: getProcessEnvWithShellPathMock, -})); +describe("HostServiceManager", () => { + beforeAll(async () => { + const childProcessModule = await import("node:child_process"); + const shellEnvModule = await import( + "../../lib/trpc/routers/workspaces/utils/shell-env" + ); -mock.module("node:child_process", () => ({ - spawn: spawnMock, -})); + spyOn(childProcessModule, "spawn").mockImplementation(((..._args) => + spawnMock(..._args)) as typeof childProcessModule.spawn); + spyOn(shellEnvModule, "getProcessEnvWithShellPath").mockImplementation((( + baseEnv: NodeJS.ProcessEnv = process.env, + ) => + getProcessEnvWithShellPathMock( + baseEnv as Record, + )) as typeof shellEnvModule.getProcessEnvWithShellPath); + + mock.module("electron", () => ({ + app: { + isPackaged: false, + getAppPath: () => "/tmp/app", + }, + })); + + ({ HostServiceManager } = await import("./host-service-manager")); + }); -const { HostServiceManager } = await import("./host-service-manager"); + afterAll(() => { + mock.restore(); + }); -describe("HostServiceManager", () => { beforeEach(() => { getProcessEnvWithShellPathMock.mockReset(); getProcessEnvWithShellPathMock.mockImplementation( diff --git a/apps/desktop/src/main/lib/host-service-manager.ts b/apps/desktop/src/main/lib/host-service-manager.ts index 218cce9c81..b64621c99d 100644 --- a/apps/desktop/src/main/lib/host-service-manager.ts +++ b/apps/desktop/src/main/lib/host-service-manager.ts @@ -1,4 +1,5 @@ -import { type ChildProcess, spawn } from "node:child_process"; +import type { ChildProcess } from "node:child_process"; +import * as childProcess from "node:child_process"; import path from "node:path"; import { app } from "electron"; import { getProcessEnvWithShellPath } from "../../lib/trpc/routers/workspaces/utils/shell-env"; @@ -125,7 +126,7 @@ export class HostServiceManager { throw new Error("Host service start cancelled"); } - const child = spawn(process.execPath, [this.scriptPath], { + const child = childProcess.spawn(process.execPath, [this.scriptPath], { stdio: ["ignore", "pipe", "pipe"], env, }); diff --git a/apps/desktop/src/main/lib/window-state/bounds-validation.test.ts b/apps/desktop/src/main/lib/window-state/bounds-validation.test.ts index febf5141b1..9bba586c62 100644 --- a/apps/desktop/src/main/lib/window-state/bounds-validation.test.ts +++ b/apps/desktop/src/main/lib/window-state/bounds-validation.test.ts @@ -1,4 +1,5 @@ import { + afterAll, beforeEach, describe, expect, @@ -7,7 +8,6 @@ import { mock, } from "bun:test"; -// Mock electron with screen API before importing anything that uses it const mockScreen = { getPrimaryDisplay: mock(() => ({ workAreaSize: { width: 1920, height: 1080 }, @@ -21,19 +21,22 @@ const mockScreen = { ]), }; -mock.module("electron", () => ({ - screen: mockScreen, -})); - -// Import module after mocks are set up -const { getInitialWindowBounds, isVisibleOnAnyDisplay } = await import( - "./bounds-validation" -); +const { getInitialWindowBounds, isVisibleOnAnyDisplay, setScreenForTesting } = + await import("./bounds-validation"); const screen = mockScreen; const MIN_VISIBLE_OVERLAP = 50; const MIN_WINDOW_SIZE = 400; +beforeEach(() => { + setScreenForTesting(screen); +}); + +afterAll(() => { + setScreenForTesting(null); + mock.restore(); +}); + describe("isVisibleOnAnyDisplay", () => { describe("single display setup", () => { beforeEach(() => { diff --git a/apps/desktop/src/main/lib/window-state/bounds-validation.ts b/apps/desktop/src/main/lib/window-state/bounds-validation.ts index 0398477314..fa70718d97 100644 --- a/apps/desktop/src/main/lib/window-state/bounds-validation.ts +++ b/apps/desktop/src/main/lib/window-state/bounds-validation.ts @@ -1,16 +1,46 @@ import type { Rectangle } from "electron"; -import { screen } from "electron"; import type { WindowState } from "./window-state"; const MIN_VISIBLE_OVERLAP = 50; const MIN_WINDOW_SIZE = 400; +interface DisplayBoundsLike { + bounds: Rectangle; +} + +interface PrimaryDisplayLike { + workAreaSize: { + width: number; + height: number; + }; +} + +interface ScreenLike { + getAllDisplays(): DisplayBoundsLike[]; + getPrimaryDisplay(): PrimaryDisplayLike; +} + +let screenOverride: ScreenLike | null = null; + +function getScreen(): ScreenLike { + if (screenOverride) { + return screenOverride; + } + + // Resolve Electron lazily so Bun tests can inject a stub without relying on + // its unsupported named-export handling for the "electron" package. + return (require("electron") as typeof import("electron")).screen; +} + +export function setScreenForTesting(screen: ScreenLike | null): void { + screenOverride = screen; +} /** * Checks if bounds overlap at least MIN_VISIBLE_OVERLAP pixels with any display. * Returns false if window would be completely off-screen (e.g., monitor disconnected). */ export function isVisibleOnAnyDisplay(bounds: Rectangle): boolean { - const displays = screen.getAllDisplays(); + const displays = getScreen().getAllDisplays(); return displays.some((display) => { const db = display.bounds; @@ -31,7 +61,7 @@ function clampToWorkArea( width: number, height: number, ): { width: number; height: number } { - const { workAreaSize } = screen.getPrimaryDisplay(); + const { workAreaSize } = getScreen().getPrimaryDisplay(); return { width: Math.min(Math.max(width, MIN_WINDOW_SIZE), workAreaSize.width), height: Math.min(Math.max(height, MIN_WINDOW_SIZE), workAreaSize.height), @@ -57,7 +87,7 @@ export interface InitialWindowBounds { export function getInitialWindowBounds( savedState: WindowState | null, ): InitialWindowBounds { - const { workAreaSize } = screen.getPrimaryDisplay(); + const { workAreaSize } = getScreen().getPrimaryDisplay(); // No saved state → default to primary display size, centered if (!savedState) { diff --git a/bun.lock b/bun.lock index 9a1fc3f0e6..646683f78c 100644 --- a/bun.lock +++ b/bun.lock @@ -211,16 +211,16 @@ "@types/express": "^5.0.5", "@types/pidusage": "^2.0.5", "@vercel/blob": "^2.0.0", - "@xterm/addon-clipboard": "0.3.0-beta.148", - "@xterm/addon-fit": "0.12.0-beta.148", - "@xterm/addon-image": "0.10.0-beta.148", - "@xterm/addon-ligatures": "0.11.0-beta.148", - "@xterm/addon-search": "0.17.0-beta.148", - "@xterm/addon-serialize": "0.15.0-beta.148", - "@xterm/addon-unicode11": "0.10.0-beta.148", - "@xterm/addon-webgl": "0.20.0-beta.147", - "@xterm/headless": "6.1.0-beta.148", - "@xterm/xterm": "6.1.0-beta.148", + "@xterm/addon-clipboard": "0.3.0-beta.195", + "@xterm/addon-fit": "0.12.0-beta.195", + "@xterm/addon-image": "0.10.0-beta.195", + "@xterm/addon-ligatures": "0.11.0-beta.195", + "@xterm/addon-search": "0.17.0-beta.195", + "@xterm/addon-serialize": "0.15.0-beta.195", + "@xterm/addon-unicode11": "0.10.0-beta.195", + "@xterm/addon-webgl": "0.20.0-beta.194", + "@xterm/headless": "6.1.0-beta.195", + "@xterm/xterm": "6.1.0-beta.195", "ai": "^6.0.0", "better-auth": "1.4.18", "better-sqlite3": "12.6.2", @@ -2846,25 +2846,25 @@ "@xmldom/xmldom": ["@xmldom/xmldom@0.8.11", "", {}, "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw=="], - "@xterm/addon-clipboard": ["@xterm/addon-clipboard@0.3.0-beta.148", "", { "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { "@xterm/xterm": "^6.1.0-beta.148" } }, "sha512-GTcpI5dPvbhmeWFw4Xo7cP7c1ocnybb/NNj0AhuEQknqk3AqHkhLsMzlQ5CsYEMRB/wkUZjkMrhoRxNBg1Q6eQ=="], + "@xterm/addon-clipboard": ["@xterm/addon-clipboard@0.3.0-beta.195", "", { "dependencies": { "js-base64": "^3.7.5" }, "peerDependencies": { "@xterm/xterm": "^6.1.0-beta.195" } }, "sha512-V266O7m5wtLCcnXGUbi70cl96vyBcwaVTImftBEBXAo6X+Pn5NpG48QgP+svtt0gB7Sc0FDA/TBVeXuFyoX+kA=="], - "@xterm/addon-fit": ["@xterm/addon-fit@0.12.0-beta.148", "", { "peerDependencies": { "@xterm/xterm": "^6.1.0-beta.148" } }, "sha512-oBcSA8OP/rbFMLZHV1SoeJ3qPKfROBzKuwNqOe9Z+Rp/bOv84GRvPv6N0OszepJXFNf7WLDF/2uP0IXC4ANHPw=="], + "@xterm/addon-fit": ["@xterm/addon-fit@0.12.0-beta.195", "", { "peerDependencies": { "@xterm/xterm": "^6.1.0-beta.195" } }, "sha512-Ihc+azRK3HFB2NVBEoWRkEUGYVxoojK2X4Jx6YxiRKdAu6bYzDTzTImE/0EDOjjz2AUUqddRwCUdSbz2/WvYfA=="], - "@xterm/addon-image": ["@xterm/addon-image@0.10.0-beta.148", "", { "peerDependencies": { "@xterm/xterm": "^6.1.0-beta.148" } }, "sha512-RsSCzmNFqVr7r/5YDy6xiNF7gQeWk8WSpx8BXl+CA/7XaIEhrdLCjzeNXiVPHUMpebuyNAqFWVNqGRM5kcRK6g=="], + "@xterm/addon-image": ["@xterm/addon-image@0.10.0-beta.195", "", { "peerDependencies": { "@xterm/xterm": "^6.1.0-beta.195" } }, "sha512-jm6uzMpfXXut+Yfza/GNCUw2pOw3Bpsn4QEHvh5KCc8dfEuyoLeqmn026Y2kXY2BGoxj+DEHlmlPnL54ihCb2Q=="], - "@xterm/addon-ligatures": ["@xterm/addon-ligatures@0.11.0-beta.148", "", { "dependencies": { "lru-cache": "^6.0.0", "opentype.js": "^0.8.0" }, "peerDependencies": { "@xterm/xterm": "^6.1.0-beta.148" } }, "sha512-cc/Ce+BrC9RHgdb6zBW4/t4cpwmV1vxkZMuxn2oCEnxl8KX8UBH1ajKQBPBgeMT7rWUHK3gJ25g/lZ5h4kLEWQ=="], + "@xterm/addon-ligatures": ["@xterm/addon-ligatures@0.11.0-beta.195", "", { "dependencies": { "lru-cache": "^6.0.0", "opentype.js": "^0.8.0" }, "peerDependencies": { "@xterm/xterm": "^6.1.0-beta.195" } }, "sha512-sGVuanoIVynj01vRlV8nT9cCPHGJU4zzlju0g37KiKn8C+W8sAQ7TlC+XUqAZTwClWMyrozRxETWOnKlfRsZKw=="], - "@xterm/addon-search": ["@xterm/addon-search@0.17.0-beta.148", "", { "peerDependencies": { "@xterm/xterm": "^6.1.0-beta.148" } }, "sha512-ayEIT2M8esFqrGJc/1xY5ukccKa//1q1BPM4WfAo5X1B7SI570uyRDXPZHXZyyGrmVAQtnYJ0h0OmYyQ9v7m5g=="], + "@xterm/addon-search": ["@xterm/addon-search@0.17.0-beta.195", "", { "peerDependencies": { "@xterm/xterm": "^6.1.0-beta.195" } }, "sha512-n7sQ3u1e1gSfKJvhKCz3W/Ov7KLAOB8auC9m+7sSaFgPCuFfi2/bP4I/TswPUDF5YpkYs9leBzeVdo08/+Cglg=="], - "@xterm/addon-serialize": ["@xterm/addon-serialize@0.15.0-beta.148", "", { "peerDependencies": { "@xterm/xterm": "^6.1.0-beta.148" } }, "sha512-EHOkxG3NTmjN7lDNDgqD+dFjz0DQu05n3hlDRBTOOkSojwnixZFbuWrIFeaEj8HNucHFyaDwzsSW4+q47X3XgQ=="], + "@xterm/addon-serialize": ["@xterm/addon-serialize@0.15.0-beta.195", "", { "peerDependencies": { "@xterm/xterm": "^6.1.0-beta.195" } }, "sha512-BKlEQ5cao0J8sLvtFU/kmc4LAkAS+2JkblVIEu/FFHWUsuEaA4yZz4BtZrWBhql2JRfOq62hZFrqahZ0ViuZgA=="], - "@xterm/addon-unicode11": ["@xterm/addon-unicode11@0.10.0-beta.148", "", { "peerDependencies": { "@xterm/xterm": "^6.1.0-beta.148" } }, "sha512-mKeoPYbvI2cZ9IVMQOrECiTooouHXJ+jbiqKR1iWqDBdTfiXiO7/TBOmpUOUO25YLxlTtPF0hfP+XyUG7Bdl7w=="], + "@xterm/addon-unicode11": ["@xterm/addon-unicode11@0.10.0-beta.195", "", { "peerDependencies": { "@xterm/xterm": "^6.1.0-beta.195" } }, "sha512-+mb/c0OhOvITYwnNvzvSPTBOdtMxGAaeNAkajPH3xvPxeHOAQRSuX+gmWJZkSPhQJRMFE5tSZ2o6BgF8fXSixw=="], - "@xterm/addon-webgl": ["@xterm/addon-webgl@0.20.0-beta.147", "", { "peerDependencies": { "@xterm/xterm": "^6.1.0-beta.148" } }, "sha512-xvoKqzZjQwvNthy8Z4SU/B9VoanA0LP7RwbGw5ewaoYCSOTsoDI6V21SQb8t9EXi89r0/nwUymuIBBsYwffgLQ=="], + "@xterm/addon-webgl": ["@xterm/addon-webgl@0.20.0-beta.194", "", { "peerDependencies": { "@xterm/xterm": "^6.1.0-beta.195" } }, "sha512-aX4yGkHyoJVmxh3ZVMha7CYdTFu7tuzTJ0ljyXKAVFrdO+Wve4luK8w3wLmxuvqa9LWA9muMx/bGeEWtwD/Nlg=="], - "@xterm/headless": ["@xterm/headless@6.1.0-beta.148", "", {}, "sha512-QVJAUl89M9A6yam2OHosRonUndgOnxOY/3qo+cxfjgFSEr8odepmY9LDDsqsk2kfnNAICLkSGZ3QSdrOvSyuPQ=="], + "@xterm/headless": ["@xterm/headless@6.1.0-beta.195", "", {}, "sha512-sUOeZ08431n5wx6ukrQ96eXEGN7z8swUfvHkl4nAEqpYEMxS/ua+E6v5PGT6fpQFbOt2R3zIm6LDIfy9GX24mA=="], - "@xterm/xterm": ["@xterm/xterm@6.1.0-beta.148", "", {}, "sha512-SGeds5I5qeb6UgJUDC0Nktkw+DtgXmh5w/bS2iJffZEKYDfLRvBfhEe6hpSXDTy5F37DAmXCzNBaX+YfJ3gzPA=="], + "@xterm/xterm": ["@xterm/xterm@6.1.0-beta.195", "", {}, "sha512-lLVfI3T4pX4W4qrbf2Qhdq5Pa00FkOOUz9vlOm6f1r5wel1mUafeJL8zacfsUVdc03MsCKHRyZkLubmDEnabcw=="], "@xtuc/ieee754": ["@xtuc/ieee754@1.2.0", "", {}, "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="],