Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions apps/memos-local-openclaw/scripts/native-binding.cjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
"use strict";

const fs = require("fs");
const path = require("path");

function errorMessage(error) {
if (error && typeof error.message === "string") return error.message;
return String(error || "Unknown native binding error");
Expand All @@ -26,7 +29,32 @@ function validateNativeBinding(bindingPath, loadBinding = defaultLoadBinding) {
}
}

function quarantineNativeBinding(bindingPath, fsImpl = fs, now = Date.now()) {
if (!bindingPath || !fsImpl.existsSync(bindingPath)) {
return { ok: false, quarantinedPath: "", reason: "missing" };
}

const parsed = path.parse(bindingPath);
const quarantinedPath = path.join(
parsed.dir,
`${parsed.name}.abi-mismatch-${now}${parsed.ext}`,
);

try {
fsImpl.renameSync(bindingPath, quarantinedPath);
return { ok: true, quarantinedPath, reason: "renamed" };
} catch (error) {
try {
fsImpl.unlinkSync(bindingPath);
return { ok: true, quarantinedPath: "", reason: "removed" };
} catch {
return { ok: false, quarantinedPath: "", reason: errorMessage(error) };
}
}
}

module.exports = {
defaultLoadBinding,
quarantineNativeBinding,
validateNativeBinding,
};
10 changes: 9 additions & 1 deletion apps/memos-local-openclaw/scripts/postinstall.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
const { spawnSync } = require("child_process");
const path = require("path");
const fs = require("fs");
const { validateNativeBinding } = require("./native-binding.cjs");
const { quarantineNativeBinding, validateNativeBinding } = require("./native-binding.cjs");

const RESET = "\x1b[0m";
const GREEN = "\x1b[32m";
Expand Down Expand Up @@ -402,6 +402,14 @@ function sqliteBindingsExist() {
if (status.ok) return true;
if (status.reason === "node-module-version") {
warn("Native binding exists but was compiled for a different Node.js version.");
const quarantine = quarantineNativeBinding(found);
if (quarantine.ok && quarantine.quarantinedPath) {
warn(`Moved stale native binding aside: ${DIM}${quarantine.quarantinedPath}${RESET}`);
} else if (quarantine.ok) {
warn("Removed stale native binding before rebuild.");
} else {
warn(`Could not quarantine stale native binding: ${quarantine.reason}`);
}
} else {
warn("Native binding exists but failed to load.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import { createRequire } from "node:module";
import { describe, expect, it } from "vitest";

const require = createRequire(import.meta.url);
const { validateNativeBinding } = require("../scripts/native-binding.cjs");
const {
quarantineNativeBinding,
validateNativeBinding,
} = require("../scripts/native-binding.cjs");

describe("postinstall native binding validation", () => {
it("accepts a loadable native binding", () => {
Expand Down Expand Up @@ -35,4 +38,56 @@ describe("postinstall native binding validation", () => {
expect(result.ok).toBe(false);
expect(result.reason).toBe("missing");
});

it("moves ABI-mismatched bindings aside before rebuild", () => {
const calls: Array<[string, string, string?]> = [];
const fsImpl = {
existsSync: () => true,
renameSync: (from: string, to: string) => calls.push(["rename", from, to]),
unlinkSync: (target: string) => calls.push(["unlink", target]),
};
const staleBinding = [
"C:",
"Users",
"me",
".openclaw",
"extensions",
"memos-local-openclaw-plugin",
"node_modules",
"better-sqlite3",
"build",
"Release",
"better_sqlite3.node",
].join("\\");

const result = quarantineNativeBinding(staleBinding, fsImpl, 123);

expect(result.ok).toBe(true);
expect(result.reason).toBe("renamed");
expect(result.quarantinedPath).toContain("better_sqlite3.abi-mismatch-123.node");
expect(calls).toEqual([
[
"rename",
staleBinding,
result.quarantinedPath,
],
]);
});

it("removes the stale binding if it cannot be renamed", () => {
const calls: Array<[string, string]> = [];
const fsImpl = {
existsSync: () => true,
renameSync: () => {
throw new Error("EPERM");
},
unlinkSync: (target: string) => calls.push(["unlink", target]),
};

const result = quarantineNativeBinding("/tmp/better_sqlite3.node", fsImpl, 123);

expect(result.ok).toBe(true);
expect(result.reason).toBe("removed");
expect(calls).toEqual([["unlink", "/tmp/better_sqlite3.node"]]);
});
});