Skip to content

Commit cd5a14f

Browse files
committed
fix Node 26 native module repair
1 parent 260edc4 commit cd5a14f

10 files changed

Lines changed: 267 additions & 456 deletions

File tree

‎apps/connect/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"@types/node": "^22.0.0",
2424
"@types/ws": "^8.18.1",
2525
"better-sqlite3": "12.10.0",
26-
"tsx": "^4.21.0",
26+
"tsx": "^4.23.1",
2727
"typescript": "npm:@typescript/typescript6@^6.0.2",
2828
"typescript-7": "npm:typescript@^7.0.2",
2929
"wrangler": "^4.100.0",

‎apps/desktop/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"electron-builder": "^26.8.1",
4040
"esbuild": "^0.28.0",
4141
"rimraf": "^6.1.0",
42-
"tsx": "^4.21.0",
42+
"tsx": "^4.23.1",
4343
"typescript": "npm:@typescript/typescript6@^6.0.2",
4444
"typescript-7": "npm:typescript@^7.0.2",
4545
"vitest": "^4.1.1",

‎apps/host-daemon/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"@types/semver": "^7.7.1",
6363
"esbuild": "^0.28.0",
6464
"hono": "^4.7.0",
65-
"tsx": "^4.21.0",
65+
"tsx": "^4.23.1",
6666
"typescript": "npm:@typescript/typescript6@^6.0.2",
6767
"typescript-7": "npm:typescript@^7.0.2"
6868
}

‎apps/server/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"@types/node": "^22.0.0",
6363
"@types/semver": "^7.7.0",
6464
"@types/ws": "^8.18.1",
65-
"tsx": "^4.21.0",
65+
"tsx": "^4.23.1",
6666
"typescript": "npm:@typescript/typescript6@^6.0.2",
6767
"typescript-7": "npm:typescript@^7.0.2",
6868
"vitest": "^4.1.1",

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"esbuild": "^0.28.0",
4848
"prettier": "^3.8.3",
4949
"rimraf": "^6.1.0",
50-
"tsx": "^4.21.0",
50+
"tsx": "^4.23.1",
5151
"turbo": "^2.4.0",
5252
"typescript": "npm:@typescript/typescript6@^6.0.2",
5353
"typescript-7": "npm:typescript@^7.0.2",

‎packages/agent-runtime/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"@bb/test-helpers": "workspace:*",
4949
"@bb/tsconfig": "workspace:*",
5050
"@types/node": "^22.0.0",
51-
"tsx": "^4.21.0",
51+
"tsx": "^4.23.1",
5252
"typescript": "npm:@typescript/typescript6@^6.0.2",
5353
"typescript-7": "npm:typescript@^7.0.2",
5454
"vitest": "^4.1.1"

‎packages/scripts/test/ensure-native-modules.test.mjs‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ function createEnsureOptions(fakeRequire, execFileSync) {
6666
],
6767
createRequire: () => fakeRequire,
6868
execFileSync,
69+
verifyRepairedNativeModule(name) {
70+
try {
71+
verifyNativeModule(name, fakeRequire);
72+
return null;
73+
} catch (err) {
74+
return err instanceof Error ? err.message : String(err);
75+
}
76+
},
6977
log: vi.fn(),
7078
};
7179
}
@@ -247,6 +255,27 @@ describe("ensure-native-modules", () => {
247255
expect(fake.state.constructorCalls).toBe(3);
248256
});
249257

