diff --git a/apps/desktop/electron/get-windows.d.ts b/apps/desktop/electron/get-windows.d.ts new file mode 100644 index 0000000000..a52c2feafe --- /dev/null +++ b/apps/desktop/electron/get-windows.d.ts @@ -0,0 +1,23 @@ +// Type declarations for the get-windows optionalDependency. +// +// get-windows ships no bundled types and is an optionalDependency: on Linux +// `npm ci` skips it when its node-pre-gyp install script fails (no Linux +// prebuilt; the node-gyp fallback needs `gyp` in the active Python), so the +// package is legitimately absent from node_modules on Linux builds. Declaring +// the module here keeps the typecheck independent of whether the package +// installed — the runtime import in window-below.ts degrades to null when it +// is absent. + +declare module 'get-windows' { + export interface GetWindowsWindow { + bounds?: { height?: number; width?: number; x?: number; y?: number } + id?: number + owner?: { name?: string; processId?: number } + title?: string + } + + export function openWindows(options?: { + accessibilityPermission?: boolean + screenRecordingPermission?: boolean + }): Promise +} diff --git a/apps/desktop/electron/window-below.ts b/apps/desktop/electron/window-below.ts index 82581added..979734be42 100644 --- a/apps/desktop/electron/window-below.ts +++ b/apps/desktop/electron/window-below.ts @@ -122,10 +122,15 @@ type GetWindowsModule = { > } -let getWindowsModule: Promise | null = null +let getWindowsModule: Promise | null = null -const loadGetWindows = (): Promise => { - getWindowsModule ??= import('get-windows') +const loadGetWindows = (): Promise => { + // get-windows is an optionalDependency: on Linux `npm ci` skips it when its + // node-pre-gyp install script fails (no Linux prebuilt, and the node-gyp + // fallback needs `gyp` in the active Python). A missing module is therefore + // a normal state, so the lazy import resolves to null instead of rejecting; + // enumeration then degrades to the failure note instead of an uncaught error. + getWindowsModule ??= import('get-windows').catch(() => null) return getWindowsModule } @@ -143,7 +148,13 @@ async function enumerateViaGetWindows(titlesAvailable: boolean): Promise { + assert.equal(stageGetWindows({ platform: 'linux', resolveRoot: () => null }), undefined) +}) + +test('darwin staging fails when get-windows is absent', () => { + assert.throws( + () => stageGetWindows({ platform: 'darwin', resolveRoot: () => null }), + /get-windows is not installed/ + ) +}) + +test('win32 staging fails when get-windows is absent', () => { + assert.throws( + () => stageGetWindows({ platform: 'win32', resolveRoot: () => null }), + /get-windows is not installed/ + ) +}) diff --git a/package-lock.json b/package-lock.json index 090b2a7bdd..451f5a10df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -108,7 +108,6 @@ "emojibase-data": "16.0.3", "fflate": "0.8.3", "frimousse": "0.3.0", - "get-windows": "9.3.0", "hast-util-from-html-isomorphic": "2.0.0", "hast-util-to-text": "4.0.2", "ignore": "7.0.6", @@ -167,6 +166,9 @@ }, "engines": { "node": ">=22.22.0" + }, + "optionalDependencies": { + "get-windows": "9.3.0" } }, "apps/desktop/node_modules/@electron/get": { @@ -11004,6 +11006,7 @@ "integrity": "sha512-DrOfQSmIcsFax28FfSUjbLTfeOkAG7yeh6NCb/9zzRkDuClXaqYqHuQBPUqcqd1uYS70ygYERSooacMAwvbyVw==", "hasInstallScript": true, "license": "MIT", + "optional": true, "engines": { "node": ">=18.18" },