mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-27 01:41:40 +00:00
191 lines
5.7 KiB
TypeScript
191 lines
5.7 KiB
TypeScript
import { loadConfig, writeConfigFile } from "../config/config.js";
|
|
import type { HookInstallRecord } from "../config/types.hooks.js";
|
|
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
|
import { updateNpmInstalledHookPacks } from "../hooks/update.js";
|
|
import { parseRegistryNpmSpec } from "../infra/npm-registry-spec.js";
|
|
import { updateNpmInstalledPlugins } from "../plugins/update.js";
|
|
import { defaultRuntime } from "../runtime.js";
|
|
import { theme } from "../terminal/theme.js";
|
|
import {
|
|
extractInstalledNpmHookPackageName,
|
|
extractInstalledNpmPackageName,
|
|
} from "./plugins-command-helpers.js";
|
|
import { promptYesNo } from "./prompt.js";
|
|
|
|
function resolvePluginUpdateSelection(params: {
|
|
installs: Record<string, PluginInstallRecord>;
|
|
rawId?: string;
|
|
all?: boolean;
|
|
}): { pluginIds: string[]; specOverrides?: Record<string, string> } {
|
|
if (params.all) {
|
|
return { pluginIds: Object.keys(params.installs) };
|
|
}
|
|
if (!params.rawId) {
|
|
return { pluginIds: [] };
|
|
}
|
|
|
|
const parsedSpec = parseRegistryNpmSpec(params.rawId);
|
|
if (!parsedSpec || parsedSpec.selectorKind === "none") {
|
|
return { pluginIds: [params.rawId] };
|
|
}
|
|
|
|
const matches = Object.entries(params.installs).filter(([, install]) => {
|
|
return extractInstalledNpmPackageName(install) === parsedSpec.name;
|
|
});
|
|
if (matches.length !== 1) {
|
|
return { pluginIds: [params.rawId] };
|
|
}
|
|
|
|
const [pluginId] = matches[0];
|
|
if (!pluginId) {
|
|
return { pluginIds: [params.rawId] };
|
|
}
|
|
return {
|
|
pluginIds: [pluginId],
|
|
specOverrides: {
|
|
[pluginId]: parsedSpec.raw,
|
|
},
|
|
};
|
|
}
|
|
|
|
function resolveHookPackUpdateSelection(params: {
|
|
installs: Record<string, HookInstallRecord>;
|
|
rawId?: string;
|
|
all?: boolean;
|
|
}): { hookIds: string[]; specOverrides?: Record<string, string> } {
|
|
if (params.all) {
|
|
return { hookIds: Object.keys(params.installs) };
|
|
}
|
|
if (!params.rawId) {
|
|
return { hookIds: [] };
|
|
}
|
|
if (params.rawId in params.installs) {
|
|
return { hookIds: [params.rawId] };
|
|
}
|
|
|
|
const parsedSpec = parseRegistryNpmSpec(params.rawId);
|
|
if (!parsedSpec || parsedSpec.selectorKind === "none") {
|
|
return { hookIds: [] };
|
|
}
|
|
|
|
const matches = Object.entries(params.installs).filter(([, install]) => {
|
|
return extractInstalledNpmHookPackageName(install) === parsedSpec.name;
|
|
});
|
|
if (matches.length !== 1) {
|
|
return { hookIds: [] };
|
|
}
|
|
|
|
const [hookId] = matches[0];
|
|
if (!hookId) {
|
|
return { hookIds: [] };
|
|
}
|
|
return {
|
|
hookIds: [hookId],
|
|
specOverrides: {
|
|
[hookId]: parsedSpec.raw,
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function runPluginUpdateCommand(params: {
|
|
id?: string;
|
|
opts: { all?: boolean; dryRun?: boolean };
|
|
}) {
|
|
const cfg = loadConfig();
|
|
const logger = {
|
|
info: (msg: string) => defaultRuntime.log(msg),
|
|
warn: (msg: string) => defaultRuntime.log(theme.warn(msg)),
|
|
};
|
|
const pluginSelection = resolvePluginUpdateSelection({
|
|
installs: cfg.plugins?.installs ?? {},
|
|
rawId: params.id,
|
|
all: params.opts.all,
|
|
});
|
|
const hookSelection = resolveHookPackUpdateSelection({
|
|
installs: cfg.hooks?.internal?.installs ?? {},
|
|
rawId: params.id,
|
|
all: params.opts.all,
|
|
});
|
|
|
|
if (pluginSelection.pluginIds.length === 0 && hookSelection.hookIds.length === 0) {
|
|
if (params.opts.all) {
|
|
defaultRuntime.log("No tracked plugins or hook packs to update.");
|
|
return;
|
|
}
|
|
defaultRuntime.error("Provide a plugin or hook-pack id, or use --all.");
|
|
return defaultRuntime.exit(1);
|
|
}
|
|
|
|
const pluginResult = await updateNpmInstalledPlugins({
|
|
config: cfg,
|
|
pluginIds: pluginSelection.pluginIds,
|
|
specOverrides: pluginSelection.specOverrides,
|
|
dryRun: params.opts.dryRun,
|
|
logger,
|
|
onIntegrityDrift: async (drift) => {
|
|
const specLabel = drift.resolvedSpec ?? drift.spec;
|
|
defaultRuntime.log(
|
|
theme.warn(
|
|
`Integrity drift detected for "${drift.pluginId}" (${specLabel})` +
|
|
`\nExpected: ${drift.expectedIntegrity}` +
|
|
`\nActual: ${drift.actualIntegrity}`,
|
|
),
|
|
);
|
|
if (drift.dryRun) {
|
|
return true;
|
|
}
|
|
return await promptYesNo(`Continue updating "${drift.pluginId}" with this artifact?`);
|
|
},
|
|
});
|
|
const hookResult = await updateNpmInstalledHookPacks({
|
|
config: pluginResult.config,
|
|
hookIds: hookSelection.hookIds,
|
|
specOverrides: hookSelection.specOverrides,
|
|
dryRun: params.opts.dryRun,
|
|
logger,
|
|
onIntegrityDrift: async (drift) => {
|
|
const specLabel = drift.resolvedSpec ?? drift.spec;
|
|
defaultRuntime.log(
|
|
theme.warn(
|
|
`Integrity drift detected for hook pack "${drift.hookId}" (${specLabel})` +
|
|
`\nExpected: ${drift.expectedIntegrity}` +
|
|
`\nActual: ${drift.actualIntegrity}`,
|
|
),
|
|
);
|
|
if (drift.dryRun) {
|
|
return true;
|
|
}
|
|
return await promptYesNo(`Continue updating hook pack "${drift.hookId}" with this artifact?`);
|
|
},
|
|
});
|
|
|
|
for (const outcome of pluginResult.outcomes) {
|
|
if (outcome.status === "error") {
|
|
defaultRuntime.log(theme.error(outcome.message));
|
|
continue;
|
|
}
|
|
if (outcome.status === "skipped") {
|
|
defaultRuntime.log(theme.warn(outcome.message));
|
|
continue;
|
|
}
|
|
defaultRuntime.log(outcome.message);
|
|
}
|
|
|
|
for (const outcome of hookResult.outcomes) {
|
|
if (outcome.status === "error") {
|
|
defaultRuntime.log(theme.error(outcome.message));
|
|
continue;
|
|
}
|
|
if (outcome.status === "skipped") {
|
|
defaultRuntime.log(theme.warn(outcome.message));
|
|
continue;
|
|
}
|
|
defaultRuntime.log(outcome.message);
|
|
}
|
|
|
|
if (!params.opts.dryRun && (pluginResult.changed || hookResult.changed)) {
|
|
await writeConfigFile(hookResult.config);
|
|
defaultRuntime.log("Restart the gateway to load plugins and hooks.");
|
|
}
|
|
}
|