fix(desktop): tolerate omitted get-windows on ARM64

This commit is contained in:
Gille
2026-08-14 01:29:30 -06:00
committed by brooklyn!
parent 689a4be6f2
commit a373a1b749
4 changed files with 39 additions and 33 deletions
+6 -7
View File
@@ -1,12 +1,11 @@
// 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.
// get-windows ships no bundled types and is an optionalDependency. `npm ci`
// can skip it when its native install fails, including Linux and Windows ARM64
// where 9.3.0 has no prebuilt, so it can legitimately be absent from
// node_modules. Declaring the module here keeps 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 {
+5 -5
View File
@@ -125,11 +125,11 @@ type GetWindowsModule = {
let getWindowsModule: Promise<GetWindowsModule | null> | null = null
const loadGetWindows = (): Promise<GetWindowsModule | null> => {
// 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.
// get-windows is an optionalDependency: `npm ci` can skip it when its native
// install fails, including Linux and Windows ARM64 where 9.3.0 has no
// prebuilt. A missing module is therefore a normal state on those targets,
// 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
+14 -14
View File
@@ -408,9 +408,9 @@ export function openWindowsSync() {
function resolveGetWindowsRoot() {
// get-windows is an optionalDependency (its node-pre-gyp install script has
// no Linux prebuilt and the node-gyp fallback needs `gyp` in the active
// Python, so `npm ci` may skip it entirely on Linux). Return null when it is
// absent; the caller decides whether that is fatal per platform.
// no Linux or Windows ARM64 prebuilt and its node-gyp fallback may fail, so
// `npm ci` can skip it entirely on those targets). Return null when it is
// absent; the caller decides whether that is fatal per platform and arch.
try {
// get-windows' exports map doesn't expose ./package.json; resolve the entry
// (index.js sits at the package root) and take its directory.
@@ -565,22 +565,22 @@ export function stageGetWindows(
const destRoot = resolve(projectRoot, 'dist/node_modules/get-windows')
if (!srcRoot) {
// get-windows is an optionalDependency: `npm ci` on Linux skips it when the
// install script fails (no Linux prebuilt, node-gyp needs `gyp` in the
// active Python), so absence here is a normal state, not a broken checkout.
// On Linux it only backs read_window_below over X11 (lib/linux.js shells
// out to xprop); skipping it degrades that tool to Hyprland IPC or an
// "unavailable" note instead of failing the whole desktop build. On
// darwin/win32 the native payload is required, so fail loudly.
if (platform === 'linux') {
// npm may omit an optional dependency whose install script fails. That is
// expected on Linux and win32-arm64 because get-windows 9.3.0 publishes no
// native prebuilt for either target. The runtime import already fails soft,
// so disable only window enumeration instead of failing the Desktop build.
// Other Windows architectures and macOS have supported native payloads and
// remain fail-closed so a broken package cannot ship silently.
const canDegrade = platform === 'linux' || (platform === 'win32' && arch === 'arm64')
if (canDegrade) {
console.warn(
'[stage-native-deps] get-windows not installed (optional dep skipped on ' +
'Linux); read_window_below will be unavailable in this build'
`[stage-native-deps] get-windows not installed (optional dep skipped for ${platform}-${arch}); ` +
'read_window_below will be unavailable in this build'
)
return undefined
}
throw new Error(
`[stage-native-deps] get-windows is not installed; cannot stage its ${platform} native payload`
`[stage-native-deps] get-windows is not installed; cannot stage its ${platform}-${arch} native payload`
)
}
@@ -551,10 +551,10 @@ test('darwin staging ships the Swift helper executable and the rewritten windows
// ─── stageGetWindows (optionalDependency gate) ──────────────────────
//
// get-windows is an optionalDependency: on Linux its node-pre-gyp install
// script fails (no Linux prebuilt; the node-gyp fallback needs `gyp` in the
// active Python), so npm skips the package and `npm ci` must still succeed.
// The staging step mirrors that: absent package on Linux is a skip, on
// darwin/win32 it is a hard failure because the native payload is required.
// script fails because no prebuilt exists. Windows ARM64 has the same package
// state: its prebuilt URL returns 404 and npm may omit the optional dependency.
// Staging skips those unsupported targets, but supported native targets remain
// a hard failure when the package is missing.
test('linux staging skips when get-windows is absent (optional dep skipped by npm)', () => {
assert.equal(stageGetWindows({ platform: 'linux', resolveRoot: () => null }), undefined)
@@ -562,14 +562,21 @@ test('linux staging skips when get-windows is absent (optional dep skipped by np
test('darwin staging fails when get-windows is absent', () => {
assert.throws(
() => stageGetWindows({ platform: 'darwin', resolveRoot: () => null }),
() => stageGetWindows({ platform: 'darwin', arch: 'arm64', resolveRoot: () => null }),
/get-windows is not installed/
)
})
test('win32 staging fails when get-windows is absent', () => {
test('win32-arm64 staging skips when get-windows is absent after its optional install fails', () => {
assert.equal(
stageGetWindows({ platform: 'win32', arch: 'arm64', resolveRoot: () => null }),
undefined
)
})
test('win32-x64 staging fails when get-windows is absent', () => {
assert.throws(
() => stageGetWindows({ platform: 'win32', resolveRoot: () => null }),
() => stageGetWindows({ platform: 'win32', arch: 'x64', resolveRoot: () => null }),
/get-windows is not installed/
)
})