openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
156 lines (155 loc) • 6.53 kB
JavaScript
import { w as pathExists } from "./fs-safe-aqmM_n6V.js";
import { g as redactMigrationPlan, l as markMigrationItemConflict, n as MIGRATION_REASON_TARGET_EXISTS, t as MIGRATION_REASON_MISSING_SOURCE_OR_TARGET, u as markMigrationItemError } from "./migration-CRXv-K-p.js";
import path from "node:path";
import fs from "node:fs/promises";
import crypto from "node:crypto";
//#region src/plugin-sdk/migration-runtime.ts
/** Wrap migration runtime config access with a cached mutable snapshot during apply. */
function withCachedMigrationConfigRuntime(runtime, fallbackConfig) {
if (!runtime) return;
const configApi = runtime.config;
if (!configApi?.current || !configApi.mutateConfigFile) return runtime;
let cachedConfig;
const current = () => {
cachedConfig ??= structuredClone(configApi.current() ?? fallbackConfig);
return cachedConfig;
};
return {
...runtime,
config: {
...runtime.config,
current,
mutateConfigFile: async (params) => {
const result = await configApi.mutateConfigFile({
...params,
mutate: async (draft, context) => {
const mutationResult = await params.mutate(draft, context);
cachedConfig = structuredClone(draft);
return mutationResult;
}
});
cachedConfig = structuredClone(result.nextConfig);
return result;
},
...configApi.replaceConfigFile ? { replaceConfigFile: async (params) => {
const result = await configApi.replaceConfigFile(params);
cachedConfig = structuredClone(result.nextConfig);
return result;
} } : {}
}
};
}
async function exists(filePath) {
return await pathExists(filePath);
}
async function backupExistingMigrationTarget(target, reportDir) {
if (!await exists(target)) return;
const backupRoot = path.join(reportDir, "item-backups");
await fs.mkdir(backupRoot, { recursive: true });
const targetHash = crypto.createHash("sha256").update(path.resolve(target)).digest("hex").slice(0, 12);
const backupDir = await fs.mkdtemp(path.join(backupRoot, `${Date.now()}-${targetHash}-${path.basename(target)}-`));
const backupPath = path.join(backupDir, path.basename(target));
await fs.cp(target, backupPath, {
recursive: true,
force: true
});
return backupPath;
}
function isFileAlreadyExistsError(err) {
return Boolean(err && typeof err === "object" && "code" in err && (err.code === "ERR_FS_CP_EEXIST" || err.code === "EEXIST"));
}
function readArchiveRelativePath(item) {
const detailPath = item.details?.archiveRelativePath;
const raw = typeof detailPath === "string" && detailPath.trim() ? detailPath : void 0;
const fallback = item.source ? path.basename(item.source) : item.id;
return path.normalize(raw ?? fallback).split(path.sep).filter((part) => part && part !== "." && part !== "..").join(path.sep) || "item";
}
async function resolveUniqueArchivePath(archiveRoot, relativePath) {
const parsed = path.parse(relativePath);
let candidate = path.join(archiveRoot, relativePath);
let index = 2;
while (await exists(candidate)) {
const filename = `${parsed.name}-${index}${parsed.ext}`;
candidate = path.join(archiveRoot, parsed.dir, filename);
index += 1;
}
return candidate;
}
/** Archive a migration item source into the report directory and mark the item migrated. */
async function archiveMigrationItem(item, reportDir) {
if (!item.source) return markMigrationItemError(item, MIGRATION_REASON_MISSING_SOURCE_OR_TARGET);
try {
if ((await fs.lstat(item.source)).isSymbolicLink()) return markMigrationItemError(item, "archive source is a symlink");
const archiveRoot = path.join(reportDir, "archive");
const relativePath = readArchiveRelativePath(item);
const archivePath = await resolveUniqueArchivePath(archiveRoot, relativePath);
await fs.mkdir(path.dirname(archivePath), { recursive: true });
await fs.cp(item.source, archivePath, {
recursive: true,
force: false,
errorOnExist: true,
verbatimSymlinks: true
});
return {
...item,
status: "migrated",
target: archivePath,
details: {
...item.details,
archivePath,
archiveRelativePath: relativePath
}
};
} catch (err) {
if (isFileAlreadyExistsError(err)) return markMigrationItemConflict(item, MIGRATION_REASON_TARGET_EXISTS);
return markMigrationItemError(item, err instanceof Error ? err.message : String(err));
}
}
/** Copy a migration item source to its target, optionally backing up an overwritten target. */
async function copyMigrationFileItem(item, reportDir, opts = {}) {
if (!item.source || !item.target) return markMigrationItemError(item, MIGRATION_REASON_MISSING_SOURCE_OR_TARGET);
try {
if (await exists(item.target) && !opts.overwrite) return markMigrationItemConflict(item, MIGRATION_REASON_TARGET_EXISTS);
const backupPath = opts.overwrite ? await backupExistingMigrationTarget(item.target, reportDir) : void 0;
await fs.mkdir(path.dirname(item.target), { recursive: true });
await fs.cp(item.source, item.target, {
recursive: true,
force: Boolean(opts.overwrite),
errorOnExist: !opts.overwrite
});
return {
...item,
status: "migrated",
details: {
...item.details,
...backupPath ? { backupPath } : {}
}
};
} catch (err) {
if (isFileAlreadyExistsError(err)) return markMigrationItemConflict(item, MIGRATION_REASON_TARGET_EXISTS);
return markMigrationItemError(item, err instanceof Error ? err.message : String(err));
}
}
/** Write redacted JSON and Markdown migration reports into the apply report directory. */
async function writeMigrationReport(result, opts = {}) {
if (!result.reportDir) return;
await fs.mkdir(result.reportDir, { recursive: true });
await fs.writeFile(path.join(result.reportDir, "report.json"), `${JSON.stringify(redactMigrationPlan(result), null, 2)}\n`, "utf8");
const lines = [
`# ${opts.title ?? "Migration Report"}`,
"",
`Source: ${result.source}`,
result.target ? `Target: ${result.target}` : void 0,
result.backupPath ? `Backup: ${result.backupPath}` : void 0,
"",
`Migrated: ${result.summary.migrated}`,
`Skipped: ${result.summary.skipped}`,
`Conflicts: ${result.summary.conflicts}`,
`Errors: ${result.summary.errors}`,
"",
...result.items.map((item) => `- ${item.status}: ${item.id}${item.reason ? ` (${item.reason})` : ""}`)
].filter((line) => typeof line === "string");
await fs.writeFile(path.join(result.reportDir, "summary.md"), `${lines.join("\n")}\n`, "utf8");
}
//#endregion
export { writeMigrationReport as i, copyMigrationFileItem as n, withCachedMigrationConfigRuntime as r, archiveMigrationItem as t };