Skip to content

Commit 8a188f6

Browse files
author
Test User
committed
fix: recover from native sqlite ABI mismatch
1 parent e450658 commit 8a188f6

5 files changed

Lines changed: 78 additions & 4 deletions

File tree

docs/reference/commands.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ night-watch doctor --fix # Auto-fix fixable issues
639639

640640
**What it checks:**
641641

642-
1. Node.js version (requires 18+)
642+
1. Node.js version (requires 22+)
643643
2. Git repository
644644
3. GitHub CLI (installed and authenticated)
645645
4. Provider CLI (Claude or Codex)

docs/reference/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -625,7 +625,7 @@ night-watch doctor --fix # Auto-fix fixable issues
625625

626626
**What it checks:**
627627

628-
1. Node.js version (requires 18+)
628+
1. Node.js version (requires 22+)
629629
2. Git repository
630630
3. GitHub CLI (installed and authenticated)
631631
4. Provider CLI (Claude or Codex)

packages/cli/bin/night-watch.mjs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,66 @@
11
#!/usr/bin/env node
2-
import('../dist/cli.js');
2+
import { spawnSync } from 'node:child_process';
3+
import { dirname, resolve } from 'node:path';
4+
import { fileURLToPath } from 'node:url';
5+
6+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
7+
const rebuildAttempted = process.env.NIGHT_WATCH_NATIVE_REBUILD_ATTEMPTED === '1';
8+
9+
function isBetterSqliteNativeMismatch(error) {
10+
const err = error ?? {};
11+
const parts = [
12+
err.code,
13+
err.message,
14+
err.stack,
15+
err.cause?.code,
16+
err.cause?.message,
17+
err.cause?.stack,
18+
];
19+
const text = parts.filter(Boolean).join('\n');
20+
21+
return (
22+
text.includes('better_sqlite3.node') &&
23+
(text.includes('NODE_MODULE_VERSION') || text.includes('ERR_DLOPEN_FAILED'))
24+
);
25+
}
26+
27+
function rebuildNativeSqlite() {
28+
const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
29+
console.error(
30+
`[night-watch] Native SQLite module was built for a different Node.js version (${process.version}).`,
31+
);
32+
console.error('[night-watch] Rebuilding better-sqlite3 for the current Node.js runtime...');
33+
34+
const result = spawnSync(npmBin, ['rebuild', 'better-sqlite3'], {
35+
cwd: packageRoot,
36+
env: { ...process.env, NIGHT_WATCH_NATIVE_REBUILD_ATTEMPTED: '1' },
37+
stdio: 'inherit',
38+
});
39+
40+
if (result.error) {
41+
console.error(`[night-watch] Failed to start npm rebuild: ${result.error.message}`);
42+
process.exit(1);
43+
}
44+
45+
if ((result.status ?? 1) !== 0) {
46+
console.error('[night-watch] Failed to rebuild better-sqlite3. Try reinstalling Night Watch:');
47+
console.error(' npm install -g @jonit-dev/night-watch-cli');
48+
process.exit(result.status ?? 1);
49+
}
50+
}
51+
52+
async function run() {
53+
try {
54+
await import('../dist/cli.js');
55+
} catch (error) {
56+
if (!isBetterSqliteNativeMismatch(error) || rebuildAttempted) {
57+
throw error;
58+
}
59+
60+
rebuildNativeSqlite();
61+
process.env.NIGHT_WATCH_NATIVE_REBUILD_ATTEMPTED = '1';
62+
await import(`../dist/cli.js?native-rebuild=${Date.now()}`);
63+
}
64+
}
65+
66+
await run();

packages/cli/src/__tests__/package.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,16 @@ describe('Package Files', () => {
112112
expect(content.startsWith('#!/usr/bin/env node')).toBe(true);
113113
});
114114

115+
it('should recover from better-sqlite3 native Node ABI mismatches', () => {
116+
const binPath = path.join(packageRoot, 'bin', 'night-watch.mjs');
117+
const content = fs.readFileSync(binPath, 'utf-8');
118+
119+
expect(content).toContain('NODE_MODULE_VERSION');
120+
expect(content).toContain('ERR_DLOPEN_FAILED');
121+
expect(content).toContain("npmBin, ['rebuild', 'better-sqlite3']");
122+
expect(content).toContain('NIGHT_WATCH_NATIVE_REBUILD_ATTEMPTED');
123+
});
124+
115125
it('should include scripts in package', () => {
116126
const scriptsDir = path.join(packageRoot, 'scripts');
117127
expect(fs.existsSync(scriptsDir)).toBe(true);

packages/cli/src/commands/doctor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export function doctorCommand(program: Command): void {
110110
checkNum++,
111111
totalChecks,
112112
'Node.js version',
113-
() => checkNodeVersion(18),
113+
() => checkNodeVersion(22),
114114
options,
115115
);
116116
if (nodeResult.passed) passedChecks++;

0 commit comments

Comments
 (0)