From 689a4be6f218570bec554143fc8e55535cee834e Mon Sep 17 00:00:00 2001 From: fangliquanflq Date: Sun, 9 Aug 2026 15:22:00 +0800 Subject: [PATCH] fix(desktop): degrade get-windows on Windows ARM64 (cherry picked from commit 6997b5a97e72aa1ffc98533aaa100e84857b1fbe) --- apps/desktop/scripts/before-pack.mjs | 9 ++-- apps/desktop/scripts/stage-native-deps.mjs | 27 ++++++++---- .../scripts/stage-native-deps.test.mjs | 41 +++++++++++++++---- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/apps/desktop/scripts/before-pack.mjs b/apps/desktop/scripts/before-pack.mjs index e49b049ba3..20045cf728 100644 --- a/apps/desktop/scripts/before-pack.mjs +++ b/apps/desktop/scripts/before-pack.mjs @@ -144,11 +144,10 @@ export default async function beforePack(context) { await stageNodePty({ platform, arch: archName }) console.log(`[before-pack] re-staged node-pty for target ${platform}-${archName}`) } - // get-windows' native payload is per-platform, not per-arch (the macOS - // helper is universal, Windows stages every prebuilt binding dir), so - // it re-stages for the universal target too. - stageGetWindows({ platform }) - console.log(`[before-pack] re-staged get-windows for target ${platform}`) + // The macOS helper is universal, while Windows bindings are arch-specific. + // Pass the target arch so an ARM64 package never stages an x64 binding. + stageGetWindows({ platform, arch: archName }) + console.log(`[before-pack] re-staged get-windows for target ${platform}-${archName}`) } } catch (err) { // This one SHOULD fail the build — a missing/wrong native binary for the diff --git a/apps/desktop/scripts/stage-native-deps.mjs b/apps/desktop/scripts/stage-native-deps.mjs index 1a96644b6a..4a59aee17e 100644 --- a/apps/desktop/scripts/stage-native-deps.mjs +++ b/apps/desktop/scripts/stage-native-deps.mjs @@ -437,7 +437,7 @@ const GET_WINDOWS_VERSION = '9.3.0' export function stageGetWindowsInto( srcRoot, destRoot, - { platform = process.platform, rebuild } = {} + { platform = process.platform, arch = process.arch, rebuild } = {} ) { // The STAGED_WINDOWS_JS rewrite mirrors this exact version's export surface. // A version bump must fail the build here until the rewrite is re-verified — @@ -490,11 +490,20 @@ export function stageGetWindowsInto( ? readdirSync(bindingRoot).filter( (dir) => dir.includes(`-${platform}-`) && + dir.endsWith(`-${arch}`) && existsSync(join(bindingRoot, dir, 'node-get-windows.node')) ) : [] let bindingDirs = scanBindingDirs() - if (bindingDirs.length === 0 && typeof rebuild === 'function') { + if (bindingDirs.length === 0 && arch === 'arm64') { + // get-windows 9.3.0 publishes win32 prebuilds for ia32/x64 only. + // The staged windows.js deliberately fails soft when binding/ is absent, + // so preserve the desktop build and disable only window enumeration. + console.warn( + '[stage-native-deps] get-windows has no win32-arm64 prebuilt binding; ' + + 'staging the fail-soft JS surface without native window enumeration.' + ) + } else if (bindingDirs.length === 0 && typeof rebuild === 'function') { // A plain `npm install` won't re-run an install script for a package // that is already on disk, so every checkout that installed while // get-windows was missing from allowScripts stays bricked even after @@ -505,9 +514,9 @@ export function stageGetWindowsInto( rebuild() bindingDirs = scanBindingDirs() } - if (bindingDirs.length === 0) { + if (bindingDirs.length === 0 && arch !== 'arm64') { throw new Error( - '[stage-native-deps] get-windows has no win32 prebuilt binding under lib/binding. ' + + `[stage-native-deps] get-windows has no win32-${arch} prebuilt binding under lib/binding. ` + 'Recover from the checkout root with:\n' + ' npm install-scripts approve get-windows\n' + ' npm rebuild get-windows' @@ -546,7 +555,11 @@ function rebuildGetWindowsViaNpm() { } export function stageGetWindows( - { platform = process.platform, resolveRoot = resolveGetWindowsRoot } = {} + { + platform = process.platform, + arch = process.arch, + resolveRoot = resolveGetWindowsRoot + } = {} ) { const srcRoot = resolveRoot() const destRoot = resolve(projectRoot, 'dist/node_modules/get-windows') @@ -575,12 +588,12 @@ export function stageGetWindows( // has nothing to gain from the rebuild. const rebuild = platform === 'win32' && process.platform === 'win32' ? rebuildGetWindowsViaNpm : undefined - return stageGetWindowsInto(srcRoot, destRoot, { platform, rebuild }) + return stageGetWindowsInto(srcRoot, destRoot, { platform, arch, rebuild }) } // Allow direct CLI invocation: node scripts/stage-native-deps.mjs [platform] [arch] if (isMain(import.meta.url)) { const [platform, arch] = process.argv.slice(2) stageNodePty({ platform, arch }) - stageGetWindows({ platform }) + stageGetWindows({ platform, arch }) } diff --git a/apps/desktop/scripts/stage-native-deps.test.mjs b/apps/desktop/scripts/stage-native-deps.test.mjs index 53b7f04018..3266158fcc 100644 --- a/apps/desktop/scripts/stage-native-deps.test.mjs +++ b/apps/desktop/scripts/stage-native-deps.test.mjs @@ -391,7 +391,7 @@ test('win32 staging skips the darwin binding the tarball bundles on every platfo ] }) - stageGetWindowsInto(srcRoot, destRoot, { platform: 'win32' }) + stageGetWindowsInto(srcRoot, destRoot, { platform: 'win32', arch: 'x64' }) assert.ok(existsSync(join(destRoot, 'lib', 'binding', 'napi-9-win32-unknown-x64', 'node-get-windows.node'))) assert.ok(!existsSync(join(destRoot, 'lib', 'binding', 'napi-9-darwin-unknown-arm64'))) @@ -411,7 +411,7 @@ test('win32 staging rejects a binding dir that claims win32 but holds a foreign }) assert.throws( - () => stageGetWindowsInto(srcRoot, destRoot, { platform: 'win32' }), + () => stageGetWindowsInto(srcRoot, destRoot, { platform: 'win32', arch: 'x64' }), /expected win32, got darwin/ ) } finally { @@ -419,7 +419,7 @@ test('win32 staging rejects a binding dir that claims win32 but holds a foreign } }) -test('win32 staging fails when only foreign bindings exist', () => { +test('win32-x64 staging fails when only foreign bindings exist', () => { const tmp = fs.mkdtempSync(join(os.tmpdir(), 'hermes-stage-')) try { const srcRoot = join(tmp, 'get-windows') @@ -430,14 +430,36 @@ test('win32 staging fails when only foreign bindings exist', () => { }) assert.throws( - () => stageGetWindowsInto(srcRoot, destRoot, { platform: 'win32' }), - /no win32 prebuilt binding/ + () => stageGetWindowsInto(srcRoot, destRoot, { platform: 'win32', arch: 'x64' }), + /no win32-x64 prebuilt binding/ ) } finally { fs.rmSync(tmp, { recursive: true, force: true }) } }) +test('win32-arm64 staging omits incompatible bindings and keeps the fail-soft JS surface', () => { + const tmp = fs.mkdtempSync(join(os.tmpdir(), 'hermes-stage-')) + try { + const srcRoot = join(tmp, 'get-windows') + const destRoot = join(tmp, 'dest') + + makeFakeGetWindows(srcRoot, { + bindings: [ + { dir: 'napi-9-darwin-unknown-arm64', platform: 'darwin' }, + { dir: 'napi-9-win32-unknown-x64', platform: 'win32' } + ] + }) + + stageGetWindowsInto(srcRoot, destRoot, { platform: 'win32', arch: 'arm64' }) + + assert.ok(existsSync(join(destRoot, 'lib', 'windows.js'))) + assert.ok(!existsSync(join(destRoot, 'lib', 'binding'))) + } finally { + fs.rmSync(tmp, { recursive: true, force: true }) + } +}) + test('win32 staging self-heals through the rebuild hook when the binding is missing', () => { const tmp = fs.mkdtempSync(join(os.tmpdir(), 'hermes-stage-')) try { @@ -457,7 +479,7 @@ test('win32 staging self-heals through the rebuild hook when the binding is miss ) } - stageGetWindowsInto(srcRoot, destRoot, { platform: 'win32', rebuild }) + stageGetWindowsInto(srcRoot, destRoot, { platform: 'win32', arch: 'x64', rebuild }) assert.equal(calls, 1) assert.ok( @@ -477,7 +499,12 @@ test('win32 staging reports the recovery steps when the rebuild hook produces no makeFakeGetWindows(srcRoot, { bindings: [] }) assert.throws( - () => stageGetWindowsInto(srcRoot, destRoot, { platform: 'win32', rebuild: () => {} }), + () => + stageGetWindowsInto(srcRoot, destRoot, { + platform: 'win32', + arch: 'x64', + rebuild: () => {} + }), /npm rebuild get-windows/ ) } finally {