mirror of
https://github.com/grp06/openclaw-studio.git
synced 2026-08-14 08:52:03 +00:00
173 lines
4.8 KiB
JavaScript
173 lines
4.8 KiB
JavaScript
import { spawnSync } from "node:child_process";
|
|
import fs from "node:fs";
|
|
import { createRequire } from "node:module";
|
|
import path from "node:path";
|
|
|
|
const require = createRequire(import.meta.url);
|
|
|
|
const mode = process.argv.includes("--repair") ? "repair" : "check";
|
|
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
const npmExecPath = process.env.npm_execpath || "";
|
|
const bundledNpmCliPath = path.resolve(
|
|
path.dirname(process.execPath),
|
|
"..",
|
|
"lib",
|
|
"node_modules",
|
|
"npm",
|
|
"bin",
|
|
"npm-cli.js"
|
|
);
|
|
|
|
const log = (message) => {
|
|
console.info(`[native-runtime] ${message}`);
|
|
};
|
|
|
|
const resolvePathEnvKey = () => {
|
|
for (const key of Object.keys(process.env)) {
|
|
if (key.toLowerCase() === "path") return key;
|
|
}
|
|
return "PATH";
|
|
};
|
|
|
|
const resolveSpawnEnv = () => {
|
|
const pathKey = resolvePathEnvKey();
|
|
const pathDelimiter = process.platform === "win32" ? ";" : ":";
|
|
const nodeBinDir = path.dirname(process.execPath);
|
|
const existingPath = process.env[pathKey] || "";
|
|
const prefixedPath = existingPath
|
|
? `${nodeBinDir}${pathDelimiter}${existingPath}`
|
|
: nodeBinDir;
|
|
return {
|
|
...process.env,
|
|
[pathKey]: prefixedPath,
|
|
npm_config_scripts_prepend_node_path: "true",
|
|
};
|
|
};
|
|
|
|
const getErrorCode = (error) => {
|
|
if (!error || typeof error !== "object" || Array.isArray(error)) return "";
|
|
const code = error.code;
|
|
if (typeof code !== "string") return "";
|
|
return code.trim().toUpperCase();
|
|
};
|
|
|
|
const getErrorMessage = (error) =>
|
|
error instanceof Error ? error.message : String(error ?? "unknown_error");
|
|
|
|
const isNativeMismatchError = (error, message) => {
|
|
const code = getErrorCode(error);
|
|
const normalized = message.toLowerCase();
|
|
const hasModuleVersionSignal =
|
|
normalized.includes("node_module_version") ||
|
|
normalized.includes("compiled against a different node.js version");
|
|
const hasBetterSqliteSignal =
|
|
normalized.includes("better_sqlite3.node") || normalized.includes("better-sqlite3");
|
|
if (!hasModuleVersionSignal || !hasBetterSqliteSignal) return false;
|
|
return code.length === 0 || code === "ERR_DLOPEN_FAILED";
|
|
};
|
|
|
|
const isMissingBetterSqliteModule = (error, message) => {
|
|
const code = getErrorCode(error);
|
|
const normalized = message.toLowerCase();
|
|
if (!normalized.includes("better-sqlite3")) return false;
|
|
if (code === "MODULE_NOT_FOUND") return true;
|
|
return normalized.includes("cannot find module");
|
|
};
|
|
|
|
const printRemediation = () => {
|
|
console.error("[native-runtime] remediation: npm rebuild better-sqlite3");
|
|
console.error("[native-runtime] remediation: npm install");
|
|
};
|
|
|
|
const verifyLoad = () => {
|
|
try {
|
|
const BetterSqlite3 = require("better-sqlite3");
|
|
const db = new BetterSqlite3(":memory:");
|
|
db.prepare("SELECT 1").get();
|
|
db.close();
|
|
return { ok: true };
|
|
} catch (error) {
|
|
return {
|
|
ok: false,
|
|
error,
|
|
message: getErrorMessage(error),
|
|
};
|
|
}
|
|
};
|
|
|
|
const rebuildBetterSqlite = () => {
|
|
const spawnEnv = resolveSpawnEnv();
|
|
if (fs.existsSync(bundledNpmCliPath)) {
|
|
const viaBundledNpm = spawnSync(
|
|
process.execPath,
|
|
[bundledNpmCliPath, "rebuild", "better-sqlite3"],
|
|
{
|
|
stdio: "inherit",
|
|
env: spawnEnv,
|
|
}
|
|
);
|
|
if (viaBundledNpm.status === 0) return true;
|
|
}
|
|
|
|
if (npmExecPath.trim()) {
|
|
const viaExecPath = spawnSync(
|
|
process.execPath,
|
|
[npmExecPath, "rebuild", "better-sqlite3"],
|
|
{
|
|
stdio: "inherit",
|
|
env: spawnEnv,
|
|
}
|
|
);
|
|
if (viaExecPath.status === 0) return true;
|
|
}
|
|
const viaPath = spawnSync(npmCommand, ["rebuild", "better-sqlite3"], {
|
|
stdio: "inherit",
|
|
env: spawnEnv,
|
|
});
|
|
return viaPath.status === 0;
|
|
};
|
|
|
|
log(`mode=${mode}`);
|
|
log(`node=${process.version} abi=${process.versions.modules}`);
|
|
log(`node_exec=${process.execPath}`);
|
|
|
|
const firstPass = verifyLoad();
|
|
if (firstPass.ok) {
|
|
log("better-sqlite3 load: ok");
|
|
process.exit(0);
|
|
}
|
|
|
|
if (!isNativeMismatchError(firstPass.error, firstPass.message)) {
|
|
if (isMissingBetterSqliteModule(firstPass.error, firstPass.message)) {
|
|
console.error(`[native-runtime] better-sqlite3 module is missing: ${firstPass.message}`);
|
|
printRemediation();
|
|
process.exit(1);
|
|
}
|
|
console.error(`[native-runtime] better-sqlite3 load failed: ${firstPass.message}`);
|
|
printRemediation();
|
|
process.exit(1);
|
|
}
|
|
|
|
console.error(`[native-runtime] detected native ABI mismatch: ${firstPass.message}`);
|
|
|
|
if (mode !== "repair") {
|
|
printRemediation();
|
|
process.exit(1);
|
|
}
|
|
|
|
log("attempting rebuild: npm rebuild better-sqlite3");
|
|
if (!rebuildBetterSqlite()) {
|
|
console.error("[native-runtime] rebuild failed");
|
|
printRemediation();
|
|
process.exit(1);
|
|
}
|
|
|
|
const secondPass = verifyLoad();
|
|
if (!secondPass.ok) {
|
|
console.error(`[native-runtime] better-sqlite3 still failing after rebuild: ${secondPass.message}`);
|
|
printRemediation();
|
|
process.exit(1);
|
|
}
|
|
|
|
log("better-sqlite3 load: ok (after rebuild)");
|