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:
@@ -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.
|
||||
@@ -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",
|
||||
});
|
||||
})();
|
||||
@@ -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`);
|
||||
}
|
||||
Generated
+273
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user