From 038270eb2facd18652a101ad883dee2dbb81039b Mon Sep 17 00:00:00 2001 From: Jaret Burkett Date: Tue, 28 Jul 2026 08:27:31 -0600 Subject: [PATCH] Fix issue with macstats on mac --- ui/src/app/api/cpu/route.ts | 6 +++--- ui/src/app/api/gpu/route.ts | 11 +++-------- ui/src/server/macstats.ts | 38 +++++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 ui/src/server/macstats.ts diff --git a/ui/src/app/api/cpu/route.ts b/ui/src/app/api/cpu/route.ts index 12eab7a1..ee9f388d 100644 --- a/ui/src/app/api/cpu/route.ts +++ b/ui/src/app/api/cpu/route.ts @@ -1,9 +1,9 @@ import { NextResponse } from 'next/server'; import si from 'systeminformation'; -import { createRequire } from 'module'; import os from 'os'; import { CpuInfo } from '@/types'; import { cached } from '@/server/apiCache'; +import { loadMacstats } from '@/server/macstats'; const isMac = os.platform() === 'darwin'; @@ -13,8 +13,8 @@ async function getCpuInfo(): Promise { if (isMac) { try { - const nativeRequire = createRequire(import.meta.url); - const ms = nativeRequire('macstats') as any; + const ms = loadMacstats(); + if (!ms) throw new Error('macstats unavailable'); const ramData = ms.getRAMUsageSync(); const cpuData = ms.getCpuDataSync(); diff --git a/ui/src/app/api/gpu/route.ts b/ui/src/app/api/gpu/route.ts index ffb47c26..643357e7 100644 --- a/ui/src/app/api/gpu/route.ts +++ b/ui/src/app/api/gpu/route.ts @@ -1,9 +1,9 @@ import { NextResponse } from 'next/server'; import { exec } from 'child_process'; import { promisify } from 'util'; -import { createRequire } from 'module'; import os from 'os'; import { cached } from '@/server/apiCache'; +import { loadMacstats } from '@/server/macstats'; const execAsync = promisify(exec); @@ -47,11 +47,8 @@ async function getMacGpuInfo(): Promise { let memUsed = 0; let memTotal = memoryTotal; - try { - // Use createRequire to hide from webpack static analysis so it doesn't fail on non-mac platforms - const nativeRequire = createRequire(import.meta.url); - const ms = nativeRequire('macstats') as any; - + const ms = loadMacstats(); + if (ms) { try { const gpuData = ms.getGpuDataSync(); temperature = gpuData.temperature || 0; @@ -84,8 +81,6 @@ async function getMacGpuInfo(): Promise { } catch { // ignore } - } catch (error) { - console.warn('macstats not available:', error); } return { name: gpuName, memUsed, memTotal, gpuLoad, temperature, fanSpeed, powerDraw }; diff --git a/ui/src/server/macstats.ts b/ui/src/server/macstats.ts new file mode 100644 index 00000000..d8326aa2 --- /dev/null +++ b/ui/src/server/macstats.ts @@ -0,0 +1,38 @@ +import * as nodeModule from 'module'; +import os from 'os'; +import path from 'path'; + +/** + * macstats is a native, macOS-only optional dependency. + * + * It must not be bundled. Webpack special-cases `createRequire(...)`: with a literal base it + * resolves and inlines the module (a hard MODULE_NOT_FOUND throw when it can't), and with a + * computed base it replaces the whole call with `void 0` — both leave macstats unreachable at + * runtime even when it is installed, regardless of serverExternalPackages/externals. So look + * createRequire up dynamically, where the bundler can't recognize it, and base the require on + * process.cwd() (the ui/ directory the Next server runs in) so it resolves against + * ui/node_modules at runtime. + */ + +// undefined = not tried yet, null = unavailable on this machine +let cachedModule: any | null | undefined; + +export function loadMacstats(): any | null { + if (cachedModule !== undefined) return cachedModule; + + if (os.platform() !== 'darwin') { + cachedModule = null; + return cachedModule; + } + + try { + const createRequire = (nodeModule as any)['create' + 'Require'] as typeof nodeModule.createRequire; + const nativeRequire = createRequire(path.join(process.cwd(), 'package.json')); + cachedModule = nativeRequire('macstats'); + } catch (error) { + console.warn('macstats not available:', error); + cachedModule = null; + } + + return cachedModule; +}