From 726844b01945379e7b5e985ee9b36f7d1c393306 Mon Sep 17 00:00:00 2001 From: David Andrews Date: Thu, 30 Jul 2026 15:27:08 -0400 Subject: [PATCH] fix(natives): preserve workspace addons --- packages/natives/CHANGELOG.md | 5 ++- packages/natives/native/loader-state.d.ts | 1 + packages/natives/native/loader-state.js | 16 +++++---- packages/natives/test/windows-staging.test.ts | 33 +++++++++++++++---- 4 files changed, 40 insertions(+), 15 deletions(-) diff --git a/packages/natives/CHANGELOG.md b/packages/natives/CHANGELOG.md index ed5e01af4..563747778 100644 --- a/packages/natives/CHANGELOG.md +++ b/packages/natives/CHANGELOG.md @@ -20,6 +20,10 @@ - Fixed the `computer` tool advertising Wayland support that never worked: on the default rootless XWayland (GNOME/KDE/sway) the X11 root window has no readable pixmap, so root `GetImage` failed on every screenshot with a raw `BadMatch` protocol dump. `Monitor::all` now probes root drawability at initialization and fails fast with an actionable `DESKTOP_BACKEND_UNAVAILABLE` message naming the rootless-XWayland constraint, and `docs/computer-use.md` now lists rootless XWayland as unsupported ([#7085](https://github.com/can1357/oh-my-pi/issues/7085)). +### Fixed + +- Fixed workspace native addon loads preferring an installed leaf package over the workspace build ([#7059](https://github.com/can1357/oh-my-pi/pull/7059) by [@GratefulDave](https://github.com/GratefulDave)). + ## [17.2.0] - 2026-07-30 ### Changed @@ -29,7 +33,6 @@ ### Fixed -- Fixed workspace native addon loads preferring an installed leaf package over the workspace build ([#7059](https://github.com/can1357/oh-my-pi/pull/7059) by [@GratefulDave](https://github.com/GratefulDave)). ## [17.1.8] - 2026-07-28 diff --git a/packages/natives/native/loader-state.d.ts b/packages/natives/native/loader-state.d.ts index f62553855..63a042cd7 100644 --- a/packages/natives/native/loader-state.d.ts +++ b/packages/natives/native/loader-state.d.ts @@ -57,6 +57,7 @@ export function resolveLoaderCandidates(input: ResolveLoaderCandidatesInput): st export interface InitLoaderContextOverrides { nativeDir?: string; + platform?: NodeJS.Platform | string; isCompiledBinary?: boolean; leafPackageDir?: string | null; } diff --git a/packages/natives/native/loader-state.js b/packages/natives/native/loader-state.js index 04d84e6ce..e77ff2ea1 100644 --- a/packages/natives/native/loader-state.js +++ b/packages/natives/native/loader-state.js @@ -129,7 +129,8 @@ export function shouldStageNodeModulesAddon({ platform, isCompiledBinary, native // Check both separators independently of the host's `path.sep`: this helper // is shared by the loader (running on Windows with `\`) and the test suite // (typically running on POSIX hosts when CI executes the regression test). - return nativeDir.includes("\\node_modules\\") || nativeDir.includes("/node_modules/"); + const normalizedNativeDir = nativeDir.toLowerCase(); + return normalizedNativeDir.includes("\\node_modules\\") || normalizedNativeDir.includes("/node_modules/"); } /** @@ -699,17 +700,18 @@ function buildHelpMessage(ctx) { * helpers from this file doesn't trigger AVX2 detection or filesystem probes. */ /** - * @param {{ nativeDir?: string; isCompiledBinary?: boolean; leafPackageDir?: string | null }} [overrides] + * @param {{ nativeDir?: string; platform?: NodeJS.Platform | string; isCompiledBinary?: boolean; leafPackageDir?: string | null }} [overrides] */ export function initLoaderContext(overrides = {}) { - const platformTag = `${process.platform}-${process.arch}`; + const platform = overrides.platform ?? process.platform; + const platformTag = `${platform}-${process.arch}`; const packageVersion = packageJson.version; const nativeDir = overrides.nativeDir ?? path.join(import.meta.dir, "..", "native"); const execDir = path.dirname(process.execPath); const nativesDir = getNativesDir(); const versionedDir = path.join(nativesDir, packageVersion); const userDataDir = - process.platform === "win32" + platform === "win32" ? path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "omp") : path.join(os.homedir(), ".local", "bin"); @@ -720,7 +722,7 @@ export function initLoaderContext(overrides = {}) { env: process.env, importMetaUrl: import.meta.url, }); - const normalizedNativeDir = nativeDir.toLowerCase(); + const normalizedNativeDir = platform === "win32" ? nativeDir.toLowerCase() : nativeDir; const isWorkspaceLoad = !isCompiledBinary && !normalizedNativeDir.includes("\\node_modules\\") && @@ -732,9 +734,9 @@ export function initLoaderContext(overrides = {}) { ? resolveLeafPackageDir(platformTag) : overrides.leafPackageDir; const stageFromNodeModules = shouldStageNodeModulesAddon({ - platform: process.platform, + platform, isCompiledBinary, - nativeDir, + nativeDir: normalizedNativeDir, }); const selectedVariant = resolveCpuVariant(getVariantOverride()); diff --git a/packages/natives/test/windows-staging.test.ts b/packages/natives/test/windows-staging.test.ts index fe510a8dd..01be98647 100644 --- a/packages/natives/test/windows-staging.test.ts +++ b/packages/natives/test/windows-staging.test.ts @@ -115,27 +115,39 @@ describe("windows native addon staging", () => { expect(candidates).not.toContain(userDataBaseline); }); - it("omits leaf-package candidates only for workspace loads", () => { + it("classifies only Windows node_modules paths case-insensitively", () => { const leafPackageDir = "/tmp/node_modules/@oh-my-pi/pi-natives-darwin-arm64"; + const uppercaseNodeModulesNativeDir = "/tmp/NODE_MODULES/@oh-my-pi/pi-natives/native"; const variantCacheKey = "__PI_NATIVE_VARIANT_CACHE"; const previousVariantCache = process.env[variantCacheKey]; try { const workspace = initLoaderContext({ + platform: "linux", isCompiledBinary: false, nativeDir: "/tmp/oh-my-pi/packages/natives/native", leafPackageDir, }); const installed = initLoaderContext({ + platform: "linux", isCompiledBinary: false, nativeDir: "/tmp/node_modules/@oh-my-pi/pi-natives/native", leafPackageDir, }); - const installedWithUppercaseNodeModules = initLoaderContext({ + const uppercaseWorkspace = initLoaderContext({ + platform: "linux", isCompiledBinary: false, - nativeDir: "/tmp/NODE_MODULES/@oh-my-pi/pi-natives/native", + nativeDir: uppercaseNodeModulesNativeDir, leafPackageDir, }); - const leafCandidate = path.join(leafPackageDir, workspace.addonFilenames[0]); + const uppercaseWindowsInstall = initLoaderContext({ + platform: "win32", + isCompiledBinary: false, + nativeDir: uppercaseNodeModulesNativeDir, + leafPackageDir, + }); + const leafCandidate = path.join(leafPackageDir, installed.addonFilenames[0]); + const windowsLeafCandidate = path.join(leafPackageDir, uppercaseWindowsInstall.addonFilenames[0]); + const workspaceCandidate = path.join(uppercaseNodeModulesNativeDir, uppercaseWorkspace.addonFilenames[0]); expect(workspace.isWorkspaceLoad).toBe(true); expect(workspace.leafPackageDir).toBeNull(); @@ -143,9 +155,16 @@ describe("windows native addon staging", () => { expect(installed.isWorkspaceLoad).toBe(false); expect(installed.leafPackageDir).toBe(leafPackageDir); expect(installed.candidates).toContain(leafCandidate); - expect(installedWithUppercaseNodeModules.isWorkspaceLoad).toBe(false); - expect(installedWithUppercaseNodeModules.leafPackageDir).toBe(leafPackageDir); - expect(installedWithUppercaseNodeModules.candidates).toContain(leafCandidate); + expect(installed.candidates[0]).toBe(leafCandidate); + + expect(uppercaseWorkspace.isWorkspaceLoad).toBe(true); + expect(uppercaseWorkspace.leafPackageDir).toBeNull(); + expect(uppercaseWorkspace.candidates[0]).toBe(workspaceCandidate); + + expect(uppercaseWindowsInstall.isWorkspaceLoad).toBe(false); + expect(uppercaseWindowsInstall.leafPackageDir).toBe(leafPackageDir); + expect(uppercaseWindowsInstall.stageFromNodeModules).toBe(true); + expect(uppercaseWindowsInstall.candidates).toContain(windowsLeafCandidate); } finally { if (previousVariantCache === undefined) delete process.env[variantCacheKey]; else process.env[variantCacheKey] = previousVariantCache;