Files
openclaw/scripts/e2e/lib/plugin-index-sqlite.mjs
Peter Steinberger 5443baa852 Persist plugin install index in SQLite (#88794)
* refactor: persist plugin install index in sqlite

* fix: merge legacy plugin index records into sqlite

* test: update plugin index sqlite fixtures

* fix: migrate custom plugin install indexes

* test: update plugin index sentinel

* fix: exclude migrated plugin index archives

* fix: read post-upgrade plugin index from sqlite

* fix: migrate legacy plugin index before agent runs

* fix: respect disabled persisted plugin registry reads

* test: type plugin install record fixtures

* fix: simplify plugin index record reader type

* test: fix sqlite plugin index CI fallout

* test: mock provider normalization in agent command tests

# Conflicts:
#	src/commands/agent-command.test-mocks.ts

* build: remove unused ui three dependency
2026-05-31 20:51:33 -04:00

161 lines
5.1 KiB
JavaScript

import fs from "node:fs";
import path from "node:path";
import { DatabaseSync } from "node:sqlite";
const INDEX_KEY = "installed-plugin-index";
export function stateDir() {
return process.env.OPENCLAW_STATE_DIR || path.join(process.env.HOME, ".openclaw");
}
export function configPath() {
return process.env.OPENCLAW_CONFIG_PATH || path.join(stateDir(), "openclaw.json");
}
function readJsonMaybe(file) {
try {
return JSON.parse(fs.readFileSync(file, "utf8"));
} catch {
return {};
}
}
function sqlitePath(root = stateDir()) {
return path.join(root, "state", "openclaw.sqlite");
}
function legacyIndexPath(root = stateDir()) {
return path.join(root, "plugins", "installs.json");
}
function readSqlitePluginIndex(root = stateDir()) {
const dbPath = sqlitePath(root);
if (!fs.existsSync(dbPath)) {
return {};
}
let db;
try {
db = new DatabaseSync(dbPath, { readOnly: true });
const row = db
.prepare(
`
SELECT version, warning, host_contract_version, compat_registry_version,
migration_version, policy_hash, generated_at_ms, refresh_reason,
install_records_json, plugins_json, diagnostics_json
FROM installed_plugin_index
WHERE index_key = ?
`,
)
.get(INDEX_KEY);
if (!row) {
return {};
}
return {
version: Number(row.version),
...(row.warning ? { warning: row.warning } : {}),
hostContractVersion: row.host_contract_version,
compatRegistryVersion: row.compat_registry_version,
migrationVersion: Number(row.migration_version),
policyHash: row.policy_hash,
generatedAtMs: Number(row.generated_at_ms),
...(row.refresh_reason ? { refreshReason: row.refresh_reason } : {}),
installRecords: JSON.parse(row.install_records_json),
plugins: JSON.parse(row.plugins_json),
diagnostics: JSON.parse(row.diagnostics_json),
};
} catch {
return {};
} finally {
db?.close();
}
}
export function readPluginInstallIndex(options = {}) {
const root = options.stateDir ?? stateDir();
const config = readJsonMaybe(options.configPath ?? configPath());
const sqliteIndex = readSqlitePluginIndex(root);
if (sqliteIndex.installRecords) {
return sqliteIndex;
}
const legacyIndex = readJsonMaybe(legacyIndexPath(root));
const installRecords =
legacyIndex.installRecords ??
legacyIndex.records ??
options.fallbackRecords ??
config.plugins?.installs ??
{};
return {
...legacyIndex,
installRecords,
};
}
export function readPluginInstallRecords(options = {}) {
return readPluginInstallIndex(options).installRecords ?? {};
}
export function writePluginInstallIndexForE2E(index, options = {}) {
const root = options.stateDir ?? stateDir();
const dbPath = sqlitePath(root);
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
const db = new DatabaseSync(dbPath);
try {
db.exec(`
CREATE TABLE IF NOT EXISTS installed_plugin_index (
index_key TEXT NOT NULL PRIMARY KEY,
version INTEGER NOT NULL,
host_contract_version TEXT NOT NULL,
compat_registry_version TEXT NOT NULL,
migration_version INTEGER NOT NULL,
policy_hash TEXT NOT NULL,
generated_at_ms INTEGER NOT NULL,
refresh_reason TEXT,
install_records_json TEXT NOT NULL,
plugins_json TEXT NOT NULL,
diagnostics_json TEXT NOT NULL,
warning TEXT,
updated_at_ms INTEGER NOT NULL
);
`);
const now = Date.now();
db.prepare(
`
INSERT INTO installed_plugin_index (
index_key, version, host_contract_version, compat_registry_version,
migration_version, policy_hash, generated_at_ms, refresh_reason,
install_records_json, plugins_json, diagnostics_json, warning, updated_at_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(index_key) DO UPDATE SET
version = excluded.version,
host_contract_version = excluded.host_contract_version,
compat_registry_version = excluded.compat_registry_version,
migration_version = excluded.migration_version,
policy_hash = excluded.policy_hash,
generated_at_ms = excluded.generated_at_ms,
refresh_reason = excluded.refresh_reason,
install_records_json = excluded.install_records_json,
plugins_json = excluded.plugins_json,
diagnostics_json = excluded.diagnostics_json,
warning = excluded.warning,
updated_at_ms = excluded.updated_at_ms
`,
).run(
INDEX_KEY,
index.version ?? 1,
index.hostContractVersion ?? "docker-e2e",
index.compatRegistryVersion ?? "docker-e2e",
index.migrationVersion ?? 1,
index.policyHash ?? "docker-e2e",
index.generatedAtMs ?? now,
index.refreshReason ?? null,
JSON.stringify(index.installRecords ?? {}),
JSON.stringify(index.plugins ?? []),
JSON.stringify(index.diagnostics ?? []),
index.warning ?? "DO NOT EDIT. This row is generated by OpenClaw plugin registry commands.",
now,
);
} finally {
db.close();
}
}