mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 23:24:07 +00:00
* 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
161 lines
5.1 KiB
JavaScript
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();
|
|
}
|
|
}
|