docs: add system-info example (native addon via @oxmc/node-gpuinfo)

Demonstrates bundling a package with a native .node binding - extracts
it to a real file next to the exe at first run (dlopen needs a real
file, can't load from the zip) and requires it from there via the
usual bundleRules pattern.

Building and running this end-to-end from a directory with no real
node_modules nearby is what surfaced and verified the four fixes in
the previous two commits.
This commit is contained in:
2026-07-26 01:47:42 -07:00
parent 181d80d2c4
commit e3659f41a7
5 changed files with 401 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
# system-info
A minimal nexe example that reads GPU information via a native addon
([`@oxmc/node-gpuinfo`](https://www.npmjs.com/package/@oxmc/node-gpuinfo)).
```js
const gpu = require("@oxmc/node-gpuinfo");
const count = gpu.getGpuCount();
console.log(`Found ${count} GPU(s)\n`);
for (const info of gpu.getAllGpuInfo()) {
console.log(`GPU ${info.index}: ${info.name} (${info.vendor})`);
console.log(` Memory: ${info.memoryUsed} / ${info.memoryTotal} MB`);
console.log(` Utilization: ${info.gpuUtilization}%`);
console.log(` Temperature: ${info.temperature}°C`);
}
```
The real `index.js` in this folder extracts the native `.node` binding to a
real file next to the exe at first run (`dlopen` needs a real file on disk -
it can't load straight out of the zip snapshot), then loads it from there.
## Building
```
npm install
node compile.js
```
`compile.js` builds `system-info.exe` with a `bundleRules` entry for
`@oxmc/node-gpuinfo` so its compiled `.node` binary and `index.js` are
included alongside the extraction logic.
## Bugs this example found and fixed in nexe
Building and running this example surfaced four separate, real bugs in
nexe's runtime module resolution and bundling - all now fixed:
1. **CJS subpath requires** (`require('pkg/subpath')`) - `Module._findPath`
treated the whole request as a package directory name instead of
splitting it into (package, subpath) and resolving the subpath against
the package's `exports` map. Broke `require('consola/basic')`
(a transitive dep of `@mapbox/node-pre-gyp`, itself a transitive dep
here).
2. **`internalModuleReadJSON` return shape** - our patch returned a bare
string/`undefined`, but Node <22's `package_json_reader.js` destructures
the result as an array-like tuple (`{0: string, 1: containsKeys}`),
which crashed on Node 20.
3. **Relative requires into a directory** (e.g. `require('..')` from deep
inside a package, reaching back to its own root) weren't covered by any
VFS-aware package.json resolution at all - only bare specifiers were.
4. **`bundleRules` was inert for modules the static resolver never traced.**
This example's own `index.js` only reaches `@oxmc/node-gpuinfo` via
`require.resolve()` and a dynamically-computed `require(path.resolve(...))`
- neither of which `resolve-dependencies` can statically follow - so the
`bundleRules` entry for it never triggered. `bundleRules` now force-scans
any named module directly from `node_modules`, regardless of whether the
tracer found it.
See `MEMORY.md`/project notes for the full technical detail on each fix
(all in `src/fs/patch.ts` and `src/steps/bundle.ts`).
Verified working: built and run from a directory with no `node_modules`
anywhere nearby (e.g. copied to Desktop) - prints real GPU info, exit 0.
+22
View File
@@ -0,0 +1,22 @@
const path = require("path");
const nexe = require(path.join(__dirname, "..", "..", "index.js"));
// Because the native .node binding needs to be a real file on disk,
// we define bundle rules to include it alongside the executable
const bundleRules = {
// The native .node binding can't be loaded from inside the zip snapshot
// (dlopen needs a real file on disk), so ship it as a real file alongside
// the exe instead of relying on the zip-only lookup nexe normally does.
"@oxmc/node-gpuinfo": {
include: ["dist/**/*.node", "index.js"],
},
};
(async () => {
await nexe.compile({
input: "./index.js",
output: "./system-info.exe",
bundleRules,
loglevel: "verbose",
});
})();
+28
View File
@@ -0,0 +1,28 @@
// Before you load a native module, ensure any necessary setup is done
// such as extracting native .node bindings to disk before requiring them
const path = require('path');
const fs = require('fs');
// Try to load prebuilt binary via node-pre-gyp
const binding_path = require('@mapbox/node-pre-gyp').find(
path.resolve(require.resolve('@oxmc/node-gpuinfo'), '../package.json')
);
// Extract the native .node binding to disk
fs.mkdirSync(path.resolve(__dirname, './native/node-gpuinfo'), { recursive: true });
fs.copyFileSync(binding_path, path.resolve(__dirname, './native/node-gpuinfo/gpuinfo.node'));
// Now we can load the native module
const gpu = require(path.resolve(__dirname, './native/node-gpuinfo/gpuinfo.node'));
// Get GPU count
const count = gpu.getGpuCount();
console.log(`Found ${count} GPU(s)\n`);
// List GPU information
for (const info of gpu.getAllGpuInfo()) {
console.log(`GPU ${info.index}: ${info.name} (${info.vendor})`);
console.log(` Memory: ${info.memoryUsed} / ${info.memoryTotal} MB`);
console.log(` Utilization: ${info.gpuUtilization}%`);
console.log(` Temperature: ${info.temperature}°C`);
}
+273
View File
@@ -0,0 +1,273 @@
{
"name": "system-info",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "system-info",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@oxmc/node-gpuinfo": "^1.0.2"
}
},
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
"license": "ISC",
"dependencies": {
"minipass": "^7.0.4"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/@mapbox/node-pre-gyp": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.3.tgz",
"integrity": "sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==",
"license": "BSD-3-Clause",
"dependencies": {
"consola": "^3.2.3",
"detect-libc": "^2.0.0",
"https-proxy-agent": "^7.0.5",
"node-fetch": "^2.6.7",
"nopt": "^8.0.0",
"semver": "^7.5.3",
"tar": "^7.4.0"
},
"bin": {
"node-pre-gyp": "bin/node-pre-gyp"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@oxmc/node-gpuinfo": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@oxmc/node-gpuinfo/-/node-gpuinfo-1.0.2.tgz",
"integrity": "sha512-FHeIhKClX0kpU9lTMzQkwaoUjmiYg7/k6OBVnDMbA9G9J9y3ETOlF1W1kIxdiuXTWhkC3hpbkOoArGZPR3Wk1Q==",
"hasInstallScript": true,
"license": "GPL-3.0-only",
"os": [
"darwin",
"linux",
"win32"
],
"dependencies": {
"@mapbox/node-pre-gyp": "^2.0.3",
"node-addon-api": "^8.0.0"
},
"engines": {
"node": ">=18.0.0"
}
},
"node_modules/abbrev": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz",
"integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==",
"license": "ISC",
"engines": {
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/chownr": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
},
"node_modules/consola": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
"integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
"license": "MIT",
"engines": {
"node": "^14.18.0 || >=16.10.0"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/minipass": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/minizlib": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz",
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
"license": "MIT",
"dependencies": {
"minipass": "^7.1.2"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/node-addon-api": {
"version": "8.9.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.0.tgz",
"integrity": "sha512-ekZMeaaIzSQTSpr7X2X3iJM7lTzgnx8ahAG9pJfT/7+14mlEM8ZYQ9cgCDvSSRbReFK0oHli3WrZdCiRsgAT9Q==",
"license": "MIT",
"engines": {
"node": "^18 || ^20 || >= 21"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/nopt": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz",
"integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==",
"license": "ISC",
"dependencies": {
"abbrev": "^3.0.0"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/semver": {
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/tar": {
"version": "7.5.22",
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz",
"integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/fs-minipass": "^4.0.0",
"chownr": "^3.0.0",
"minipass": "^7.1.2",
"minizlib": "^3.1.0",
"yallist": "^5.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/yallist": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=18"
}
}
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"name": "system-info",
"version": "1.0.0",
"description": "nexe example: reading GPU info via a native addon",
"main": "index.js",
"scripts": {
"compile": "node compile.js"
},
"license": "ISC",
"dependencies": {
"@oxmc/node-gpuinfo": "^1.0.2"
}
}