diff --git a/.github/workflows/openclaw-plugin-publish.yml b/.github/workflows/openclaw-plugin-publish.yml new file mode 100644 index 000000000..7c7ac971c --- /dev/null +++ b/.github/workflows/openclaw-plugin-publish.yml @@ -0,0 +1,106 @@ +name: OpenClaw Plugin — Build Prebuilds & Publish + +on: + workflow_dispatch: + inputs: + version: + description: "Version to publish (e.g. 1.0.4 or 1.0.4-beta.1)" + required: true + tag: + description: "npm dist-tag (latest for production, beta/next/alpha for testing)" + required: true + default: "latest" + +defaults: + run: + working-directory: apps/memos-local-openclaw + +permissions: + contents: write + +jobs: + build-prebuilds: + strategy: + matrix: + include: + - os: macos-14 + platform: darwin-arm64 + - os: macos-13 + platform: darwin-x64 + - os: ubuntu-latest + platform: linux-x64 + - os: windows-latest + platform: win32-x64 + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Install dependencies + run: npm install + + - name: Collect prebuild + shell: bash + run: | + mkdir -p prebuilds/${{ matrix.platform }} + cp node_modules/better-sqlite3/build/Release/better_sqlite3.node prebuilds/${{ matrix.platform }}/ + + - name: Upload prebuild artifact + uses: actions/upload-artifact@v4 + with: + name: prebuild-${{ matrix.platform }} + path: apps/memos-local-openclaw/prebuilds/${{ matrix.platform }}/better_sqlite3.node + + publish: + needs: build-prebuilds + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + registry-url: https://registry.npmjs.org + + - name: Download all prebuilds + uses: actions/download-artifact@v4 + with: + path: apps/memos-local-openclaw/prebuilds + pattern: prebuild-* + merge-multiple: false + + - name: Organize prebuilds + run: | + cd prebuilds + for dir in prebuild-*; do + platform="${dir#prebuild-}" + mkdir -p "$platform" + mv "$dir/better_sqlite3.node" "$platform/" + rmdir "$dir" + done + echo "Prebuilds collected:" + find . -name "*.node" -exec ls -lh {} \; + + - name: Install dependencies (skip native build) + run: npm install --ignore-scripts + + - name: Bump version + run: npm version ${{ inputs.version }} --no-git-tag-version + + - name: Publish to npm + run: npm publish --access public --tag ${{ inputs.tag }} + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Create git tag and push + working-directory: . + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add apps/memos-local-openclaw/package.json + git commit -m "release: openclaw-plugin v${{ inputs.version }}" + git tag "openclaw-plugin-v${{ inputs.version }}" + git push origin HEAD --tags diff --git a/apps/memos-local-openclaw/.gitignore b/apps/memos-local-openclaw/.gitignore index 3db3e1643..0fbe7b05a 100644 --- a/apps/memos-local-openclaw/.gitignore +++ b/apps/memos-local-openclaw/.gitignore @@ -17,6 +17,9 @@ www/ docs/ ppt/ +# Prebuilt native binaries (included in npm package via `files`, not in git) +prebuilds/ + # Database files *.sqlite *.sqlite-journal diff --git a/apps/memos-local-openclaw/index.ts b/apps/memos-local-openclaw/index.ts index d84d94dcd..351c033c2 100644 --- a/apps/memos-local-openclaw/index.ts +++ b/apps/memos-local-openclaw/index.ts @@ -10,6 +10,7 @@ import { Type } from "@sinclair/typebox"; import * as fs from "fs"; import * as path from "path"; import { buildContext } from "./src/config"; +import { ensureSqliteBinding } from "./src/storage/ensure-binding"; import { SqliteStore } from "./src/storage/sqlite"; import { Embedder } from "./src/embedding"; import { IngestWorker } from "./src/ingest/worker"; @@ -165,6 +166,8 @@ const memosLocalPlugin = { error: (msg: string) => api.logger.warn(`[error] ${msg}`), }); + ensureSqliteBinding(ctx.log); + const store = new SqliteStore(ctx.config.storage!.dbPath!, ctx.log); const embedder = new Embedder(ctx.config.embedding, ctx.log); const worker = new IngestWorker(store, embedder, ctx); diff --git a/apps/memos-local-openclaw/package.json b/apps/memos-local-openclaw/package.json index 7ee152e49..49957fd59 100644 --- a/apps/memos-local-openclaw/package.json +++ b/apps/memos-local-openclaw/package.json @@ -10,6 +10,7 @@ "src", "dist", "skill", + "prebuilds", "scripts/postinstall.cjs", "openclaw.plugin.json", "README.md", diff --git a/apps/memos-local-openclaw/src/index.ts b/apps/memos-local-openclaw/src/index.ts index dcea12d86..b59d75835 100644 --- a/apps/memos-local-openclaw/src/index.ts +++ b/apps/memos-local-openclaw/src/index.ts @@ -1,5 +1,6 @@ import { v4 as uuid } from "uuid"; import { buildContext } from "./config"; +import { ensureSqliteBinding } from "./storage/ensure-binding"; import { SqliteStore } from "./storage/sqlite"; import { Embedder } from "./embedding"; import { IngestWorker } from "./ingest/worker"; @@ -54,6 +55,8 @@ export function initPlugin(opts: PluginInitOptions = {}): MemosLocalPlugin { ctx.log.info("Initializing memos-local plugin..."); + ensureSqliteBinding(ctx.log); + const store = new SqliteStore(ctx.config.storage!.dbPath!, ctx.log); const embedder = new Embedder(ctx.config.embedding, ctx.log); const worker = new IngestWorker(store, embedder, ctx); diff --git a/apps/memos-local-openclaw/src/storage/ensure-binding.ts b/apps/memos-local-openclaw/src/storage/ensure-binding.ts new file mode 100644 index 000000000..ac7f9bf6c --- /dev/null +++ b/apps/memos-local-openclaw/src/storage/ensure-binding.ts @@ -0,0 +1,53 @@ +import { existsSync, mkdirSync, copyFileSync } from "fs"; +import { execSync } from "child_process"; +import path from "path"; +import { createRequire } from "module"; + +const require = createRequire(import.meta.url); + +/** + * Ensure the better-sqlite3 native binary is available. + * + * OpenClaw installs plugins with `--ignore-scripts`, which skips + * the native compilation step. This function checks for the binary + * and restores it from bundled prebuilds if missing. + */ +export function ensureSqliteBinding(log?: { info: (msg: string) => void; warn: (msg: string) => void }): void { + const bsqlPkg = require.resolve("better-sqlite3/package.json"); + const bsqlDir = path.dirname(bsqlPkg); + const bindingPath = path.join(bsqlDir, "build", "Release", "better_sqlite3.node"); + + if (existsSync(bindingPath)) return; + + const platform = `${process.platform}-${process.arch}`; + const pluginRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..", ".."); + const prebuildSrc = path.join(pluginRoot, "prebuilds", platform, "better_sqlite3.node"); + + if (existsSync(prebuildSrc)) { + log?.info(`[ensure-binding] Copying prebuild for ${platform}...`); + mkdirSync(path.dirname(bindingPath), { recursive: true }); + copyFileSync(prebuildSrc, bindingPath); + log?.info(`[ensure-binding] Prebuild installed successfully.`); + return; + } + + log?.warn(`[ensure-binding] No prebuild for ${platform}, attempting npm rebuild...`); + try { + const installDir = path.resolve(bsqlDir, "..", ".."); + execSync("npm rebuild better-sqlite3", { + cwd: installDir, + stdio: "pipe", + timeout: 180_000, + }); + if (existsSync(bindingPath)) { + log?.info(`[ensure-binding] Rebuilt better-sqlite3 successfully.`); + return; + } + } catch { /* fall through */ } + + throw new Error( + `better-sqlite3 native binary not found for ${platform}.\n` + + `Prebuild not bundled and npm rebuild failed.\n` + + `Fix: cd ${path.resolve(bsqlDir, "..", "..")} && npm rebuild better-sqlite3`, + ); +}