258+
it("rebuilds when Node reports that the native module did not self-register", () => {
259+
const registrationError = new Error(
260+
"Module did not self-register: '/tmp/better_sqlite3.node'",
261+
);
262+
const fake = createBetterSqliteRequire(registrationError);
263+
const execFileSync = vi.fn((nodePath, args) => {
264+
if (args[0] === "/tmp/fake-node-modules/node-gyp/bin/node-gyp.js") {
265+
fake.clearConstructorError();
266+
}
267+
});
268+
269+
expect(() =>
270+
ensureNativeModules(
271+
createEnsureOptions(fake.requireModule, execFileSync),
272+
),
273+
).not.toThrow();
274+
275+
expect(execFileSync).toHaveBeenCalledTimes(2);
276+
expect(fake.state.constructorCalls).toBe(3);
277+
});
278+
250279
it("exits non-zero when the post-rebuild instantiation still fails", () => {
251280
const result = spawnSync(
252281
process.execPath,
@@ -278,6 +307,9 @@ describe("ensure-native-modules", () => {
278307
modules: [{ name: "better-sqlite3", resolveFrom: "packages/db/package.json" }],
279308
createRequire,
280309
execFileSync() {},
310+
verifyRepairedNativeModule() {
311+
return "Wrong native binary NODE_MODULE_VERSION";
312+
},
281313
log() {},
282314
});
283315
`,

‎pnpm-lock.yaml‎

Lines changed: 193 additions & 443 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎scripts/ensure-native-modules.mjs‎

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@ export function verifyNativeModule(name, requireModule) {
5151
}
5252

5353
function shouldRebuildNativeModule(errorMessage) {
54-
return /NODE_MODULE_VERSION|Could not locate the bindings file/.test(
55-
errorMessage,
54+
return (
55+
/NODE_MODULE_VERSION|Could not locate the bindings file|Module did not self-register/.test(
56+
errorMessage,
57+
)
5658
);
5759
}
5860

@@ -67,11 +69,38 @@ function getRepairableNativeModuleError(name, requireModule) {
6769
}
6870
}
6971

72+
function getRepairedNativeModuleError(name, pkgJsonPath) {
73+
try {
74+
// A failed dlopen remains cached for the life of the process. Verify a
75+
// replacement binary in a fresh process so the old handle cannot poison it.
76+
execFileSync(
77+
process.execPath,
78+
[
79+
"--input-type=module",
80+
"--eval",
81+
`import { createRequire } from "node:module";
82+
const requireModule = createRequire(${JSON.stringify(pkgJsonPath)});
83+
const NativeModule = requireModule(${JSON.stringify(name)});
84+
const instance = new NativeModule(":memory:");
85+
instance.close();`,
86+
],
87+
{ stdio: ["ignore", "pipe", "pipe"] },
88+
);
89+
return null;
90+
} catch (err) {
91+
const message = formatChildProcessFailure(err);
92+
if (!shouldRebuildNativeModule(message)) throw err;
93+
return message;
94+
}
95+
}
96+
7097
export function ensureNativeModules({
7198
repoRoot = defaultRepoRoot,
7299
modules = nativeModules,
73100
createRequire: createRequireImpl = createRequire,
74101
execFileSync: execFileSyncImpl = execFileSync,
102+
verifyRepairedNativeModule: verifyRepairedNativeModuleImpl =
103+
getRepairedNativeModuleError,
75104
log = console.log,
76105
} = {}) {
77106
for (const { name, resolveFrom } of modules) {
@@ -108,9 +137,9 @@ export function ensureNativeModules({
108137
);
109138
}
110139

111-
const prebuildVerifyError = getRepairableNativeModuleError(
140+
const prebuildVerifyError = verifyRepairedNativeModuleImpl(
112141
name,
113-
requireModule,
142+
pkgJsonPath,
114143
);
115144
if (prebuildVerifyError === null) {
116145
if (!prebuildInstalled) {
@@ -147,9 +176,9 @@ export function ensureNativeModules({
147176
},
148177
);
149178

150-
const rebuildVerifyError = getRepairableNativeModuleError(
179+
const rebuildVerifyError = verifyRepairedNativeModuleImpl(
151180
name,
152-
requireModule,
181+
pkgJsonPath,
153182
);
154183
if (rebuildVerifyError !== null) {
155184
throw new Error(

‎tests/qa/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"@bb/tsconfig": "workspace:*",
2222
"@types/node": "^22.0.0",
2323
"rimraf": "^6.1.0",
24-
"tsx": "^4.21.0",
24+
"tsx": "^4.23.1",
2525
"typescript": "npm:@typescript/typescript6@^6.0.2",
2626
"typescript-7": "npm:typescript@^7.0.2",
2727
"vitest": "^4.1.1"

0 commit comments

Comments
 (0)