66 * to the existing WASM pipeline.
77 */
88
9+ import { existsSync } from 'node:fs' ;
910import { createRequire } from 'node:module' ;
1011import os from 'node:os' ;
12+ import { fileURLToPath } from 'node:url' ;
1113import { EngineError , toErrorMessage } from '../shared/errors.js' ;
1214import type { NativeAddon } from '../types.js' ;
13- import { debug } from './logger.js' ;
15+ import { debug , warn } from './logger.js' ;
1416
1517let _cached : NativeAddon | null | undefined ; // undefined = not yet tried, null = failed, NativeAddon = module
1618let _loadError : Error | null = null ;
@@ -44,47 +46,107 @@ const PLATFORM_PACKAGES: Record<string, string> = {
4446 'win32-x64' : '@optave/codegraph-win32-x64-msvc' ,
4547} ;
4648
49+ /**
50+ * Map of (platform-arch[-libc]) → locally compiled binary filename.
51+ * Checked before the npm package so that locally compiled Rust changes
52+ * are picked up immediately in development without publishing a new release.
53+ */
54+ const PLATFORM_LOCAL_BINARIES : Record < string , string > = {
55+ 'linux-x64-gnu' : 'codegraph-core.linux-x64-gnu.node' ,
56+ 'linux-x64-musl' : 'codegraph-core.linux-x64-musl.node' ,
57+ 'linux-arm64-gnu' : 'codegraph-core.linux-arm64-gnu.node' ,
58+ 'linux-arm64-musl' : 'codegraph-core.linux-arm64-musl.node' ,
59+ 'darwin-arm64' : 'codegraph-core.darwin-arm64.node' ,
60+ 'darwin-x64' : 'codegraph-core.darwin-x64.node' ,
61+ 'win32-x64' : 'codegraph-core.win32-x64-msvc.node' ,
62+ } ;
63+
64+ /** Compute the platform key used to index PLATFORM_PACKAGES / PLATFORM_LOCAL_BINARIES. */
65+ function resolvePlatformKey ( ) : string {
66+ const platform = os . platform ( ) ;
67+ const arch = os . arch ( ) ;
68+ return platform === 'linux' ? `${ platform } -${ arch } -${ detectLibc ( ) } ` : `${ platform } -${ arch } ` ;
69+ }
70+
4771/**
4872 * Resolve the platform-specific npm package name for the native addon.
4973 * Returns null if the current platform is not supported.
5074 */
5175function resolvePlatformPackage ( ) : string | null {
52- const platform = os . platform ( ) ;
53- const arch = os . arch ( ) ;
54- const key = platform === 'linux' ? `${ platform } -${ arch } -${ detectLibc ( ) } ` : `${ platform } -${ arch } ` ;
55- return PLATFORM_PACKAGES [ key ] || null ;
76+ return PLATFORM_PACKAGES [ resolvePlatformKey ( ) ] ?? null ;
5677}
5778
5879/**
5980 * Try to load the native napi addon.
6081 * Returns the module on success, null on failure.
6182 *
62- * Dev override: CODEGRAPH_NATIVE_ADDON_PATH can point to a locally built
63- * .node file (e.g. crates/codegraph-core/index.node from `cargo build`).
64- * Only honoured when set explicitly — never falls back to it implicitly.
83+ * Load order:
84+ * 1. NAPI_RS_NATIVE_LIBRARY_PATH env var (explicit override)
85+ * 2. locally compiled binary in crates/codegraph-core/ (dev mode — preferred
86+ * over the npm package so that Rust changes are picked up immediately
87+ * without publishing a new release)
88+ * 3. npm platform package (production path)
6589 */
6690export function loadNative ( ) : NativeAddon | null {
6791 if ( _cached !== undefined ) return _cached ;
6892
69- const devOverride = process . env . CODEGRAPH_NATIVE_ADDON_PATH ;
70- if ( devOverride ) {
93+ const platformKey = resolvePlatformKey ( ) ;
94+
95+ // 1. Explicit path override — highest priority. Failure is fatal: if the
96+ // operator set this variable, silently loading a different binary would
97+ // be harder to diagnose than an explicit error.
98+ const envPath = process . env . NAPI_RS_NATIVE_LIBRARY_PATH ;
99+ if ( envPath ) {
71100 try {
72- _cached = _require ( devOverride ) as NativeAddon ;
101+ _cached = _require ( envPath ) as NativeAddon ;
102+ debug ( `loadNative: loaded from NAPI_RS_NATIVE_LIBRARY_PATH: ${ envPath } ` ) ;
73103 return _cached ;
74104 } catch ( err ) {
75105 _loadError = err as Error ;
106+ warn (
107+ `loadNative: NAPI_RS_NATIVE_LIBRARY_PATH is set but failed to load "${ envPath } ": ${ toErrorMessage ( err as Error ) } ` ,
108+ ) ;
76109 _cached = null ;
77110 return null ;
78111 }
79112 }
80113
114+ // 2. Locally compiled dev binary — preferred over npm package so that Rust
115+ // changes are visible without publishing. Only used when the file exists.
116+ // If the file exists but fails to load (e.g. stale ABI), we warn and halt
117+ // rather than silently falling through to the npm package — that would
118+ // defeat the purpose of this priority order.
119+ const localFile = PLATFORM_LOCAL_BINARIES [ platformKey ] ;
120+ if ( localFile ) {
121+ const localPath = fileURLToPath (
122+ new URL ( `../../crates/codegraph-core/${ localFile } ` , import . meta. url ) ,
123+ ) ;
124+ if ( existsSync ( localPath ) ) {
125+ try {
126+ _cached = _require ( localPath ) as NativeAddon ;
127+ debug ( `loadNative: loaded local dev binary: ${ localPath } ` ) ;
128+ return _cached ;
129+ } catch ( err ) {
130+ _loadError = err as Error ;
131+ warn (
132+ `loadNative: local dev binary exists but failed to load "${ localPath } ": ${ toErrorMessage ( err as Error ) } ` ,
133+ ) ;
134+ _cached = null ;
135+ return null ;
136+ }
137+ }
138+ }
139+
140+ // 3. Published npm platform package — production path.
81141 const pkg = resolvePlatformPackage ( ) ;
82142 if ( pkg ) {
83143 try {
84144 _cached = _require ( pkg ) as NativeAddon ;
145+ debug ( `loadNative: loaded npm package: ${ pkg } ` ) ;
85146 return _cached ;
86147 } catch ( err ) {
87148 _loadError = err as Error ;
149+ debug ( `loadNative: npm package ${ pkg } not available: ${ toErrorMessage ( err as Error ) } ` ) ;
88150 }
89151 } else {
90152 _loadError = new Error ( `Unsupported platform: ${ os . platform ( ) } -${ os . arch ( ) } ` ) ;
@@ -104,6 +166,10 @@ export function isNativeAvailable(): boolean {
104166/**
105167 * Read the version from the platform-specific npm package.json.
106168 * Returns null if the package is not installed or has no version.
169+ *
170+ * Note: always reports the npm package version. When the local dev binary or
171+ * NAPI_RS_NATIVE_LIBRARY_PATH is loaded instead, this version may not match
172+ * the running binary.
107173 */
108174export function getNativePackageVersion ( ) : string | null {
109175 const pkg = resolvePlatformPackage ( ) ;
0 commit comments