fix: dev native startup issues

This commit is contained in:
digua
2026-05-15 18:54:50 +08:00
parent 347c7d69dd
commit 653bd966fa
5 changed files with 135 additions and 3 deletions
+3
View File
@@ -14,6 +14,9 @@ export default defineConfig(() => {
},
define: {
'process.env.APTABASE_APP_KEY': JSON.stringify(process.env.APTABASE_APP_KEY || ''),
// ws 的原生加速依赖是可选项;主进程打包时禁用它们,避免 Vite 将缺失的可选依赖改写为启动即抛错。
'process.env.WS_NO_BUFFER_UTIL': JSON.stringify('true'),
'process.env.WS_NO_UTF_8_VALIDATE': JSON.stringify('true'),
},
build: {
rollupOptions: {
+3 -2
View File
@@ -29,8 +29,9 @@
"build:win": "pnpm run build && electron-builder --win --config electron-builder.yml -p never",
"build:linux": "pnpm run build && electron-builder --linux --config electron-builder.yml -p never",
"dev:app": "vite --config vite.web.config.mts",
"dev:serve": "tsx watch packages/server/src/cli.ts serve",
"dev:web": "CHATLAB_AUTO_SERVE=1 vite --config vite.web.config.mts",
"ensure:server-native": "pnpm --filter chatlab run ensure-native",
"dev:serve": "pnpm run ensure:server-native && tsx watch packages/server/src/cli.ts serve",
"dev:web": "pnpm run ensure:server-native && CHATLAB_AUTO_SERVE=1 vite --config vite.web.config.mts",
"build:web": "vite build --config vite.web.config.mts",
"type-check:web": "vue-tsc --noEmit -p tsconfig.web.json",
"type-check:node": "tsc --noEmit -p tsconfig.node.json",
+2 -1
View File
@@ -9,7 +9,8 @@
"main": "./src/index.ts",
"types": "./src/index.ts",
"scripts": {
"cli": "tsx src/cli.ts",
"cli": "node scripts/ensure-native.mjs && tsx src/cli.ts",
"ensure-native": "node scripts/ensure-native.mjs",
"rebuild-native": "bash scripts/rebuild-native.sh",
"rebuild-sqlite": "npm rebuild better-sqlite3"
},
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env node
import { existsSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { spawnSync } from 'node:child_process'
const currentFile = fileURLToPath(import.meta.url)
const scriptDir = dirname(currentFile)
const serverDir = dirname(scriptDir)
const nativePath = resolve(serverDir, 'native/better_sqlite3.node')
const rebuildScript = resolve(scriptDir, 'rebuild-native.sh')
export function getNativeStatus(bindingPath, nodeExecutable = process.execPath) {
if (!existsSync(bindingPath)) {
return { ok: false, reason: 'missing', message: `Native binding not found: ${bindingPath}` }
}
const result = spawnSync(
nodeExecutable,
['-e', 'require(process.argv[1]); process.stdout.write(process.versions.modules)', bindingPath],
{ encoding: 'utf8' }
)
if (result.status === 0) {
return { ok: true, reason: 'valid', abi: result.stdout.trim() }
}
return {
ok: false,
reason: 'invalid',
message: (result.stderr || result.stdout || result.error?.message || 'Native binding failed to load').trim(),
}
}
function runRebuild() {
const result = spawnSync('bash', [rebuildScript], {
cwd: serverDir,
stdio: 'inherit',
})
if (result.status !== 0) {
process.exit(result.status || 1)
}
}
function main() {
const checkOnly = process.argv.includes('--check')
const status = getNativeStatus(nativePath)
if (status.ok) {
console.error(`[server native] better-sqlite3 ready (Node ABI ${status.abi})`)
return
}
if (checkOnly) {
console.error(`[server native] ${status.message}`)
process.exit(1)
}
console.error(`[server native] ${status.message}`)
console.error('[server native] Rebuilding better-sqlite3 for the current system Node.js...')
runRebuild()
const rebuilt = getNativeStatus(nativePath)
if (!rebuilt.ok) {
console.error(`[server native] Rebuild completed, but native binding is still unusable: ${rebuilt.message}`)
process.exit(1)
}
console.error(`[server native] better-sqlite3 ready (Node ABI ${rebuilt.abi})`)
}
if (process.argv[1] && currentFile === resolve(process.argv[1])) {
main()
}
@@ -0,0 +1,51 @@
import assert from 'node:assert/strict'
import { spawnSync } from 'node:child_process'
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import path from 'node:path'
import test from 'node:test'
import { getNativeStatus } from './ensure-native.mjs'
test('reports missing native binding', () => {
const dir = mkdtempSync(path.join(tmpdir(), 'chatlab-native-missing-'))
try {
const status = getNativeStatus(path.join(dir, 'better_sqlite3.node'))
assert.equal(status.ok, false)
assert.equal(status.reason, 'missing')
} finally {
rmSync(dir, { recursive: true, force: true })
}
})
test('reports invalid native binding load failure', () => {
const dir = mkdtempSync(path.join(tmpdir(), 'chatlab-native-invalid-'))
try {
const nativePath = path.join(dir, 'better_sqlite3.node')
writeFileSync(nativePath, 'not a native module')
const status = getNativeStatus(nativePath)
assert.equal(status.ok, false)
assert.equal(status.reason, 'invalid')
assert.match(status.message, /file too short|not a mach-o file|invalid/)
} finally {
rmSync(dir, { recursive: true, force: true })
}
})
test('reports valid native binding when it can be loaded by current Node', () => {
const nativePath = path.resolve('packages/server/native/better_sqlite3.node')
const status = getNativeStatus(nativePath)
assert.equal(status.ok, true)
assert.equal(status.reason, 'valid')
})
test('prints status to stderr so CLI stdout stays machine-readable', () => {
const result = spawnSync(process.execPath, ['packages/server/scripts/ensure-native.mjs', '--check'], {
encoding: 'utf8',
})
assert.equal(result.status, 0)
assert.equal(result.stdout, '')
assert.match(result.stderr, /better-sqlite3 ready/)
})