diff --git a/packages/native/src/native.ts b/packages/native/src/native.ts index 944c50c69..a8f81b2f9 100644 --- a/packages/native/src/native.ts +++ b/packages/native/src/native.ts @@ -27,6 +27,8 @@ const platformPackageMap: Record = { "win32-x64": "win32-x64-msvc", }; +let _loadedSuccessfully = false; + function loadNative(): Record { const errors: string[] = []; @@ -34,7 +36,7 @@ function loadNative(): Record { const packageSuffix = platformPackageMap[platformTag]; if (packageSuffix) { try { - return require(`@gsd-build/engine-${packageSuffix}`) as Record; + _loadedSuccessfully = true; return require(`@gsd-build/engine-${packageSuffix}`) as Record; } catch (err) { const message = err instanceof Error ? err.message : String(err); errors.push(`@gsd-build/engine-${packageSuffix}: ${message}`); @@ -44,7 +46,7 @@ function loadNative(): Record { // 2. Try local release build (native/addon/gsd_engine.{platform}.node) const releasePath = path.join(addonDir, `gsd_engine.${platformTag}.node`); try { - return require(releasePath) as Record; + _loadedSuccessfully = true; return require(releasePath) as Record; } catch (err) { const message = err instanceof Error ? err.message : String(err); errors.push(`${releasePath}: ${message}`); @@ -53,7 +55,7 @@ function loadNative(): Record { // 3. Try local dev build (native/addon/gsd_engine.dev.node) const devPath = path.join(addonDir, "gsd_engine.dev.node"); try { - return require(devPath) as Record; + _loadedSuccessfully = true; return require(devPath) as Record; } catch (err) { const message = err instanceof Error ? err.message : String(err); errors.push(`${devPath}: ${message}`); @@ -61,13 +63,22 @@ function loadNative(): Record { const details = errors.map((e) => ` - ${e}`).join("\n"); const supportedPlatforms = Object.keys(platformPackageMap); - throw new Error( - `Failed to load gsd_engine native addon for ${platformTag}.\n\n` + - `Tried:\n${details}\n\n` + - `Supported platforms: ${supportedPlatforms.join(", ")}\n` + - `If your platform is listed, try reinstalling: npm i -g gsd-pi\n` + - `Otherwise, please open an issue: https://github.com/gsd-build/gsd-2/issues`, + + // Graceful fallback: on unsupported platforms (e.g., win32-arm64), return a + // proxy that throws on individual function calls rather than crashing the + // entire import chain at startup (#1223). Consumers with JS fallbacks + // (parseRoadmap, parsePlan, fuzzyFind, etc.) catch these and degrade gracefully. + process.stderr.write( + `[gsd] Native addon not available for ${platformTag}. Falling back to JS implementations (slower).\n` + + ` Supported native platforms: ${supportedPlatforms.join(", ")}\n`, ); + return new Proxy({} as Record, { + get(_target, prop) { + return (..._args: unknown[]) => { + throw new Error(`Native function '${String(prop)}' is not available on ${platformTag}`); + }; + }, + }); } export const native = loadNative() as { @@ -140,3 +151,6 @@ export const native = loadNative() as { parseStreamingJson: (text: string) => unknown; xxHash32: (input: string, seed: number) => number; }; + +/** True when the native addon loaded successfully. False on unsupported platforms. */ +export const nativeAvailable = _loadedSuccessfully;